v0.4.8-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/decoder/metadata/metaplex_token_metadata/decoder.rs
|
||||
// version: 31
|
||||
// version: 32
|
||||
|
||||
//! Exact contextual dispatch for Metaplex Token Metadata.
|
||||
|
||||
@@ -144,9 +144,6 @@ impl crate::DcApiInstructionDecoder for crate::DcMetadataMetaplexTokenMetadataDe
|
||||
mod tests {
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
|
||||
const AUDIT_MATRIX_JSON: &str = include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_MATRIX.json"
|
||||
);
|
||||
const MAX_SDK_INSTRUCTION_MODULES: usize = 128;
|
||||
const MIN_SDK_INSTRUCTION_MODULES: usize = 90;
|
||||
const MIN_PDA_FAMILIES: usize = 8;
|
||||
@@ -2023,7 +2020,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn audit_matrix_remains_machine_readable_and_bounded() {
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(AUDIT_MATRIX_JSON);
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_MATRIX.json"
|
||||
));
|
||||
assert!(parsed.is_ok());
|
||||
let matrix = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -3053,7 +3052,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn pre_002_contract_closes_all_instruction_statuses_and_idl_accounts() {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(AUDIT_MATRIX_JSON) {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_MATRIX.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("matrix parse failed: {error}"),
|
||||
};
|
||||
@@ -3093,7 +3094,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn pre_002_fact_ownership_is_unique_and_keeps_metadata_domains_separate() {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(AUDIT_MATRIX_JSON) {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_MATRIX.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("matrix parse failed: {error}"),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/decoder/metadata/solana_program_metadata/account.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Bounded account decoding for the Solana Program Metadata program.
|
||||
|
||||
@@ -61,66 +61,61 @@ pub enum DcMetadataSpmAccountDecodeError {
|
||||
|
||||
impl std::fmt::Display for DcMetadataSpmAccountDecodeError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
return match self {
|
||||
Self::InvalidAccountKey => {
|
||||
return formatter.write_str("program metadata account key is invalid");
|
||||
},
|
||||
Self::InvalidOwner => {
|
||||
return formatter.write_str("program metadata account owner is invalid");
|
||||
formatter.write_str("program metadata account key is invalid")
|
||||
},
|
||||
Self::InvalidOwner => formatter.write_str("program metadata account owner is invalid"),
|
||||
Self::ForeignOwner => {
|
||||
return formatter.write_str("program metadata account is owned by another program");
|
||||
},
|
||||
Self::EmptyAccountData => {
|
||||
return formatter.write_str("program metadata account data is empty");
|
||||
formatter.write_str("program metadata account is owned by another program")
|
||||
},
|
||||
Self::EmptyAccountData => formatter.write_str("program metadata account data is empty"),
|
||||
Self::UninitializedAccount => {
|
||||
return formatter.write_str("program metadata account is uninitialized");
|
||||
formatter.write_str("program metadata account is uninitialized")
|
||||
},
|
||||
Self::AccountDataTooLarge { actual_bytes, max_bytes } => {
|
||||
return write!(
|
||||
write!(
|
||||
formatter,
|
||||
"program metadata account data length {actual_bytes} exceeds {max_bytes} bytes",
|
||||
);
|
||||
)
|
||||
},
|
||||
Self::TruncatedHeader { actual_bytes, minimum_bytes } => {
|
||||
return write!(
|
||||
write!(
|
||||
formatter,
|
||||
"program metadata account header length {actual_bytes} is below {minimum_bytes} bytes",
|
||||
);
|
||||
)
|
||||
},
|
||||
Self::UnexpectedAccountDiscriminator { expected, actual } => {
|
||||
return write!(
|
||||
write!(
|
||||
formatter,
|
||||
"program metadata account discriminator {actual:?} does not match {expected:?}",
|
||||
);
|
||||
)
|
||||
},
|
||||
Self::LogicalDataOutOfBounds { declared_bytes, available_bytes } => {
|
||||
return write!(
|
||||
write!(
|
||||
formatter,
|
||||
"program metadata logical data length {declared_bytes} exceeds {available_bytes} allocated bytes",
|
||||
);
|
||||
)
|
||||
},
|
||||
Self::LogicalDataLengthUnsupported { declared_bytes } => {
|
||||
return write!(
|
||||
write!(
|
||||
formatter,
|
||||
"program metadata logical data length {declared_bytes} cannot be represented on this architecture",
|
||||
);
|
||||
)
|
||||
},
|
||||
Self::MissingNonCanonicalAuthority => {
|
||||
return formatter
|
||||
.write_str("non-canonical program metadata account has no authority");
|
||||
formatter.write_str("non-canonical program metadata account has no authority")
|
||||
},
|
||||
Self::InvalidMetadataPda => {
|
||||
return formatter.write_str("program metadata account PDA is invalid");
|
||||
formatter.write_str("program metadata account PDA is invalid")
|
||||
},
|
||||
Self::InvalidUrlUtf8 => {
|
||||
return formatter.write_str("program metadata URL data is not valid UTF-8");
|
||||
formatter.write_str("program metadata URL data is not valid UTF-8")
|
||||
},
|
||||
Self::Model(error) => {
|
||||
return write!(formatter, "program metadata model conversion failed: {error}");
|
||||
write!(formatter, "program metadata model conversion failed: {error}")
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +202,7 @@ pub fn decoder_metadata_solana_program_metadata_decode_account(
|
||||
return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error));
|
||||
},
|
||||
};
|
||||
match discriminator {
|
||||
return match discriminator {
|
||||
crate::DcMetadataSpmAccountDiscriminator::Buffer => {
|
||||
let snapshot =
|
||||
match crate::decoder_metadata_solana_program_metadata_decode_buffer_account(
|
||||
@@ -216,7 +211,7 @@ pub fn decoder_metadata_solana_program_metadata_decode_account(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Buffer(snapshot));
|
||||
std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Buffer(snapshot))
|
||||
},
|
||||
crate::DcMetadataSpmAccountDiscriminator::Metadata => {
|
||||
let snapshot =
|
||||
@@ -226,16 +221,12 @@ pub fn decoder_metadata_solana_program_metadata_decode_account(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Metadata(
|
||||
snapshot,
|
||||
));
|
||||
std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Metadata(snapshot))
|
||||
},
|
||||
crate::DcMetadataSpmAccountDiscriminator::Empty => {
|
||||
return std::result::Result::Err(
|
||||
crate::DcMetadataSpmAccountDecodeError::UninitializedAccount,
|
||||
);
|
||||
std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::UninitializedAccount)
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Decodes one Solana Program Metadata Buffer account without claiming a stable PDA after authority changes.
|
||||
@@ -530,12 +521,12 @@ fn decoder_metadata_solana_program_metadata_decode_typed_data(
|
||||
crate::DcMetadataSpmData::External(external)
|
||||
},
|
||||
};
|
||||
match model.validate() {
|
||||
std::result::Result::Ok(()) => return std::result::Result::Ok(model),
|
||||
return match model.validate() {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(model),
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error));
|
||||
std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error))
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn decoder_metadata_solana_program_metadata_validate_metadata_pda(
|
||||
@@ -576,12 +567,12 @@ fn decoder_metadata_solana_program_metadata_validate_metadata_pda(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn metadata_program_id() -> std::option::Option<solana_pubkey::Pubkey> {
|
||||
match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(
|
||||
return match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(
|
||||
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID,
|
||||
) {
|
||||
std::result::Result::Ok(value) => return std::option::Option::Some(value),
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
}
|
||||
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||
std::result::Result::Err(_) => std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/decoder/metadata/solana_program_metadata/instruction.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Exact contextual instruction decoding for Solana Program Metadata.
|
||||
|
||||
@@ -890,10 +890,6 @@ fn failed(
|
||||
mod tests {
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
|
||||
const MATRIX_JSON: &str = include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_INSTRUCTION_MATRIX.json"
|
||||
);
|
||||
|
||||
fn input(
|
||||
payload: &[u8],
|
||||
accounts: &[(&str, bool, bool)],
|
||||
@@ -1262,7 +1258,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn instruction_matrix_is_machine_readable_and_matches_compiled_inventory() {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(MATRIX_JSON) {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_INSTRUCTION_MATRIX.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("instruction matrix must parse: {error}"),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/decoder/metadata/solana_program_metadata/model.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Public bounded models for the Solana Program Metadata wire contract.
|
||||
|
||||
@@ -24,16 +24,14 @@ impl DcMetadataSpmAccountDiscriminator {
|
||||
|
||||
/// Resolves an exact wire discriminant.
|
||||
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
|
||||
match value {
|
||||
0 => return std::result::Result::Ok(Self::Empty),
|
||||
1 => return std::result::Result::Ok(Self::Buffer),
|
||||
2 => return std::result::Result::Ok(Self::Metadata),
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
crate::DcMetadataSpmModelError::UnknownAccountDiscriminator { value },
|
||||
);
|
||||
},
|
||||
}
|
||||
return match value {
|
||||
0 => std::result::Result::Ok(Self::Empty),
|
||||
1 => std::result::Result::Ok(Self::Buffer),
|
||||
2 => std::result::Result::Ok(Self::Metadata),
|
||||
_ => std::result::Result::Err(
|
||||
crate::DcMetadataSpmModelError::UnknownAccountDiscriminator { value },
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,17 +58,15 @@ impl DcMetadataSpmEncoding {
|
||||
|
||||
/// Resolves an exact wire discriminant.
|
||||
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
|
||||
match value {
|
||||
0 => return std::result::Result::Ok(Self::None),
|
||||
1 => return std::result::Result::Ok(Self::Utf8),
|
||||
2 => return std::result::Result::Ok(Self::Base58),
|
||||
3 => return std::result::Result::Ok(Self::Base64),
|
||||
return match value {
|
||||
0 => std::result::Result::Ok(Self::None),
|
||||
1 => std::result::Result::Ok(Self::Utf8),
|
||||
2 => std::result::Result::Ok(Self::Base58),
|
||||
3 => std::result::Result::Ok(Self::Base64),
|
||||
_ => {
|
||||
return std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownEncoding {
|
||||
value,
|
||||
});
|
||||
std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownEncoding { value })
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,16 +91,14 @@ impl DcMetadataSpmCompression {
|
||||
|
||||
/// Resolves an exact wire discriminant.
|
||||
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
|
||||
match value {
|
||||
0 => return std::result::Result::Ok(Self::None),
|
||||
1 => return std::result::Result::Ok(Self::Gzip),
|
||||
2 => return std::result::Result::Ok(Self::Zlib),
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
crate::DcMetadataSpmModelError::UnknownCompression { value },
|
||||
);
|
||||
},
|
||||
}
|
||||
return match value {
|
||||
0 => std::result::Result::Ok(Self::None),
|
||||
1 => std::result::Result::Ok(Self::Gzip),
|
||||
2 => std::result::Result::Ok(Self::Zlib),
|
||||
_ => std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownCompression {
|
||||
value,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,17 +125,13 @@ impl DcMetadataSpmFormat {
|
||||
|
||||
/// Resolves an exact wire discriminant.
|
||||
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
|
||||
match value {
|
||||
0 => return std::result::Result::Ok(Self::None),
|
||||
1 => return std::result::Result::Ok(Self::Json),
|
||||
2 => return std::result::Result::Ok(Self::Yaml),
|
||||
3 => return std::result::Result::Ok(Self::Toml),
|
||||
_ => {
|
||||
return std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownFormat {
|
||||
value,
|
||||
});
|
||||
},
|
||||
}
|
||||
return match value {
|
||||
0 => std::result::Result::Ok(Self::None),
|
||||
1 => std::result::Result::Ok(Self::Json),
|
||||
2 => std::result::Result::Ok(Self::Yaml),
|
||||
3 => std::result::Result::Ok(Self::Toml),
|
||||
_ => std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownFormat { value }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,16 +156,14 @@ impl DcMetadataSpmDataSource {
|
||||
|
||||
/// Resolves an exact wire discriminant.
|
||||
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
|
||||
match value {
|
||||
0 => return std::result::Result::Ok(Self::Direct),
|
||||
1 => return std::result::Result::Ok(Self::Url),
|
||||
2 => return std::result::Result::Ok(Self::External),
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
crate::DcMetadataSpmModelError::UnknownDataSource { value },
|
||||
);
|
||||
},
|
||||
}
|
||||
return match value {
|
||||
0 => std::result::Result::Ok(Self::Direct),
|
||||
1 => std::result::Result::Ok(Self::Url),
|
||||
2 => std::result::Result::Ok(Self::External),
|
||||
_ => std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownDataSource {
|
||||
value,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,18 +192,16 @@ impl DcMetadataSpmProgramError {
|
||||
|
||||
/// Resolves an exact custom program error code.
|
||||
pub fn from_code(value: u32) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
|
||||
match value {
|
||||
0 => return std::result::Result::Ok(Self::NotExecutableAccount),
|
||||
1 => return std::result::Result::Ok(Self::InvalidProgramState),
|
||||
2 => return std::result::Result::Ok(Self::InvalidProgramDataAccount),
|
||||
3 => return std::result::Result::Ok(Self::ImmutableMetadataAccount),
|
||||
4 => return std::result::Result::Ok(Self::InvalidDataLength),
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
crate::DcMetadataSpmModelError::UnknownProgramError { value },
|
||||
);
|
||||
},
|
||||
}
|
||||
return match value {
|
||||
0 => std::result::Result::Ok(Self::NotExecutableAccount),
|
||||
1 => std::result::Result::Ok(Self::InvalidProgramState),
|
||||
2 => std::result::Result::Ok(Self::InvalidProgramDataAccount),
|
||||
3 => std::result::Result::Ok(Self::ImmutableMetadataAccount),
|
||||
4 => std::result::Result::Ok(Self::InvalidDataLength),
|
||||
_ => std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownProgramError {
|
||||
value,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,12 +258,12 @@ impl DcMetadataSpmSeed {
|
||||
while length > 0 && self.0[length - 1] == 0 {
|
||||
length -= 1;
|
||||
}
|
||||
match std::str::from_utf8(&self.0[..length]) {
|
||||
std::result::Result::Ok(value) => return std::result::Result::Ok(value),
|
||||
return match std::str::from_utf8(&self.0[..length]) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedUtf8);
|
||||
std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedUtf8)
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,11 +347,11 @@ pub enum DcMetadataSpmData {
|
||||
impl DcMetadataSpmData {
|
||||
/// Returns the source discriminator associated with this content.
|
||||
pub const fn source(&self) -> crate::DcMetadataSpmDataSource {
|
||||
match self {
|
||||
Self::Direct(_) => return crate::DcMetadataSpmDataSource::Direct,
|
||||
Self::Url(_) => return crate::DcMetadataSpmDataSource::Url,
|
||||
Self::External(_) => return crate::DcMetadataSpmDataSource::External,
|
||||
}
|
||||
return match self {
|
||||
Self::Direct(_) => crate::DcMetadataSpmDataSource::Direct,
|
||||
Self::Url(_) => crate::DcMetadataSpmDataSource::Url,
|
||||
Self::External(_) => crate::DcMetadataSpmDataSource::External,
|
||||
};
|
||||
}
|
||||
|
||||
/// Validates source-specific length requirements and the `u32` header bound.
|
||||
@@ -389,11 +375,11 @@ impl DcMetadataSpmData {
|
||||
|
||||
/// Returns the exact number of bytes represented on wire.
|
||||
pub fn wire_byte_len(&self) -> usize {
|
||||
match self {
|
||||
Self::Direct(value) => return value.len(),
|
||||
Self::Url(value) => return value.len(),
|
||||
Self::External(_) => return crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
|
||||
}
|
||||
return match self {
|
||||
Self::Direct(value) => value.len(),
|
||||
Self::Url(value) => value.len(),
|
||||
Self::External(_) => crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the exact little-endian header length after validation.
|
||||
@@ -403,14 +389,14 @@ impl DcMetadataSpmData {
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
let actual_bytes = self.wire_byte_len();
|
||||
match u32::try_from(actual_bytes) {
|
||||
std::result::Result::Ok(value) => return std::result::Result::Ok(value),
|
||||
return match u32::try_from(actual_bytes) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(
|
||||
crate::DcMetadataSpmModelError::DataLengthExceedsU32 { actual_bytes },
|
||||
);
|
||||
std::result::Result::Err(crate::DcMetadataSpmModelError::DataLengthExceedsU32 {
|
||||
actual_bytes,
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Serializes the exact content bytes without fetching URL or external-account data.
|
||||
@@ -421,15 +407,11 @@ impl DcMetadataSpmData {
|
||||
std::result::Result::Ok(()) => {},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
match self {
|
||||
Self::Direct(value) => return std::result::Result::Ok(value.clone()),
|
||||
Self::Url(value) => {
|
||||
return std::result::Result::Ok(value.as_bytes().to_vec());
|
||||
},
|
||||
Self::External(value) => {
|
||||
return std::result::Result::Ok(value.to_wire_bytes().to_vec());
|
||||
},
|
||||
}
|
||||
return match self {
|
||||
Self::Direct(value) => std::result::Result::Ok(value.clone()),
|
||||
Self::Url(value) => std::result::Result::Ok(value.as_bytes().to_vec()),
|
||||
Self::External(value) => std::result::Result::Ok(value.to_wire_bytes().to_vec()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,56 +485,50 @@ pub enum DcMetadataSpmModelError {
|
||||
|
||||
impl std::fmt::Display for DcMetadataSpmModelError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
return match self {
|
||||
Self::UnknownAccountDiscriminator { value } => {
|
||||
return write!(formatter, "unknown account discriminator {value}");
|
||||
write!(formatter, "unknown account discriminator {value}")
|
||||
},
|
||||
Self::UnknownEncoding { value } => {
|
||||
return write!(formatter, "unknown encoding discriminator {value}");
|
||||
write!(formatter, "unknown encoding discriminator {value}")
|
||||
},
|
||||
Self::UnknownCompression { value } => {
|
||||
return write!(formatter, "unknown compression discriminator {value}");
|
||||
write!(formatter, "unknown compression discriminator {value}")
|
||||
},
|
||||
Self::UnknownFormat { value } => {
|
||||
return write!(formatter, "unknown format discriminator {value}");
|
||||
write!(formatter, "unknown format discriminator {value}")
|
||||
},
|
||||
Self::UnknownDataSource { value } => {
|
||||
return write!(formatter, "unknown data-source discriminator {value}");
|
||||
write!(formatter, "unknown data-source discriminator {value}")
|
||||
},
|
||||
Self::UnknownProgramError { value } => {
|
||||
return write!(formatter, "unknown program metadata error code {value}");
|
||||
write!(formatter, "unknown program metadata error code {value}")
|
||||
},
|
||||
Self::SeedUtf8TooLong { actual_bytes, max_bytes } => {
|
||||
return write!(
|
||||
formatter,
|
||||
"seed UTF-8 length {actual_bytes} exceeds {max_bytes} bytes",
|
||||
);
|
||||
write!(formatter, "seed UTF-8 length {actual_bytes} exceeds {max_bytes} bytes",)
|
||||
},
|
||||
Self::InvalidSeedLength { actual_bytes, expected_bytes } => {
|
||||
return write!(
|
||||
write!(
|
||||
formatter,
|
||||
"seed length {actual_bytes} does not equal {expected_bytes} bytes",
|
||||
);
|
||||
)
|
||||
},
|
||||
Self::InvalidSeedUtf8 => {
|
||||
return formatter.write_str("seed contains invalid UTF-8 before terminal padding");
|
||||
formatter.write_str("seed contains invalid UTF-8 before terminal padding")
|
||||
},
|
||||
Self::InvalidExternalDataLength { actual_bytes, expected_bytes } => {
|
||||
return write!(
|
||||
write!(
|
||||
formatter,
|
||||
"external data length {actual_bytes} does not equal {expected_bytes} bytes",
|
||||
);
|
||||
)
|
||||
},
|
||||
Self::EmptyData { source } => {
|
||||
return write!(formatter, "metadata data source {source:?} cannot be empty");
|
||||
write!(formatter, "metadata data source {source:?} cannot be empty")
|
||||
},
|
||||
Self::DataLengthExceedsU32 { actual_bytes } => {
|
||||
return write!(
|
||||
formatter,
|
||||
"metadata data length {actual_bytes} exceeds the u32 wire bound",
|
||||
);
|
||||
write!(formatter, "metadata data length {actual_bytes} exceeds the u32 wire bound",)
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/executor.rs
|
||||
// version: 14
|
||||
// version: 16
|
||||
|
||||
//! Consolidated executor modules.
|
||||
|
||||
@@ -208,6 +208,30 @@ pub use self::lending::ExLendingKaminoExecutor;
|
||||
pub use self::lending::ExLendingMarginfiV2Executor;
|
||||
/// Exposes the reserved `lock/raydium_lp` executor.
|
||||
pub use self::lock::ExLockRaydiumLpExecutor;
|
||||
/// Stable Solana Program Metadata `Allocate` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_ALLOCATE_OPERATION;
|
||||
/// Stable Solana Program Metadata `Close` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_CLOSE_OPERATION;
|
||||
/// Solana Program Metadata operations requiring explicit destructive approval.
|
||||
pub use self::metadata::EX_METADATA_SPM_DESTRUCTIVE_OPERATION_CODES;
|
||||
/// Stable Solana Program Metadata `Extend` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_EXTEND_OPERATION;
|
||||
/// Stable Solana Program Metadata `Initialize` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_INITIALIZE_OPERATION;
|
||||
/// Conservative maximum inline Solana Program Metadata content size.
|
||||
pub use self::metadata::EX_METADATA_SPM_MAX_INLINE_DATA_BYTES;
|
||||
/// Stable Solana Program Metadata `SetAuthority` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_SET_AUTHORITY_OPERATION;
|
||||
/// Stable Solana Program Metadata `SetData` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_SET_DATA_OPERATION;
|
||||
/// Stable Solana Program Metadata `SetImmutable` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_SET_IMMUTABLE_OPERATION;
|
||||
/// Exact Solana Program Metadata operation inventory.
|
||||
pub use self::metadata::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES;
|
||||
/// Stable Solana Program Metadata `Trim` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_TRIM_OPERATION;
|
||||
/// Stable Solana Program Metadata `Write` operation code.
|
||||
pub use self::metadata::EX_METADATA_SPM_WRITE_OPERATION;
|
||||
/// Exposes `EX_METAPLEX_TOKEN_METADATA_APPROVE_USE_AUTHORITY_OPERATION`.
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_APPROVE_USE_AUTHORITY_OPERATION;
|
||||
/// Exposes `EX_METAPLEX_TOKEN_METADATA_BURN_OPERATION`.
|
||||
@@ -284,14 +308,38 @@ pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_UTILIZE_OPERATION;
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_VERIFY_OPERATION;
|
||||
/// Exposes the Metaplex Token Metadata executor.
|
||||
pub use self::metadata::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
/// Reserved `kb-lib.executor.metadata.solana_program_metadata` executor.
|
||||
/// Typed Solana Program Metadata executor.
|
||||
pub use self::metadata::ExMetadataSolanaProgramMetadataExecutor;
|
||||
/// Exposes the reserved `metadata/spl_name_service` executor.
|
||||
pub use self::metadata::ExMetadataSplNameServiceExecutor;
|
||||
/// Solana Program Metadata compression intent.
|
||||
pub use self::metadata::ExMetadataSpmCompression;
|
||||
/// Solana Program Metadata inline data intent.
|
||||
pub use self::metadata::ExMetadataSpmDataInput;
|
||||
/// Solana Program Metadata data-source intent.
|
||||
pub use self::metadata::ExMetadataSpmDataSource;
|
||||
/// Solana Program Metadata encoding intent.
|
||||
pub use self::metadata::ExMetadataSpmEncoding;
|
||||
/// Complete Solana Program Metadata execution intent.
|
||||
pub use self::metadata::ExMetadataSpmExecutionIntent;
|
||||
/// Solana Program Metadata format intent.
|
||||
pub use self::metadata::ExMetadataSpmFormat;
|
||||
/// One Solana Program Metadata operation.
|
||||
pub use self::metadata::ExMetadataSpmOperation;
|
||||
/// Optional Solana Program Metadata program context.
|
||||
pub use self::metadata::ExMetadataSpmProgramContext;
|
||||
/// Solana Program Metadata `SetData` source intent.
|
||||
pub use self::metadata::ExMetadataSpmSetDataSource;
|
||||
/// Solana Program Metadata `Write` source intent.
|
||||
pub use self::metadata::ExMetadataSpmWriteSource;
|
||||
/// Exposes the complete Metaplex Token Metadata execution intent.
|
||||
pub use self::metadata::ExMetaplexTokenMetadataExecutionIntent;
|
||||
/// Exposes one current Metaplex Token Metadata operation.
|
||||
pub use self::metadata::ExMetaplexTokenMetadataOperation;
|
||||
/// Derives the canonical Solana Program Metadata PDA.
|
||||
pub use self::metadata::executor_metadata_solana_program_metadata_derive_canonical_pda;
|
||||
/// Derives the non-canonical Solana Program Metadata PDA.
|
||||
pub use self::metadata::executor_metadata_solana_program_metadata_derive_non_canonical_pda;
|
||||
/// Exposes the reserved `nft/metaplex_bubblegum` executor.
|
||||
pub use self::nft::ExNftMetaplexBubblegumExecutor;
|
||||
/// Reserved `kb-lib.executor.nft.tensor_cnft` executor.
|
||||
@@ -993,6 +1041,10 @@ pub use self::wallet::ExWalletJupiterApeproSmartWalletExecutor;
|
||||
/// Exposes the reserved `weighted/swap_stabble` executor.
|
||||
pub use self::weighted::ExWeightedSwapStabbleExecutor;
|
||||
|
||||
/// Stable Solana Program Metadata executor name.
|
||||
pub(crate) use self::metadata::EX_METADATA_SPM_EXECUTOR_NAME;
|
||||
/// Internal Solana Program Metadata prepared-plan builder.
|
||||
pub(crate) use self::metadata::executor_metadata_solana_program_metadata_build_prepared_plan;
|
||||
/// Internal Metaplex Token Metadata prepared-plan builder.
|
||||
pub(crate) use self::metadata::executor_metaplex_token_metadata_build_prepared_plan;
|
||||
/// Stable operation code for the nonce advance injected by durable transaction assembly.
|
||||
@@ -1078,11 +1130,10 @@ pub(crate) use self::spl::executor_spl_token_build_prepared_plan;
|
||||
mod reserved_executor_tests {
|
||||
#[test]
|
||||
fn every_reserved_executor_is_registered_and_builds_only_an_empty_plan() {
|
||||
let executors: [&dyn crate::ExApiInstructionExecutor; 104] = [
|
||||
let executors: [&dyn crate::ExApiInstructionExecutor; 103] = [
|
||||
&crate::ExAdapterSaberDecimalWrapperExecutor,
|
||||
&crate::ExAdapterSplTokenWrapExecutor,
|
||||
&crate::ExGovernanceSquadsMultisigExecutor,
|
||||
&crate::ExMetadataSolanaProgramMetadataExecutor,
|
||||
&crate::ExNftTensorCnftExecutor,
|
||||
&crate::ExPerpetualsPhoenixEternalExecutor,
|
||||
&crate::ExStakingJitoTipDistributionExecutor,
|
||||
@@ -1184,7 +1235,7 @@ mod reserved_executor_tests {
|
||||
&crate::ExWalletJupiterApeproSmartWalletExecutor,
|
||||
&crate::ExWeightedSwapStabbleExecutor,
|
||||
];
|
||||
assert_eq!(executors.len(), 104);
|
||||
assert_eq!(executors.len(), 103);
|
||||
for executor in executors {
|
||||
assert!(!executor.program_ids().is_empty());
|
||||
for program_id in executor.program_ids() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/executor/metadata.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! `metadata` executor family.
|
||||
|
||||
@@ -87,9 +87,61 @@ pub use self::metaplex_token_metadata::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
pub use self::metaplex_token_metadata::ExMetaplexTokenMetadataExecutionIntent;
|
||||
/// Exposes the Metaplex Token Metadata operation intent.
|
||||
pub use self::metaplex_token_metadata::ExMetaplexTokenMetadataOperation;
|
||||
/// Exposes the reserved `metadata/solana_program_metadata` executor.
|
||||
/// Stable Solana Program Metadata `Allocate` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_ALLOCATE_OPERATION;
|
||||
/// Stable Solana Program Metadata `Close` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_CLOSE_OPERATION;
|
||||
/// Solana Program Metadata operations requiring explicit destructive approval.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_DESTRUCTIVE_OPERATION_CODES;
|
||||
/// Stable Solana Program Metadata `Extend` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_EXTEND_OPERATION;
|
||||
/// Stable Solana Program Metadata `Initialize` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_INITIALIZE_OPERATION;
|
||||
/// Conservative maximum inline Solana Program Metadata content size.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_MAX_INLINE_DATA_BYTES;
|
||||
/// Stable Solana Program Metadata `SetAuthority` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_SET_AUTHORITY_OPERATION;
|
||||
/// Stable Solana Program Metadata `SetData` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_SET_DATA_OPERATION;
|
||||
/// Stable Solana Program Metadata `SetImmutable` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_SET_IMMUTABLE_OPERATION;
|
||||
/// Exact Solana Program Metadata operation inventory.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES;
|
||||
/// Stable Solana Program Metadata `Trim` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_TRIM_OPERATION;
|
||||
/// Stable Solana Program Metadata `Write` operation code.
|
||||
pub use self::solana_program_metadata::EX_METADATA_SPM_WRITE_OPERATION;
|
||||
/// Typed Solana Program Metadata executor.
|
||||
pub use self::solana_program_metadata::ExMetadataSolanaProgramMetadataExecutor;
|
||||
/// Solana Program Metadata compression intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmCompression;
|
||||
/// Solana Program Metadata inline data intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmDataInput;
|
||||
/// Solana Program Metadata data-source intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmDataSource;
|
||||
/// Solana Program Metadata encoding intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmEncoding;
|
||||
/// Complete Solana Program Metadata execution intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmExecutionIntent;
|
||||
/// Solana Program Metadata format intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmFormat;
|
||||
/// One Solana Program Metadata operation.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmOperation;
|
||||
/// Optional Solana Program Metadata program context.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmProgramContext;
|
||||
/// Solana Program Metadata `SetData` source intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmSetDataSource;
|
||||
/// Solana Program Metadata `Write` source intent.
|
||||
pub use self::solana_program_metadata::ExMetadataSpmWriteSource;
|
||||
/// Derives the canonical Solana Program Metadata PDA.
|
||||
pub use self::solana_program_metadata::executor_metadata_solana_program_metadata_derive_canonical_pda;
|
||||
/// Derives the non-canonical Solana Program Metadata PDA.
|
||||
pub use self::solana_program_metadata::executor_metadata_solana_program_metadata_derive_non_canonical_pda;
|
||||
/// Exposes the reserved `metadata/spl_name_service` executor.
|
||||
pub use self::spl_name_service::ExMetadataSplNameServiceExecutor;
|
||||
|
||||
pub(crate) use self::metaplex_token_metadata::executor_metaplex_token_metadata_build_prepared_plan;
|
||||
/// Stable Solana Program Metadata executor name.
|
||||
pub(crate) use self::solana_program_metadata::EX_METADATA_SPM_EXECUTOR_NAME;
|
||||
/// Internal Solana Program Metadata prepared-plan builder.
|
||||
pub(crate) use self::solana_program_metadata::executor_metadata_solana_program_metadata_build_prepared_plan;
|
||||
|
||||
@@ -1,49 +1,65 @@
|
||||
// file: kb-lib/src/executor/metadata/solana_program_metadata.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Reserved executor for `metadata_solana_program_metadata`.
|
||||
//! Typed executor for Solana Program Metadata.
|
||||
|
||||
/// Reserved executor for the `metadata_solana_program_metadata` program surface.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExMetadataSolanaProgramMetadataExecutor;
|
||||
mod builder;
|
||||
mod constants;
|
||||
mod executor;
|
||||
mod intent;
|
||||
|
||||
impl crate::ExApiInstructionExecutor for crate::ExMetadataSolanaProgramMetadataExecutor {
|
||||
fn executor_name(&self) -> &'static str {
|
||||
return "kb-lib.executor.metadata.solana_program_metadata";
|
||||
}
|
||||
/// Derives the canonical Metadata PDA for one program and seed.
|
||||
pub use self::builder::executor_metadata_solana_program_metadata_derive_canonical_pda;
|
||||
/// Derives the non-canonical Metadata PDA for one program, authority and seed.
|
||||
pub use self::builder::executor_metadata_solana_program_metadata_derive_non_canonical_pda;
|
||||
/// Stable operation code for `Allocate`.
|
||||
pub use self::constants::EX_METADATA_SPM_ALLOCATE_OPERATION;
|
||||
/// Stable operation code for `Close`.
|
||||
pub use self::constants::EX_METADATA_SPM_CLOSE_OPERATION;
|
||||
/// Operations requiring explicit destructive-operation approval.
|
||||
pub use self::constants::EX_METADATA_SPM_DESTRUCTIVE_OPERATION_CODES;
|
||||
/// Stable operation code for `Extend`.
|
||||
pub use self::constants::EX_METADATA_SPM_EXTEND_OPERATION;
|
||||
/// Stable operation code for `Initialize`.
|
||||
pub use self::constants::EX_METADATA_SPM_INITIALIZE_OPERATION;
|
||||
/// Conservative maximum inline content accepted by Solana Program Metadata builders.
|
||||
pub use self::constants::EX_METADATA_SPM_MAX_INLINE_DATA_BYTES;
|
||||
/// Stable operation code for `SetAuthority`.
|
||||
pub use self::constants::EX_METADATA_SPM_SET_AUTHORITY_OPERATION;
|
||||
/// Stable operation code for `SetData`.
|
||||
pub use self::constants::EX_METADATA_SPM_SET_DATA_OPERATION;
|
||||
/// Stable operation code for `SetImmutable`.
|
||||
pub use self::constants::EX_METADATA_SPM_SET_IMMUTABLE_OPERATION;
|
||||
/// Exact current operation inventory implemented by the executor.
|
||||
pub use self::constants::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES;
|
||||
/// Stable operation code for `Trim`.
|
||||
pub use self::constants::EX_METADATA_SPM_TRIM_OPERATION;
|
||||
/// Stable operation code for `Write`.
|
||||
pub use self::constants::EX_METADATA_SPM_WRITE_OPERATION;
|
||||
/// Typed Solana Program Metadata executor.
|
||||
pub use self::executor::ExMetadataSolanaProgramMetadataExecutor;
|
||||
/// Compression written by execution intents.
|
||||
pub use self::intent::ExMetadataSpmCompression;
|
||||
/// Exact inline content accepted by metadata instructions.
|
||||
pub use self::intent::ExMetadataSpmDataInput;
|
||||
/// On-chain data-source discriminator.
|
||||
pub use self::intent::ExMetadataSpmDataSource;
|
||||
/// Encoding written by execution intents.
|
||||
pub use self::intent::ExMetadataSpmEncoding;
|
||||
/// Complete Solana Program Metadata execution intent.
|
||||
pub use self::intent::ExMetadataSpmExecutionIntent;
|
||||
/// Structured content format written by execution intents.
|
||||
pub use self::intent::ExMetadataSpmFormat;
|
||||
/// One exact Solana Program Metadata operation.
|
||||
pub use self::intent::ExMetadataSpmOperation;
|
||||
/// Optional program-upgrade-authority validation context.
|
||||
pub use self::intent::ExMetadataSpmProgramContext;
|
||||
/// Source used by `SetData`.
|
||||
pub use self::intent::ExMetadataSpmSetDataSource;
|
||||
/// Source used by `Write`.
|
||||
pub use self::intent::ExMetadataSpmWriteSource;
|
||||
|
||||
fn executor_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn program_ids(&self) -> &'static [&'static str] {
|
||||
return &[kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID];
|
||||
}
|
||||
|
||||
fn supports_request(
|
||||
&self,
|
||||
request: &crate::ExApiExecutionRequest,
|
||||
) -> crate::ExApiExecutionSupport {
|
||||
if crate::ExApiInstructionExecutor::handles_program_id(self, &request.program_id) {
|
||||
return crate::ExApiExecutionSupport::Maybe;
|
||||
}
|
||||
return crate::ExApiExecutionSupport::No;
|
||||
}
|
||||
|
||||
fn build_plan(
|
||||
&self,
|
||||
_request: &crate::ExApiExecutionRequest,
|
||||
) -> kb_core::Result<crate::ExApiExecutionPlan> {
|
||||
let payload_json = match crate::executor_api_serialize_payload_json(
|
||||
&serde_json::json!({"status":"reserved_executor","surface":"metadata_solana_program_metadata"}),
|
||||
) {
|
||||
std::result::Result::Ok(serialized) => serialized,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::ExApiExecutionPlan {
|
||||
executor_name: "kb-lib.executor.metadata.solana_program_metadata".to_string(),
|
||||
instruction_count: 0,
|
||||
payload_json,
|
||||
});
|
||||
}
|
||||
}
|
||||
/// Crate-root access to the exact prepared-plan builder.
|
||||
pub(crate) use self::builder::executor_metadata_solana_program_metadata_build_prepared_plan;
|
||||
/// Stable executor name.
|
||||
pub(crate) use self::constants::EX_METADATA_SPM_EXECUTOR_NAME;
|
||||
|
||||
1270
kb-lib/src/executor/metadata/solana_program_metadata/builder.rs
Normal file
1270
kb-lib/src/executor/metadata/solana_program_metadata/builder.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
// file: kb-lib/src/executor/metadata/solana_program_metadata/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Stable Solana Program Metadata executor constants.
|
||||
|
||||
/// Stable executor name.
|
||||
pub(crate) const EX_METADATA_SPM_EXECUTOR_NAME: &str =
|
||||
"kb-lib.executor.metadata.solana_program_metadata";
|
||||
/// Conservative maximum inline content accepted by one instruction builder.
|
||||
///
|
||||
/// Larger payloads must use a pre-funded Buffer account so the final transaction remains within
|
||||
/// the Solana packet boundary after signatures, account keys and message framing are included.
|
||||
pub const EX_METADATA_SPM_MAX_INLINE_DATA_BYTES: usize = 1_024;
|
||||
/// Stable operation code for `Write`.
|
||||
pub const EX_METADATA_SPM_WRITE_OPERATION: &str = "metadata.solana_program_metadata.write";
|
||||
/// Stable operation code for `Initialize`.
|
||||
pub const EX_METADATA_SPM_INITIALIZE_OPERATION: &str =
|
||||
"metadata.solana_program_metadata.initialize";
|
||||
/// Stable operation code for `SetAuthority`.
|
||||
pub const EX_METADATA_SPM_SET_AUTHORITY_OPERATION: &str =
|
||||
"metadata.solana_program_metadata.set_authority";
|
||||
/// Stable operation code for `SetData`.
|
||||
pub const EX_METADATA_SPM_SET_DATA_OPERATION: &str = "metadata.solana_program_metadata.set_data";
|
||||
/// Stable operation code for `SetImmutable`.
|
||||
pub const EX_METADATA_SPM_SET_IMMUTABLE_OPERATION: &str =
|
||||
"metadata.solana_program_metadata.set_immutable";
|
||||
/// Stable operation code for `Trim`.
|
||||
pub const EX_METADATA_SPM_TRIM_OPERATION: &str = "metadata.solana_program_metadata.trim";
|
||||
/// Stable operation code for `Close`.
|
||||
pub const EX_METADATA_SPM_CLOSE_OPERATION: &str = "metadata.solana_program_metadata.close";
|
||||
/// Stable operation code for `Allocate`.
|
||||
pub const EX_METADATA_SPM_ALLOCATE_OPERATION: &str = "metadata.solana_program_metadata.allocate";
|
||||
/// Stable operation code for `Extend`.
|
||||
pub const EX_METADATA_SPM_EXTEND_OPERATION: &str = "metadata.solana_program_metadata.extend";
|
||||
/// Exact current operation inventory implemented by the executor.
|
||||
pub const EX_METADATA_SPM_SUPPORTED_OPERATION_CODES: &[&str] = &[
|
||||
crate::EX_METADATA_SPM_WRITE_OPERATION,
|
||||
crate::EX_METADATA_SPM_INITIALIZE_OPERATION,
|
||||
crate::EX_METADATA_SPM_SET_AUTHORITY_OPERATION,
|
||||
crate::EX_METADATA_SPM_SET_DATA_OPERATION,
|
||||
crate::EX_METADATA_SPM_SET_IMMUTABLE_OPERATION,
|
||||
crate::EX_METADATA_SPM_TRIM_OPERATION,
|
||||
crate::EX_METADATA_SPM_CLOSE_OPERATION,
|
||||
crate::EX_METADATA_SPM_ALLOCATE_OPERATION,
|
||||
crate::EX_METADATA_SPM_EXTEND_OPERATION,
|
||||
];
|
||||
/// Operations that require explicit operator approval before plan construction.
|
||||
pub const EX_METADATA_SPM_DESTRUCTIVE_OPERATION_CODES: &[&str] = &[
|
||||
crate::EX_METADATA_SPM_WRITE_OPERATION,
|
||||
crate::EX_METADATA_SPM_SET_AUTHORITY_OPERATION,
|
||||
crate::EX_METADATA_SPM_SET_DATA_OPERATION,
|
||||
crate::EX_METADATA_SPM_SET_IMMUTABLE_OPERATION,
|
||||
crate::EX_METADATA_SPM_TRIM_OPERATION,
|
||||
crate::EX_METADATA_SPM_CLOSE_OPERATION,
|
||||
];
|
||||
224
kb-lib/src/executor/metadata/solana_program_metadata/executor.rs
Normal file
224
kb-lib/src/executor/metadata/solana_program_metadata/executor.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
// file: kb-lib/src/executor/metadata/solana_program_metadata/executor.rs
|
||||
// version: 1
|
||||
|
||||
//! Exact capability dispatch for Solana Program Metadata.
|
||||
|
||||
/// Typed Solana Program Metadata executor.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExMetadataSolanaProgramMetadataExecutor;
|
||||
|
||||
impl crate::ExMetadataSolanaProgramMetadataExecutor {
|
||||
fn exact_capability(
|
||||
&self,
|
||||
program_id: &crate::MdProgramId,
|
||||
operation_code: &str,
|
||||
) -> crate::ExApiExecutionCapability {
|
||||
if program_id.0 != kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID {
|
||||
return crate::ExApiExecutionCapability::unsupported(
|
||||
"execution_metadata_spm_program_not_owned",
|
||||
format!("program {} is not Solana Program Metadata", program_id.0),
|
||||
);
|
||||
}
|
||||
if crate::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES
|
||||
.iter()
|
||||
.any(|candidate| return *candidate == operation_code)
|
||||
{
|
||||
return crate::ExApiExecutionCapability::supported(operation_code);
|
||||
}
|
||||
return crate::ExApiExecutionCapability::unsupported(
|
||||
"execution_metadata_spm_operation_unsupported",
|
||||
format!("Solana Program Metadata operation {operation_code} is not implemented"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ExApiTypedInstructionExecutor for crate::ExMetadataSolanaProgramMetadataExecutor {
|
||||
type Intent = crate::ExMetadataSpmExecutionIntent;
|
||||
|
||||
fn capability(
|
||||
&self,
|
||||
program_id: &crate::MdProgramId,
|
||||
operation_code: &str,
|
||||
) -> crate::ExApiExecutionCapability {
|
||||
return self.exact_capability(program_id, operation_code);
|
||||
}
|
||||
|
||||
fn build_prepared_plan(
|
||||
&self,
|
||||
intent: &Self::Intent,
|
||||
) -> kb_core::Result<crate::ExApiPreparedExecutionPlan> {
|
||||
let program_id = crate::MdProgramId(
|
||||
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
|
||||
);
|
||||
return match self.exact_capability(&program_id, intent.operation.operation_code()) {
|
||||
crate::ExApiExecutionCapability::Supported { operation_code: _ } => {
|
||||
crate::executor_metadata_solana_program_metadata_build_prepared_plan(intent)
|
||||
},
|
||||
crate::ExApiExecutionCapability::Unsupported { reason_code, reason } => {
|
||||
std::result::Result::Err(kb_core::Error::new(reason_code, reason))
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ExApiInstructionExecutor for crate::ExMetadataSolanaProgramMetadataExecutor {
|
||||
fn executor_name(&self) -> &'static str {
|
||||
return crate::EX_METADATA_SPM_EXECUTOR_NAME;
|
||||
}
|
||||
|
||||
fn executor_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn program_ids(&self) -> &'static [&'static str] {
|
||||
return &[kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID];
|
||||
}
|
||||
|
||||
fn supports_request(
|
||||
&self,
|
||||
request: &crate::ExApiExecutionRequest,
|
||||
) -> crate::ExApiExecutionSupport {
|
||||
return if self
|
||||
.exact_capability(&request.program_id, &request.operation_code)
|
||||
.is_supported()
|
||||
{
|
||||
crate::ExApiExecutionSupport::Yes
|
||||
} else {
|
||||
crate::ExApiExecutionSupport::No
|
||||
};
|
||||
}
|
||||
|
||||
fn build_plan(
|
||||
&self,
|
||||
request: &crate::ExApiExecutionRequest,
|
||||
) -> kb_core::Result<crate::ExApiExecutionPlan> {
|
||||
match self.exact_capability(&request.program_id, &request.operation_code) {
|
||||
crate::ExApiExecutionCapability::Supported { operation_code: _ } => {},
|
||||
crate::ExApiExecutionCapability::Unsupported { reason_code, reason } => {
|
||||
return std::result::Result::Err(kb_core::Error::new(reason_code, reason));
|
||||
},
|
||||
}
|
||||
let intent = match serde_json::from_str::<crate::ExMetadataSpmExecutionIntent>(
|
||||
&request.payload_json,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metadata_spm_intent_deserialize_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
if intent.operation.operation_code() != request.operation_code {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_operation_code_mismatch",
|
||||
"request and typed Solana Program Metadata intent operations differ",
|
||||
));
|
||||
}
|
||||
let prepared =
|
||||
match crate::ExApiTypedInstructionExecutor::build_prepared_plan(self, &intent) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payload = match serde_json::to_value(&prepared) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metadata_spm_plan_serialize_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let payload_json = match crate::executor_api_serialize_payload_json(&payload) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::ExApiExecutionPlan {
|
||||
executor_name: crate::EX_METADATA_SPM_EXECUTOR_NAME.to_string(),
|
||||
instruction_count: prepared.instructions.len(),
|
||||
payload_json,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn executor_matrix_matches_the_compiled_capability_inventory() {
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_EXECUTOR_MATRIX.json"
|
||||
));
|
||||
assert!(parsed.is_ok());
|
||||
let matrix = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let operations = match matrix.get("operations").and_then(serde_json::Value::as_array) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
assert_eq!(
|
||||
matrix.get("maxInlineContentBytes").and_then(serde_json::Value::as_u64),
|
||||
std::option::Option::Some(crate::EX_METADATA_SPM_MAX_INLINE_DATA_BYTES as u64)
|
||||
);
|
||||
assert_eq!(
|
||||
matrix.get("canonicitySelection").and_then(serde_json::Value::as_str),
|
||||
std::option::Option::Some(
|
||||
"explicit_intent_flag_not_inferred_from_program_data_presence"
|
||||
)
|
||||
);
|
||||
assert_eq!(operations.len(), crate::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.len());
|
||||
for (position, row) in operations.iter().enumerate() {
|
||||
assert_eq!(
|
||||
row.get("tag").and_then(serde_json::Value::as_u64),
|
||||
std::option::Option::Some(position as u64)
|
||||
);
|
||||
assert_eq!(
|
||||
row.get("operationCode").and_then(serde_json::Value::as_str),
|
||||
std::option::Option::Some(
|
||||
crate::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES[position]
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
row.get("requiresExplicitApproval").and_then(serde_json::Value::as_bool),
|
||||
std::option::Option::Some(
|
||||
crate::EX_METADATA_SPM_DESTRUCTIVE_OPERATION_CODES
|
||||
.contains(&crate::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES[position])
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
row.get("deprecated").and_then(serde_json::Value::as_bool),
|
||||
std::option::Option::Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
row.get("replaced").and_then(serde_json::Value::as_bool),
|
||||
std::option::Option::Some(false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capabilities_cover_exactly_the_nine_stable_operations() {
|
||||
let executor = crate::ExMetadataSolanaProgramMetadataExecutor;
|
||||
let program_id = crate::MdProgramId(
|
||||
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
|
||||
);
|
||||
for operation in crate::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES {
|
||||
let capability =
|
||||
crate::ExApiTypedInstructionExecutor::capability(&executor, &program_id, operation);
|
||||
assert!(capability.is_supported());
|
||||
}
|
||||
let unknown = crate::ExApiTypedInstructionExecutor::capability(
|
||||
&executor,
|
||||
&program_id,
|
||||
"metadata.solana_program_metadata.unknown",
|
||||
);
|
||||
assert!(!unknown.is_supported());
|
||||
let foreign = crate::ExApiTypedInstructionExecutor::capability(
|
||||
&executor,
|
||||
&crate::MdProgramId(kb_program_ids::SYSTEM_PROGRAM_ID.to_string()),
|
||||
crate::EX_METADATA_SPM_WRITE_OPERATION,
|
||||
);
|
||||
assert!(!foreign.is_supported());
|
||||
}
|
||||
}
|
||||
471
kb-lib/src/executor/metadata/solana_program_metadata/intent.rs
Normal file
471
kb-lib/src/executor/metadata/solana_program_metadata/intent.rs
Normal file
@@ -0,0 +1,471 @@
|
||||
// file: kb-lib/src/executor/metadata/solana_program_metadata/intent.rs
|
||||
// version: 1
|
||||
|
||||
//! Typed intents for the Solana Program Metadata executor.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Encoding written to a Solana Program Metadata account.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmEncoding.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmEncoding {
|
||||
/// No encoding declaration.
|
||||
None = 0,
|
||||
/// UTF-8 encoding.
|
||||
Utf8 = 1,
|
||||
/// Base58 encoding.
|
||||
Base58 = 2,
|
||||
/// Base64 encoding.
|
||||
Base64 = 3,
|
||||
}
|
||||
|
||||
impl crate::ExMetadataSpmEncoding {
|
||||
/// Returns the exact wire value.
|
||||
pub const fn wire_value(self) -> u8 {
|
||||
return self as u8;
|
||||
}
|
||||
}
|
||||
|
||||
/// Compression written to a Solana Program Metadata account.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmCompression.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmCompression {
|
||||
/// No compression declaration.
|
||||
None = 0,
|
||||
/// Gzip compression.
|
||||
Gzip = 1,
|
||||
/// Zlib compression.
|
||||
Zlib = 2,
|
||||
}
|
||||
|
||||
impl crate::ExMetadataSpmCompression {
|
||||
/// Returns the exact wire value.
|
||||
pub const fn wire_value(self) -> u8 {
|
||||
return self as u8;
|
||||
}
|
||||
}
|
||||
|
||||
/// Structured format written to a Solana Program Metadata account.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmFormat.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmFormat {
|
||||
/// No structured format declaration.
|
||||
None = 0,
|
||||
/// JSON format.
|
||||
Json = 1,
|
||||
/// YAML format.
|
||||
Yaml = 2,
|
||||
/// TOML format.
|
||||
Toml = 3,
|
||||
}
|
||||
|
||||
impl crate::ExMetadataSpmFormat {
|
||||
/// Returns the exact wire value.
|
||||
pub const fn wire_value(self) -> u8 {
|
||||
return self as u8;
|
||||
}
|
||||
}
|
||||
|
||||
/// On-chain data source written to a Solana Program Metadata account.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[repr(u8)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmDataSource.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmDataSource {
|
||||
/// Direct on-chain bytes.
|
||||
Direct = 0,
|
||||
/// UTF-8 URL stored on-chain without fetching it.
|
||||
Url = 1,
|
||||
/// Fixed reference to another Solana account.
|
||||
External = 2,
|
||||
}
|
||||
|
||||
impl crate::ExMetadataSpmDataSource {
|
||||
/// Returns the exact wire value.
|
||||
pub const fn wire_value(self) -> u8 {
|
||||
return self as u8;
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact inline content accepted by `Initialize` and `SetData`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[serde(tag = "source", rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmDataInput.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmDataInput {
|
||||
/// Direct on-chain bytes.
|
||||
Direct {
|
||||
/// Exact bytes to store.
|
||||
bytes: std::vec::Vec<u8>,
|
||||
},
|
||||
/// UTF-8 URL retained on-chain without remote resolution.
|
||||
Url {
|
||||
/// URL text to store.
|
||||
url: std::string::String,
|
||||
},
|
||||
/// Fixed reference to another account's data.
|
||||
External {
|
||||
/// Referenced account.
|
||||
address: crate::MdPubkey,
|
||||
/// Byte offset inside the referenced account.
|
||||
offset: u32,
|
||||
/// Optional non-zero length; `None` means the remainder of the account.
|
||||
length: std::option::Option<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
impl crate::ExMetadataSpmDataInput {
|
||||
/// Returns the exact data-source discriminator.
|
||||
pub const fn data_source(&self) -> crate::ExMetadataSpmDataSource {
|
||||
return match self {
|
||||
Self::Direct { bytes: _ } => crate::ExMetadataSpmDataSource::Direct,
|
||||
Self::Url { url: _ } => crate::ExMetadataSpmDataSource::Url,
|
||||
Self::External { address: _, offset: _, length: _ } => {
|
||||
crate::ExMetadataSpmDataSource::External
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Source used by the `Write` instruction.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[serde(tag = "source", rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmWriteSource.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmWriteSource {
|
||||
/// Inline bytes placed after the offset argument.
|
||||
Inline {
|
||||
/// Exact bytes to write.
|
||||
data: std::vec::Vec<u8>,
|
||||
},
|
||||
/// Copy all data from another Buffer account.
|
||||
Buffer {
|
||||
/// Source Buffer account.
|
||||
source_buffer: crate::MdPubkey,
|
||||
},
|
||||
}
|
||||
|
||||
/// Source used by the `SetData` instruction.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[serde(tag = "source", rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmSetDataSource.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmSetDataSource {
|
||||
/// Runtime-supported three-byte variant that preserves current data bytes.
|
||||
PreserveExisting,
|
||||
/// Replace data with inline content.
|
||||
Inline {
|
||||
/// Exact inline content.
|
||||
data: crate::ExMetadataSpmDataInput,
|
||||
},
|
||||
/// Copy data from one Buffer account.
|
||||
Buffer {
|
||||
/// Source Buffer account.
|
||||
buffer: crate::MdPubkey,
|
||||
/// Source discriminator that the resulting metadata account must declare.
|
||||
data_source: crate::ExMetadataSpmDataSource,
|
||||
},
|
||||
}
|
||||
|
||||
/// Optional program-upgrade-authority validation context.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmProgramContext.ts"
|
||||
)]
|
||||
pub struct ExMetadataSpmProgramContext {
|
||||
/// Executable program account.
|
||||
pub program: crate::MdPubkey,
|
||||
/// Optional upgradeable-loader ProgramData account used when the runtime must validate it.
|
||||
pub program_data: std::option::Option<crate::MdPubkey>,
|
||||
}
|
||||
|
||||
/// One exact Solana Program Metadata operation.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[serde(tag = "operation", rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmOperation.ts"
|
||||
)]
|
||||
pub enum ExMetadataSpmOperation {
|
||||
/// Write bytes to a Buffer account.
|
||||
Write {
|
||||
/// Buffer account to mutate.
|
||||
buffer: crate::MdPubkey,
|
||||
/// Current Buffer authority.
|
||||
authority: crate::MdPubkey,
|
||||
/// Byte offset inside the Buffer payload.
|
||||
offset: u32,
|
||||
/// Inline or Buffer source.
|
||||
source: crate::ExMetadataSpmWriteSource,
|
||||
},
|
||||
/// Initialize one canonical or non-canonical Metadata PDA.
|
||||
Initialize {
|
||||
/// Metadata PDA to initialize.
|
||||
metadata: crate::MdPubkey,
|
||||
/// Authority signing the initialization.
|
||||
authority: crate::MdPubkey,
|
||||
/// Program described by this metadata account.
|
||||
program: crate::MdPubkey,
|
||||
/// Optional ProgramData account used for an upgradeable program.
|
||||
program_data: std::option::Option<crate::MdPubkey>,
|
||||
/// Whether the Metadata PDA uses the canonical `[program, seed]` derivation.
|
||||
canonical: bool,
|
||||
/// Exact fixed-size seed bytes.
|
||||
seed: std::vec::Vec<u8>,
|
||||
/// Content encoding.
|
||||
encoding: crate::ExMetadataSpmEncoding,
|
||||
/// Content compression.
|
||||
compression: crate::ExMetadataSpmCompression,
|
||||
/// Structured content format.
|
||||
format: crate::ExMetadataSpmFormat,
|
||||
/// Declared data source when using a preallocated Buffer.
|
||||
data_source: crate::ExMetadataSpmDataSource,
|
||||
/// Optional inline content; absent means the Metadata account is a preallocated Buffer.
|
||||
data: std::option::Option<crate::ExMetadataSpmDataInput>,
|
||||
/// Whether the instruction may invoke the System Program to allocate the PDA.
|
||||
allocate_account: bool,
|
||||
},
|
||||
/// Change or remove one Buffer or Metadata authority.
|
||||
SetAuthority {
|
||||
/// Buffer or Metadata account.
|
||||
account: crate::MdPubkey,
|
||||
/// Current authority.
|
||||
authority: crate::MdPubkey,
|
||||
/// New authority, or `None` to remove a Metadata authority.
|
||||
new_authority: std::option::Option<crate::MdPubkey>,
|
||||
/// Optional upgrade-authority validation context.
|
||||
program_context: std::option::Option<crate::ExMetadataSpmProgramContext>,
|
||||
},
|
||||
/// Change Metadata content descriptors and data.
|
||||
SetData {
|
||||
/// Metadata account.
|
||||
metadata: crate::MdPubkey,
|
||||
/// Current authority.
|
||||
authority: crate::MdPubkey,
|
||||
/// Content encoding.
|
||||
encoding: crate::ExMetadataSpmEncoding,
|
||||
/// Content compression.
|
||||
compression: crate::ExMetadataSpmCompression,
|
||||
/// Structured content format.
|
||||
format: crate::ExMetadataSpmFormat,
|
||||
/// Inline, Buffer or preserve-existing source.
|
||||
source: crate::ExMetadataSpmSetDataSource,
|
||||
/// Optional upgrade-authority validation context.
|
||||
program_context: std::option::Option<crate::ExMetadataSpmProgramContext>,
|
||||
},
|
||||
/// Permanently make one Metadata account immutable.
|
||||
SetImmutable {
|
||||
/// Metadata account.
|
||||
metadata: crate::MdPubkey,
|
||||
/// Current authority.
|
||||
authority: crate::MdPubkey,
|
||||
/// Optional upgrade-authority validation context.
|
||||
program_context: std::option::Option<crate::ExMetadataSpmProgramContext>,
|
||||
},
|
||||
/// Resize one Buffer or Metadata account to its logical minimum and refund excess lamports.
|
||||
Trim {
|
||||
/// Buffer or Metadata account.
|
||||
account: crate::MdPubkey,
|
||||
/// Current authority.
|
||||
authority: crate::MdPubkey,
|
||||
/// Lamport destination.
|
||||
destination: crate::MdPubkey,
|
||||
/// Optional upgrade-authority validation context.
|
||||
program_context: std::option::Option<crate::ExMetadataSpmProgramContext>,
|
||||
},
|
||||
/// Close one mutable Buffer or Metadata account.
|
||||
Close {
|
||||
/// Buffer or Metadata account.
|
||||
account: crate::MdPubkey,
|
||||
/// Current authority.
|
||||
authority: crate::MdPubkey,
|
||||
/// Lamport destination.
|
||||
destination: crate::MdPubkey,
|
||||
/// Optional upgrade-authority validation context.
|
||||
program_context: std::option::Option<crate::ExMetadataSpmProgramContext>,
|
||||
},
|
||||
/// Allocate one pre-funded Buffer account.
|
||||
Allocate {
|
||||
/// Buffer account.
|
||||
buffer: crate::MdPubkey,
|
||||
/// Authority signing the allocation.
|
||||
authority: crate::MdPubkey,
|
||||
/// Optional seed for a PDA Buffer; absent selects a pre-created keypair account.
|
||||
seed: std::option::Option<std::vec::Vec<u8>>,
|
||||
/// Optional upgrade-authority validation context.
|
||||
program_context: std::option::Option<crate::ExMetadataSpmProgramContext>,
|
||||
/// Whether a PDA Buffer uses the canonical `[program, seed]` derivation.
|
||||
canonical: bool,
|
||||
/// Whether the instruction may invoke the System Program to allocate the account.
|
||||
allocate_account: bool,
|
||||
},
|
||||
/// Extend one pre-funded Buffer or Metadata account.
|
||||
Extend {
|
||||
/// Buffer or Metadata account.
|
||||
account: crate::MdPubkey,
|
||||
/// Current authority.
|
||||
authority: crate::MdPubkey,
|
||||
/// Number of bytes to append to account capacity.
|
||||
length: u16,
|
||||
/// Optional upgrade-authority validation context.
|
||||
program_context: std::option::Option<crate::ExMetadataSpmProgramContext>,
|
||||
},
|
||||
}
|
||||
|
||||
impl crate::ExMetadataSpmOperation {
|
||||
/// Returns the stable operation code.
|
||||
pub const fn operation_code(&self) -> &'static str {
|
||||
return match self {
|
||||
Self::Write {
|
||||
buffer: _,
|
||||
authority: _,
|
||||
offset: _,
|
||||
source: _,
|
||||
} => crate::EX_METADATA_SPM_WRITE_OPERATION,
|
||||
Self::Initialize {
|
||||
metadata: _,
|
||||
authority: _,
|
||||
program: _,
|
||||
program_data: _,
|
||||
canonical: _,
|
||||
seed: _,
|
||||
encoding: _,
|
||||
compression: _,
|
||||
format: _,
|
||||
data_source: _,
|
||||
data: _,
|
||||
allocate_account: _,
|
||||
} => crate::EX_METADATA_SPM_INITIALIZE_OPERATION,
|
||||
Self::SetAuthority {
|
||||
account: _,
|
||||
authority: _,
|
||||
new_authority: _,
|
||||
program_context: _,
|
||||
} => crate::EX_METADATA_SPM_SET_AUTHORITY_OPERATION,
|
||||
Self::SetData {
|
||||
metadata: _,
|
||||
authority: _,
|
||||
encoding: _,
|
||||
compression: _,
|
||||
format: _,
|
||||
source: _,
|
||||
program_context: _,
|
||||
} => crate::EX_METADATA_SPM_SET_DATA_OPERATION,
|
||||
Self::SetImmutable {
|
||||
metadata: _,
|
||||
authority: _,
|
||||
program_context: _,
|
||||
} => crate::EX_METADATA_SPM_SET_IMMUTABLE_OPERATION,
|
||||
Self::Trim {
|
||||
account: _,
|
||||
authority: _,
|
||||
destination: _,
|
||||
program_context: _,
|
||||
} => crate::EX_METADATA_SPM_TRIM_OPERATION,
|
||||
Self::Close {
|
||||
account: _,
|
||||
authority: _,
|
||||
destination: _,
|
||||
program_context: _,
|
||||
} => crate::EX_METADATA_SPM_CLOSE_OPERATION,
|
||||
Self::Allocate {
|
||||
buffer: _,
|
||||
authority: _,
|
||||
seed: _,
|
||||
program_context: _,
|
||||
canonical: _,
|
||||
allocate_account: _,
|
||||
} => crate::EX_METADATA_SPM_ALLOCATE_OPERATION,
|
||||
Self::Extend {
|
||||
account: _,
|
||||
authority: _,
|
||||
length: _,
|
||||
program_context: _,
|
||||
} => crate::EX_METADATA_SPM_EXTEND_OPERATION,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns whether the operation can overwrite, destroy or irreversibly constrain state.
|
||||
pub const fn requires_explicit_approval(&self) -> bool {
|
||||
return matches!(
|
||||
self,
|
||||
Self::Write {
|
||||
buffer: _,
|
||||
authority: _,
|
||||
offset: _,
|
||||
source: _
|
||||
} | Self::SetAuthority {
|
||||
account: _,
|
||||
authority: _,
|
||||
new_authority: _,
|
||||
program_context: _,
|
||||
} | Self::SetData {
|
||||
metadata: _,
|
||||
authority: _,
|
||||
encoding: _,
|
||||
compression: _,
|
||||
format: _,
|
||||
source: _,
|
||||
program_context: _,
|
||||
} | Self::SetImmutable {
|
||||
metadata: _,
|
||||
authority: _,
|
||||
program_context: _
|
||||
} | Self::Trim {
|
||||
account: _,
|
||||
authority: _,
|
||||
destination: _,
|
||||
program_context: _
|
||||
} | Self::Close {
|
||||
account: _,
|
||||
authority: _,
|
||||
destination: _,
|
||||
program_context: _
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete Solana Program Metadata execution intent.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/solana_program_metadata/intent/ExMetadataSpmExecutionIntent.ts"
|
||||
)]
|
||||
pub struct ExMetadataSpmExecutionIntent {
|
||||
/// Caller-provided correlation identifier.
|
||||
pub intent_id: std::string::String,
|
||||
/// Transaction fee payer.
|
||||
pub fee_payer: crate::MdPubkey,
|
||||
/// Conservative common execution policy.
|
||||
pub policy: crate::ExApiExecutionPolicy,
|
||||
/// Explicit approval for overwrite, destructive or irreversible operations.
|
||||
pub allow_destructive_operation: bool,
|
||||
/// Exact operation to build.
|
||||
pub operation: crate::ExMetadataSpmOperation,
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/lib.rs
|
||||
// version: 33
|
||||
// version: 36
|
||||
|
||||
//! Consolidated decoder, executor, materializer and shared model library.
|
||||
#![warn(missing_docs)]
|
||||
@@ -401,6 +401,30 @@ pub use self::decoder::decoder_metadata_solana_program_metadata_decode_metadata_
|
||||
pub use self::decoder::decoder_spl_elgamal_registry_parse_elgamal_registry_state;
|
||||
/// Parses one Token-2022 Mint, Account, or Multisig state.
|
||||
pub use self::decoder::decoder_spl_token_2022_parse_token_2022_state;
|
||||
/// Stable Solana Program Metadata `Allocate` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_ALLOCATE_OPERATION;
|
||||
/// Stable Solana Program Metadata `Close` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_CLOSE_OPERATION;
|
||||
/// Solana Program Metadata operations requiring explicit destructive approval.
|
||||
pub use self::executor::EX_METADATA_SPM_DESTRUCTIVE_OPERATION_CODES;
|
||||
/// Stable Solana Program Metadata `Extend` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_EXTEND_OPERATION;
|
||||
/// Stable Solana Program Metadata `Initialize` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_INITIALIZE_OPERATION;
|
||||
/// Conservative maximum inline Solana Program Metadata content size.
|
||||
pub use self::executor::EX_METADATA_SPM_MAX_INLINE_DATA_BYTES;
|
||||
/// Stable Solana Program Metadata `SetAuthority` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_SET_AUTHORITY_OPERATION;
|
||||
/// Stable Solana Program Metadata `SetData` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_SET_DATA_OPERATION;
|
||||
/// Stable Solana Program Metadata `SetImmutable` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_SET_IMMUTABLE_OPERATION;
|
||||
/// Exact Solana Program Metadata operation inventory.
|
||||
pub use self::executor::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES;
|
||||
/// Stable Solana Program Metadata `Trim` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_TRIM_OPERATION;
|
||||
/// Stable Solana Program Metadata `Write` operation code.
|
||||
pub use self::executor::EX_METADATA_SPM_WRITE_OPERATION;
|
||||
/// Exposes `EX_METAPLEX_TOKEN_METADATA_APPROVE_USE_AUTHORITY_OPERATION`.
|
||||
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_APPROVE_USE_AUTHORITY_OPERATION;
|
||||
/// Exposes `EX_METAPLEX_TOKEN_METADATA_BURN_OPERATION`.
|
||||
@@ -1135,10 +1159,30 @@ pub use self::executor::ExLendingMarginfiV2Executor;
|
||||
pub use self::executor::ExLockRaydiumLpExecutor;
|
||||
/// Exposes the Metaplex Token Metadata executor.
|
||||
pub use self::executor::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
/// Exposes the reserved `ExMetadataSolanaProgramMetadataExecutor` implementation.
|
||||
/// Exposes the typed `ExMetadataSolanaProgramMetadataExecutor` implementation.
|
||||
pub use self::executor::ExMetadataSolanaProgramMetadataExecutor;
|
||||
/// Exposes the reserved `ExMetadataSplNameServiceExecutor` implementation.
|
||||
pub use self::executor::ExMetadataSplNameServiceExecutor;
|
||||
/// Solana Program Metadata compression intent.
|
||||
pub use self::executor::ExMetadataSpmCompression;
|
||||
/// Solana Program Metadata inline data intent.
|
||||
pub use self::executor::ExMetadataSpmDataInput;
|
||||
/// Solana Program Metadata data-source intent.
|
||||
pub use self::executor::ExMetadataSpmDataSource;
|
||||
/// Solana Program Metadata encoding intent.
|
||||
pub use self::executor::ExMetadataSpmEncoding;
|
||||
/// Complete Solana Program Metadata execution intent.
|
||||
pub use self::executor::ExMetadataSpmExecutionIntent;
|
||||
/// Solana Program Metadata format intent.
|
||||
pub use self::executor::ExMetadataSpmFormat;
|
||||
/// One Solana Program Metadata operation.
|
||||
pub use self::executor::ExMetadataSpmOperation;
|
||||
/// Optional Solana Program Metadata program context.
|
||||
pub use self::executor::ExMetadataSpmProgramContext;
|
||||
/// Solana Program Metadata `SetData` source intent.
|
||||
pub use self::executor::ExMetadataSpmSetDataSource;
|
||||
/// Solana Program Metadata `Write` source intent.
|
||||
pub use self::executor::ExMetadataSpmWriteSource;
|
||||
/// Exposes the complete Metaplex Token Metadata execution intent.
|
||||
pub use self::executor::ExMetaplexTokenMetadataExecutionIntent;
|
||||
/// Exposes one current Metaplex Token Metadata operation.
|
||||
@@ -1349,6 +1393,10 @@ pub use self::executor::ExWeightedSwapStabbleExecutor;
|
||||
pub use self::executor::executor_api_serialize_payload_json;
|
||||
/// Exposes the pretty JSON payload serializer.
|
||||
pub use self::executor::executor_api_serialize_payload_json_pretty;
|
||||
/// Derives the canonical Solana Program Metadata PDA.
|
||||
pub use self::executor::executor_metadata_solana_program_metadata_derive_canonical_pda;
|
||||
/// Derives the non-canonical Solana Program Metadata PDA.
|
||||
pub use self::executor::executor_metadata_solana_program_metadata_derive_non_canonical_pda;
|
||||
/// Builds an unsigned durable nonce transaction from a prepared execution plan and validated nonce state.
|
||||
pub use self::executor::executor_solana_build_durable_nonce_transaction;
|
||||
/// Builds an unsigned legacy Solana transaction from a prepared execution plan.
|
||||
@@ -1391,7 +1439,7 @@ pub use self::materializer::MtLifecycleMaterializer;
|
||||
pub use self::materializer::MtLiquidityMaterializer;
|
||||
/// Exposes the legacy common business materializer trait.
|
||||
pub use self::materializer::MtMaterializer;
|
||||
/// Token-2022, Metaplex and Solana Program Metadata materializer.
|
||||
/// Compatibility wrapper for the Solana Program Metadata materializer.
|
||||
pub use self::materializer::MtMetadataMaterializer;
|
||||
/// Reserved NFT materializer.
|
||||
pub use self::materializer::MtNftMaterializer;
|
||||
@@ -1409,6 +1457,8 @@ pub use self::materializer::MtRewardsMaterializer;
|
||||
pub use self::materializer::MtRiskMaterializer;
|
||||
/// Reserved routing materializer.
|
||||
pub use self::materializer::MtRoutingMaterializer;
|
||||
/// Stable Solana Program Metadata instruction materializer.
|
||||
pub use self::materializer::MtSolanaProgramMetadataMaterializer;
|
||||
/// Stable native stake and vote materializer.
|
||||
pub use self::materializer::MtStakingMaterializer;
|
||||
/// Stable Token, ATA and Token-2022 account materializer.
|
||||
@@ -1972,6 +2022,8 @@ pub(crate) use self::decoder::decoder_spl_token_decode;
|
||||
pub(crate) use self::decoder::decoder_spl_token_entry_for_tag;
|
||||
/// Returns the first byte of one retained classic SPL Token payload.
|
||||
pub(crate) use self::decoder::decoder_spl_token_payload_tag;
|
||||
/// Stable Solana Program Metadata executor name.
|
||||
pub(crate) use self::executor::EX_METADATA_SPM_EXECUTOR_NAME;
|
||||
/// Stable operation code for the nonce advance injected by durable transaction assembly.
|
||||
pub(crate) use self::executor::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION;
|
||||
/// Maximum over-the-wire size of a Solana transaction packet.
|
||||
@@ -2004,6 +2056,8 @@ pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_MEMO;
|
||||
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_TOKEN;
|
||||
/// Canonical SPL Token-2022 tracing target.
|
||||
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_TOKEN_2022;
|
||||
/// Internal Solana Program Metadata prepared-plan builder.
|
||||
pub(crate) use self::executor::executor_metadata_solana_program_metadata_build_prepared_plan;
|
||||
/// Internal Metaplex Token Metadata prepared-plan builder.
|
||||
pub(crate) use self::executor::executor_metaplex_token_metadata_build_prepared_plan;
|
||||
/// Crate-root access to `build_prepared_plan` from `address_lookup_table`.
|
||||
@@ -2052,41 +2106,315 @@ pub(crate) use self::executor::executor_spl_memo_build_prepared_plan;
|
||||
pub(crate) use self::executor::executor_spl_token_2022_build_prepared_plan;
|
||||
/// Crate-root access to `executor_spl_token_build_prepared_plan`.
|
||||
pub(crate) use self::executor::executor_spl_token_build_prepared_plan;
|
||||
/// Canonical processor name for the native and SPL administration materializer.
|
||||
/// Crate-root access to `MT_ADMIN_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Crate-root access to `MT_ADMIN_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_ADMIN_CONFIG_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_CONFIG_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_CONFIG_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_CONFIG_SURFACE;
|
||||
/// Crate-root access to `MT_ADMIN_LOADER_V4_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_LOADER_V4_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_LOADER_V4_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_LOADER_V4_SURFACE;
|
||||
/// Crate-root access to `MT_ADMIN_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_PROCESSOR_NAME;
|
||||
/// Event families accepted by the Solana Program Metadata instruction materializer.
|
||||
pub(crate) use self::materializer::MT_METADATA_SPM_ACCEPTED_FAMILIES;
|
||||
/// Stable processor name for Solana Program Metadata projections.
|
||||
pub(crate) use self::materializer::MT_METADATA_SPM_PROCESSOR_NAME;
|
||||
/// Stable projection schema version for Solana Program Metadata outputs.
|
||||
pub(crate) use self::materializer::MT_METADATA_SPM_PROJECTION_VERSION;
|
||||
/// Stable processor name for SPL Token and ATA risk facts.
|
||||
/// Crate-root access to `MT_ADMIN_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE;
|
||||
/// Crate-root access to `MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_SPL_TOKEN_2022_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SPL_TOKEN_2022_SURFACE;
|
||||
/// Crate-root access to `MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_SPL_TOKEN_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SPL_TOKEN_SURFACE;
|
||||
/// Crate-root access to `MT_ADMIN_SYSTEM_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SYSTEM_ENTRIES;
|
||||
/// Crate-root access to `MT_ADMIN_SYSTEM_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_ADMIN_SYSTEM_SURFACE;
|
||||
/// Crate-root access to `MT_BRIDGE_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_BRIDGE_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_BRIDGE_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_BRIDGE_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_BRIDGE_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_BRIDGE_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_COMPLIANCE_AUDIT_WRITE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_COMPLIANCE_AUDIT_WRITE_ENTRIES;
|
||||
/// Crate-root access to `MT_FEES_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_FEES_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_FEES_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_FEES_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_FEES_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_FEES_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_FEES_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_FEES_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_FEES_SPL_TOKEN_2022_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_FEES_SPL_TOKEN_2022_SURFACE;
|
||||
/// Crate-root access to `MT_FEES_TOKEN_2022_FEE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_FEES_TOKEN_2022_FEE_ENTRIES;
|
||||
/// Crate-root access to `MT_GOVERNANCE_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_GOVERNANCE_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_GOVERNANCE_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_GOVERNANCE_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_GOVERNANCE_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_GOVERNANCE_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_LENDING_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_LENDING_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_LENDING_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_LENDING_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_LENDING_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_LENDING_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_LIFECYCLE_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_BPF_LOADER_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_BPF_LOADER_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_LIFECYCLE_FEATURE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_FEATURE_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_FEATURE_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_FEATURE_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_LOADER_V4_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_LOADER_V4_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_LOADER_V4_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_LOADER_V4_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_LIFECYCLE_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_LIFECYCLE_SLASHING_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_SLASHING_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_SLASHING_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_SLASHING_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_SYSTEM_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_SYSTEM_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES;
|
||||
/// Crate-root access to `MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE;
|
||||
/// Crate-root access to `MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES;
|
||||
/// Crate-root access to `MT_LIQUIDITY_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_LIQUIDITY_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_LIQUIDITY_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_LIQUIDITY_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_LIQUIDITY_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_LIQUIDITY_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_METADATA_TOKEN_2022_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_METADATA_TOKEN_2022_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_METADATA_TOKEN_2022_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_NFT_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_NFT_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_NFT_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_NFT_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_NFT_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_NFT_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_ORACLE_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_ORACLE_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_ORACLE_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_ORACLE_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_ORACLE_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_ORACLE_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_ORDERBOOK_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_ORDERBOOK_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_ORDERBOOK_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_ORDERBOOK_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_ORDERBOOK_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_ORDERBOOK_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_PERPETUALS_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_PERPETUALS_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_PERPETUALS_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_PERPETUALS_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_PERPETUALS_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_PERPETUALS_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_POOL_STATE_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_POOL_STATE_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_POOL_STATE_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_POOL_STATE_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_POOL_STATE_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_POOL_STATE_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_REWARDS_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_REWARDS_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_REWARDS_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_REWARDS_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_REWARDS_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_REWARDS_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_RISK_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_RISK_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_RISK_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_RISK_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_RISK_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_RISK_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for SPL Token and ATA risk facts.
|
||||
/// Crate-root access to `MT_RISK_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_RISK_PROJECTION_VERSION;
|
||||
/// Stable processor name for Token, ATA and Token-2022 account projections.
|
||||
/// Crate-root access to `MT_RISK_RISK_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_RISK_RISK_ENTRIES;
|
||||
/// Crate-root access to `MT_ROUTING_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_ROUTING_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_ROUTING_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_ROUTING_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_ROUTING_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_ROUTING_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_STAKING_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_STAKING_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_STAKING_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_STAKING_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_STAKING_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_STAKING_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_STAKING_PROJECTION_VERSION;
|
||||
/// Crate-root access to `MT_STAKING_STAKE_ACCOUNT_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_STAKE_ACCOUNT_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_STAKE_AUTHORITY_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_STAKE_AUTHORITY_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_STAKE_LOCKUP_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_STAKE_LOCKUP_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_STAKE_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_STAKING_STAKE_SURFACE;
|
||||
/// Crate-root access to `MT_STAKING_STAKE_VALUE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_STAKE_VALUE_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_VOTE_ACCOUNT_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_VOTE_ACCOUNT_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_VOTE_ADMIN_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_VOTE_ADMIN_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_VOTE_AUTHORITY_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_VOTE_AUTHORITY_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_VOTE_REWARD_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_VOTE_REWARD_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_VOTE_STATE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_VOTE_STATE_ENTRIES;
|
||||
/// Crate-root access to `MT_STAKING_VOTE_SURFACE`.
|
||||
pub(crate) use self::materializer::MT_STAKING_VOTE_SURFACE;
|
||||
/// Crate-root access to `MT_STAKING_VOTE_VALUE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_STAKING_VOTE_VALUE_ENTRIES;
|
||||
/// Crate-root access to `MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES;
|
||||
/// Crate-root access to `MT_TOKEN_ACCOUNTS_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES;
|
||||
/// Crate-root access to `MT_TOKEN_ACCOUNTS_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for Token, ATA and Token-2022 account projections.
|
||||
/// Crate-root access to `MT_TOKEN_ACCOUNTS_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION;
|
||||
/// Maximum Memo payload accepted by transaction annotations.
|
||||
/// Crate-root access to `MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_TOKEN_METADATA_RISK_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_METADATA_RISK_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_TOKEN_METADATA_RISK_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_TOKEN_METADATA_RISK_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_TRADES_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_TRADES_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_TRADES_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_TRADES_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_TRADES_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_TRADES_PROCESSOR_NAME;
|
||||
/// Crate-root access to `MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES`.
|
||||
pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES;
|
||||
/// Stable processor name for transaction annotations.
|
||||
/// Crate-root access to `MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES`.
|
||||
pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES;
|
||||
/// Crate-root access to `MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for transaction annotations.
|
||||
/// Crate-root access to `MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION`.
|
||||
pub(crate) use self::materializer::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for the native and SPL administration materializer.
|
||||
/// Crate-root access to `MT_VAULT_ACCEPTED_FAMILIES`.
|
||||
pub(crate) use self::materializer::MT_VAULT_ACCEPTED_FAMILIES;
|
||||
/// Crate-root access to `MT_VAULT_COMPONENT_NAME`.
|
||||
pub(crate) use self::materializer::MT_VAULT_COMPONENT_NAME;
|
||||
/// Crate-root access to `MT_VAULT_PROCESSOR_NAME`.
|
||||
pub(crate) use self::materializer::MT_VAULT_PROCESSOR_NAME;
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_ADMIN`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_ADMIN;
|
||||
/// Canonical tracing target for the native compliance audit materializer.
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT;
|
||||
/// Canonical tracing target for the native lifecycle materializer.
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_FEES`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_FEES;
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_LIFECYCLE`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_LIFECYCLE;
|
||||
/// Canonical tracing target for Solana Program Metadata materialization.
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA;
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;
|
||||
/// Canonical tracing target for SPL Token and ATA risk facts.
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022;
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_RISK`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_RISK;
|
||||
/// Canonical tracing target for the native stake and vote materializer.
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_STAKING`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_STAKING;
|
||||
/// Canonical tracing target for Token, ATA and Token-2022 account projections.
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS;
|
||||
/// Canonical tracing target for transaction annotations.
|
||||
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS`.
|
||||
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer.rs
|
||||
// version: 9
|
||||
// version: 14
|
||||
|
||||
//! Consolidated materializer modules.
|
||||
|
||||
@@ -73,8 +73,10 @@ pub use self::lending::MtLendingMaterializer;
|
||||
pub use self::lifecycle::MtLifecycleMaterializer;
|
||||
/// Reserved liquidity materializer.
|
||||
pub use self::liquidity::MtLiquidityMaterializer;
|
||||
/// Token-2022, Metaplex and Solana Program Metadata materializer.
|
||||
/// Compatibility wrapper for the Solana Program Metadata materializer.
|
||||
pub use self::metadata::MtMetadataMaterializer;
|
||||
/// Stable Solana Program Metadata instruction materializer.
|
||||
pub use self::metadata::MtSolanaProgramMetadataMaterializer;
|
||||
/// Materializes one canonical non-Metadata Metaplex account snapshot.
|
||||
pub use self::metadata::materializer_metadata_materialize_metaplex_account_snapshot;
|
||||
/// Materializes one canonical Metaplex Metadata account snapshot.
|
||||
@@ -118,100 +120,533 @@ pub use self::transaction::MtTransactionAnnotationMaterializer;
|
||||
/// Reserved vault materializer.
|
||||
pub use self::vault::MtVaultMaterializer;
|
||||
|
||||
/// Canonical processor name for the native and SPL administration materializer.
|
||||
/// Crate-visible `MT_ADMIN_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_ADMIN_CONFIG_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_CONFIG_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_CONFIG_SURFACE` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_CONFIG_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_LOADER_V4_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_LOADER_V4_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_LOADER_V4_SURFACE` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_LOADER_V4_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_PROCESSOR_NAME;
|
||||
/// Canonical tracing target for the native and SPL administration materializer.
|
||||
/// Crate-visible `MT_ADMIN_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_2022_SURFACE` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SPL_TOKEN_2022_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_SURFACE` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SPL_TOKEN_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_SYSTEM_ENTRIES` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SYSTEM_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SYSTEM_SURFACE` component constant.
|
||||
pub(crate) use self::admin::MT_ADMIN_SYSTEM_SURFACE;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_ADMIN` component constant.
|
||||
pub(crate) use self::admin::TRACING_TARGET_MATERIALIZER_ADMIN;
|
||||
/// Canonical tracing target for the native compliance audit materializer.
|
||||
/// Crate-visible `MT_BRIDGE_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::bridge::MT_BRIDGE_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_BRIDGE_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::bridge::MT_BRIDGE_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_BRIDGE_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::bridge::MT_BRIDGE_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_WRITE_ENTRIES` component constant.
|
||||
pub(crate) use self::compliance::MT_COMPLIANCE_AUDIT_WRITE_ENTRIES;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT` component constant.
|
||||
pub(crate) use self::compliance::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT;
|
||||
/// Canonical tracing target for the native lifecycle materializer.
|
||||
/// Crate-visible `MT_FEES_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::fees::MT_FEES_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_FEES_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::fees::MT_FEES_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_FEES_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::fees::MT_FEES_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_FEES_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::fees::MT_FEES_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_FEES_SPL_TOKEN_2022_SURFACE` component constant.
|
||||
pub(crate) use self::fees::MT_FEES_SPL_TOKEN_2022_SURFACE;
|
||||
/// Crate-visible `MT_FEES_TOKEN_2022_FEE_ENTRIES` component constant.
|
||||
pub(crate) use self::fees::MT_FEES_TOKEN_2022_FEE_ENTRIES;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_FEES` component constant.
|
||||
pub(crate) use self::fees::TRACING_TARGET_MATERIALIZER_FEES;
|
||||
/// Crate-visible `MT_GOVERNANCE_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::governance::MT_GOVERNANCE_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_GOVERNANCE_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::governance::MT_GOVERNANCE_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_GOVERNANCE_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::governance::MT_GOVERNANCE_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_LENDING_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::lending::MT_LENDING_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_LENDING_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::lending::MT_LENDING_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_LENDING_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::lending::MT_LENDING_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_LIFECYCLE_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_BPF_LOADER_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_BPF_LOADER_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_LIFECYCLE_FEATURE_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_FEATURE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_FEATURE_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_FEATURE_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_LOADER_V4_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_LOADER_V4_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_LOADER_V4_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_LOADER_V4_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_LIFECYCLE_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_LIFECYCLE_SLASHING_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_SLASHING_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_SLASHING_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_SLASHING_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_SYSTEM_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_SYSTEM_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES` component constant.
|
||||
pub(crate) use self::lifecycle::MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_LIFECYCLE` component constant.
|
||||
pub(crate) use self::lifecycle::TRACING_TARGET_MATERIALIZER_LIFECYCLE;
|
||||
/// Event families accepted by the Solana Program Metadata instruction materializer.
|
||||
pub(crate) use self::metadata::MT_METADATA_SPM_ACCEPTED_FAMILIES;
|
||||
/// Stable processor name for Solana Program Metadata projections.
|
||||
pub(crate) use self::metadata::MT_METADATA_SPM_PROCESSOR_NAME;
|
||||
/// Stable projection schema version for Solana Program Metadata outputs.
|
||||
pub(crate) use self::metadata::MT_METADATA_SPM_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Solana Program Metadata materialization.
|
||||
/// Crate-visible `MT_LIQUIDITY_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::liquidity::MT_LIQUIDITY_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_LIQUIDITY_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::liquidity::MT_LIQUIDITY_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_LIQUIDITY_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::liquidity::MT_LIQUIDITY_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_METADATA_TOKEN_2022_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_METADATA_TOKEN_2022_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_METADATA_TOKEN_2022_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_PROJECTION_VERSION;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA` component constant.
|
||||
pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA` component constant.
|
||||
pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;
|
||||
/// Stable processor name for SPL Token and ATA risk facts.
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022` component constant.
|
||||
pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022;
|
||||
/// Crate-visible `MT_NFT_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::nft::MT_NFT_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_NFT_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::nft::MT_NFT_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_NFT_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::nft::MT_NFT_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_ORACLE_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::oracle::MT_ORACLE_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_ORACLE_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::oracle::MT_ORACLE_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_ORACLE_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::oracle::MT_ORACLE_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_ORDERBOOK_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::orderbook::MT_ORDERBOOK_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_ORDERBOOK_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::orderbook::MT_ORDERBOOK_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_ORDERBOOK_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::orderbook::MT_ORDERBOOK_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_PERPETUALS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::perpetuals::MT_PERPETUALS_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_PERPETUALS_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::perpetuals::MT_PERPETUALS_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_PERPETUALS_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::perpetuals::MT_PERPETUALS_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_POOL_STATE_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::pool::MT_POOL_STATE_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_POOL_STATE_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::pool::MT_POOL_STATE_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_POOL_STATE_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::pool::MT_POOL_STATE_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_REWARDS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::rewards::MT_REWARDS_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_REWARDS_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::rewards::MT_REWARDS_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_REWARDS_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::rewards::MT_REWARDS_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_RISK_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::risk::MT_RISK_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_RISK_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::risk::MT_RISK_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_RISK_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::risk::MT_RISK_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for SPL Token and ATA risk facts.
|
||||
/// Crate-visible `MT_RISK_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::risk::MT_RISK_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for SPL Token and ATA risk facts.
|
||||
/// Crate-visible `MT_RISK_RISK_ENTRIES` component constant.
|
||||
pub(crate) use self::risk::MT_RISK_RISK_ENTRIES;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_RISK` component constant.
|
||||
pub(crate) use self::risk::TRACING_TARGET_MATERIALIZER_RISK;
|
||||
/// Canonical tracing target for the native stake and vote materializer.
|
||||
/// Crate-visible `MT_ROUTING_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::routing::MT_ROUTING_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_ROUTING_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::routing::MT_ROUTING_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_ROUTING_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::routing::MT_ROUTING_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_STAKING_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_STAKING_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_STAKING_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_STAKING_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_STAKING_STAKE_ACCOUNT_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_STAKE_ACCOUNT_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_STAKE_AUTHORITY_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_STAKE_AUTHORITY_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_STAKE_LOCKUP_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_STAKE_LOCKUP_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_STAKE_SURFACE` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_STAKE_SURFACE;
|
||||
/// Crate-visible `MT_STAKING_STAKE_VALUE_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_STAKE_VALUE_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_ACCOUNT_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_VOTE_ACCOUNT_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_ADMIN_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_VOTE_ADMIN_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_AUTHORITY_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_VOTE_AUTHORITY_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_REWARD_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_VOTE_REWARD_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_STATE_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_VOTE_STATE_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_SURFACE` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_VOTE_SURFACE;
|
||||
/// Crate-visible `MT_STAKING_VOTE_VALUE_ENTRIES` component constant.
|
||||
pub(crate) use self::staking::MT_STAKING_VOTE_VALUE_ENTRIES;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_STAKING` component constant.
|
||||
pub(crate) use self::staking::TRACING_TARGET_MATERIALIZER_STAKING;
|
||||
/// Stable processor name for Token, ATA and Token-2022 account projections.
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_ACCOUNTS_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for Token, ATA and Token-2022 account projections.
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Token, ATA and Token-2022 account projections.
|
||||
/// Crate-visible `MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_TOKEN_METADATA_RISK_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_METADATA_RISK_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_TOKEN_METADATA_RISK_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::token::MT_TOKEN_METADATA_RISK_PROCESSOR_NAME;
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS` component constant.
|
||||
pub(crate) use self::token::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS;
|
||||
/// Maximum Memo payload accepted by transaction annotations.
|
||||
/// Crate-visible `MT_TRADES_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::trades::MT_TRADES_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_TRADES_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::trades::MT_TRADES_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_TRADES_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::trades::MT_TRADES_PROCESSOR_NAME;
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES` component constant.
|
||||
pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES;
|
||||
/// Stable processor name for transaction annotations.
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES` component constant.
|
||||
pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES;
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for transaction annotations.
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION` component constant.
|
||||
pub(crate) use self::transaction::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for transaction annotations.
|
||||
/// Crate-visible `TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS` component constant.
|
||||
pub(crate) use self::transaction::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS;
|
||||
/// Crate-visible `MT_VAULT_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::vault::MT_VAULT_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_VAULT_COMPONENT_NAME` component constant.
|
||||
pub(crate) use self::vault::MT_VAULT_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_VAULT_PROCESSOR_NAME` component constant.
|
||||
pub(crate) use self::vault::MT_VAULT_PROCESSOR_NAME;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn reserved_materializers_expose_inactive_public_contracts() {
|
||||
let materializers: &[(&dyn crate::MtApiEventMaterializer, &str)] = &[
|
||||
(&crate::MtBridgeMaterializer, "kb-lib.materializer.bridge"),
|
||||
(&crate::MtGovernanceMaterializer, "kb-lib.materializer.governance"),
|
||||
(&crate::MtLendingMaterializer, "kb-lib.materializer.lending"),
|
||||
(&crate::MtLiquidityMaterializer, "kb-lib.materializer.liquidity"),
|
||||
(&crate::MtNftMaterializer, "kb-lib.materializer.nft"),
|
||||
(&crate::MtOracleMaterializer, "kb-lib.materializer.oracle"),
|
||||
(&crate::MtOrderbookMaterializer, "kb-lib.materializer.orderbook"),
|
||||
(&crate::MtPerpetualsMaterializer, "kb-lib.materializer.perpetuals"),
|
||||
(&crate::MtPoolStateMaterializer, "kb-lib.materializer.pool.state"),
|
||||
(&crate::MtRewardsMaterializer, "kb-lib.materializer.rewards"),
|
||||
(&crate::MtRoutingMaterializer, "kb-lib.materializer.routing"),
|
||||
fn materializer_contracts() -> &'static [(
|
||||
&'static dyn crate::MtMaterializer,
|
||||
&'static dyn crate::MtApiEventMaterializer,
|
||||
&'static str,
|
||||
&'static str,
|
||||
)] {
|
||||
return &[
|
||||
(
|
||||
&crate::MtAdminMaterializer,
|
||||
&crate::MtAdminMaterializer,
|
||||
crate::MT_ADMIN_COMPONENT_NAME,
|
||||
crate::MT_ADMIN_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtBridgeMaterializer,
|
||||
&crate::MtBridgeMaterializer,
|
||||
crate::MT_BRIDGE_COMPONENT_NAME,
|
||||
crate::MT_BRIDGE_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtComplianceAuditMaterializer,
|
||||
&crate::MtComplianceAuditMaterializer,
|
||||
crate::MT_COMPLIANCE_AUDIT_COMPONENT_NAME,
|
||||
crate::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtFeesMaterializer,
|
||||
&crate::MtFeesMaterializer,
|
||||
crate::MT_FEES_COMPONENT_NAME,
|
||||
crate::MT_FEES_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtGovernanceMaterializer,
|
||||
&crate::MtGovernanceMaterializer,
|
||||
crate::MT_GOVERNANCE_COMPONENT_NAME,
|
||||
crate::MT_GOVERNANCE_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtLendingMaterializer,
|
||||
&crate::MtLendingMaterializer,
|
||||
crate::MT_LENDING_COMPONENT_NAME,
|
||||
crate::MT_LENDING_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtLifecycleMaterializer,
|
||||
&crate::MtLifecycleMaterializer,
|
||||
crate::MT_LIFECYCLE_COMPONENT_NAME,
|
||||
crate::MT_LIFECYCLE_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtLiquidityMaterializer,
|
||||
&crate::MtLiquidityMaterializer,
|
||||
crate::MT_LIQUIDITY_COMPONENT_NAME,
|
||||
crate::MT_LIQUIDITY_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
crate::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME,
|
||||
crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtNftMaterializer,
|
||||
&crate::MtNftMaterializer,
|
||||
crate::MT_NFT_COMPONENT_NAME,
|
||||
crate::MT_NFT_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtOracleMaterializer,
|
||||
&crate::MtOracleMaterializer,
|
||||
crate::MT_ORACLE_COMPONENT_NAME,
|
||||
crate::MT_ORACLE_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtOrderbookMaterializer,
|
||||
&crate::MtOrderbookMaterializer,
|
||||
crate::MT_ORDERBOOK_COMPONENT_NAME,
|
||||
crate::MT_ORDERBOOK_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtPerpetualsMaterializer,
|
||||
&crate::MtPerpetualsMaterializer,
|
||||
crate::MT_PERPETUALS_COMPONENT_NAME,
|
||||
crate::MT_PERPETUALS_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtPoolStateMaterializer,
|
||||
&crate::MtPoolStateMaterializer,
|
||||
crate::MT_POOL_STATE_COMPONENT_NAME,
|
||||
crate::MT_POOL_STATE_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtRewardsMaterializer,
|
||||
&crate::MtRewardsMaterializer,
|
||||
crate::MT_REWARDS_COMPONENT_NAME,
|
||||
crate::MT_REWARDS_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtRiskMaterializer,
|
||||
&crate::MtRiskMaterializer,
|
||||
crate::MT_RISK_COMPONENT_NAME,
|
||||
crate::MT_RISK_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtRoutingMaterializer,
|
||||
&crate::MtRoutingMaterializer,
|
||||
crate::MT_ROUTING_COMPONENT_NAME,
|
||||
crate::MT_ROUTING_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtStakingMaterializer,
|
||||
&crate::MtStakingMaterializer,
|
||||
crate::MT_STAKING_COMPONENT_NAME,
|
||||
crate::MT_STAKING_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
crate::MT_TOKEN_ACCOUNTS_COMPONENT_NAME,
|
||||
crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtTokenMetadataRiskMaterializer,
|
||||
"kb-lib.materializer.token.metadata_risk",
|
||||
&crate::MtTokenMetadataRiskMaterializer,
|
||||
crate::MT_TOKEN_METADATA_RISK_COMPONENT_NAME,
|
||||
crate::MT_TOKEN_METADATA_RISK_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtTradesMaterializer,
|
||||
&crate::MtTradesMaterializer,
|
||||
crate::MT_TRADES_COMPONENT_NAME,
|
||||
crate::MT_TRADES_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
crate::MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME,
|
||||
crate::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME,
|
||||
),
|
||||
(
|
||||
&crate::MtVaultMaterializer,
|
||||
&crate::MtVaultMaterializer,
|
||||
crate::MT_VAULT_COMPONENT_NAME,
|
||||
crate::MT_VAULT_PROCESSOR_NAME,
|
||||
),
|
||||
(&crate::MtTradesMaterializer, "kb-lib.materializer.trades"),
|
||||
(&crate::MtVaultMaterializer, "kb-lib.materializer.vault"),
|
||||
];
|
||||
for (materializer, expected_name) in materializers {
|
||||
let identity = materializer.identity();
|
||||
assert_eq!(identity.name, *expected_name);
|
||||
assert!(!identity.version.is_empty());
|
||||
assert!(materializer.accepted_families().is_empty());
|
||||
}
|
||||
|
||||
fn state_only_materializer_contracts() -> &'static [(&'static str, &'static str, &'static str)]
|
||||
{
|
||||
return &[
|
||||
(
|
||||
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
|
||||
crate::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA,
|
||||
),
|
||||
(
|
||||
crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME,
|
||||
crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
|
||||
crate::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_materializer_uses_the_component_and_processor_name_convention() {
|
||||
for (legacy, contextual, component_name, processor_name) in materializer_contracts() {
|
||||
assert_eq!(legacy.materializer_name(), *component_name);
|
||||
assert_eq!(contextual.identity().name, *processor_name);
|
||||
assert!(component_name.starts_with("kb-lib.materializer."));
|
||||
assert!(processor_name.starts_with("materializer."));
|
||||
assert_eq!(
|
||||
component_name.strip_prefix("kb-lib."),
|
||||
std::option::Option::Some(*processor_name)
|
||||
);
|
||||
assert!(!legacy.materializer_version().is_empty());
|
||||
assert!(!contextual.identity().version.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_materializers_preserve_legacy_trait_contracts() {
|
||||
let materializers: &[(&dyn crate::MtMaterializer, &str)] = &[
|
||||
(&crate::MtBridgeMaterializer, "kb-lib.materializer.bridge"),
|
||||
(&crate::MtGovernanceMaterializer, "kb-lib.materializer.governance"),
|
||||
(&crate::MtLendingMaterializer, "kb-lib.materializer.lending"),
|
||||
(&crate::MtLiquidityMaterializer, "kb-lib.materializer.liquidity"),
|
||||
(&crate::MtNftMaterializer, "kb-lib.materializer.nft"),
|
||||
(&crate::MtOracleMaterializer, "kb-lib.materializer.oracle"),
|
||||
(&crate::MtOrderbookMaterializer, "kb-lib.materializer.orderbook"),
|
||||
(&crate::MtPerpetualsMaterializer, "kb-lib.materializer.perpetuals"),
|
||||
(&crate::MtPoolStateMaterializer, "kb-lib.materializer.pool.state"),
|
||||
(&crate::MtRewardsMaterializer, "kb-lib.materializer.rewards"),
|
||||
(&crate::MtRoutingMaterializer, "kb-lib.materializer.routing"),
|
||||
(
|
||||
&crate::MtTokenMetadataRiskMaterializer,
|
||||
"kb-lib.materializer.token.metadata_risk",
|
||||
),
|
||||
(&crate::MtTradesMaterializer, "kb-lib.materializer.trades"),
|
||||
(&crate::MtVaultMaterializer, "kb-lib.materializer.vault"),
|
||||
fn state_only_materializers_keep_runtime_processor_and_tracing_identity_aligned() {
|
||||
for (component_name, processor_name, tracing_target) in state_only_materializer_contracts()
|
||||
{
|
||||
assert_eq!(*tracing_target, *component_name);
|
||||
assert_eq!(
|
||||
component_name.strip_prefix("kb-lib."),
|
||||
std::option::Option::Some(*processor_name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_materializers_are_empty_shells() {
|
||||
let materializers: &[&dyn crate::MtApiEventMaterializer] = &[
|
||||
&crate::MtBridgeMaterializer,
|
||||
&crate::MtGovernanceMaterializer,
|
||||
&crate::MtLendingMaterializer,
|
||||
&crate::MtLiquidityMaterializer,
|
||||
&crate::MtNftMaterializer,
|
||||
&crate::MtOracleMaterializer,
|
||||
&crate::MtOrderbookMaterializer,
|
||||
&crate::MtPerpetualsMaterializer,
|
||||
&crate::MtPoolStateMaterializer,
|
||||
&crate::MtRewardsMaterializer,
|
||||
&crate::MtRoutingMaterializer,
|
||||
&crate::MtTokenMetadataRiskMaterializer,
|
||||
&crate::MtTradesMaterializer,
|
||||
&crate::MtVaultMaterializer,
|
||||
];
|
||||
for (materializer, expected_name) in materializers {
|
||||
assert_eq!(materializer.materializer_name(), *expected_name);
|
||||
assert!(!materializer.materializer_version().is_empty());
|
||||
for materializer in materializers {
|
||||
assert!(materializer.accepted_families().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,55 @@
|
||||
// file: kb-lib/src/materializer/admin.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! `admin` materializer family.
|
||||
|
||||
mod constants;
|
||||
mod core;
|
||||
mod materializer;
|
||||
|
||||
/// Stable native and SPL administration materializer.
|
||||
pub use self::core::MtAdminMaterializer;
|
||||
pub use self::materializer::MtAdminMaterializer;
|
||||
/// Materializes one exact SPL ElGamal registry account snapshot.
|
||||
pub use self::core::materializer_admin_materialize_elgamal_registry_state_snapshot;
|
||||
pub use self::materializer::materializer_admin_materialize_elgamal_registry_state_snapshot;
|
||||
/// Materializes exact Token-2022 administration account snapshots.
|
||||
pub use self::core::materializer_admin_materialize_token_2022_state_snapshots;
|
||||
pub use self::materializer::materializer_admin_materialize_token_2022_state_snapshots;
|
||||
|
||||
/// Canonical processor name for the native and SPL administration materializer.
|
||||
/// Crate-visible `MT_ADMIN_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Stable runtime component name for the `admin` materializer.
|
||||
pub(crate) use self::constants::MT_ADMIN_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_ADMIN_CONFIG_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_CONFIG_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_CONFIG_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_CONFIG_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_LOADER_V4_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_LOADER_V4_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_LOADER_V4_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_LOADER_V4_SURFACE;
|
||||
/// Stable processor name for the `admin` materializer.
|
||||
pub(crate) use self::constants::MT_ADMIN_PROCESSOR_NAME;
|
||||
/// Canonical tracing target for the native and SPL administration materializer.
|
||||
/// Stable projection version for the `admin` materializer.
|
||||
pub(crate) use self::constants::MT_ADMIN_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_2022_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SPL_TOKEN_2022_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SPL_TOKEN_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SPL_TOKEN_SURFACE;
|
||||
/// Crate-visible `MT_ADMIN_SYSTEM_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SYSTEM_ENTRIES;
|
||||
/// Crate-visible `MT_ADMIN_SYSTEM_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_ADMIN_SYSTEM_SURFACE;
|
||||
/// Canonical tracing target for the `admin` materializer.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_ADMIN;
|
||||
|
||||
@@ -1,9 +1,67 @@
|
||||
// file: kb-lib/src/materializer/admin/constants.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! Local constants for the native and SPL administration materializer.
|
||||
//! Local constants for the admin materializer.
|
||||
|
||||
/// Canonical processor name for this component.
|
||||
/// Stable runtime component name for the admin materializer.
|
||||
pub(crate) const MT_ADMIN_COMPONENT_NAME: &str = "kb-lib.materializer.admin";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_ADMIN_PROCESSOR_NAME: &str = "materializer.admin";
|
||||
/// Canonical tracing target for this component.
|
||||
/// Stable projection contract version for the admin materializer.
|
||||
pub(crate) const MT_ADMIN_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for the admin materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_ADMIN: &str = "kb-lib.materializer.admin";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_ADMIN_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
|
||||
&[crate::MdEventFamily::Admin, crate::MdEventFamily::TokenAccount];
|
||||
/// Component contract for `system_surface`.
|
||||
pub(crate) const MT_ADMIN_SYSTEM_SURFACE: &str = "solana.core.system";
|
||||
/// Component contract for `system_entries`.
|
||||
pub(crate) const MT_ADMIN_SYSTEM_ENTRIES: &[&str] = &["assign", "assign_with_seed"];
|
||||
/// Component contract for `config_surface`.
|
||||
pub(crate) const MT_ADMIN_CONFIG_SURFACE: &str = "solana.core.config";
|
||||
/// Component contract for `config_entries`.
|
||||
pub(crate) const MT_ADMIN_CONFIG_ENTRIES: &[&str] = &["store"];
|
||||
/// Component contract for `bpf_loader_upgradeable_surface`.
|
||||
pub(crate) const MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE: &str =
|
||||
"solana.core.bpf_loader_upgradeable";
|
||||
/// Component contract for `bpf_loader_upgradeable_entries`.
|
||||
pub(crate) const MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES: &[&str] =
|
||||
&["set_authority", "set_authority_checked"];
|
||||
/// Component contract for `loader_v4_surface`.
|
||||
pub(crate) const MT_ADMIN_LOADER_V4_SURFACE: &str = "solana.core.loader_v4";
|
||||
/// Component contract for `loader_v4_entries`.
|
||||
pub(crate) const MT_ADMIN_LOADER_V4_ENTRIES: &[&str] = &["transfer_authority"];
|
||||
/// Component contract for `spl_token_surface`.
|
||||
pub(crate) const MT_ADMIN_SPL_TOKEN_SURFACE: &str = "spl.token";
|
||||
/// Component contract for `spl_token_2022_surface`.
|
||||
pub(crate) const MT_ADMIN_SPL_TOKEN_2022_SURFACE: &str = "spl.token_2022";
|
||||
/// Component contract for `spl_elgamal_registry_surface`.
|
||||
pub(crate) const MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE: &str = "spl.elgamal_registry";
|
||||
/// Component contract for `spl_elgamal_registry_entries`.
|
||||
pub(crate) const MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES: &[&str] =
|
||||
&["create_registry", "update_registry"];
|
||||
/// Component contract for `spl_token_authority_entries`.
|
||||
pub(crate) const MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES: &[&str] = &[
|
||||
"set_authority",
|
||||
"initialize_permanent_delegate",
|
||||
"initialize_interest_bearing_mint",
|
||||
"update_interest_bearing_rate",
|
||||
];
|
||||
/// Component contract for `spl_token_multisig_entries`.
|
||||
pub(crate) const MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES: &[&str] =
|
||||
&["initialize_multisig", "initialize_multisig2"];
|
||||
/// Component contract for `spl_token_2022_extension_admin_entries`.
|
||||
pub(crate) const MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES: &[&str] = &[
|
||||
"initialize_transfer_hook",
|
||||
"update_transfer_hook_program_id",
|
||||
"initialize_metadata_pointer",
|
||||
"update_metadata_pointer",
|
||||
"initialize_group_pointer",
|
||||
"update_group_pointer",
|
||||
"initialize_group_member_pointer",
|
||||
"update_group_member_pointer",
|
||||
"initialize_scaled_ui_amount",
|
||||
"update_scaled_ui_amount_multiplier",
|
||||
];
|
||||
|
||||
@@ -1,42 +1,8 @@
|
||||
// file: kb-lib/src/materializer/admin/core.rs
|
||||
// version: 20
|
||||
// file: kb-lib/src/materializer/admin/materializer.rs
|
||||
// version: 22
|
||||
|
||||
//! Stable administrative projections derived from exact committed observations.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
|
||||
&[crate::MdEventFamily::Admin, crate::MdEventFamily::TokenAccount];
|
||||
const SYSTEM_SURFACE: &str = "solana.core.system";
|
||||
const SYSTEM_ENTRIES: &[&str] = &["assign", "assign_with_seed"];
|
||||
const CONFIG_SURFACE: &str = "solana.core.config";
|
||||
const CONFIG_ENTRIES: &[&str] = &["store"];
|
||||
const BPF_LOADER_UPGRADEABLE_SURFACE: &str = "solana.core.bpf_loader_upgradeable";
|
||||
const BPF_LOADER_UPGRADEABLE_ENTRIES: &[&str] = &["set_authority", "set_authority_checked"];
|
||||
const LOADER_V4_SURFACE: &str = "solana.core.loader_v4";
|
||||
const LOADER_V4_ENTRIES: &[&str] = &["transfer_authority"];
|
||||
const SPL_TOKEN_SURFACE: &str = "spl.token";
|
||||
const SPL_TOKEN_2022_SURFACE: &str = "spl.token_2022";
|
||||
const SPL_ELGAMAL_REGISTRY_SURFACE: &str = "spl.elgamal_registry";
|
||||
const SPL_ELGAMAL_REGISTRY_ENTRIES: &[&str] = &["create_registry", "update_registry"];
|
||||
const SPL_TOKEN_AUTHORITY_ENTRIES: &[&str] = &[
|
||||
"set_authority",
|
||||
"initialize_permanent_delegate",
|
||||
"initialize_interest_bearing_mint",
|
||||
"update_interest_bearing_rate",
|
||||
];
|
||||
const SPL_TOKEN_MULTISIG_ENTRIES: &[&str] = &["initialize_multisig", "initialize_multisig2"];
|
||||
const SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES: &[&str] = &[
|
||||
"initialize_transfer_hook",
|
||||
"update_transfer_hook_program_id",
|
||||
"initialize_metadata_pointer",
|
||||
"update_metadata_pointer",
|
||||
"initialize_group_pointer",
|
||||
"update_group_pointer",
|
||||
"initialize_group_member_pointer",
|
||||
"update_group_member_pointer",
|
||||
"initialize_scaled_ui_amount",
|
||||
"update_scaled_ui_amount_multiplier",
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ProjectionKind {
|
||||
SystemAccountOwnership,
|
||||
@@ -91,7 +57,7 @@ pub struct MtAdminMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtAdminMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.admin";
|
||||
return crate::MT_ADMIN_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -99,7 +65,7 @@ impl crate::MtMaterializer for crate::MtAdminMaterializer {
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return ACCEPTED_FAMILIES.contains(&event.event_family)
|
||||
return crate::MT_ADMIN_ACCEPTED_FAMILIES.contains(&event.event_family)
|
||||
&& program_matches_surface(event)
|
||||
&& projection(event.surface_code.0.as_str(), event.event_name.0.as_str()).is_some();
|
||||
}
|
||||
@@ -118,17 +84,17 @@ impl crate::MtMaterializer for crate::MtAdminMaterializer {
|
||||
impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "materializer.admin".to_string(),
|
||||
name: crate::MT_ADMIN_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
return crate::MT_ADMIN_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return ACCEPTED_FAMILIES.contains(&observation.event.event_family)
|
||||
return crate::MT_ADMIN_ACCEPTED_FAMILIES.contains(&observation.event.event_family)
|
||||
&& program_matches_surface(&observation.event)
|
||||
&& projection(
|
||||
observation.event.surface_code.0.as_str(),
|
||||
@@ -165,7 +131,7 @@ impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
},
|
||||
};
|
||||
if !ACCEPTED_FAMILIES.contains(&observation.event.event_family) {
|
||||
if !crate::MT_ADMIN_ACCEPTED_FAMILIES.contains(&observation.event.event_family) {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if !program_matches_surface(&observation.event) {
|
||||
@@ -202,7 +168,7 @@ impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer {
|
||||
.output_key(observation.event.surface_code.0.as_str(), operation.as_str()),
|
||||
family: crate::MdMaterializedEventFamily::Admin,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionVersion": crate::MT_ADMIN_PROJECTION_VERSION,
|
||||
"projectionSemantics": "committed_instruction_admin_event",
|
||||
"domain": projection.domain(),
|
||||
"loaderSurface": projection.loader_surface(),
|
||||
@@ -212,6 +178,10 @@ impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer {
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_ADMIN_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
},
|
||||
"accounts": accounts,
|
||||
"parameters": parameters,
|
||||
"projection": projection_details(
|
||||
@@ -240,41 +210,51 @@ impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer {
|
||||
}
|
||||
|
||||
fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<ProjectionKind> {
|
||||
if surface_code == SYSTEM_SURFACE && contains(SYSTEM_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_ADMIN_SYSTEM_SURFACE
|
||||
&& contains(crate::MT_ADMIN_SYSTEM_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::SystemAccountOwnership);
|
||||
}
|
||||
if surface_code == CONFIG_SURFACE && contains(CONFIG_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_ADMIN_CONFIG_SURFACE
|
||||
&& contains(crate::MT_ADMIN_CONFIG_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ConfigAccountUpdate);
|
||||
}
|
||||
if surface_code == BPF_LOADER_UPGRADEABLE_SURFACE
|
||||
&& contains(BPF_LOADER_UPGRADEABLE_ENTRIES, entry_code)
|
||||
if surface_code == crate::MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE
|
||||
&& contains(crate::MT_ADMIN_BPF_LOADER_UPGRADEABLE_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ProgramLoaderAuthority(
|
||||
BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
crate::MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
));
|
||||
}
|
||||
if surface_code == LOADER_V4_SURFACE && contains(LOADER_V4_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_ADMIN_LOADER_V4_SURFACE
|
||||
&& contains(crate::MT_ADMIN_LOADER_V4_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ProgramLoaderAuthority(
|
||||
LOADER_V4_SURFACE,
|
||||
crate::MT_ADMIN_LOADER_V4_SURFACE,
|
||||
));
|
||||
}
|
||||
if matches!(surface_code, SPL_TOKEN_SURFACE | SPL_TOKEN_2022_SURFACE)
|
||||
&& contains(SPL_TOKEN_AUTHORITY_ENTRIES, entry_code)
|
||||
if matches!(
|
||||
surface_code,
|
||||
crate::MT_ADMIN_SPL_TOKEN_SURFACE | crate::MT_ADMIN_SPL_TOKEN_2022_SURFACE
|
||||
) && contains(crate::MT_ADMIN_SPL_TOKEN_AUTHORITY_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::SplTokenAuthority);
|
||||
}
|
||||
if matches!(surface_code, SPL_TOKEN_SURFACE | SPL_TOKEN_2022_SURFACE)
|
||||
&& contains(SPL_TOKEN_MULTISIG_ENTRIES, entry_code)
|
||||
if matches!(
|
||||
surface_code,
|
||||
crate::MT_ADMIN_SPL_TOKEN_SURFACE | crate::MT_ADMIN_SPL_TOKEN_2022_SURFACE
|
||||
) && contains(crate::MT_ADMIN_SPL_TOKEN_MULTISIG_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::SplTokenMultisig);
|
||||
}
|
||||
if surface_code == SPL_TOKEN_2022_SURFACE
|
||||
&& contains(SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES, entry_code)
|
||||
if surface_code == crate::MT_ADMIN_SPL_TOKEN_2022_SURFACE
|
||||
&& contains(crate::MT_ADMIN_SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::SplToken2022ExtensionAdmin);
|
||||
}
|
||||
if surface_code == SPL_ELGAMAL_REGISTRY_SURFACE
|
||||
&& contains(SPL_ELGAMAL_REGISTRY_ENTRIES, entry_code)
|
||||
if surface_code == crate::MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE
|
||||
&& contains(crate::MT_ADMIN_SPL_ELGAMAL_REGISTRY_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::SplElgamalRegistry);
|
||||
}
|
||||
@@ -282,13 +262,13 @@ fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<Proje
|
||||
}
|
||||
|
||||
fn program_matches_surface(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
if event.surface_code.0 == SPL_TOKEN_SURFACE {
|
||||
if event.surface_code.0 == crate::MT_ADMIN_SPL_TOKEN_SURFACE {
|
||||
return event.program_id.0 == kb_program_ids::SPL_TOKEN_PROGRAM_ID;
|
||||
}
|
||||
if event.surface_code.0 == SPL_TOKEN_2022_SURFACE {
|
||||
if event.surface_code.0 == crate::MT_ADMIN_SPL_TOKEN_2022_SURFACE {
|
||||
return event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID;
|
||||
}
|
||||
if event.surface_code.0 == SPL_ELGAMAL_REGISTRY_SURFACE {
|
||||
if event.surface_code.0 == crate::MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE {
|
||||
return event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID;
|
||||
}
|
||||
return true;
|
||||
@@ -537,7 +517,7 @@ mod tests {
|
||||
fn system_assign_creates_owner_projection() {
|
||||
let materializer = crate::MtAdminMaterializer;
|
||||
let observation = observation(
|
||||
super::SYSTEM_SURFACE,
|
||||
crate::MT_ADMIN_SYSTEM_SURFACE,
|
||||
"assign",
|
||||
false,
|
||||
serde_json::json!([account("assigned_account", "account111",)]),
|
||||
@@ -554,7 +534,7 @@ mod tests {
|
||||
fn seeded_assign_preserves_base_seed_and_owner() {
|
||||
let materializer = crate::MtAdminMaterializer;
|
||||
let observation = observation(
|
||||
super::SYSTEM_SURFACE,
|
||||
crate::MT_ADMIN_SYSTEM_SURFACE,
|
||||
"assign_with_seed",
|
||||
false,
|
||||
serde_json::json!([
|
||||
@@ -573,7 +553,7 @@ mod tests {
|
||||
fn config_store_preserves_bounded_opaque_audit_fields() {
|
||||
let materializer = crate::MtAdminMaterializer;
|
||||
let observation = observation(
|
||||
super::CONFIG_SURFACE,
|
||||
crate::MT_ADMIN_CONFIG_SURFACE,
|
||||
"store",
|
||||
false,
|
||||
serde_json::json!([
|
||||
@@ -600,13 +580,17 @@ mod tests {
|
||||
fn loader_authority_variants_create_exact_admin_outputs() {
|
||||
let materializer = crate::MtAdminMaterializer;
|
||||
for (surface, entry, target_role) in [
|
||||
(super::BPF_LOADER_UPGRADEABLE_SURFACE, "set_authority", "authority_target"),
|
||||
(
|
||||
super::BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
crate::MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
"set_authority",
|
||||
"authority_target",
|
||||
),
|
||||
(
|
||||
crate::MT_ADMIN_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
"set_authority_checked",
|
||||
"authority_target",
|
||||
),
|
||||
(super::LOADER_V4_SURFACE, "transfer_authority", "program_account"),
|
||||
(crate::MT_ADMIN_LOADER_V4_SURFACE, "transfer_authority", "program_account"),
|
||||
] {
|
||||
let observation = observation(
|
||||
surface,
|
||||
@@ -629,7 +613,7 @@ mod tests {
|
||||
#[test]
|
||||
fn classic_token_authority_and_multisig_create_distinct_admin_outputs() {
|
||||
let mut authority = observation(
|
||||
super::SPL_TOKEN_SURFACE,
|
||||
crate::MT_ADMIN_SPL_TOKEN_SURFACE,
|
||||
"set_authority",
|
||||
false,
|
||||
serde_json::json!([
|
||||
@@ -649,7 +633,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut multisig = observation(
|
||||
super::SPL_TOKEN_SURFACE,
|
||||
crate::MT_ADMIN_SPL_TOKEN_SURFACE,
|
||||
"initialize_multisig2",
|
||||
false,
|
||||
serde_json::json!([
|
||||
@@ -678,7 +662,7 @@ mod tests {
|
||||
#[test]
|
||||
fn token_2022_authority_change_uses_the_admin_owner_once() {
|
||||
let mut observation = observation(
|
||||
super::SPL_TOKEN_2022_SURFACE,
|
||||
crate::MT_ADMIN_SPL_TOKEN_2022_SURFACE,
|
||||
"set_authority",
|
||||
false,
|
||||
serde_json::json!([
|
||||
@@ -703,7 +687,7 @@ mod tests {
|
||||
fn failed_admin_mutation_is_refused() {
|
||||
let materializer = crate::MtAdminMaterializer;
|
||||
let observation = observation(
|
||||
super::SYSTEM_SURFACE,
|
||||
crate::MT_ADMIN_SYSTEM_SURFACE,
|
||||
"assign",
|
||||
true,
|
||||
serde_json::json!([account("assigned_account", "account111",)]),
|
||||
@@ -754,7 +738,7 @@ mod tests {
|
||||
fn output_serialization_is_deterministic() {
|
||||
let materializer = crate::MtAdminMaterializer;
|
||||
let observation = observation(
|
||||
super::CONFIG_SURFACE,
|
||||
crate::MT_ADMIN_CONFIG_SURFACE,
|
||||
"store",
|
||||
false,
|
||||
serde_json::json!([account("config_account", "config111",)]),
|
||||
@@ -788,7 +772,7 @@ mod tests {
|
||||
("update_registry", 0_i64, 2_u64, "elgamal_registry_key_updated"),
|
||||
] {
|
||||
let mut observation = observation(
|
||||
super::SPL_ELGAMAL_REGISTRY_SURFACE,
|
||||
crate::MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE,
|
||||
operation,
|
||||
false,
|
||||
serde_json::json!([
|
||||
@@ -830,7 +814,7 @@ mod tests {
|
||||
fn elgamal_registry_foreign_program_and_failed_transaction_are_refused() {
|
||||
let materializer = crate::MtAdminMaterializer;
|
||||
let mut foreign = observation(
|
||||
super::SPL_ELGAMAL_REGISTRY_SURFACE,
|
||||
crate::MT_ADMIN_SPL_ELGAMAL_REGISTRY_SURFACE,
|
||||
"create_registry",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
@@ -858,7 +842,7 @@ mod tests {
|
||||
("update_group_member_pointer", "memberAddress", "member111"),
|
||||
] {
|
||||
let mut observation = observation(
|
||||
super::SPL_TOKEN_2022_SURFACE,
|
||||
crate::MT_ADMIN_SPL_TOKEN_2022_SURFACE,
|
||||
operation,
|
||||
false,
|
||||
serde_json::json!([{"position":0,"accountKey":"mint111"}]),
|
||||
@@ -881,7 +865,7 @@ mod tests {
|
||||
#[test]
|
||||
fn token_2022_extension_admin_rejects_foreign_program_and_failed_transaction() {
|
||||
let mut observation = observation(
|
||||
super::SPL_TOKEN_2022_SURFACE,
|
||||
crate::MT_ADMIN_SPL_TOKEN_2022_SURFACE,
|
||||
"initialize_metadata_pointer",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
@@ -937,7 +921,7 @@ pub fn materializer_admin_materialize_token_2022_state_snapshots(
|
||||
output_key: output_key.clone(),
|
||||
family: crate::MdMaterializedEventFamily::Admin,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionVersion": crate::MT_ADMIN_PROJECTION_VERSION,
|
||||
"projectionSemantics": "authoritative_bounded_admin_state_snapshot",
|
||||
"domain": "token_2022_extension_admin_state",
|
||||
"idempotenceKey": output_key,
|
||||
@@ -1072,7 +1056,7 @@ pub fn materializer_admin_materialize_elgamal_registry_state_snapshot(
|
||||
output_key: format!("spl_elgamal_registry_state:{registry_account_key}:{slot}"),
|
||||
family: crate::MdMaterializedEventFamily::Admin,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionVersion": crate::MT_ADMIN_PROJECTION_VERSION,
|
||||
"domain": "spl_elgamal_registry_account_state",
|
||||
"projectionSemantics": "authoritative_exact_account_snapshot",
|
||||
"idempotenceKey": format!("spl_elgamal_registry:{registry_account_key}:{slot}"),
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: kb-lib/src/materializer/api/contracts.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Backend-neutral decoded observation materialization contracts.
|
||||
|
||||
/// Stable identity of one decoded event materializer.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct MtApiMaterializerIdentity {
|
||||
/// Stable lower snake case processor name.
|
||||
/// Stable dotted lower-snake-case processor name without the runtime crate prefix.
|
||||
pub name: std::string::String,
|
||||
/// Semantic or deterministic implementation version.
|
||||
pub version: std::string::String,
|
||||
@@ -177,7 +177,7 @@ impl MtApiMaterializerExecutionResult {
|
||||
|
||||
/// Stable decoded observation materializer contract used by the common pipeline.
|
||||
pub trait MtApiEventMaterializer: std::marker::Send + std::marker::Sync {
|
||||
/// Returns the stable materializer identity.
|
||||
/// Returns the stable persisted processor identity for this materializer.
|
||||
fn identity(&self) -> MtApiMaterializerIdentity;
|
||||
/// Returns every decoded event family explicitly accepted by this materializer.
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily];
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// file: kb-lib/src/materializer/api/materializer.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Materializer API contract used by business materializer crates.
|
||||
//! Legacy materializer API contract used by business materializer modules.
|
||||
|
||||
/// Common contract implemented by all business materializer crates.
|
||||
/// Common compatibility contract implemented by all business materializer modules.
|
||||
pub trait MtMaterializer {
|
||||
/// Returns the stable materializer name.
|
||||
/// Returns the stable runtime component name with the `kb-lib.materializer.` prefix.
|
||||
fn materializer_name(&self) -> &'static str;
|
||||
|
||||
/// Returns the materializer version.
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/bridge.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `bridge` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved bridge materializer.
|
||||
pub use self::core::MtBridgeMaterializer;
|
||||
pub use self::materializer::MtBridgeMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_BRIDGE_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `bridge` materializer.
|
||||
pub(crate) use self::constants::MT_BRIDGE_COMPONENT_NAME;
|
||||
/// Stable processor name for the `bridge` materializer.
|
||||
pub(crate) use self::constants::MT_BRIDGE_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/bridge/constants.rs
Normal file
11
kb-lib/src/materializer/bridge/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/bridge/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the bridge materializer.
|
||||
|
||||
/// Stable runtime component name for the bridge materializer.
|
||||
pub(crate) const MT_BRIDGE_COMPONENT_NAME: &str = "kb-lib.materializer.bridge";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_BRIDGE_PROCESSOR_NAME: &str = "materializer.bridge";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_BRIDGE_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/bridge/core.rs
|
||||
// version: 3
|
||||
// file: kb-lib/src/materializer/bridge/materializer.rs
|
||||
// version: 4
|
||||
|
||||
//! Reserved bridge materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtBridgeMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtBridgeMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.bridge";
|
||||
return crate::MT_BRIDGE_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtBridgeMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Bridge,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtBridgeMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.bridge".to_string(),
|
||||
name: crate::MT_BRIDGE_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_BRIDGE_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,13 +1,36 @@
|
||||
// file: kb-lib/src/materializer/compliance.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! `compliance` materializer family.
|
||||
|
||||
mod audit;
|
||||
mod constants;
|
||||
|
||||
/// Stable native compliance audit materializer.
|
||||
pub use self::audit::MtComplianceAuditMaterializer;
|
||||
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Stable runtime component name for the native compliance audit materializer.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE;
|
||||
/// Stable processor name for the native compliance audit materializer.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME;
|
||||
/// Stable projection version for native compliance audit outputs.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_WRITE_ENTRIES` component constant.
|
||||
pub(crate) use self::audit::MT_COMPLIANCE_AUDIT_WRITE_ENTRIES;
|
||||
/// Canonical tracing target for the native compliance audit materializer.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT;
|
||||
pub(crate) use self::audit::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT;
|
||||
|
||||
@@ -1,577 +1,37 @@
|
||||
// file: kb-lib/src/materializer/compliance/audit.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
//! Stable compliance-audit projections for committed native bytecode mutations and execution profiles.
|
||||
//! Native compliance-audit materializer component.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[crate::MdEventFamily::Audit];
|
||||
const COMPUTE_BUDGET_SURFACE: &str = "solana.core.compute_budget";
|
||||
const COMPUTE_BUDGET_ENTRIES: &[&str] = &[
|
||||
"request_units_deprecated",
|
||||
"request_heap_frame",
|
||||
"set_compute_unit_limit",
|
||||
"set_compute_unit_price",
|
||||
"set_loaded_accounts_data_size_limit",
|
||||
];
|
||||
const BPF_LOADER_DEPRECATED_SURFACE: &str = "solana.core.bpf_loader_deprecated";
|
||||
const BPF_LOADER_SURFACE: &str = "solana.core.bpf_loader";
|
||||
const BPF_LOADER_UPGRADEABLE_SURFACE: &str = "solana.core.bpf_loader_upgradeable";
|
||||
const LOADER_V4_SURFACE: &str = "solana.core.loader_v4";
|
||||
const WRITE_ENTRIES: &[&str] = &["write"];
|
||||
const LOADER_V4_COPY_ENTRIES: &[&str] = &["copy"];
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ProjectionKind {
|
||||
ProgramByteWrite(&'static str),
|
||||
ProgramByteCopy,
|
||||
ComputeBudgetProfile,
|
||||
}
|
||||
/// Stable native compliance audit materializer.
|
||||
pub use self::materializer::MtComplianceAuditMaterializer;
|
||||
|
||||
impl ProjectionKind {
|
||||
fn output_key(self, surface_code: &str, operation: &str) -> std::string::String {
|
||||
return match self {
|
||||
Self::ProgramByteWrite(_) | Self::ProgramByteCopy => {
|
||||
format!("program_loader_code:{surface_code}:{operation}:0")
|
||||
},
|
||||
Self::ComputeBudgetProfile => "transaction_compute_budget_profile:0".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn loader_surface(self) -> &'static str {
|
||||
return match self {
|
||||
Self::ProgramByteWrite(surface) => surface,
|
||||
Self::ProgramByteCopy => LOADER_V4_SURFACE,
|
||||
Self::ComputeBudgetProfile => COMPUTE_BUDGET_SURFACE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Stable compliance-audit materializer for native bytecode mutations and execution profiles.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtComplianceAuditMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtComplianceAuditMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.compliance.audit";
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.event_family == crate::MdEventFamily::Audit
|
||||
&& projection(event.surface_code.0.as_str(), event.event_name.0.as_str()).is_some();
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
if !crate::MtMaterializer::accepts_event(self, event) {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtComplianceAuditMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "materializer.compliance.audit".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return observation.event.event_family == crate::MdEventFamily::Audit
|
||||
&& projection(
|
||||
observation.event.surface_code.0.as_str(),
|
||||
observation.event.event_name.0.as_str(),
|
||||
)
|
||||
.is_some();
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulOrFailedAudit;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
let projection = match projection(
|
||||
observation.event.surface_code.0.as_str(),
|
||||
observation.event.event_name.0.as_str(),
|
||||
) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compliance_audit",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
accepted = false,
|
||||
"ignore observation outside supported native compliance-audit projections"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
},
|
||||
};
|
||||
if observation.event.event_family != crate::MdEventFamily::Audit {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if projection != ProjectionKind::ComputeBudgetProfile
|
||||
&& (observation.transaction_failed || !observation.observation_committed)
|
||||
{
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compliance_audit",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
committed = observation.observation_committed,
|
||||
transaction_failed = observation.transaction_failed,
|
||||
"refuse uncommitted native bytecode mutation observation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"failed_transaction_compliance_audit_refused",
|
||||
"failed or uncommitted native observations cannot create committed bytecode mutation outputs",
|
||||
);
|
||||
}
|
||||
let accounts = observation
|
||||
.payload_json
|
||||
.get("accounts")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let parameters = observation
|
||||
.payload_json
|
||||
.get("parameters")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let operation = observation.event.event_name.0.clone();
|
||||
if projection == ProjectionKind::ComputeBudgetProfile {
|
||||
return materialize_compute_budget_profile(observation, parameters, operation);
|
||||
}
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: projection
|
||||
.output_key(observation.event.surface_code.0.as_str(), operation.as_str()),
|
||||
family: crate::MdMaterializedEventFamily::ComplianceAudit,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionSemantics": "committed_instruction_compliance_audit_event",
|
||||
"domain": "program_loader_code_mutation",
|
||||
"loaderSurface": projection.loader_surface(),
|
||||
"operation": operation,
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": true,
|
||||
"accounts": accounts,
|
||||
"parameters": parameters,
|
||||
"projection": projection_details(projection, &accounts, ¶meters),
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compliance_audit",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
loader_surface = projection.loader_surface(),
|
||||
output_count = 1_usize,
|
||||
"materialize committed native bytecode mutation observation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize_compute_budget_profile(
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
parameters: serde_json::Value,
|
||||
operation: std::string::String,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
let profile = parameters.get("transactionProfile").cloned().unwrap_or(serde_json::Value::Null);
|
||||
if !compute_budget_profile_emits(&profile) {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: "transaction_compute_budget_profile:0".to_string(),
|
||||
family: crate::MdMaterializedEventFamily::ComplianceAudit,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionSemantics": "transaction_compute_budget_profile",
|
||||
"domain": "transaction_compute_budget_profile",
|
||||
"operation": operation,
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": !observation.transaction_failed,
|
||||
"profile": profile,
|
||||
"profileSource": {
|
||||
"surfaceCode": observation.event.surface_code.0.clone(),
|
||||
"entryCode": observation.event.event_name.0.clone(),
|
||||
"eventKey": observation.event_key.clone()
|
||||
},
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compute_budget_profile",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
output_count = 1_usize,
|
||||
transaction_failed = observation.transaction_failed,
|
||||
"materialize native Compute Budget transaction profile"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
fn compute_budget_profile_emits(profile: &serde_json::Value) -> bool {
|
||||
return profile
|
||||
.get("currentInstructionIsProfileEmitter")
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== std::option::Option::Some(true);
|
||||
}
|
||||
|
||||
fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<ProjectionKind> {
|
||||
if surface_code == COMPUTE_BUDGET_SURFACE && contains(COMPUTE_BUDGET_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::ComputeBudgetProfile);
|
||||
}
|
||||
if contains(WRITE_ENTRIES, entry_code) {
|
||||
for supported in [
|
||||
BPF_LOADER_DEPRECATED_SURFACE,
|
||||
BPF_LOADER_SURFACE,
|
||||
BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
LOADER_V4_SURFACE,
|
||||
] {
|
||||
if surface_code == supported {
|
||||
return std::option::Option::Some(ProjectionKind::ProgramByteWrite(supported));
|
||||
}
|
||||
}
|
||||
}
|
||||
if surface_code == LOADER_V4_SURFACE && contains(LOADER_V4_COPY_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::ProgramByteCopy);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn projection_details(
|
||||
projection: ProjectionKind,
|
||||
accounts: &serde_json::Value,
|
||||
parameters: &serde_json::Value,
|
||||
) -> serde_json::Value {
|
||||
return match projection {
|
||||
ProjectionKind::ProgramByteWrite(_) => serde_json::json!({
|
||||
"targetAccount": first_account_key_for_roles(
|
||||
accounts,
|
||||
&["program_account", "buffer_account"],
|
||||
),
|
||||
"authorityAccount": first_account_key_for_roles(
|
||||
accounts,
|
||||
&["authority", "buffer_authority"],
|
||||
),
|
||||
"offset": parameters.get("offset").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"byteLength": parameters.get("byteLength").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"bytesSha256": parameters.get("bytesSha256").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"bytesPrefixHex": parameters.get("bytesPrefixHex").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"stateTransition": "program_bytes_written",
|
||||
"codeBytesMaterialized": false,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
}),
|
||||
ProjectionKind::ProgramByteCopy => serde_json::json!({
|
||||
"targetAccount": account_key_for_role(accounts, "program_account"),
|
||||
"authorityAccount": account_key_for_role(accounts, "authority"),
|
||||
"sourceProgramAccount": account_key_for_role(accounts, "source_program_account"),
|
||||
"destinationOffset": parameters.get("destinationOffset").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"sourceOffset": parameters.get("sourceOffset").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"byteLength": parameters.get("length").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"stateTransition": "program_bytes_copied",
|
||||
"codeBytesMaterialized": false,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
}),
|
||||
ProjectionKind::ComputeBudgetProfile => serde_json::Value::Null,
|
||||
};
|
||||
}
|
||||
|
||||
fn first_account_key_for_roles(accounts: &serde_json::Value, roles: &[&str]) -> serde_json::Value {
|
||||
for role in roles {
|
||||
let value = account_key_for_role(accounts, role);
|
||||
if !value.is_null() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return serde_json::Value::Null;
|
||||
}
|
||||
|
||||
fn account_key_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
|
||||
let account_key = accounts.as_array().and_then(|values| {
|
||||
return values.iter().find_map(|value| {
|
||||
if value.get("role").and_then(serde_json::Value::as_str)
|
||||
!= std::option::Option::Some(role)
|
||||
{
|
||||
return std::option::Option::None;
|
||||
}
|
||||
return value
|
||||
.get("accountKey")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(|account_key| return account_key.to_string());
|
||||
});
|
||||
});
|
||||
return match account_key {
|
||||
std::option::Option::Some(value) => serde_json::Value::String(value),
|
||||
std::option::Option::None => serde_json::Value::Null,
|
||||
};
|
||||
}
|
||||
|
||||
fn contains(entries: &[&str], entry_code: &str) -> bool {
|
||||
return entries.iter().any(|entry| return *entry == entry_code);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn observation(
|
||||
surface_code: &str,
|
||||
entry_code: &str,
|
||||
transaction_failed: bool,
|
||||
accounts: serde_json::Value,
|
||||
parameters: serde_json::Value,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: format!("{entry_code}:0"),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("0".to_string()),
|
||||
program_id: crate::MdProgramId("program-id".to_string()),
|
||||
protocol_code: crate::MdProtocolCode("solana.core".to_string()),
|
||||
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
|
||||
event_code: crate::MdEventCode(format!("{surface_code}.{entry_code}")),
|
||||
event_name: crate::MdEventName(entry_code.to_string()),
|
||||
event_family: crate::MdEventFamily::Audit,
|
||||
source_kind: crate::MdEventSourceKind::Instruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({"accounts": accounts, "parameters": parameters}),
|
||||
transaction_failed,
|
||||
transaction_error: if transaction_failed {
|
||||
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
observation_committed: !transaction_failed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["fixture".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn account(role: &str, account_key: &str) -> serde_json::Value {
|
||||
return serde_json::json!({"role": role, "accountKey": account_key});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_write_variants_create_bounded_code_audits() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
for (surface, target_role, authority_role) in [
|
||||
(super::BPF_LOADER_DEPRECATED_SURFACE, "program_account", "missing"),
|
||||
(super::BPF_LOADER_SURFACE, "program_account", "missing"),
|
||||
(super::BPF_LOADER_UPGRADEABLE_SURFACE, "buffer_account", "buffer_authority"),
|
||||
(super::LOADER_V4_SURFACE, "program_account", "authority"),
|
||||
] {
|
||||
let observation = observation(
|
||||
surface,
|
||||
"write",
|
||||
false,
|
||||
serde_json::json!([
|
||||
account(target_role, "target111"),
|
||||
account(authority_role, "authority111"),
|
||||
]),
|
||||
serde_json::json!({
|
||||
"offset": 7,
|
||||
"byteLength": 32,
|
||||
"bytesSha256": "abc",
|
||||
"bytesPrefixHex": "0102",
|
||||
}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs[0].family, crate::MdMaterializedEventFamily::ComplianceAudit);
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "target111");
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["bytesSha256"], "abc");
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["projection"]["codeBytesMaterialized"],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_v4_copy_preserves_source_and_bounded_ranges() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
super::LOADER_V4_SURFACE,
|
||||
"copy",
|
||||
false,
|
||||
serde_json::json!([
|
||||
account("program_account", "target111"),
|
||||
account("authority", "authority111"),
|
||||
account("source_program_account", "source111"),
|
||||
]),
|
||||
serde_json::json!({"destinationOffset": 8, "sourceOffset": 4, "length": 16}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(
|
||||
result.outputs[0].output_key,
|
||||
"program_loader_code:solana.core.loader_v4:copy:0"
|
||||
);
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["projection"]["sourceProgramAccount"],
|
||||
"source111"
|
||||
);
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["byteLength"], 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_budget_profile_is_emitted_only_by_profile_emitter() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let emitter = observation(
|
||||
super::COMPUTE_BUDGET_SURFACE,
|
||||
"set_compute_unit_limit",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({
|
||||
"transactionProfile": {
|
||||
"currentInstructionIsProfileEmitter": true,
|
||||
"profileEmitterInstructionPath": "0",
|
||||
"computeBudgetInstructionCount": 2,
|
||||
"effectiveValues": {
|
||||
"set_compute_unit_limit": {
|
||||
"value": 200000,
|
||||
"sourceInstructionPath": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
let emitted = crate::MtApiEventMaterializer::materialize(&materializer, &emitter);
|
||||
assert_eq!(emitted.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(emitted.outputs[0].output_key, "transaction_compute_budget_profile:0");
|
||||
assert_eq!(emitted.outputs[0].family, crate::MdMaterializedEventFamily::ComplianceAudit);
|
||||
assert_eq!(emitted.outputs[0].payload_json["profile"]["computeBudgetInstructionCount"], 2);
|
||||
let non_emitter = observation(
|
||||
super::COMPUTE_BUDGET_SURFACE,
|
||||
"set_compute_unit_price",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({
|
||||
"transactionProfile": {
|
||||
"currentInstructionIsProfileEmitter": false,
|
||||
"profileEmitterInstructionPath": "0"
|
||||
}
|
||||
}),
|
||||
);
|
||||
let ignored = crate::MtApiEventMaterializer::materialize(&materializer, &non_emitter);
|
||||
assert_eq!(ignored.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_compute_budget_profile_remains_audit_materializable() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
super::COMPUTE_BUDGET_SURFACE,
|
||||
"set_compute_unit_price",
|
||||
true,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({
|
||||
"transactionProfile": {
|
||||
"currentInstructionIsProfileEmitter": true,
|
||||
"profileEmitterInstructionPath": "0",
|
||||
"computeBudgetInstructionCount": 1
|
||||
}
|
||||
}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs[0].payload_json["transactionSucceeded"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_code_mutation_is_refused() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
super::LOADER_V4_SURFACE,
|
||||
"write",
|
||||
true,
|
||||
serde_json::json!([account("program_account", "target111")]),
|
||||
serde_json::json!({"offset": 0, "byteLength": 1, "bytesSha256": "abc"}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrelated_audit_observation_is_ignored() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
"other_surface",
|
||||
"write",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_serialization_is_deterministic() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
super::BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
"write",
|
||||
false,
|
||||
serde_json::json!([
|
||||
account("buffer_account", "buffer111"),
|
||||
account("buffer_authority", "authority111"),
|
||||
]),
|
||||
serde_json::json!({
|
||||
"offset": 0,
|
||||
"byteLength": 2,
|
||||
"bytesSha256": "abc",
|
||||
"bytesPrefixHex": "0102",
|
||||
}),
|
||||
);
|
||||
let first = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
let second = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
let first_json = match serde_json::to_string(&first) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("first compliance serialization failed: {error}")
|
||||
},
|
||||
};
|
||||
let second_json = match serde_json::to_string(&second) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("second compliance serialization failed: {error}")
|
||||
},
|
||||
};
|
||||
assert_eq!(first_json, second_json);
|
||||
}
|
||||
}
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Stable runtime component name for the native compliance audit materializer.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE;
|
||||
/// Stable processor name for the native compliance audit materializer.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME;
|
||||
/// Stable projection version for native compliance audit outputs.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_COMPLIANCE_AUDIT_WRITE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_WRITE_ENTRIES;
|
||||
/// Canonical tracing target for the native compliance audit materializer.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT;
|
||||
|
||||
42
kb-lib/src/materializer/compliance/audit/constants.rs
Normal file
42
kb-lib/src/materializer/compliance/audit/constants.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
// file: kb-lib/src/materializer/compliance/audit/constants.rs
|
||||
// version: 4
|
||||
|
||||
//! Local constants for the compliance audit materializer.
|
||||
|
||||
/// Stable runtime component name for the compliance audit materializer.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_COMPONENT_NAME: &str = "kb-lib.materializer.compliance.audit";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_PROCESSOR_NAME: &str = "materializer.compliance.audit";
|
||||
/// Stable projection contract version for the compliance audit materializer.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for the compliance audit materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT: &str =
|
||||
"kb-lib.materializer.compliance.audit";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
|
||||
&[crate::MdEventFamily::Audit];
|
||||
/// Component contract for `compute_budget_surface`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE: &str = "solana.core.compute_budget";
|
||||
/// Component contract for `compute_budget_entries`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES: &[&str] = &[
|
||||
"request_units_deprecated",
|
||||
"request_heap_frame",
|
||||
"set_compute_unit_limit",
|
||||
"set_compute_unit_price",
|
||||
"set_loaded_accounts_data_size_limit",
|
||||
];
|
||||
/// Component contract for `bpf_loader_deprecated_surface`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE: &str =
|
||||
"solana.core.bpf_loader_deprecated";
|
||||
/// Component contract for `bpf_loader_surface`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE: &str = "solana.core.bpf_loader";
|
||||
/// Component contract for `bpf_loader_upgradeable_surface`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE: &str =
|
||||
"solana.core.bpf_loader_upgradeable";
|
||||
/// Component contract for `loader_v4_surface`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE: &str = "solana.core.loader_v4";
|
||||
/// Component contract for `write_entries`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_WRITE_ENTRIES: &[&str] = &["write"];
|
||||
/// Component contract for `loader_v4_copy_entries`.
|
||||
pub(crate) const MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES: &[&str] = &["copy"];
|
||||
581
kb-lib/src/materializer/compliance/audit/materializer.rs
Normal file
581
kb-lib/src/materializer/compliance/audit/materializer.rs
Normal file
@@ -0,0 +1,581 @@
|
||||
// file: kb-lib/src/materializer/compliance/audit/materializer.rs
|
||||
// version: 12
|
||||
|
||||
//! Stable compliance-audit projections for committed native bytecode mutations and execution profiles.
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ProjectionKind {
|
||||
ProgramByteWrite(&'static str),
|
||||
ProgramByteCopy,
|
||||
ComputeBudgetProfile,
|
||||
}
|
||||
|
||||
impl ProjectionKind {
|
||||
fn output_key(self, surface_code: &str, operation: &str) -> std::string::String {
|
||||
return match self {
|
||||
Self::ProgramByteWrite(_) | Self::ProgramByteCopy => {
|
||||
format!("program_loader_code:{surface_code}:{operation}:0")
|
||||
},
|
||||
Self::ComputeBudgetProfile => "transaction_compute_budget_profile:0".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn loader_surface(self) -> &'static str {
|
||||
return match self {
|
||||
Self::ProgramByteWrite(surface) => surface,
|
||||
Self::ProgramByteCopy => crate::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE,
|
||||
Self::ComputeBudgetProfile => crate::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Stable compliance-audit materializer for native bytecode mutations and execution profiles.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtComplianceAuditMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtComplianceAuditMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return crate::MT_COMPLIANCE_AUDIT_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.event_family == crate::MdEventFamily::Audit
|
||||
&& projection(event.surface_code.0.as_str(), event.event_name.0.as_str()).is_some();
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
if !crate::MtMaterializer::accepts_event(self, event) {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtComplianceAuditMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return crate::MT_COMPLIANCE_AUDIT_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return observation.event.event_family == crate::MdEventFamily::Audit
|
||||
&& projection(
|
||||
observation.event.surface_code.0.as_str(),
|
||||
observation.event.event_name.0.as_str(),
|
||||
)
|
||||
.is_some();
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulOrFailedAudit;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
let projection = match projection(
|
||||
observation.event.surface_code.0.as_str(),
|
||||
observation.event.event_name.0.as_str(),
|
||||
) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compliance_audit",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
accepted = false,
|
||||
"ignore observation outside supported native compliance-audit projections"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
},
|
||||
};
|
||||
if observation.event.event_family != crate::MdEventFamily::Audit {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if projection != ProjectionKind::ComputeBudgetProfile
|
||||
&& (observation.transaction_failed || !observation.observation_committed)
|
||||
{
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compliance_audit",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
committed = observation.observation_committed,
|
||||
transaction_failed = observation.transaction_failed,
|
||||
"refuse uncommitted native bytecode mutation observation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"failed_transaction_compliance_audit_refused",
|
||||
"failed or uncommitted native observations cannot create committed bytecode mutation outputs",
|
||||
);
|
||||
}
|
||||
let accounts = observation
|
||||
.payload_json
|
||||
.get("accounts")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let parameters = observation
|
||||
.payload_json
|
||||
.get("parameters")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let operation = observation.event.event_name.0.clone();
|
||||
if projection == ProjectionKind::ComputeBudgetProfile {
|
||||
return materialize_compute_budget_profile(observation, parameters, operation);
|
||||
}
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: projection
|
||||
.output_key(observation.event.surface_code.0.as_str(), operation.as_str()),
|
||||
family: crate::MdMaterializedEventFamily::ComplianceAudit,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_COMPLIANCE_AUDIT_PROJECTION_VERSION,
|
||||
"projectionSemantics": "committed_instruction_compliance_audit_event",
|
||||
"domain": "program_loader_code_mutation",
|
||||
"loaderSurface": projection.loader_surface(),
|
||||
"operation": operation,
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
},
|
||||
"accounts": accounts,
|
||||
"parameters": parameters,
|
||||
"projection": projection_details(projection, &accounts, ¶meters),
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compliance_audit",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
loader_surface = projection.loader_surface(),
|
||||
output_count = 1_usize,
|
||||
"materialize committed native bytecode mutation observation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize_compute_budget_profile(
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
parameters: serde_json::Value,
|
||||
operation: std::string::String,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
let profile = parameters.get("transactionProfile").cloned().unwrap_or(serde_json::Value::Null);
|
||||
if !compute_budget_profile_emits(&profile) {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: "transaction_compute_budget_profile:0".to_string(),
|
||||
family: crate::MdMaterializedEventFamily::ComplianceAudit,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_COMPLIANCE_AUDIT_PROJECTION_VERSION,
|
||||
"projectionSemantics": "transaction_compute_budget_profile",
|
||||
"domain": "transaction_compute_budget_profile",
|
||||
"operation": operation,
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": !observation.transaction_failed,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_COMPLIANCE_AUDIT_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
},
|
||||
"profile": profile,
|
||||
"profileSource": {
|
||||
"surfaceCode": observation.event.surface_code.0.clone(),
|
||||
"entryCode": observation.event.event_name.0.clone(),
|
||||
"eventKey": observation.event_key.clone()
|
||||
},
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT,
|
||||
action = "materialize_compute_budget_profile",
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
output_count = 1_usize,
|
||||
transaction_failed = observation.transaction_failed,
|
||||
"materialize native Compute Budget transaction profile"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
fn compute_budget_profile_emits(profile: &serde_json::Value) -> bool {
|
||||
return profile
|
||||
.get("currentInstructionIsProfileEmitter")
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== std::option::Option::Some(true);
|
||||
}
|
||||
|
||||
fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<ProjectionKind> {
|
||||
if surface_code == crate::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE
|
||||
&& contains(crate::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ComputeBudgetProfile);
|
||||
}
|
||||
if contains(crate::MT_COMPLIANCE_AUDIT_WRITE_ENTRIES, entry_code) {
|
||||
for supported in [
|
||||
crate::MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE,
|
||||
crate::MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE,
|
||||
crate::MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
crate::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE,
|
||||
] {
|
||||
if surface_code == supported {
|
||||
return std::option::Option::Some(ProjectionKind::ProgramByteWrite(supported));
|
||||
}
|
||||
}
|
||||
}
|
||||
if surface_code == crate::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE
|
||||
&& contains(crate::MT_COMPLIANCE_AUDIT_LOADER_V4_COPY_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ProgramByteCopy);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn projection_details(
|
||||
projection: ProjectionKind,
|
||||
accounts: &serde_json::Value,
|
||||
parameters: &serde_json::Value,
|
||||
) -> serde_json::Value {
|
||||
return match projection {
|
||||
ProjectionKind::ProgramByteWrite(_) => serde_json::json!({
|
||||
"targetAccount": first_account_key_for_roles(
|
||||
accounts,
|
||||
&["program_account", "buffer_account"],
|
||||
),
|
||||
"authorityAccount": first_account_key_for_roles(
|
||||
accounts,
|
||||
&["authority", "buffer_authority"],
|
||||
),
|
||||
"offset": parameters.get("offset").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"byteLength": parameters.get("byteLength").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"bytesSha256": parameters.get("bytesSha256").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"bytesPrefixHex": parameters.get("bytesPrefixHex").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"stateTransition": "program_bytes_written",
|
||||
"codeBytesMaterialized": false,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
}),
|
||||
ProjectionKind::ProgramByteCopy => serde_json::json!({
|
||||
"targetAccount": account_key_for_role(accounts, "program_account"),
|
||||
"authorityAccount": account_key_for_role(accounts, "authority"),
|
||||
"sourceProgramAccount": account_key_for_role(accounts, "source_program_account"),
|
||||
"destinationOffset": parameters.get("destinationOffset").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"sourceOffset": parameters.get("sourceOffset").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"byteLength": parameters.get("length").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"stateTransition": "program_bytes_copied",
|
||||
"codeBytesMaterialized": false,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
}),
|
||||
ProjectionKind::ComputeBudgetProfile => serde_json::Value::Null,
|
||||
};
|
||||
}
|
||||
|
||||
fn first_account_key_for_roles(accounts: &serde_json::Value, roles: &[&str]) -> serde_json::Value {
|
||||
for role in roles {
|
||||
let value = account_key_for_role(accounts, role);
|
||||
if !value.is_null() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return serde_json::Value::Null;
|
||||
}
|
||||
|
||||
fn account_key_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
|
||||
let account_key = accounts.as_array().and_then(|values| {
|
||||
return values.iter().find_map(|value| {
|
||||
if value.get("role").and_then(serde_json::Value::as_str)
|
||||
!= std::option::Option::Some(role)
|
||||
{
|
||||
return std::option::Option::None;
|
||||
}
|
||||
return value
|
||||
.get("accountKey")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(|account_key| return account_key.to_string());
|
||||
});
|
||||
});
|
||||
return match account_key {
|
||||
std::option::Option::Some(value) => serde_json::Value::String(value),
|
||||
std::option::Option::None => serde_json::Value::Null,
|
||||
};
|
||||
}
|
||||
|
||||
fn contains(entries: &[&str], entry_code: &str) -> bool {
|
||||
return entries.iter().any(|entry| return *entry == entry_code);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn observation(
|
||||
surface_code: &str,
|
||||
entry_code: &str,
|
||||
transaction_failed: bool,
|
||||
accounts: serde_json::Value,
|
||||
parameters: serde_json::Value,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: format!("{entry_code}:0"),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("0".to_string()),
|
||||
program_id: crate::MdProgramId("program-id".to_string()),
|
||||
protocol_code: crate::MdProtocolCode("solana.core".to_string()),
|
||||
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
|
||||
event_code: crate::MdEventCode(format!("{surface_code}.{entry_code}")),
|
||||
event_name: crate::MdEventName(entry_code.to_string()),
|
||||
event_family: crate::MdEventFamily::Audit,
|
||||
source_kind: crate::MdEventSourceKind::Instruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({"accounts": accounts, "parameters": parameters}),
|
||||
transaction_failed,
|
||||
transaction_error: if transaction_failed {
|
||||
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
observation_committed: !transaction_failed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["fixture".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn account(role: &str, account_key: &str) -> serde_json::Value {
|
||||
return serde_json::json!({"role": role, "accountKey": account_key});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_write_variants_create_bounded_code_audits() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
for (surface, target_role, authority_role) in [
|
||||
(
|
||||
crate::MT_COMPLIANCE_AUDIT_BPF_LOADER_DEPRECATED_SURFACE,
|
||||
"program_account",
|
||||
"missing",
|
||||
),
|
||||
(crate::MT_COMPLIANCE_AUDIT_BPF_LOADER_SURFACE, "program_account", "missing"),
|
||||
(
|
||||
crate::MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
"buffer_account",
|
||||
"buffer_authority",
|
||||
),
|
||||
(crate::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE, "program_account", "authority"),
|
||||
] {
|
||||
let observation = observation(
|
||||
surface,
|
||||
"write",
|
||||
false,
|
||||
serde_json::json!([
|
||||
account(target_role, "target111"),
|
||||
account(authority_role, "authority111"),
|
||||
]),
|
||||
serde_json::json!({
|
||||
"offset": 7,
|
||||
"byteLength": 32,
|
||||
"bytesSha256": "abc",
|
||||
"bytesPrefixHex": "0102",
|
||||
}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs[0].family, crate::MdMaterializedEventFamily::ComplianceAudit);
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "target111");
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["bytesSha256"], "abc");
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["projection"]["codeBytesMaterialized"],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_v4_copy_preserves_source_and_bounded_ranges() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
crate::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE,
|
||||
"copy",
|
||||
false,
|
||||
serde_json::json!([
|
||||
account("program_account", "target111"),
|
||||
account("authority", "authority111"),
|
||||
account("source_program_account", "source111"),
|
||||
]),
|
||||
serde_json::json!({"destinationOffset": 8, "sourceOffset": 4, "length": 16}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(
|
||||
result.outputs[0].output_key,
|
||||
"program_loader_code:solana.core.loader_v4:copy:0"
|
||||
);
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["projection"]["sourceProgramAccount"],
|
||||
"source111"
|
||||
);
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["byteLength"], 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_budget_profile_is_emitted_only_by_profile_emitter() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let emitter = observation(
|
||||
crate::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE,
|
||||
"set_compute_unit_limit",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({
|
||||
"transactionProfile": {
|
||||
"currentInstructionIsProfileEmitter": true,
|
||||
"profileEmitterInstructionPath": "0",
|
||||
"computeBudgetInstructionCount": 2,
|
||||
"effectiveValues": {
|
||||
"set_compute_unit_limit": {
|
||||
"value": 200000,
|
||||
"sourceInstructionPath": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
let emitted = crate::MtApiEventMaterializer::materialize(&materializer, &emitter);
|
||||
assert_eq!(emitted.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(emitted.outputs[0].output_key, "transaction_compute_budget_profile:0");
|
||||
assert_eq!(emitted.outputs[0].family, crate::MdMaterializedEventFamily::ComplianceAudit);
|
||||
assert_eq!(emitted.outputs[0].payload_json["profile"]["computeBudgetInstructionCount"], 2);
|
||||
let non_emitter = observation(
|
||||
crate::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE,
|
||||
"set_compute_unit_price",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({
|
||||
"transactionProfile": {
|
||||
"currentInstructionIsProfileEmitter": false,
|
||||
"profileEmitterInstructionPath": "0"
|
||||
}
|
||||
}),
|
||||
);
|
||||
let ignored = crate::MtApiEventMaterializer::materialize(&materializer, &non_emitter);
|
||||
assert_eq!(ignored.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_compute_budget_profile_remains_audit_materializable() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
crate::MT_COMPLIANCE_AUDIT_COMPUTE_BUDGET_SURFACE,
|
||||
"set_compute_unit_price",
|
||||
true,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({
|
||||
"transactionProfile": {
|
||||
"currentInstructionIsProfileEmitter": true,
|
||||
"profileEmitterInstructionPath": "0",
|
||||
"computeBudgetInstructionCount": 1
|
||||
}
|
||||
}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs[0].payload_json["transactionSucceeded"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_code_mutation_is_refused() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
crate::MT_COMPLIANCE_AUDIT_LOADER_V4_SURFACE,
|
||||
"write",
|
||||
true,
|
||||
serde_json::json!([account("program_account", "target111")]),
|
||||
serde_json::json!({"offset": 0, "byteLength": 1, "bytesSha256": "abc"}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrelated_audit_observation_is_ignored() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
"other_surface",
|
||||
"write",
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_serialization_is_deterministic() {
|
||||
let materializer = crate::MtComplianceAuditMaterializer;
|
||||
let observation = observation(
|
||||
crate::MT_COMPLIANCE_AUDIT_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
"write",
|
||||
false,
|
||||
serde_json::json!([
|
||||
account("buffer_account", "buffer111"),
|
||||
account("buffer_authority", "authority111"),
|
||||
]),
|
||||
serde_json::json!({
|
||||
"offset": 0,
|
||||
"byteLength": 2,
|
||||
"bytesSha256": "abc",
|
||||
"bytesPrefixHex": "0102",
|
||||
}),
|
||||
);
|
||||
let first = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
let second = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
let first_json = match serde_json::to_string(&first) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("first compliance serialization failed: {error}")
|
||||
},
|
||||
};
|
||||
let second_json = match serde_json::to_string(&second) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("second compliance serialization failed: {error}")
|
||||
},
|
||||
};
|
||||
assert_eq!(first_json, second_json);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// file: kb-lib/src/materializer/compliance/constants.rs
|
||||
// version: 2
|
||||
|
||||
//! Local constants for the native compliance audit materializer.
|
||||
|
||||
/// Canonical tracing target for this component.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT: &str =
|
||||
"kb-lib.materializer.compliance";
|
||||
@@ -1,11 +1,27 @@
|
||||
// file: kb-lib/src/materializer/fees.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! `fees` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Fee materializer for committed Token-2022 fee observations.
|
||||
pub use self::core::MtFeesMaterializer;
|
||||
pub use self::materializer::MtFeesMaterializer;
|
||||
/// Materializes authoritative Token-2022 fee extension snapshots.
|
||||
pub use self::core::materializer_fees_materialize_token_2022_state_snapshots;
|
||||
pub use self::materializer::materializer_fees_materialize_token_2022_state_snapshots;
|
||||
|
||||
/// Crate-visible `MT_FEES_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_FEES_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `fees` materializer.
|
||||
pub(crate) use self::constants::MT_FEES_COMPONENT_NAME;
|
||||
/// Stable processor name for the `fees` materializer.
|
||||
pub(crate) use self::constants::MT_FEES_PROCESSOR_NAME;
|
||||
/// Stable projection version for the `fees` materializer.
|
||||
pub(crate) use self::constants::MT_FEES_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_FEES_SPL_TOKEN_2022_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_FEES_SPL_TOKEN_2022_SURFACE;
|
||||
/// Crate-visible `MT_FEES_TOKEN_2022_FEE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_FEES_TOKEN_2022_FEE_ENTRIES;
|
||||
/// Canonical tracing target for the `fees` materializer.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_FEES;
|
||||
|
||||
34
kb-lib/src/materializer/fees/constants.rs
Normal file
34
kb-lib/src/materializer/fees/constants.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
// file: kb-lib/src/materializer/fees/constants.rs
|
||||
// version: 2
|
||||
|
||||
//! Local constants for the fees materializer.
|
||||
|
||||
/// Stable runtime component name for the fees materializer.
|
||||
pub(crate) const MT_FEES_COMPONENT_NAME: &str = "kb-lib.materializer.fees";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_FEES_PROCESSOR_NAME: &str = "materializer.fees";
|
||||
/// Stable projection contract version for the fees materializer.
|
||||
pub(crate) const MT_FEES_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for the fees materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_FEES: &str = "kb-lib.materializer.fees";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_FEES_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[crate::MdEventFamily::Fee];
|
||||
/// Component contract for `spl_token_2022_surface`.
|
||||
pub(crate) const MT_FEES_SPL_TOKEN_2022_SURFACE: &str = "spl.token_2022";
|
||||
/// Component contract for `token_2022_fee_entries`.
|
||||
pub(crate) const MT_FEES_TOKEN_2022_FEE_ENTRIES: &[&str] = &[
|
||||
"initialize_transfer_fee_config",
|
||||
"set_transfer_fee",
|
||||
"withdraw_withheld_tokens_from_mint",
|
||||
"withdraw_withheld_tokens_from_accounts",
|
||||
"harvest_withheld_tokens_to_mint",
|
||||
"transfer_checked_with_fee",
|
||||
"initialize_confidential_transfer_fee_config",
|
||||
"withdraw_confidential_withheld_tokens_from_mint",
|
||||
"withdraw_confidential_withheld_tokens_from_accounts",
|
||||
"harvest_confidential_withheld_tokens_to_mint",
|
||||
"enable_confidential_harvest_to_mint",
|
||||
"disable_confidential_harvest_to_mint",
|
||||
"transfer_confidential_tokens_with_fee",
|
||||
];
|
||||
@@ -1,33 +1,15 @@
|
||||
// file: kb-lib/src/materializer/fees/core.rs
|
||||
// version: 5
|
||||
// file: kb-lib/src/materializer/fees/materializer.rs
|
||||
// version: 8
|
||||
|
||||
//! Stable fee projections derived from exact committed Token-2022 observations.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[crate::MdEventFamily::Fee];
|
||||
const SPL_TOKEN_2022_SURFACE: &str = "spl.token_2022";
|
||||
const TOKEN_2022_FEE_ENTRIES: &[&str] = &[
|
||||
"initialize_transfer_fee_config",
|
||||
"set_transfer_fee",
|
||||
"withdraw_withheld_tokens_from_mint",
|
||||
"withdraw_withheld_tokens_from_accounts",
|
||||
"harvest_withheld_tokens_to_mint",
|
||||
"transfer_checked_with_fee",
|
||||
"initialize_confidential_transfer_fee_config",
|
||||
"withdraw_confidential_withheld_tokens_from_mint",
|
||||
"withdraw_confidential_withheld_tokens_from_accounts",
|
||||
"harvest_confidential_withheld_tokens_to_mint",
|
||||
"enable_confidential_harvest_to_mint",
|
||||
"disable_confidential_harvest_to_mint",
|
||||
"transfer_confidential_tokens_with_fee",
|
||||
];
|
||||
|
||||
/// Fee materializer for committed Token-2022 fee instructions.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtFeesMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtFeesMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.fees";
|
||||
return crate::MT_FEES_COMPONENT_NAME;
|
||||
}
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
@@ -46,12 +28,12 @@ impl crate::MtMaterializer for crate::MtFeesMaterializer {
|
||||
impl crate::MtApiEventMaterializer for crate::MtFeesMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "materializer.fees".to_string(),
|
||||
name: crate::MT_FEES_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
return crate::MT_FEES_ACCEPTED_FAMILIES;
|
||||
}
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return accepts_event(&observation.event);
|
||||
@@ -67,9 +49,35 @@ impl crate::MtApiEventMaterializer for crate::MtFeesMaterializer {
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
if !accepts_event(&observation.event) {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_FEES,
|
||||
action = "materialize_fee_observation",
|
||||
component_name = crate::MT_FEES_COMPONENT_NAME,
|
||||
processor_name = crate::MT_FEES_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
accepted = false,
|
||||
status = "ignored",
|
||||
"ignore observation outside supported Token-2022 fee projections"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if observation.transaction_failed || !observation.observation_committed {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_FEES,
|
||||
action = "materialize_fee_observation",
|
||||
component_name = crate::MT_FEES_COMPONENT_NAME,
|
||||
processor_name = crate::MT_FEES_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
committed = observation.observation_committed,
|
||||
transaction_failed = observation.transaction_failed,
|
||||
status = "refused",
|
||||
error_code = "failed_transaction_fee_refused",
|
||||
"refuse failed or uncommitted Token-2022 fee observation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"failed_transaction_fee_refused",
|
||||
"failed or uncommitted Token-2022 observations cannot create fee outputs",
|
||||
@@ -90,7 +98,7 @@ impl crate::MtApiEventMaterializer for crate::MtFeesMaterializer {
|
||||
output_key: format!("token_2022_fee:{operation}:0"),
|
||||
family: crate::MdMaterializedEventFamily::Fee,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionVersion": crate::MT_FEES_PROJECTION_VERSION,
|
||||
"projectionSemantics": "committed_instruction_fee_event",
|
||||
"domain": fee_domain(observation.event.event_name.0.as_str()),
|
||||
"operation": operation,
|
||||
@@ -99,6 +107,10 @@ impl crate::MtApiEventMaterializer for crate::MtFeesMaterializer {
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_FEES_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
},
|
||||
"accounts": accounts,
|
||||
"parameters": parameters,
|
||||
"stateReadRequired": state_read_required(
|
||||
@@ -107,6 +119,21 @@ impl crate::MtApiEventMaterializer for crate::MtFeesMaterializer {
|
||||
"confidentialValuesDecrypted": false,
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_FEES,
|
||||
action = "materialize_fee_observation",
|
||||
component_name = crate::MT_FEES_COMPONENT_NAME,
|
||||
processor_name = crate::MT_FEES_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
surface_code = %observation.event.surface_code.0.as_str(),
|
||||
entry_code = %observation.event.event_name.0.as_str(),
|
||||
signature = %observation.event.signature.0.as_str(),
|
||||
slot = observation.event.slot.0,
|
||||
instruction_path = %observation.event.instruction_path.0.as_str(),
|
||||
output_count = 1_usize,
|
||||
status = "inserted",
|
||||
"materialize committed Token-2022 fee observation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
@@ -117,9 +144,9 @@ impl crate::MtApiEventMaterializer for crate::MtFeesMaterializer {
|
||||
|
||||
fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.event_family == crate::MdEventFamily::Fee
|
||||
&& event.surface_code.0 == SPL_TOKEN_2022_SURFACE
|
||||
&& event.surface_code.0 == crate::MT_FEES_SPL_TOKEN_2022_SURFACE
|
||||
&& event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
|
||||
&& contains(TOKEN_2022_FEE_ENTRIES, event.event_name.0.as_str());
|
||||
&& contains(crate::MT_FEES_TOKEN_2022_FEE_ENTRIES, event.event_name.0.as_str());
|
||||
}
|
||||
|
||||
fn fee_domain(operation: &str) -> &'static str {
|
||||
@@ -195,7 +222,7 @@ pub fn materializer_fees_materialize_token_2022_state_snapshots(
|
||||
),
|
||||
family: crate::MdMaterializedEventFamily::Fee,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionVersion": crate::MT_FEES_PROJECTION_VERSION,
|
||||
"projectionSemantics": "authoritative_bounded_fee_state_snapshot",
|
||||
"domain": if confidential {
|
||||
"token_2022_confidential_transfer_fee_state"
|
||||
@@ -218,13 +245,27 @@ pub fn materializer_fees_materialize_token_2022_state_snapshots(
|
||||
"confidentialValuesDecrypted": false,
|
||||
"finalAccountStateCaptured": true,
|
||||
"provenance": {
|
||||
"processorName": "materializer.fees",
|
||||
"processorName": crate::MT_FEES_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"source": "bounded_token_2022_state_parser"
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
let status = if outputs.is_empty() { "ignored" } else { "inserted" };
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_FEES,
|
||||
action = "materialize_token_2022_fee_state_snapshots",
|
||||
component_name = crate::MT_FEES_COMPONENT_NAME,
|
||||
processor_name = crate::MT_FEES_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account_key = %account_key,
|
||||
slot,
|
||||
state_kind,
|
||||
output_count = outputs.len(),
|
||||
status,
|
||||
"materialize bounded Token-2022 fee state snapshots"
|
||||
);
|
||||
return std::result::Result::Ok(outputs);
|
||||
}
|
||||
|
||||
@@ -273,7 +314,7 @@ mod tests {
|
||||
#[test]
|
||||
fn committed_token_2022_fee_instruction_creates_one_stable_output() {
|
||||
let observation = observation(
|
||||
super::SPL_TOKEN_2022_SURFACE,
|
||||
crate::MT_FEES_SPL_TOKEN_2022_SURFACE,
|
||||
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"set_transfer_fee",
|
||||
false,
|
||||
@@ -290,7 +331,7 @@ mod tests {
|
||||
#[test]
|
||||
fn failed_token_2022_fee_instruction_is_refused() {
|
||||
let observation = observation(
|
||||
super::SPL_TOKEN_2022_SURFACE,
|
||||
crate::MT_FEES_SPL_TOKEN_2022_SURFACE,
|
||||
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"harvest_withheld_tokens_to_mint",
|
||||
true,
|
||||
@@ -310,7 +351,7 @@ mod tests {
|
||||
false,
|
||||
);
|
||||
let wrong_program = observation(
|
||||
super::SPL_TOKEN_2022_SURFACE,
|
||||
crate::MT_FEES_SPL_TOKEN_2022_SURFACE,
|
||||
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
|
||||
"set_transfer_fee",
|
||||
false,
|
||||
@@ -334,7 +375,7 @@ mod tests {
|
||||
#[test]
|
||||
fn committed_confidential_fee_instruction_is_projected_without_decryption() {
|
||||
let observation = observation(
|
||||
super::SPL_TOKEN_2022_SURFACE,
|
||||
crate::MT_FEES_SPL_TOKEN_2022_SURFACE,
|
||||
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"initialize_confidential_transfer_fee_config",
|
||||
false,
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/governance.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `governance` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved governance materializer.
|
||||
pub use self::core::MtGovernanceMaterializer;
|
||||
pub use self::materializer::MtGovernanceMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_GOVERNANCE_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `governance` materializer.
|
||||
pub(crate) use self::constants::MT_GOVERNANCE_COMPONENT_NAME;
|
||||
/// Stable processor name for the `governance` materializer.
|
||||
pub(crate) use self::constants::MT_GOVERNANCE_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/governance/constants.rs
Normal file
11
kb-lib/src/materializer/governance/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/governance/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the governance materializer.
|
||||
|
||||
/// Stable runtime component name for the governance materializer.
|
||||
pub(crate) const MT_GOVERNANCE_COMPONENT_NAME: &str = "kb-lib.materializer.governance";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_GOVERNANCE_PROCESSOR_NAME: &str = "materializer.governance";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_GOVERNANCE_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/governance/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/governance/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved governance materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtGovernanceMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtGovernanceMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.governance";
|
||||
return crate::MT_GOVERNANCE_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtGovernanceMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Governance,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtGovernanceMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.governance".to_string(),
|
||||
name: crate::MT_GOVERNANCE_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_GOVERNANCE_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/lending.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `lending` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved lending materializer.
|
||||
pub use self::core::MtLendingMaterializer;
|
||||
pub use self::materializer::MtLendingMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_LENDING_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `lending` materializer.
|
||||
pub(crate) use self::constants::MT_LENDING_COMPONENT_NAME;
|
||||
/// Stable processor name for the `lending` materializer.
|
||||
pub(crate) use self::constants::MT_LENDING_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/lending/constants.rs
Normal file
11
kb-lib/src/materializer/lending/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/lending/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the lending materializer.
|
||||
|
||||
/// Stable runtime component name for the lending materializer.
|
||||
pub(crate) const MT_LENDING_COMPONENT_NAME: &str = "kb-lib.materializer.lending";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_LENDING_PROCESSOR_NAME: &str = "materializer.lending";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_LENDING_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/lending/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/lending/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved lending materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtLendingMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtLendingMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.lending";
|
||||
return crate::MT_LENDING_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtLendingMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Lending,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtLendingMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.lending".to_string(),
|
||||
name: crate::MT_LENDING_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_LENDING_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,13 +1,59 @@
|
||||
// file: kb-lib/src/materializer/lifecycle.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! `lifecycle` materializer family.
|
||||
|
||||
mod constants;
|
||||
mod core;
|
||||
mod materializer;
|
||||
|
||||
/// Stable native lifecycle materializer.
|
||||
pub use self::core::MtLifecycleMaterializer;
|
||||
pub use self::materializer::MtLifecycleMaterializer;
|
||||
|
||||
/// Canonical tracing target for the native lifecycle materializer.
|
||||
/// Crate-visible `MT_LIFECYCLE_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_BPF_LOADER_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_BPF_LOADER_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE;
|
||||
/// Stable runtime component name for the `lifecycle` materializer.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_LIFECYCLE_FEATURE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_FEATURE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_FEATURE_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_FEATURE_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_LOADER_V4_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_LOADER_V4_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_LOADER_V4_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_LOADER_V4_SURFACE;
|
||||
/// Stable processor name for the `lifecycle` materializer.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_PROCESSOR_NAME;
|
||||
/// Stable projection version for the `lifecycle` materializer.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_LIFECYCLE_SLASHING_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_SLASHING_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_SLASHING_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_SLASHING_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_SYSTEM_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_SYSTEM_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES;
|
||||
/// Crate-visible `MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE;
|
||||
/// Crate-visible `MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES;
|
||||
/// Canonical tracing target for the `lifecycle` materializer.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_LIFECYCLE;
|
||||
|
||||
@@ -1,7 +1,100 @@
|
||||
// file: kb-lib/src/materializer/lifecycle/constants.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
//! Local constants for the native lifecycle materializer.
|
||||
//! Local constants for the lifecycle materializer.
|
||||
|
||||
/// Canonical tracing target for this component.
|
||||
/// Stable runtime component name for the lifecycle materializer.
|
||||
pub(crate) const MT_LIFECYCLE_COMPONENT_NAME: &str = "kb-lib.materializer.lifecycle";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_LIFECYCLE_PROCESSOR_NAME: &str = "materializer.lifecycle";
|
||||
/// Stable projection contract version for the lifecycle materializer.
|
||||
pub(crate) const MT_LIFECYCLE_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for the lifecycle materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_LIFECYCLE: &str = "kb-lib.materializer.lifecycle";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_LIFECYCLE_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
crate::MdEventFamily::Admin,
|
||||
crate::MdEventFamily::Audit,
|
||||
];
|
||||
/// Component contract for `address_lookup_table_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE: &str =
|
||||
"solana.core.address_lookup_table";
|
||||
/// Component contract for `address_lookup_table_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES: &[&str] = &[
|
||||
"create_lookup_table",
|
||||
"freeze_lookup_table",
|
||||
"extend_lookup_table",
|
||||
"deactivate_lookup_table",
|
||||
"close_lookup_table",
|
||||
];
|
||||
/// Component contract for `bpf_loader_deprecated_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE: &str =
|
||||
"solana.core.bpf_loader_deprecated";
|
||||
/// Component contract for `bpf_loader_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_BPF_LOADER_SURFACE: &str = "solana.core.bpf_loader";
|
||||
/// Component contract for `bpf_loader_upgradeable_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE: &str =
|
||||
"solana.core.bpf_loader_upgradeable";
|
||||
/// Component contract for `loader_v4_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_LOADER_V4_SURFACE: &str = "solana.core.loader_v4";
|
||||
/// Component contract for `feature_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_FEATURE_SURFACE: &str = "solana.core.feature";
|
||||
/// Component contract for `feature_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_FEATURE_ENTRIES: &[&str] = &["revoke_pending_activation"];
|
||||
/// Component contract for `immutable_loader_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES: &[&str] = &["finalize"];
|
||||
/// Component contract for `upgradeable_loader_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES: &[&str] = &[
|
||||
"initialize_buffer",
|
||||
"deploy_with_max_data_len",
|
||||
"upgrade",
|
||||
"close",
|
||||
"extend_program",
|
||||
];
|
||||
/// Component contract for `loader_v4_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_LOADER_V4_ENTRIES: &[&str] =
|
||||
&["set_program_length", "deploy", "retract", "finalize"];
|
||||
/// Component contract for `slashing_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_SLASHING_SURFACE: &str = "solana.core.slashing";
|
||||
/// Component contract for `slashing_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_SLASHING_ENTRIES: &[&str] =
|
||||
&["close_violation_report", "duplicate_block_proof"];
|
||||
/// Component contract for `system_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_SYSTEM_SURFACE: &str = "solana.core.system";
|
||||
/// Component contract for `system_account_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES: &[&str] = &[
|
||||
"create_account",
|
||||
"create_account_with_seed",
|
||||
"allocate",
|
||||
"allocate_with_seed",
|
||||
"create_account_allow_prefund",
|
||||
];
|
||||
/// Component contract for `system_nonce_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES: &[&str] = &[
|
||||
"advance_nonce_account",
|
||||
"withdraw_nonce_account",
|
||||
"initialize_nonce_account",
|
||||
"authorize_nonce_account",
|
||||
"upgrade_nonce_account",
|
||||
];
|
||||
/// Component contract for `zk_elgamal_proof_surface`.
|
||||
pub(crate) const MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE: &str = "solana.core.zk_elgamal_proof";
|
||||
/// Component contract for `zk_elgamal_close_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES: &[&str] = &["close_context_state"];
|
||||
/// Component contract for `zk_elgamal_verify_entries`.
|
||||
pub(crate) const MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES: &[&str] = &[
|
||||
"verify_zero_ciphertext",
|
||||
"verify_ciphertext_ciphertext_equality",
|
||||
"verify_ciphertext_commitment_equality",
|
||||
"verify_pubkey_validity",
|
||||
"verify_percentage_with_cap",
|
||||
"verify_batched_range_proof_u64",
|
||||
"verify_batched_range_proof_u128",
|
||||
"verify_batched_range_proof_u256",
|
||||
"verify_grouped_ciphertext_2_handles_validity",
|
||||
"verify_batched_grouped_ciphertext_2_handles_validity",
|
||||
"verify_grouped_ciphertext_3_handles_validity",
|
||||
"verify_batched_grouped_ciphertext_3_handles_validity",
|
||||
];
|
||||
|
||||
@@ -1,70 +1,8 @@
|
||||
// file: kb-lib/src/materializer/lifecycle/core.rs
|
||||
// version: 16
|
||||
// file: kb-lib/src/materializer/lifecycle/materializer.rs
|
||||
// version: 18
|
||||
|
||||
//! Stable lifecycle projections derived from exact decoded observations.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
crate::MdEventFamily::Admin,
|
||||
crate::MdEventFamily::Audit,
|
||||
];
|
||||
const ADDRESS_LOOKUP_TABLE_SURFACE: &str = "solana.core.address_lookup_table";
|
||||
const ADDRESS_LOOKUP_TABLE_ENTRIES: &[&str] = &[
|
||||
"create_lookup_table",
|
||||
"freeze_lookup_table",
|
||||
"extend_lookup_table",
|
||||
"deactivate_lookup_table",
|
||||
"close_lookup_table",
|
||||
];
|
||||
const BPF_LOADER_DEPRECATED_SURFACE: &str = "solana.core.bpf_loader_deprecated";
|
||||
const BPF_LOADER_SURFACE: &str = "solana.core.bpf_loader";
|
||||
const BPF_LOADER_UPGRADEABLE_SURFACE: &str = "solana.core.bpf_loader_upgradeable";
|
||||
const LOADER_V4_SURFACE: &str = "solana.core.loader_v4";
|
||||
const FEATURE_SURFACE: &str = "solana.core.feature";
|
||||
const FEATURE_ENTRIES: &[&str] = &["revoke_pending_activation"];
|
||||
const IMMUTABLE_LOADER_ENTRIES: &[&str] = &["finalize"];
|
||||
const UPGRADEABLE_LOADER_ENTRIES: &[&str] = &[
|
||||
"initialize_buffer",
|
||||
"deploy_with_max_data_len",
|
||||
"upgrade",
|
||||
"close",
|
||||
"extend_program",
|
||||
];
|
||||
const LOADER_V4_ENTRIES: &[&str] = &["set_program_length", "deploy", "retract", "finalize"];
|
||||
const SLASHING_SURFACE: &str = "solana.core.slashing";
|
||||
const SLASHING_ENTRIES: &[&str] = &["close_violation_report", "duplicate_block_proof"];
|
||||
const SYSTEM_SURFACE: &str = "solana.core.system";
|
||||
const SYSTEM_ACCOUNT_ENTRIES: &[&str] = &[
|
||||
"create_account",
|
||||
"create_account_with_seed",
|
||||
"allocate",
|
||||
"allocate_with_seed",
|
||||
"create_account_allow_prefund",
|
||||
];
|
||||
const SYSTEM_NONCE_ENTRIES: &[&str] = &[
|
||||
"advance_nonce_account",
|
||||
"withdraw_nonce_account",
|
||||
"initialize_nonce_account",
|
||||
"authorize_nonce_account",
|
||||
"upgrade_nonce_account",
|
||||
];
|
||||
const ZK_ELGAMAL_PROOF_SURFACE: &str = "solana.core.zk_elgamal_proof";
|
||||
const ZK_ELGAMAL_CLOSE_ENTRIES: &[&str] = &["close_context_state"];
|
||||
const ZK_ELGAMAL_VERIFY_ENTRIES: &[&str] = &[
|
||||
"verify_zero_ciphertext",
|
||||
"verify_ciphertext_ciphertext_equality",
|
||||
"verify_ciphertext_commitment_equality",
|
||||
"verify_pubkey_validity",
|
||||
"verify_percentage_with_cap",
|
||||
"verify_batched_range_proof_u64",
|
||||
"verify_batched_range_proof_u128",
|
||||
"verify_batched_range_proof_u256",
|
||||
"verify_grouped_ciphertext_2_handles_validity",
|
||||
"verify_batched_grouped_ciphertext_2_handles_validity",
|
||||
"verify_grouped_ciphertext_3_handles_validity",
|
||||
"verify_batched_grouped_ciphertext_3_handles_validity",
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ProjectionKind {
|
||||
AddressLookupTable,
|
||||
@@ -125,7 +63,7 @@ pub struct MtLifecycleMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtLifecycleMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.lifecycle";
|
||||
return crate::MT_LIFECYCLE_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -152,13 +90,13 @@ impl crate::MtMaterializer for crate::MtLifecycleMaterializer {
|
||||
impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "materializer.lifecycle".to_string(),
|
||||
name: crate::MT_LIFECYCLE_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
return crate::MT_LIFECYCLE_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
@@ -239,7 +177,7 @@ impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer {
|
||||
output_key,
|
||||
family: crate::MdMaterializedEventFamily::Lifecycle,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionVersion": crate::MT_LIFECYCLE_PROJECTION_VERSION,
|
||||
"projectionSemantics": "committed_instruction_lifecycle_event",
|
||||
"domain": domain,
|
||||
"loaderSurface": loader_surface,
|
||||
@@ -249,6 +187,10 @@ impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer {
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_LIFECYCLE_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
},
|
||||
"accounts": accounts,
|
||||
"parameters": parameters,
|
||||
"projection": projection_details,
|
||||
@@ -263,7 +205,9 @@ impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer {
|
||||
}
|
||||
|
||||
fn family_is_accepted(family: crate::MdEventFamily) -> bool {
|
||||
return ACCEPTED_FAMILIES.iter().any(|accepted| return *accepted == family);
|
||||
return crate::MT_LIFECYCLE_ACCEPTED_FAMILIES
|
||||
.iter()
|
||||
.any(|accepted| return *accepted == family);
|
||||
}
|
||||
|
||||
fn observation_projection(
|
||||
@@ -275,8 +219,8 @@ fn observation_projection(
|
||||
if static_projection.is_some() {
|
||||
return static_projection;
|
||||
}
|
||||
if surface_code == ZK_ELGAMAL_PROOF_SURFACE
|
||||
&& contains(ZK_ELGAMAL_VERIFY_ENTRIES, entry_code)
|
||||
if surface_code == crate::MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_ZK_ELGAMAL_VERIFY_ENTRIES, entry_code)
|
||||
&& observation
|
||||
.payload_json
|
||||
.get("parameters")
|
||||
@@ -290,48 +234,63 @@ fn observation_projection(
|
||||
}
|
||||
|
||||
fn static_projection(surface_code: &str, entry_code: &str) -> std::option::Option<ProjectionKind> {
|
||||
if surface_code == FEATURE_SURFACE && contains(FEATURE_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_LIFECYCLE_FEATURE_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_FEATURE_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::FeatureGate);
|
||||
}
|
||||
if surface_code == ADDRESS_LOOKUP_TABLE_SURFACE
|
||||
&& contains(ADDRESS_LOOKUP_TABLE_ENTRIES, entry_code)
|
||||
if surface_code == crate::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::AddressLookupTable);
|
||||
}
|
||||
if (surface_code == BPF_LOADER_DEPRECATED_SURFACE || surface_code == BPF_LOADER_SURFACE)
|
||||
&& contains(IMMUTABLE_LOADER_ENTRIES, entry_code)
|
||||
if (surface_code == crate::MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE
|
||||
|| surface_code == crate::MT_LIFECYCLE_BPF_LOADER_SURFACE)
|
||||
&& contains(crate::MT_LIFECYCLE_IMMUTABLE_LOADER_ENTRIES, entry_code)
|
||||
{
|
||||
let loader_surface = if surface_code == BPF_LOADER_DEPRECATED_SURFACE {
|
||||
BPF_LOADER_DEPRECATED_SURFACE
|
||||
let loader_surface = if surface_code == crate::MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE {
|
||||
crate::MT_LIFECYCLE_BPF_LOADER_DEPRECATED_SURFACE
|
||||
} else {
|
||||
BPF_LOADER_SURFACE
|
||||
crate::MT_LIFECYCLE_BPF_LOADER_SURFACE
|
||||
};
|
||||
return std::option::Option::Some(ProjectionKind::ProgramLoader(loader_surface));
|
||||
}
|
||||
if surface_code == BPF_LOADER_UPGRADEABLE_SURFACE
|
||||
&& contains(UPGRADEABLE_LOADER_ENTRIES, entry_code)
|
||||
if surface_code == crate::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_UPGRADEABLE_LOADER_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ProgramLoader(
|
||||
BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
crate::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
));
|
||||
}
|
||||
if surface_code == LOADER_V4_SURFACE && contains(LOADER_V4_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::ProgramLoader(LOADER_V4_SURFACE));
|
||||
if surface_code == crate::MT_LIFECYCLE_LOADER_V4_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_LOADER_V4_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ProgramLoader(
|
||||
crate::MT_LIFECYCLE_LOADER_V4_SURFACE,
|
||||
));
|
||||
}
|
||||
if surface_code == SLASHING_SURFACE && contains(SLASHING_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_LIFECYCLE_SLASHING_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_SLASHING_ENTRIES, entry_code)
|
||||
{
|
||||
return if entry_code == "duplicate_block_proof" {
|
||||
std::option::Option::Some(ProjectionKind::SlashingViolationReportInitialization)
|
||||
} else {
|
||||
std::option::Option::Some(ProjectionKind::SlashingViolationReportClosure)
|
||||
};
|
||||
}
|
||||
if surface_code == SYSTEM_SURFACE && contains(SYSTEM_ACCOUNT_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_LIFECYCLE_SYSTEM_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_SYSTEM_ACCOUNT_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::SystemAccountLifecycle);
|
||||
}
|
||||
if surface_code == SYSTEM_SURFACE && contains(SYSTEM_NONCE_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_LIFECYCLE_SYSTEM_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_SYSTEM_NONCE_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::DurableNonceAccount);
|
||||
}
|
||||
if surface_code == ZK_ELGAMAL_PROOF_SURFACE && contains(ZK_ELGAMAL_CLOSE_ENTRIES, entry_code) {
|
||||
if surface_code == crate::MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE
|
||||
&& contains(crate::MT_LIFECYCLE_ZK_ELGAMAL_CLOSE_ENTRIES, entry_code)
|
||||
{
|
||||
return std::option::Option::Some(ProjectionKind::ZkProofContextClosure);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
@@ -539,7 +498,7 @@ mod tests {
|
||||
transaction_failed: bool,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
return observation_with_payload(
|
||||
super::SYSTEM_SURFACE,
|
||||
crate::MT_LIFECYCLE_SYSTEM_SURFACE,
|
||||
entry_code,
|
||||
family,
|
||||
transaction_failed,
|
||||
@@ -579,7 +538,7 @@ mod tests {
|
||||
)
|
||||
};
|
||||
return observation_with_payload(
|
||||
super::SLASHING_SURFACE,
|
||||
crate::MT_LIFECYCLE_SLASHING_SURFACE,
|
||||
entry_code,
|
||||
family,
|
||||
transaction_failed,
|
||||
@@ -620,7 +579,7 @@ mod tests {
|
||||
crate::MdEventFamily::Audit
|
||||
};
|
||||
return observation_with_payload(
|
||||
super::ZK_ELGAMAL_PROOF_SURFACE,
|
||||
crate::MT_LIFECYCLE_ZK_ELGAMAL_PROOF_SURFACE,
|
||||
entry_code,
|
||||
family,
|
||||
transaction_failed,
|
||||
@@ -637,7 +596,11 @@ mod tests {
|
||||
let materializer = crate::MtLifecycleMaterializer;
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&materializer,
|
||||
&observation(super::ADDRESS_LOOKUP_TABLE_SURFACE, "create_lookup_table", false),
|
||||
&observation(
|
||||
crate::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE,
|
||||
"create_lookup_table",
|
||||
false,
|
||||
),
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs.len(), 1);
|
||||
@@ -651,7 +614,11 @@ mod tests {
|
||||
let materializer = crate::MtLifecycleMaterializer;
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&materializer,
|
||||
&observation(super::BPF_LOADER_UPGRADEABLE_SURFACE, "deploy_with_max_data_len", false),
|
||||
&observation(
|
||||
crate::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE,
|
||||
"deploy_with_max_data_len",
|
||||
false,
|
||||
),
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(
|
||||
@@ -661,7 +628,7 @@ mod tests {
|
||||
assert_eq!(result.outputs[0].payload_json["domain"], "program_loader");
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["loaderSurface"],
|
||||
super::BPF_LOADER_UPGRADEABLE_SURFACE
|
||||
crate::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE
|
||||
);
|
||||
}
|
||||
|
||||
@@ -670,7 +637,7 @@ mod tests {
|
||||
let materializer = crate::MtLifecycleMaterializer;
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&materializer,
|
||||
&observation(super::FEATURE_SURFACE, "revoke_pending_activation", false),
|
||||
&observation(crate::MT_LIFECYCLE_FEATURE_SURFACE, "revoke_pending_activation", false),
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs[0].output_key, "feature_gate:revoke_pending_activation:0");
|
||||
@@ -696,7 +663,7 @@ mod tests {
|
||||
),
|
||||
] {
|
||||
let observation = observation_with_payload(
|
||||
super::SYSTEM_SURFACE,
|
||||
crate::MT_LIFECYCLE_SYSTEM_SURFACE,
|
||||
operation,
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
false,
|
||||
@@ -724,7 +691,7 @@ mod tests {
|
||||
fn system_account_creation_does_not_duplicate_balance_changes() {
|
||||
let materializer = crate::MtLifecycleMaterializer;
|
||||
let observation = observation_with_payload(
|
||||
super::SYSTEM_SURFACE,
|
||||
crate::MT_LIFECYCLE_SYSTEM_SURFACE,
|
||||
"create_account",
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
false,
|
||||
@@ -895,7 +862,8 @@ mod tests {
|
||||
fn loader_write_and_admin_events_are_not_lifecycle_projections() {
|
||||
let materializer = crate::MtLifecycleMaterializer;
|
||||
for entry_code in ["write", "set_authority", "transfer_authority"] {
|
||||
let observation = observation(super::BPF_LOADER_UPGRADEABLE_SURFACE, entry_code, false);
|
||||
let observation =
|
||||
observation(crate::MT_LIFECYCLE_BPF_LOADER_UPGRADEABLE_SURFACE, entry_code, false);
|
||||
assert!(!crate::MtApiEventMaterializer::accepts_observation(
|
||||
&materializer,
|
||||
&observation,
|
||||
@@ -908,7 +876,7 @@ mod tests {
|
||||
let materializer = crate::MtLifecycleMaterializer;
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&materializer,
|
||||
&observation(super::SYSTEM_SURFACE, "transfer", false),
|
||||
&observation(crate::MT_LIFECYCLE_SYSTEM_SURFACE, "transfer", false),
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
@@ -934,8 +902,12 @@ mod tests {
|
||||
fn failed_native_lifecycle_is_refused() {
|
||||
let materializer = crate::MtLifecycleMaterializer;
|
||||
let observations = [
|
||||
observation(super::ADDRESS_LOOKUP_TABLE_SURFACE, "close_lookup_table", true),
|
||||
observation(super::LOADER_V4_SURFACE, "deploy", true),
|
||||
observation(
|
||||
crate::MT_LIFECYCLE_ADDRESS_LOOKUP_TABLE_SURFACE,
|
||||
"close_lookup_table",
|
||||
true,
|
||||
),
|
||||
observation(crate::MT_LIFECYCLE_LOADER_V4_SURFACE, "deploy", true),
|
||||
nonce_observation("authorize_nonce_account", crate::MdEventFamily::Admin, true),
|
||||
zk_observation("verify_zero_ciphertext", true, true),
|
||||
slashing_observation("duplicate_block_proof", true),
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/liquidity.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `liquidity` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved liquidity materializer.
|
||||
pub use self::core::MtLiquidityMaterializer;
|
||||
pub use self::materializer::MtLiquidityMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_LIQUIDITY_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `liquidity` materializer.
|
||||
pub(crate) use self::constants::MT_LIQUIDITY_COMPONENT_NAME;
|
||||
/// Stable processor name for the `liquidity` materializer.
|
||||
pub(crate) use self::constants::MT_LIQUIDITY_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/liquidity/constants.rs
Normal file
11
kb-lib/src/materializer/liquidity/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/liquidity/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the liquidity materializer.
|
||||
|
||||
/// Stable runtime component name for the liquidity materializer.
|
||||
pub(crate) const MT_LIQUIDITY_COMPONENT_NAME: &str = "kb-lib.materializer.liquidity";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_LIQUIDITY_PROCESSOR_NAME: &str = "materializer.liquidity";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_LIQUIDITY_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/liquidity/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/liquidity/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved liquidity materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtLiquidityMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtLiquidityMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.liquidity";
|
||||
return crate::MT_LIQUIDITY_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtLiquidityMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Liquidity,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtLiquidityMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.liquidity".to_string(),
|
||||
name: crate::MT_LIQUIDITY_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_LIQUIDITY_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,31 +1,52 @@
|
||||
// file: kb-lib/src/materializer/metadata.rs
|
||||
// version: 5
|
||||
// version: 8
|
||||
|
||||
//! `metadata` materializer family.
|
||||
|
||||
mod core;
|
||||
mod metaplex_token_metadata;
|
||||
mod solana_program_metadata;
|
||||
mod token_2022;
|
||||
|
||||
/// Token-2022, Metaplex and Solana Program Metadata materializer.
|
||||
pub use self::core::MtMetadataMaterializer;
|
||||
/// Materializes one canonical non-Metadata Metaplex account snapshot.
|
||||
pub use self::core::materializer_metadata_materialize_metaplex_account_snapshot;
|
||||
pub use self::metaplex_token_metadata::materializer_metadata_materialize_metaplex_account_snapshot;
|
||||
/// Materializes one canonical Metaplex Metadata account snapshot.
|
||||
pub use self::core::materializer_metadata_materialize_metaplex_metadata_snapshot;
|
||||
/// Materializes Token-2022 embedded metadata and group snapshots.
|
||||
pub use self::core::materializer_metadata_materialize_token_2022_snapshot;
|
||||
pub use self::metaplex_token_metadata::materializer_metadata_materialize_metaplex_metadata_snapshot;
|
||||
/// Compatibility materializer retaining the historical generic metadata type name.
|
||||
pub use self::solana_program_metadata::MtMetadataMaterializer;
|
||||
/// Stable Solana Program Metadata instruction materializer.
|
||||
pub use self::solana_program_metadata::MtSolanaProgramMetadataMaterializer;
|
||||
/// Materializes one decoded Solana Program Metadata account snapshot.
|
||||
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
|
||||
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
|
||||
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot;
|
||||
/// Materializes one bounded Solana Program Metadata Metadata account snapshot.
|
||||
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
|
||||
/// Materializes Token-2022 embedded metadata and group snapshots.
|
||||
pub use self::token_2022::materializer_metadata_materialize_token_2022_snapshot;
|
||||
|
||||
/// Stable runtime component name for Metaplex Token Metadata projections.
|
||||
pub(crate) use self::metaplex_token_metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
|
||||
/// Stable processor name for Metaplex Token Metadata projections.
|
||||
pub(crate) use self::metaplex_token_metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME;
|
||||
/// Stable Metaplex Token Metadata projection version.
|
||||
pub(crate) use self::metaplex_token_metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Metaplex Token Metadata materialization.
|
||||
pub(crate) use self::metaplex_token_metadata::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA;
|
||||
/// Event families accepted by the Solana Program Metadata instruction materializer.
|
||||
pub(crate) use self::solana_program_metadata::MT_METADATA_SPM_ACCEPTED_FAMILIES;
|
||||
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for Solana Program Metadata projections.
|
||||
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME;
|
||||
/// Stable processor name for Solana Program Metadata projections.
|
||||
pub(crate) use self::solana_program_metadata::MT_METADATA_SPM_PROCESSOR_NAME;
|
||||
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME;
|
||||
/// Stable projection schema version for Solana Program Metadata outputs.
|
||||
pub(crate) use self::solana_program_metadata::MT_METADATA_SPM_PROJECTION_VERSION;
|
||||
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Solana Program Metadata materialization.
|
||||
pub(crate) use self::solana_program_metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;
|
||||
/// Stable runtime component name for Token-2022 embedded metadata projections.
|
||||
pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
|
||||
/// Stable processor name for Token-2022 embedded metadata projections.
|
||||
pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
|
||||
/// Stable Token-2022 embedded metadata projection version.
|
||||
pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Token-2022 embedded metadata materialization.
|
||||
pub(crate) use self::token_2022::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022;
|
||||
|
||||
@@ -1,608 +0,0 @@
|
||||
// file: kb-lib/src/materializer/metadata/core.rs
|
||||
// version: 7
|
||||
|
||||
//! Stable Token-2022, Metaplex and Solana Program Metadata materializer.
|
||||
|
||||
/// Initial materializer implementation placeholder.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtMetadataMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtMetadataMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.metadata";
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.event_family == crate::MdEventFamily::Metadata
|
||||
&& event.program_id.0 == kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
|
||||
&& event.surface_code.0 == crate::DC_METADATA_SPM_SURFACE_CODE
|
||||
&& crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES
|
||||
.iter()
|
||||
.any(|(_, code)| return *code == event.event_name.0.as_str());
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
let materialized = crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Metadata,
|
||||
};
|
||||
return std::result::Result::Ok(std::vec![materialized]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Materialize embedded Token-2022 metadata, group, and group-member extensions.
|
||||
pub fn materializer_metadata_materialize_token_2022_snapshot(
|
||||
account_key: &str,
|
||||
slot: u64,
|
||||
state: &crate::DcToken2022State,
|
||||
) -> std::result::Result<std::vec::Vec<crate::MtApiMaterializedOutput>, String> {
|
||||
if account_key.is_empty() {
|
||||
return std::result::Result::Err(
|
||||
"Token-2022 metadata snapshot requires a non-empty account key".to_string(),
|
||||
);
|
||||
}
|
||||
if state.kind != crate::DcToken2022StateKind::Mint {
|
||||
return std::result::Result::Err(
|
||||
"Token-2022 metadata snapshot requires a parsed mint state".to_string(),
|
||||
);
|
||||
}
|
||||
let mut outputs = std::vec::Vec::new();
|
||||
for entry in &state.extensions {
|
||||
let projection_kind = match entry.extension_name {
|
||||
"token_metadata" => std::option::Option::Some("token_metadata"),
|
||||
"token_group" => std::option::Option::Some("token_group"),
|
||||
"token_group_member" => std::option::Option::Some("token_group_member"),
|
||||
_ => std::option::Option::None,
|
||||
};
|
||||
let kind = match projection_kind {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => continue,
|
||||
};
|
||||
outputs.push(crate::MtApiMaterializedOutput {
|
||||
output_key: format!("token_2022_metadata_snapshot:{account_key}:{slot}:{kind}"),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"domain": "spl_token_2022_embedded_metadata",
|
||||
"projectionSemantics": "authoritative_bounded_mint_extension_snapshot",
|
||||
"idempotenceKey": format!("token_2022_metadata:{account_key}:{slot}:{kind}"),
|
||||
"accountKey": account_key,
|
||||
"slot": slot,
|
||||
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"metadataSource": "token_2022_embedded_tlv",
|
||||
"projectionKind": kind,
|
||||
"extensionType": entry.extension_type,
|
||||
"extensionName": entry.extension_name,
|
||||
"valueHex": entry.value_hex,
|
||||
"valueFields": entry.value_fields,
|
||||
"externalUriFetched": false,
|
||||
"identityAuthority": "mint_address",
|
||||
"finalAccountStateCaptured": true,
|
||||
"provenance": {
|
||||
"processorName": "kb-lib.materializer.metadata",
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"source": "bounded_token_2022_state_parser"
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
return std::result::Result::Ok(outputs);
|
||||
}
|
||||
|
||||
/// Materialize one canonical Metaplex `MetadataV1` account snapshot.
|
||||
///
|
||||
/// Account snapshots are authoritative for current state. Instruction observations may describe
|
||||
/// mutations, but must not emit a second competing metadata-state projection.
|
||||
pub fn materializer_metadata_materialize_metaplex_metadata_snapshot(
|
||||
snapshot: &crate::DcMetadataMtmCanonicalMetadataAccountSnapshot,
|
||||
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
|
||||
if !snapshot.provenance.committed {
|
||||
return std::result::Result::Err(
|
||||
"Metaplex metadata state materialization requires committed provenance".to_string(),
|
||||
);
|
||||
}
|
||||
if snapshot.identity.materialization
|
||||
!= crate::DcMetadataMtmCanonicalMaterializationPolicy::ActiveState
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Metaplex MetadataV1 materialization requires active-state policy".to_string(),
|
||||
);
|
||||
}
|
||||
let metadata = match &snapshot.state {
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::Metadata(value) => value,
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
"Metaplex metadata materializer received another account layout".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let slot = snapshot.provenance.slot.map(|value| return value.to_string());
|
||||
let write_version = snapshot.provenance.write_version.map(|value| return value.to_string());
|
||||
let payload = serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"domain": "metaplex_token_metadata",
|
||||
"projectionSemantics": "authoritative_committed_account_snapshot",
|
||||
"stableId": snapshot.identity.stable_id,
|
||||
"account": metadata.account,
|
||||
"mint": metadata.mint,
|
||||
"updateAuthority": metadata.update_authority,
|
||||
"name": metadata.name,
|
||||
"symbol": metadata.symbol,
|
||||
"uri": metadata.uri,
|
||||
"sellerFeeBasisPoints": metadata.seller_fee_basis_points,
|
||||
"creators": metadata.payload_json.get("creators").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"collection": metadata.payload_json.get("collection").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"uses": metadata.payload_json.get("uses").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"tokenStandard": metadata.payload_json.get("tokenStandard").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"programmableConfig": metadata.payload_json.get("programmableConfig").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"primarySaleHappened": metadata.primary_sale_happened,
|
||||
"isMutable": metadata.is_mutable,
|
||||
"layout": snapshot.identity.layout,
|
||||
"layoutDiscriminant": snapshot.identity.discriminant,
|
||||
"canonicalVersion": snapshot.identity.canonical_version,
|
||||
"lifecycle": "active",
|
||||
"materializationPolicy": "active_state",
|
||||
"metadataSource": "metaplex_owned_account",
|
||||
"accountState": metadata.payload_json,
|
||||
"externalUriFetched": false,
|
||||
"stateAuthority": "owned_account_snapshot",
|
||||
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
|
||||
"provenance": {
|
||||
"source": format!("{:?}", snapshot.provenance.source),
|
||||
"slot": slot,
|
||||
"writeVersion": write_version,
|
||||
"signature": snapshot.provenance.signature,
|
||||
"instructionIndex": snapshot.provenance.instruction_index,
|
||||
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
|
||||
"committed": snapshot.provenance.committed,
|
||||
"processorName": "kb-lib.materializer.metadata",
|
||||
"processorVersion": env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
});
|
||||
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
|
||||
output_key: format!("metaplex_metadata_state:{}", metadata.account),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: payload,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_metaplex_metadata_state {
|
||||
pub(super) fn snapshot(
|
||||
committed: bool,
|
||||
) -> crate::DcMetadataMtmCanonicalMetadataAccountSnapshot {
|
||||
let metadata = crate::DcMetadataMtmMetadataAccountSnapshot {
|
||||
account: "metadata111".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint111".to_string(),
|
||||
update_authority: "authority111".to_string(),
|
||||
name: "Asset".to_string(),
|
||||
symbol: "AST".to_string(),
|
||||
uri: "https://example.invalid/asset.json".to_string(),
|
||||
seller_fee_basis_points: 500,
|
||||
primary_sale_happened: true,
|
||||
is_mutable: false,
|
||||
payload_json: serde_json::json!({
|
||||
"creators": [{"address":"creator111","verified":true,"share":100}],
|
||||
"collection": {"verified":true,"key":"collection111"},
|
||||
"uses": {"useMethod":"Single","remaining":"0","total":"1"},
|
||||
"tokenStandard": "ProgrammableNonFungible",
|
||||
"programmableConfig": {"ruleSet":"rules111"}
|
||||
}),
|
||||
};
|
||||
return crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_metadata(
|
||||
metadata,
|
||||
crate::DcMetadataMtmCanonicalAccountProvenance {
|
||||
source: crate::DcMetadataMtmCanonicalObservationSource::Backfill,
|
||||
slot: std::option::Option::Some(42),
|
||||
write_version: std::option::Option::Some(7),
|
||||
signature: std::option::Option::Some("signature111".to_string()),
|
||||
instruction_index: std::option::Option::Some(3),
|
||||
inner_instruction_index: std::option::Option::None,
|
||||
committed,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_snapshot_is_one_stable_authoritative_metadata_projection() {
|
||||
let first =
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&snapshot(true));
|
||||
let second =
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&snapshot(true));
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.output_key.as_str()),
|
||||
std::result::Result::Ok("metaplex_metadata_state:metadata111")
|
||||
);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["metadataSource"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("metaplex_owned_account"))
|
||||
);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["creators"].clone()),
|
||||
std::result::Result::Ok(
|
||||
serde_json::json!([{"address":"creator111","verified":true,"share":100}])
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
first
|
||||
.as_ref()
|
||||
.map(|value| return value.payload_json["programmableConfig"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!({"ruleSet":"rules111"}))
|
||||
);
|
||||
assert_eq!(
|
||||
first
|
||||
.as_ref()
|
||||
.map(|value| return value.payload_json["instructionProjectionRole"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!(
|
||||
"mutation_observation_only_no_duplicate_state"
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
first
|
||||
.as_ref()
|
||||
.map(|value| return value.payload_json["accountState"]["creators"][0]["share"]
|
||||
.clone()),
|
||||
std::result::Result::Ok(serde_json::json!(100))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncommitted_or_non_metadata_state_is_rejected() {
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&snapshot(false))
|
||||
.is_err()
|
||||
);
|
||||
let mut wrong = snapshot(true);
|
||||
wrong.state = crate::DcMetadataMtmCanonicalMetadataAccountState::EditionMarker(
|
||||
crate::DcMetadataMtmEditionMarkerAccountSnapshot {
|
||||
account: "marker111".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint111".to_string(),
|
||||
kind: crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV1,
|
||||
marker_group: std::option::Option::Some(0),
|
||||
edition: 1,
|
||||
byte_index: 0,
|
||||
bit_mask: 64,
|
||||
edition_taken: true,
|
||||
ledger: std::vec![64],
|
||||
payload_json: serde_json::json!({}),
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&wrong).is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Materialize any canonical Metaplex account state other than `MetadataV1`.
|
||||
///
|
||||
/// Every account address maps to one stable state key. Historical layouts remain materializable
|
||||
/// with an explicit historical lifecycle, while execution policy remains independently disabled.
|
||||
pub fn materializer_metadata_materialize_metaplex_account_snapshot(
|
||||
snapshot: &crate::DcMetadataMtmCanonicalMetadataAccountSnapshot,
|
||||
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
|
||||
if !snapshot.provenance.committed {
|
||||
return std::result::Result::Err(
|
||||
"Metaplex account-state materialization requires committed provenance".to_string(),
|
||||
);
|
||||
}
|
||||
let (projection_kind, fact_owner, state_json) = match &snapshot.state {
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::Metadata(_) => {
|
||||
return std::result::Result::Err(
|
||||
"MetadataV1 must use the dedicated metadata-state materializer".to_string(),
|
||||
);
|
||||
},
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::Edition(value) => {
|
||||
("edition_state", "kb-lib.materializer.metadata", value.payload_json.clone())
|
||||
},
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::EditionMarker(value) => (
|
||||
"edition_marker_state",
|
||||
"kb-lib.materializer.metadata",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::TokenRecord(value) => (
|
||||
"programmable_token_state",
|
||||
"kb-lib.materializer.lifecycle",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::DelegateRecord(value) => {
|
||||
("delegate_admin_state", "kb-lib.materializer.admin", value.payload_json.clone())
|
||||
},
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::AuthorityRecord(value) => {
|
||||
("authority_admin_state", "kb-lib.materializer.admin", value.payload_json.clone())
|
||||
},
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::TokenOwnedEscrow(value) => (
|
||||
"token_owned_escrow_state",
|
||||
"kb-lib.materializer.lifecycle",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::ReservationList(value) => (
|
||||
"historical_reservation_state",
|
||||
"kb-lib.materializer.metadata",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
};
|
||||
let lifecycle = match snapshot.identity.lifecycle {
|
||||
crate::DcMetadataMtmCanonicalAccountLifecycle::Active => "active",
|
||||
crate::DcMetadataMtmCanonicalAccountLifecycle::ActiveExperimental => "active_experimental",
|
||||
crate::DcMetadataMtmCanonicalAccountLifecycle::Historical => "historical",
|
||||
};
|
||||
let materialization_policy = match snapshot.identity.materialization {
|
||||
crate::DcMetadataMtmCanonicalMaterializationPolicy::ActiveState => "active_state",
|
||||
crate::DcMetadataMtmCanonicalMaterializationPolicy::HistoricalState => "historical_state",
|
||||
};
|
||||
let execution_policy = match snapshot.identity.execution {
|
||||
crate::DcMetadataMtmCanonicalExecutionPolicy::CurrentCandidate => "current_candidate",
|
||||
crate::DcMetadataMtmCanonicalExecutionPolicy::ExperimentalCandidate => {
|
||||
"experimental_candidate"
|
||||
},
|
||||
crate::DcMetadataMtmCanonicalExecutionPolicy::NeverHistorical => "never_historical",
|
||||
};
|
||||
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
|
||||
output_key: format!("metaplex_account_state:{}", snapshot.identity.account),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"domain": "metaplex_token_metadata",
|
||||
"projectionKind": projection_kind,
|
||||
"projectionSemantics": "authoritative_committed_owned_account_snapshot",
|
||||
"stableId": snapshot.identity.stable_id,
|
||||
"account": snapshot.identity.account,
|
||||
"programId": snapshot.identity.program_id,
|
||||
"layout": snapshot.identity.layout,
|
||||
"layoutDiscriminant": snapshot.identity.discriminant,
|
||||
"canonicalVersion": snapshot.identity.canonical_version,
|
||||
"lifecycle": lifecycle,
|
||||
"materializationPolicy": materialization_policy,
|
||||
"executionPolicy": execution_policy,
|
||||
"factOwner": fact_owner,
|
||||
"metadataSource": "metaplex_owned_account",
|
||||
"accountState": state_json,
|
||||
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
|
||||
"provenance": {
|
||||
"source": format!("{:?}", snapshot.provenance.source),
|
||||
"slot": snapshot.provenance.slot.map(|value: u64| return value.to_string()),
|
||||
"writeVersion": snapshot.provenance.write_version.map(|value: u64| return value.to_string()),
|
||||
"signature": snapshot.provenance.signature,
|
||||
"instructionIndex": snapshot.provenance.instruction_index,
|
||||
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
|
||||
"committed": snapshot.provenance.committed,
|
||||
"processorName": "kb-lib.materializer.metadata",
|
||||
"processorVersion": env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_metaplex_account_state {
|
||||
fn provenance() -> crate::DcMetadataMtmCanonicalAccountProvenance {
|
||||
return crate::DcMetadataMtmCanonicalAccountProvenance {
|
||||
source: crate::DcMetadataMtmCanonicalObservationSource::Backfill,
|
||||
slot: std::option::Option::Some(90),
|
||||
write_version: std::option::Option::Some(2),
|
||||
signature: std::option::Option::None,
|
||||
instruction_index: std::option::Option::None,
|
||||
inner_instruction_index: std::option::Option::None,
|
||||
committed: true,
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn historical_edition_and_reservation_are_stable_materializable_state() {
|
||||
let edition = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_edition(
|
||||
crate::DcMetadataMtmEditionAccountSnapshot {
|
||||
account: "master-v1".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint-v1".to_string(),
|
||||
kind: crate::DcMetadataMtmEditionAccountKind::MasterEditionV1,
|
||||
supply: std::option::Option::Some(3),
|
||||
max_supply: std::option::Option::Some(10),
|
||||
parent: std::option::Option::None,
|
||||
edition: std::option::Option::None,
|
||||
printing_mint: std::option::Option::Some("printing".to_string()),
|
||||
one_time_printing_authorization_mint: std::option::Option::Some(
|
||||
"authorization".to_string(),
|
||||
),
|
||||
payload_json: serde_json::json!({"supply":"3","maxSupply":"10"}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
let reservation =
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_reservation_list(
|
||||
crate::DcMetadataMtmReservationListAccountSnapshot {
|
||||
account: "reservation-v2".to_string(),
|
||||
bump: 2,
|
||||
kind: crate::DcMetadataMtmReservationListAccountKind::V2,
|
||||
master_edition: "master-v1".to_string(),
|
||||
resource: "resource".to_string(),
|
||||
supply_snapshot: std::option::Option::Some(3),
|
||||
reservations: std::vec::Vec::new(),
|
||||
total_reservation_spots: 0,
|
||||
current_reservation_spots: 0,
|
||||
payload_json: serde_json::json!({"reservations":[]}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
let first = crate::materializer_metadata_materialize_metaplex_account_snapshot(&edition);
|
||||
let second =
|
||||
crate::materializer_metadata_materialize_metaplex_account_snapshot(&reservation);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["lifecycle"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("historical"))
|
||||
);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["executionPolicy"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("never_historical"))
|
||||
);
|
||||
assert_eq!(
|
||||
second.as_ref().map(|value| return value.output_key.as_str()),
|
||||
std::result::Result::Ok("metaplex_account_state:reservation-v2")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn programmable_delegate_authority_and_escrow_keep_unique_fact_owners() {
|
||||
let token = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_token_record(
|
||||
crate::DcMetadataMtmTokenRecordAccountSnapshot {
|
||||
account: "token-record".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint".to_string(),
|
||||
token: "token".to_string(),
|
||||
stored_bump: 1,
|
||||
state: "Locked".to_string(),
|
||||
rule_set_revision: std::option::Option::Some(4),
|
||||
delegate: std::option::Option::Some("delegate".to_string()),
|
||||
delegate_role: std::option::Option::Some("Transfer".to_string()),
|
||||
locked_transfer: std::option::Option::Some("destination".to_string()),
|
||||
locked: true,
|
||||
payload_json: serde_json::json!({"state":"Locked"}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
let output = crate::materializer_metadata_materialize_metaplex_account_snapshot(&token);
|
||||
assert_eq!(
|
||||
output.as_ref().map(|value| return value.payload_json["factOwner"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("kb-lib.materializer.lifecycle"))
|
||||
);
|
||||
assert_eq!(
|
||||
output.as_ref().map(|value| return value.payload_json["projectionKind"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("programmable_token_state"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncommitted_and_metadata_snapshots_use_explicit_boundaries() {
|
||||
let mut marker = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_edition_marker(
|
||||
crate::DcMetadataMtmEditionMarkerAccountSnapshot {
|
||||
account: "marker".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint".to_string(),
|
||||
kind: crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV2,
|
||||
marker_group: std::option::Option::None,
|
||||
edition: 8,
|
||||
byte_index: 1,
|
||||
bit_mask: 128,
|
||||
edition_taken: true,
|
||||
ledger: std::vec![0, 128],
|
||||
payload_json: serde_json::json!({"editionTaken":true}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
marker.provenance.committed = false;
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_account_snapshot(&marker).is_err()
|
||||
);
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_account_snapshot(
|
||||
&super::tests_metaplex_metadata_state::snapshot(true)
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_token_2022_state {
|
||||
#[test]
|
||||
fn embedded_metadata_group_and_member_have_distinct_stable_outputs() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Mint,
|
||||
base_fields: serde_json::json!({"supply":"1","decimals":0}),
|
||||
base_hex: "00".repeat(82),
|
||||
account_type: std::option::Option::Some(1),
|
||||
extensions: std::vec![
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 19,
|
||||
extension_name: "token_metadata",
|
||||
value_hex: "01".to_string(),
|
||||
value_fields: serde_json::json!({"name":"Token","symbol":"TOK","uri":"https://example.invalid/token.json"}),
|
||||
},
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 20,
|
||||
extension_name: "token_group",
|
||||
value_hex: "02".to_string(),
|
||||
value_fields: serde_json::json!({"mint":"mint111","size":"1","maxSize":"10"}),
|
||||
},
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 21,
|
||||
extension_name: "token_group_member",
|
||||
value_hex: "03".to_string(),
|
||||
value_fields: serde_json::json!({"mint":"mint111","group":"group111","memberNumber":"1"}),
|
||||
},
|
||||
],
|
||||
};
|
||||
let outputs =
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("mint111", 9, &state);
|
||||
assert_eq!(outputs.as_ref().map(|values| return values.len()), std::result::Result::Ok(3));
|
||||
assert_eq!(
|
||||
outputs
|
||||
.as_ref()
|
||||
.map(|values| return values[0].payload_json["metadataSource"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("token_2022_embedded_tlv"))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs
|
||||
.as_ref()
|
||||
.map(|values| return values[0].payload_json["externalUriFetched"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!(false))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs
|
||||
.as_ref()
|
||||
.map(|values| return values[1].payload_json["projectionKind"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("token_group"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_is_idempotent_filters_unrelated_extensions_and_requires_mint_identity() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Mint,
|
||||
base_fields: serde_json::json!({}),
|
||||
base_hex: "00".repeat(82),
|
||||
account_type: std::option::Option::Some(1),
|
||||
extensions: std::vec![crate::DcToken2022TlvEntry {
|
||||
extension_type: 1,
|
||||
extension_name: "transfer_fee_config",
|
||||
value_hex: "00".to_string(),
|
||||
value_fields: serde_json::json!({}),
|
||||
}],
|
||||
};
|
||||
let first =
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("mint111", 9, &state);
|
||||
let second =
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("mint111", 9, &state);
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(first.as_ref().map(|values| return values.len()), std::result::Result::Ok(0));
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("", 9, &state).is_err()
|
||||
);
|
||||
let account_state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Account,
|
||||
base_fields: serde_json::json!({}),
|
||||
base_hex: "00".repeat(165),
|
||||
account_type: std::option::Option::None,
|
||||
extensions: std::vec::Vec::new(),
|
||||
};
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot(
|
||||
"account111",
|
||||
9,
|
||||
&account_state
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
22
kb-lib/src/materializer/metadata/metaplex_token_metadata.rs
Normal file
22
kb-lib/src/materializer/metadata/metaplex_token_metadata.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// file: kb-lib/src/materializer/metadata/metaplex_token_metadata.rs
|
||||
// version: 2
|
||||
|
||||
//! Metaplex Token Metadata account-state materializer.
|
||||
|
||||
mod account;
|
||||
mod constants;
|
||||
mod state;
|
||||
|
||||
/// Materializes one canonical non-Metadata Metaplex account snapshot.
|
||||
pub use self::account::materializer_metadata_materialize_metaplex_account_snapshot;
|
||||
/// Materializes one canonical Metaplex Metadata account snapshot.
|
||||
pub use self::state::materializer_metadata_materialize_metaplex_metadata_snapshot;
|
||||
|
||||
/// Stable runtime component name for Metaplex Token Metadata projections.
|
||||
pub(crate) use self::constants::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
|
||||
/// Stable processor name for Metaplex Token Metadata projections.
|
||||
pub(crate) use self::constants::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME;
|
||||
/// Stable Metaplex Token Metadata projection version.
|
||||
pub(crate) use self::constants::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Metaplex Token Metadata materialization.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA;
|
||||
@@ -0,0 +1,283 @@
|
||||
// file: kb-lib/src/materializer/metadata/metaplex_token_metadata/account.rs
|
||||
// version: 5
|
||||
|
||||
//! Authoritative non-Metadata Metaplex account-state materialization.
|
||||
|
||||
/// Materialize any canonical Metaplex account state other than `MetadataV1`.
|
||||
///
|
||||
/// Every account address maps to one stable state key. Historical layouts remain materializable
|
||||
/// with an explicit historical lifecycle, while execution policy remains independently disabled.
|
||||
pub fn materializer_metadata_materialize_metaplex_account_snapshot(
|
||||
snapshot: &crate::DcMetadataMtmCanonicalMetadataAccountSnapshot,
|
||||
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
|
||||
if !snapshot.provenance.committed {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA,
|
||||
action = "materialize_metaplex_account_snapshot",
|
||||
component_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
processor_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account = %snapshot.identity.account.as_str(),
|
||||
committed = false,
|
||||
status = "refused",
|
||||
error_code = "uncommitted_metaplex_account_snapshot",
|
||||
"refuse uncommitted Metaplex account snapshot"
|
||||
);
|
||||
return std::result::Result::Err(
|
||||
"Metaplex account-state materialization requires committed provenance".to_string(),
|
||||
);
|
||||
}
|
||||
let (projection_kind, fact_owner, state_json) = match &snapshot.state {
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::Metadata(_) => {
|
||||
return std::result::Result::Err(
|
||||
"MetadataV1 must use the dedicated metadata-state materializer".to_string(),
|
||||
);
|
||||
},
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::Edition(value) => (
|
||||
"edition_state",
|
||||
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::EditionMarker(value) => (
|
||||
"edition_marker_state",
|
||||
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::TokenRecord(value) => (
|
||||
"programmable_token_state",
|
||||
crate::MT_LIFECYCLE_COMPONENT_NAME,
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::DelegateRecord(value) => (
|
||||
"delegate_admin_state",
|
||||
crate::MT_ADMIN_COMPONENT_NAME,
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::AuthorityRecord(value) => (
|
||||
"authority_admin_state",
|
||||
crate::MT_ADMIN_COMPONENT_NAME,
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::TokenOwnedEscrow(value) => (
|
||||
"token_owned_escrow_state",
|
||||
crate::MT_LIFECYCLE_COMPONENT_NAME,
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::ReservationList(value) => (
|
||||
"historical_reservation_state",
|
||||
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
};
|
||||
let lifecycle = match snapshot.identity.lifecycle {
|
||||
crate::DcMetadataMtmCanonicalAccountLifecycle::Active => "active",
|
||||
crate::DcMetadataMtmCanonicalAccountLifecycle::ActiveExperimental => "active_experimental",
|
||||
crate::DcMetadataMtmCanonicalAccountLifecycle::Historical => "historical",
|
||||
};
|
||||
let materialization_policy = match snapshot.identity.materialization {
|
||||
crate::DcMetadataMtmCanonicalMaterializationPolicy::ActiveState => "active_state",
|
||||
crate::DcMetadataMtmCanonicalMaterializationPolicy::HistoricalState => "historical_state",
|
||||
};
|
||||
let execution_policy = match snapshot.identity.execution {
|
||||
crate::DcMetadataMtmCanonicalExecutionPolicy::CurrentCandidate => "current_candidate",
|
||||
crate::DcMetadataMtmCanonicalExecutionPolicy::ExperimentalCandidate => {
|
||||
"experimental_candidate"
|
||||
},
|
||||
crate::DcMetadataMtmCanonicalExecutionPolicy::NeverHistorical => "never_historical",
|
||||
};
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: format!("metaplex_account_state:{}", snapshot.identity.account),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION,
|
||||
"domain": "metaplex_token_metadata",
|
||||
"projectionKind": projection_kind,
|
||||
"projectionSemantics": "authoritative_committed_owned_account_snapshot",
|
||||
"stableId": snapshot.identity.stable_id,
|
||||
"account": snapshot.identity.account,
|
||||
"programId": snapshot.identity.program_id,
|
||||
"layout": snapshot.identity.layout,
|
||||
"layoutDiscriminant": snapshot.identity.discriminant,
|
||||
"canonicalVersion": snapshot.identity.canonical_version,
|
||||
"lifecycle": lifecycle,
|
||||
"materializationPolicy": materialization_policy,
|
||||
"executionPolicy": execution_policy,
|
||||
"factOwner": fact_owner,
|
||||
"metadataSource": "metaplex_owned_account",
|
||||
"accountState": state_json,
|
||||
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
|
||||
"provenance": {
|
||||
"source": format!("{:?}", snapshot.provenance.source),
|
||||
"slot": snapshot.provenance.slot.map(|value: u64| return value.to_string()),
|
||||
"writeVersion": snapshot.provenance.write_version.map(|value: u64| return value.to_string()),
|
||||
"signature": snapshot.provenance.signature,
|
||||
"instructionIndex": snapshot.provenance.instruction_index,
|
||||
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
|
||||
"committed": snapshot.provenance.committed,
|
||||
"processorName": crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA,
|
||||
action = "materialize_metaplex_account_snapshot",
|
||||
component_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
processor_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account = %snapshot.identity.account.as_str(),
|
||||
projection_kind,
|
||||
fact_owner,
|
||||
slot = ?snapshot.provenance.slot,
|
||||
status = "inserted",
|
||||
"materialize committed Metaplex account snapshot"
|
||||
);
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_metaplex_account_state {
|
||||
fn metadata_snapshot() -> crate::DcMetadataMtmCanonicalMetadataAccountSnapshot {
|
||||
return crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_metadata(
|
||||
crate::DcMetadataMtmMetadataAccountSnapshot {
|
||||
account: "metadata-boundary".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint-boundary".to_string(),
|
||||
update_authority: "authority-boundary".to_string(),
|
||||
name: "Boundary".to_string(),
|
||||
symbol: "BND".to_string(),
|
||||
uri: "https://example.invalid/boundary.json".to_string(),
|
||||
seller_fee_basis_points: 0,
|
||||
primary_sale_happened: false,
|
||||
is_mutable: true,
|
||||
payload_json: serde_json::json!({}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
}
|
||||
|
||||
fn provenance() -> crate::DcMetadataMtmCanonicalAccountProvenance {
|
||||
return crate::DcMetadataMtmCanonicalAccountProvenance {
|
||||
source: crate::DcMetadataMtmCanonicalObservationSource::Backfill,
|
||||
slot: std::option::Option::Some(90),
|
||||
write_version: std::option::Option::Some(2),
|
||||
signature: std::option::Option::None,
|
||||
instruction_index: std::option::Option::None,
|
||||
inner_instruction_index: std::option::Option::None,
|
||||
committed: true,
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn historical_edition_and_reservation_are_stable_materializable_state() {
|
||||
let edition = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_edition(
|
||||
crate::DcMetadataMtmEditionAccountSnapshot {
|
||||
account: "master-v1".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint-v1".to_string(),
|
||||
kind: crate::DcMetadataMtmEditionAccountKind::MasterEditionV1,
|
||||
supply: std::option::Option::Some(3),
|
||||
max_supply: std::option::Option::Some(10),
|
||||
parent: std::option::Option::None,
|
||||
edition: std::option::Option::None,
|
||||
printing_mint: std::option::Option::Some("printing".to_string()),
|
||||
one_time_printing_authorization_mint: std::option::Option::Some(
|
||||
"authorization".to_string(),
|
||||
),
|
||||
payload_json: serde_json::json!({"supply":"3","maxSupply":"10"}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
let reservation =
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_reservation_list(
|
||||
crate::DcMetadataMtmReservationListAccountSnapshot {
|
||||
account: "reservation-v2".to_string(),
|
||||
bump: 2,
|
||||
kind: crate::DcMetadataMtmReservationListAccountKind::V2,
|
||||
master_edition: "master-v1".to_string(),
|
||||
resource: "resource".to_string(),
|
||||
supply_snapshot: std::option::Option::Some(3),
|
||||
reservations: std::vec::Vec::new(),
|
||||
total_reservation_spots: 0,
|
||||
current_reservation_spots: 0,
|
||||
payload_json: serde_json::json!({"reservations":[]}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
let first = crate::materializer_metadata_materialize_metaplex_account_snapshot(&edition);
|
||||
let second =
|
||||
crate::materializer_metadata_materialize_metaplex_account_snapshot(&reservation);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["lifecycle"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("historical"))
|
||||
);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["executionPolicy"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("never_historical"))
|
||||
);
|
||||
assert_eq!(
|
||||
second.as_ref().map(|value| return value.output_key.as_str()),
|
||||
std::result::Result::Ok("metaplex_account_state:reservation-v2")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn programmable_delegate_authority_and_escrow_keep_unique_fact_owners() {
|
||||
let token = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_token_record(
|
||||
crate::DcMetadataMtmTokenRecordAccountSnapshot {
|
||||
account: "token-record".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint".to_string(),
|
||||
token: "token".to_string(),
|
||||
stored_bump: 1,
|
||||
state: "Locked".to_string(),
|
||||
rule_set_revision: std::option::Option::Some(4),
|
||||
delegate: std::option::Option::Some("delegate".to_string()),
|
||||
delegate_role: std::option::Option::Some("Transfer".to_string()),
|
||||
locked_transfer: std::option::Option::Some("destination".to_string()),
|
||||
locked: true,
|
||||
payload_json: serde_json::json!({"state":"Locked"}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
let output = crate::materializer_metadata_materialize_metaplex_account_snapshot(&token);
|
||||
assert_eq!(
|
||||
output.as_ref().map(|value| return value.payload_json["factOwner"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!(crate::MT_LIFECYCLE_COMPONENT_NAME))
|
||||
);
|
||||
assert_eq!(
|
||||
output.as_ref().map(|value| return value.payload_json["projectionKind"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("programmable_token_state"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncommitted_and_metadata_snapshots_use_explicit_boundaries() {
|
||||
let mut marker = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_edition_marker(
|
||||
crate::DcMetadataMtmEditionMarkerAccountSnapshot {
|
||||
account: "marker".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint".to_string(),
|
||||
kind: crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV2,
|
||||
marker_group: std::option::Option::None,
|
||||
edition: 8,
|
||||
byte_index: 1,
|
||||
bit_mask: 128,
|
||||
edition_taken: true,
|
||||
ledger: std::vec![0, 128],
|
||||
payload_json: serde_json::json!({"editionTaken":true}),
|
||||
},
|
||||
provenance(),
|
||||
);
|
||||
marker.provenance.committed = false;
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_account_snapshot(&marker).is_err()
|
||||
);
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_account_snapshot(
|
||||
&metadata_snapshot()
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// file: kb-lib/src/materializer/metadata/metaplex_token_metadata/constants.rs
|
||||
// version: 4
|
||||
|
||||
//! Local constants for the metadata metaplex_token_metadata materializer.
|
||||
|
||||
/// Stable runtime component name for the metadata metaplex_token_metadata materializer.
|
||||
pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME: &str =
|
||||
"kb-lib.materializer.metadata.metaplex_token_metadata";
|
||||
/// Stable processor name persisted by metadata projections.
|
||||
pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME: &str =
|
||||
"materializer.metadata.metaplex_token_metadata";
|
||||
/// Stable projection contract version.
|
||||
pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for the metadata metaplex_token_metadata materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA: &str =
|
||||
"kb-lib.materializer.metadata.metaplex_token_metadata";
|
||||
@@ -0,0 +1,229 @@
|
||||
// file: kb-lib/src/materializer/metadata/metaplex_token_metadata/state.rs
|
||||
// version: 5
|
||||
|
||||
//! Authoritative Metaplex MetadataV1 state materialization.
|
||||
|
||||
/// Materialize one canonical Metaplex `MetadataV1` account snapshot.
|
||||
///
|
||||
/// Account snapshots are authoritative for current state. Instruction observations may describe
|
||||
/// mutations, but must not emit a second competing metadata-state projection.
|
||||
pub fn materializer_metadata_materialize_metaplex_metadata_snapshot(
|
||||
snapshot: &crate::DcMetadataMtmCanonicalMetadataAccountSnapshot,
|
||||
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
|
||||
if !snapshot.provenance.committed {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA,
|
||||
action = "materialize_metaplex_metadata_snapshot",
|
||||
component_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
processor_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account = %snapshot.identity.account.as_str(),
|
||||
committed = false,
|
||||
status = "refused",
|
||||
error_code = "uncommitted_metaplex_metadata_snapshot",
|
||||
"refuse uncommitted Metaplex metadata snapshot"
|
||||
);
|
||||
return std::result::Result::Err(
|
||||
"Metaplex metadata state materialization requires committed provenance".to_string(),
|
||||
);
|
||||
}
|
||||
if snapshot.identity.materialization
|
||||
!= crate::DcMetadataMtmCanonicalMaterializationPolicy::ActiveState
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Metaplex MetadataV1 materialization requires active-state policy".to_string(),
|
||||
);
|
||||
}
|
||||
let metadata = match &snapshot.state {
|
||||
crate::DcMetadataMtmCanonicalMetadataAccountState::Metadata(value) => value,
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
"Metaplex metadata materializer received another account layout".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let slot = snapshot.provenance.slot.map(|value| return value.to_string());
|
||||
let write_version = snapshot.provenance.write_version.map(|value| return value.to_string());
|
||||
let payload = serde_json::json!({
|
||||
"projectionVersion": crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION,
|
||||
"domain": "metaplex_token_metadata",
|
||||
"projectionSemantics": "authoritative_committed_account_snapshot",
|
||||
"stableId": snapshot.identity.stable_id,
|
||||
"account": metadata.account,
|
||||
"mint": metadata.mint,
|
||||
"updateAuthority": metadata.update_authority,
|
||||
"name": metadata.name,
|
||||
"symbol": metadata.symbol,
|
||||
"uri": metadata.uri,
|
||||
"sellerFeeBasisPoints": metadata.seller_fee_basis_points,
|
||||
"creators": json_field_or_null(&metadata.payload_json, "creators"),
|
||||
"collection": json_field_or_null(&metadata.payload_json, "collection"),
|
||||
"uses": json_field_or_null(&metadata.payload_json, "uses"),
|
||||
"tokenStandard": json_field_or_null(&metadata.payload_json, "tokenStandard"),
|
||||
"programmableConfig": json_field_or_null(&metadata.payload_json, "programmableConfig"),
|
||||
"primarySaleHappened": metadata.primary_sale_happened,
|
||||
"isMutable": metadata.is_mutable,
|
||||
"layout": snapshot.identity.layout,
|
||||
"layoutDiscriminant": snapshot.identity.discriminant,
|
||||
"canonicalVersion": snapshot.identity.canonical_version,
|
||||
"lifecycle": "active",
|
||||
"materializationPolicy": "active_state",
|
||||
"metadataSource": "metaplex_owned_account",
|
||||
"accountState": metadata.payload_json,
|
||||
"externalUriFetched": false,
|
||||
"stateAuthority": "owned_account_snapshot",
|
||||
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
|
||||
"provenance": {
|
||||
"source": format!("{:?}", snapshot.provenance.source),
|
||||
"slot": slot,
|
||||
"writeVersion": write_version,
|
||||
"signature": snapshot.provenance.signature,
|
||||
"instructionIndex": snapshot.provenance.instruction_index,
|
||||
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
|
||||
"committed": snapshot.provenance.committed,
|
||||
"processorName": crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
});
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: format!("metaplex_metadata_state:{}", metadata.account),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: payload,
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA,
|
||||
action = "materialize_metaplex_metadata_snapshot",
|
||||
component_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
|
||||
processor_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account = %metadata.account.as_str(),
|
||||
mint = %metadata.mint.as_str(),
|
||||
slot = ?snapshot.provenance.slot,
|
||||
status = "inserted",
|
||||
"materialize committed Metaplex MetadataV1 snapshot"
|
||||
);
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn json_field_or_null(value: &serde_json::Value, field: &str) -> serde_json::Value {
|
||||
return match value.get(field) {
|
||||
std::option::Option::Some(value) => value.clone(),
|
||||
std::option::Option::None => serde_json::Value::Null,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_metaplex_metadata_state {
|
||||
fn snapshot(committed: bool) -> crate::DcMetadataMtmCanonicalMetadataAccountSnapshot {
|
||||
let metadata = crate::DcMetadataMtmMetadataAccountSnapshot {
|
||||
account: "metadata111".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint111".to_string(),
|
||||
update_authority: "authority111".to_string(),
|
||||
name: "Asset".to_string(),
|
||||
symbol: "AST".to_string(),
|
||||
uri: "https://example.invalid/asset.json".to_string(),
|
||||
seller_fee_basis_points: 500,
|
||||
primary_sale_happened: true,
|
||||
is_mutable: false,
|
||||
payload_json: serde_json::json!({
|
||||
"creators": [{"address":"creator111","verified":true,"share":100}],
|
||||
"collection": {"verified":true,"key":"collection111"},
|
||||
"uses": {"useMethod":"Single","remaining":"0","total":"1"},
|
||||
"tokenStandard": "ProgrammableNonFungible",
|
||||
"programmableConfig": {"ruleSet":"rules111"}
|
||||
}),
|
||||
};
|
||||
return crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_metadata(
|
||||
metadata,
|
||||
crate::DcMetadataMtmCanonicalAccountProvenance {
|
||||
source: crate::DcMetadataMtmCanonicalObservationSource::Backfill,
|
||||
slot: std::option::Option::Some(42),
|
||||
write_version: std::option::Option::Some(7),
|
||||
signature: std::option::Option::Some("signature111".to_string()),
|
||||
instruction_index: std::option::Option::Some(3),
|
||||
inner_instruction_index: std::option::Option::None,
|
||||
committed,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_snapshot_is_one_stable_authoritative_metadata_projection() {
|
||||
let first =
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&snapshot(true));
|
||||
let second =
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&snapshot(true));
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.output_key.as_str()),
|
||||
std::result::Result::Ok("metaplex_metadata_state:metadata111")
|
||||
);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["metadataSource"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("metaplex_owned_account"))
|
||||
);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| return value.payload_json["creators"].clone()),
|
||||
std::result::Result::Ok(
|
||||
serde_json::json!([{"address":"creator111","verified":true,"share":100}])
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
first
|
||||
.as_ref()
|
||||
.map(|value| return value.payload_json["programmableConfig"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!({"ruleSet":"rules111"}))
|
||||
);
|
||||
assert_eq!(
|
||||
first
|
||||
.as_ref()
|
||||
.map(|value| return value.payload_json["instructionProjectionRole"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!(
|
||||
"mutation_observation_only_no_duplicate_state"
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
first.as_ref().map(|value| {
|
||||
return value.payload_json["provenance"]["processorName"].clone();
|
||||
}),
|
||||
std::result::Result::Ok(serde_json::json!(
|
||||
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
first
|
||||
.as_ref()
|
||||
.map(|value| return value.payload_json["accountState"]["creators"][0]["share"]
|
||||
.clone()),
|
||||
std::result::Result::Ok(serde_json::json!(100))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncommitted_or_non_metadata_state_is_rejected() {
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&snapshot(false))
|
||||
.is_err()
|
||||
);
|
||||
let mut wrong = snapshot(true);
|
||||
wrong.state = crate::DcMetadataMtmCanonicalMetadataAccountState::EditionMarker(
|
||||
crate::DcMetadataMtmEditionMarkerAccountSnapshot {
|
||||
account: "marker111".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint111".to_string(),
|
||||
kind: crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV1,
|
||||
marker_group: std::option::Option::Some(0),
|
||||
edition: 1,
|
||||
byte_index: 0,
|
||||
bit_mask: 64,
|
||||
edition_taken: true,
|
||||
ledger: std::vec![64],
|
||||
payload_json: serde_json::json!({}),
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_metaplex_metadata_snapshot(&wrong).is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
// file: kb-lib/src/materializer/metadata/solana_program_metadata.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Solana Program Metadata materialization surface.
|
||||
|
||||
mod constants;
|
||||
mod instruction;
|
||||
mod materializer;
|
||||
mod state;
|
||||
|
||||
/// Compatibility materializer retaining the historical generic metadata type name.
|
||||
pub use self::materializer::MtMetadataMaterializer;
|
||||
/// Stable Solana Program Metadata instruction materializer.
|
||||
pub use self::materializer::MtSolanaProgramMetadataMaterializer;
|
||||
/// Materializes one decoded Solana Program Metadata account snapshot.
|
||||
pub use self::state::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
|
||||
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
|
||||
@@ -15,10 +19,12 @@ pub use self::state::materializer_metadata_materialize_solana_program_metadata_b
|
||||
pub use self::state::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
|
||||
|
||||
/// Event families accepted by the Solana Program Metadata instruction materializer.
|
||||
pub(crate) use self::constants::MT_METADATA_SPM_ACCEPTED_FAMILIES;
|
||||
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for Solana Program Metadata projections.
|
||||
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME;
|
||||
/// Stable processor name for Solana Program Metadata projections.
|
||||
pub(crate) use self::constants::MT_METADATA_SPM_PROCESSOR_NAME;
|
||||
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME;
|
||||
/// Stable projection schema version for Solana Program Metadata outputs.
|
||||
pub(crate) use self::constants::MT_METADATA_SPM_PROJECTION_VERSION;
|
||||
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Solana Program Metadata materialization.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
// file: kb-lib/src/materializer/metadata/solana_program_metadata/constants.rs
|
||||
// version: 1
|
||||
// version: 4
|
||||
|
||||
//! Solana Program Metadata materializer constants.
|
||||
//! Local constants for the Solana Program Metadata materializer.
|
||||
|
||||
/// Event families accepted by the Solana Program Metadata instruction materializer.
|
||||
pub(crate) const MT_METADATA_SPM_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
|
||||
&[crate::MdEventFamily::Metadata];
|
||||
/// Stable processor name for Solana Program Metadata projections.
|
||||
pub(crate) const MT_METADATA_SPM_PROCESSOR_NAME: &str =
|
||||
/// Stable runtime component name for Solana Program Metadata materialization.
|
||||
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME: &str =
|
||||
"kb-lib.materializer.metadata.solana_program_metadata";
|
||||
/// Stable processor name persisted by Solana Program Metadata projections.
|
||||
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME: &str =
|
||||
"materializer.metadata.solana_program_metadata";
|
||||
/// Stable projection schema version for Solana Program Metadata outputs.
|
||||
pub(crate) const MT_METADATA_SPM_PROJECTION_VERSION: u16 = 1;
|
||||
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION: u32 = 1;
|
||||
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
|
||||
&[crate::MdEventFamily::Metadata];
|
||||
/// Canonical tracing target for Solana Program Metadata materialization.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA: &str =
|
||||
"kb-lib.materializer.metadata.solana_program_metadata";
|
||||
|
||||
@@ -1,18 +1,128 @@
|
||||
// file: kb-lib/src/materializer/metadata/solana_program_metadata/instruction.rs
|
||||
// version: 1
|
||||
// file: kb-lib/src/materializer/metadata/solana_program_metadata/materializer.rs
|
||||
// version: 4
|
||||
|
||||
//! Committed Solana Program Metadata instruction projections.
|
||||
|
||||
/// Stable Solana Program Metadata instruction materializer.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtSolanaProgramMetadataMaterializer;
|
||||
|
||||
/// Compatibility materializer retaining the historical generic metadata type name.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtMetadataMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtSolanaProgramMetadataMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return crate::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.event_family == crate::MdEventFamily::Metadata
|
||||
&& event.program_id.0 == kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
|
||||
&& event.surface_code.0 == crate::DC_METADATA_SPM_SURFACE_CODE
|
||||
&& crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES
|
||||
.iter()
|
||||
.any(|(_, code)| return *code == event.event_name.0.as_str());
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
if !crate::MtMaterializer::accepts_event(self, event) {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Metadata,
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtMaterializer for crate::MtMetadataMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return crate::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return crate::MtMaterializer::accepts_event(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
event,
|
||||
);
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return crate::MtMaterializer::materialize_event(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
event,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtMetadataMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiEventMaterializer::identity(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
);
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return crate::MtApiEventMaterializer::accepted_families(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
);
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return crate::MtApiEventMaterializer::accepts_observation(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
observation,
|
||||
);
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiEventMaterializer::transaction_policy(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
family,
|
||||
);
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
return crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtSolanaProgramMetadataMaterializer,
|
||||
observation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtSolanaProgramMetadataMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_METADATA_SPM_PROCESSOR_NAME.to_string(),
|
||||
name: crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return crate::MT_METADATA_SPM_ACCEPTED_FAMILIES;
|
||||
return crate::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
@@ -55,7 +165,7 @@ impl crate::MtApiEventMaterializer for crate::MtMetadataMaterializer {
|
||||
),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_METADATA_SPM_PROJECTION_VERSION,
|
||||
"projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION,
|
||||
"domain": "solana_program_metadata",
|
||||
"projectionKind": "committed_instruction_mutation",
|
||||
"projectionSemantics": "committed_instruction_fact_non_authoritative_state",
|
||||
@@ -73,7 +183,7 @@ impl crate::MtApiEventMaterializer for crate::MtMetadataMaterializer {
|
||||
"externalContentFetched": false,
|
||||
"externalAccountRead": false,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_METADATA_SPM_PROCESSOR_NAME,
|
||||
"processorName": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"decoderProof": observation.proof.clone()
|
||||
}
|
||||
@@ -141,18 +251,18 @@ fn instruction_projection(operation: &str, payload: &serde_json::Value) -> serde
|
||||
}
|
||||
|
||||
fn state_transition(operation: &str) -> &'static str {
|
||||
match operation {
|
||||
"write" => return "buffer_content_written",
|
||||
"initialize" => return "metadata_account_initialized",
|
||||
"set_authority" => return "account_authority_updated",
|
||||
"set_data" => return "metadata_data_configuration_updated",
|
||||
"set_immutable" => return "metadata_account_marked_immutable",
|
||||
"trim" => return "account_capacity_trimmed",
|
||||
"close" => return "account_closed",
|
||||
"allocate" => return "buffer_account_allocated",
|
||||
"extend" => return "account_capacity_extended",
|
||||
_ => return "unknown",
|
||||
}
|
||||
return match operation {
|
||||
"write" => "buffer_content_written",
|
||||
"initialize" => "metadata_account_initialized",
|
||||
"set_authority" => "account_authority_updated",
|
||||
"set_data" => "metadata_data_configuration_updated",
|
||||
"set_immutable" => "metadata_account_marked_immutable",
|
||||
"trim" => "account_capacity_trimmed",
|
||||
"close" => "account_closed",
|
||||
"allocate" => "buffer_account_allocated",
|
||||
"extend" => "account_capacity_extended",
|
||||
_ => "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
fn target_account(operation: &str, accounts: &serde_json::Value) -> serde_json::Value {
|
||||
@@ -194,10 +304,6 @@ fn account_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Val
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
const MATRIX_JSON: &str = include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_MATERIALIZATION_MATRIX.json"
|
||||
);
|
||||
|
||||
fn observation(operation: &str, committed: bool) -> crate::DcApiDecodedObservation {
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: format!("solana_program_metadata:{operation}:0"),
|
||||
@@ -249,7 +355,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_stable_instruction_has_one_materialized_mutation_fact() {
|
||||
let materializer = crate::MtMetadataMaterializer;
|
||||
let materializer = crate::MtSolanaProgramMetadataMaterializer;
|
||||
let identity = crate::MtApiEventMaterializer::identity(&materializer);
|
||||
assert_eq!(identity.name, crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME);
|
||||
for (_, operation) in crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES.iter().copied() {
|
||||
let value = observation(operation, true);
|
||||
assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &value));
|
||||
@@ -259,12 +367,16 @@ mod tests {
|
||||
assert_eq!(result.outputs[0].payload_json["operation"], *operation);
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["deprecated"], false);
|
||||
assert_eq!(result.outputs[0].payload_json["projection"]["replaced"], false);
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["provenance"]["processorName"],
|
||||
crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_instruction_is_refused_and_foreign_surface_is_ignored() {
|
||||
let materializer = crate::MtMetadataMaterializer;
|
||||
let materializer = crate::MtSolanaProgramMetadataMaterializer;
|
||||
let failed = observation("set_data", false);
|
||||
let failure = crate::MtApiEventMaterializer::materialize(&materializer, &failed);
|
||||
assert_eq!(failure.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
@@ -277,7 +389,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn materialization_matrix_matches_two_account_states_and_nine_instructions() {
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(MATRIX_JSON);
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_MATERIALIZATION_MATRIX.json"
|
||||
));
|
||||
assert!(parsed.is_ok());
|
||||
let matrix = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -310,7 +424,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn trim_keeps_prestable_alias_as_provenance_only() {
|
||||
let materializer = crate::MtMetadataMaterializer;
|
||||
let materializer = crate::MtSolanaProgramMetadataMaterializer;
|
||||
let value = observation("trim", true);
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &value);
|
||||
assert_eq!(
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/metadata/solana_program_metadata/state.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
//! Authoritative Solana Program Metadata account-state projections.
|
||||
|
||||
@@ -11,20 +11,18 @@ pub fn materializer_metadata_materialize_solana_program_metadata_account_snapsho
|
||||
slot: u64,
|
||||
snapshot: &crate::DcMetadataSpmAccountSnapshot,
|
||||
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
|
||||
match snapshot {
|
||||
return match snapshot {
|
||||
crate::DcMetadataSpmAccountSnapshot::Buffer(value) => {
|
||||
return crate::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot(
|
||||
slot,
|
||||
value,
|
||||
);
|
||||
crate::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot(
|
||||
slot, value,
|
||||
)
|
||||
},
|
||||
crate::DcMetadataSpmAccountSnapshot::Metadata(value) => {
|
||||
return crate::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot(
|
||||
slot,
|
||||
value,
|
||||
);
|
||||
crate::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot(
|
||||
slot, value,
|
||||
)
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
|
||||
@@ -65,7 +63,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_buffer_snapshot
|
||||
output_key: format!("solana_program_metadata_account_state:{}", snapshot.account),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_METADATA_SPM_PROJECTION_VERSION,
|
||||
"projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION,
|
||||
"domain": "solana_program_metadata",
|
||||
"projectionKind": "buffer_state",
|
||||
"projectionSemantics": "authoritative_bounded_owned_account_snapshot",
|
||||
@@ -85,7 +83,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_buffer_snapshot
|
||||
"externalAccountRead": false,
|
||||
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
|
||||
"provenance": {
|
||||
"processorName": crate::MT_METADATA_SPM_PROCESSOR_NAME,
|
||||
"processorName": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"source": "bounded_solana_program_metadata_account_decoder"
|
||||
}
|
||||
@@ -152,7 +150,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_metadata_snapsh
|
||||
output_key: format!("solana_program_metadata_account_state:{}", snapshot.account),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_METADATA_SPM_PROJECTION_VERSION,
|
||||
"projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION,
|
||||
"domain": "solana_program_metadata",
|
||||
"projectionKind": "metadata_state",
|
||||
"projectionSemantics": "authoritative_bounded_owned_account_snapshot",
|
||||
@@ -179,7 +177,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_metadata_snapsh
|
||||
"externalAccountRead": false,
|
||||
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
|
||||
"provenance": {
|
||||
"processorName": crate::MT_METADATA_SPM_PROCESSOR_NAME,
|
||||
"processorName": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"source": "bounded_solana_program_metadata_account_decoder"
|
||||
}
|
||||
@@ -188,32 +186,32 @@ pub fn materializer_metadata_materialize_solana_program_metadata_metadata_snapsh
|
||||
}
|
||||
|
||||
fn data_projection(data: &crate::DcMetadataSpmData) -> serde_json::Value {
|
||||
match data {
|
||||
return match data {
|
||||
crate::DcMetadataSpmData::Direct(value) => {
|
||||
let mut projection = bytes_projection(value.as_slice());
|
||||
projection["kind"] = serde_json::json!("direct");
|
||||
return projection;
|
||||
projection
|
||||
},
|
||||
crate::DcMetadataSpmData::Url(value) => {
|
||||
return serde_json::json!({
|
||||
serde_json::json!({
|
||||
"kind": "url",
|
||||
"value": value,
|
||||
"lengthBytes": value.len(),
|
||||
"sha256": hash(value.as_bytes()),
|
||||
"fetched": false
|
||||
});
|
||||
})
|
||||
},
|
||||
crate::DcMetadataSpmData::External(value) => {
|
||||
return serde_json::json!({
|
||||
serde_json::json!({
|
||||
"kind": "external",
|
||||
"address": value.address.to_string(),
|
||||
"offset": value.offset,
|
||||
"length": value.length.map(std::num::NonZeroU32::get),
|
||||
"wireLengthBytes": crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
|
||||
"accountRead": false
|
||||
});
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn bytes_projection(bytes: &[u8]) -> serde_json::Value {
|
||||
|
||||
19
kb-lib/src/materializer/metadata/token_2022.rs
Normal file
19
kb-lib/src/materializer/metadata/token_2022.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
// file: kb-lib/src/materializer/metadata/token_2022.rs
|
||||
// version: 2
|
||||
|
||||
//! Embedded SPL Token-2022 metadata materialization surface.
|
||||
|
||||
mod constants;
|
||||
mod state;
|
||||
|
||||
/// Materializes Token-2022 embedded metadata and group snapshots.
|
||||
pub use self::state::materializer_metadata_materialize_token_2022_snapshot;
|
||||
|
||||
/// Stable runtime component name for Token-2022 embedded metadata projections.
|
||||
pub(crate) use self::constants::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
|
||||
/// Stable processor name for Token-2022 embedded metadata projections.
|
||||
pub(crate) use self::constants::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
|
||||
/// Stable Token-2022 embedded metadata projection version.
|
||||
pub(crate) use self::constants::MT_METADATA_TOKEN_2022_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Token-2022 embedded metadata materialization.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022;
|
||||
15
kb-lib/src/materializer/metadata/token_2022/constants.rs
Normal file
15
kb-lib/src/materializer/metadata/token_2022/constants.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
// file: kb-lib/src/materializer/metadata/token_2022/constants.rs
|
||||
// version: 3
|
||||
|
||||
//! Local constants for the metadata token_2022 materializer.
|
||||
|
||||
/// Stable runtime component name for the metadata token_2022 materializer.
|
||||
pub(crate) const MT_METADATA_TOKEN_2022_COMPONENT_NAME: &str =
|
||||
"kb-lib.materializer.metadata.token_2022";
|
||||
/// Stable processor name persisted by metadata projections.
|
||||
pub(crate) const MT_METADATA_TOKEN_2022_PROCESSOR_NAME: &str = "materializer.metadata.token_2022";
|
||||
/// Stable projection contract version.
|
||||
pub(crate) const MT_METADATA_TOKEN_2022_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for the metadata token_2022 materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022: &str =
|
||||
"kb-lib.materializer.metadata.token_2022";
|
||||
210
kb-lib/src/materializer/metadata/token_2022/state.rs
Normal file
210
kb-lib/src/materializer/metadata/token_2022/state.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
// file: kb-lib/src/materializer/metadata/token_2022/state.rs
|
||||
// version: 4
|
||||
|
||||
//! Authoritative embedded SPL Token-2022 metadata state materialization.
|
||||
|
||||
/// Materialize embedded Token-2022 metadata, group, and group-member extensions.
|
||||
pub fn materializer_metadata_materialize_token_2022_snapshot(
|
||||
account_key: &str,
|
||||
slot: u64,
|
||||
state: &crate::DcToken2022State,
|
||||
) -> std::result::Result<std::vec::Vec<crate::MtApiMaterializedOutput>, String> {
|
||||
if account_key.is_empty() {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022,
|
||||
action = "materialize_token_2022_metadata_snapshot",
|
||||
component_name = crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME,
|
||||
processor_name = crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account_key = "",
|
||||
slot,
|
||||
status = "refused",
|
||||
error_code = "empty_account_key",
|
||||
"refuse Token-2022 metadata snapshot without account identity"
|
||||
);
|
||||
return std::result::Result::Err(
|
||||
"Token-2022 metadata snapshot requires a non-empty account key".to_string(),
|
||||
);
|
||||
}
|
||||
if state.kind != crate::DcToken2022StateKind::Mint {
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022,
|
||||
action = "materialize_token_2022_metadata_snapshot",
|
||||
component_name = crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME,
|
||||
processor_name = crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account_key = %account_key,
|
||||
slot,
|
||||
state_kind = ?state.kind,
|
||||
status = "refused",
|
||||
error_code = "non_mint_state",
|
||||
"refuse Token-2022 metadata snapshot for non-mint state"
|
||||
);
|
||||
return std::result::Result::Err(
|
||||
"Token-2022 metadata snapshot requires a parsed mint state".to_string(),
|
||||
);
|
||||
}
|
||||
let mut outputs = std::vec::Vec::new();
|
||||
for entry in &state.extensions {
|
||||
let projection_kind = match entry.extension_name {
|
||||
"token_metadata" => std::option::Option::Some("token_metadata"),
|
||||
"token_group" => std::option::Option::Some("token_group"),
|
||||
"token_group_member" => std::option::Option::Some("token_group_member"),
|
||||
_ => std::option::Option::None,
|
||||
};
|
||||
let kind = match projection_kind {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => continue,
|
||||
};
|
||||
outputs.push(crate::MtApiMaterializedOutput {
|
||||
output_key: format!("token_2022_metadata_snapshot:{account_key}:{slot}:{kind}"),
|
||||
family: crate::MdMaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_METADATA_TOKEN_2022_PROJECTION_VERSION,
|
||||
"domain": "spl_token_2022_embedded_metadata",
|
||||
"projectionSemantics": "authoritative_bounded_mint_extension_snapshot",
|
||||
"idempotenceKey": format!("token_2022_metadata:{account_key}:{slot}:{kind}"),
|
||||
"accountKey": account_key,
|
||||
"slot": slot,
|
||||
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"metadataSource": "token_2022_embedded_tlv",
|
||||
"projectionKind": kind,
|
||||
"extensionType": entry.extension_type,
|
||||
"extensionName": entry.extension_name,
|
||||
"valueHex": entry.value_hex,
|
||||
"valueFields": entry.value_fields,
|
||||
"externalUriFetched": false,
|
||||
"identityAuthority": "mint_address",
|
||||
"finalAccountStateCaptured": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"source": "bounded_token_2022_state_parser"
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
let status = if outputs.is_empty() { "ignored" } else { "inserted" };
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022,
|
||||
action = "materialize_token_2022_metadata_snapshot",
|
||||
component_name = crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME,
|
||||
processor_name = crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
account_key = %account_key,
|
||||
slot,
|
||||
state_kind = "mint",
|
||||
output_count = outputs.len(),
|
||||
status,
|
||||
"materialize embedded Token-2022 metadata state snapshots"
|
||||
);
|
||||
return std::result::Result::Ok(outputs);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_token_2022_state {
|
||||
#[test]
|
||||
fn embedded_metadata_group_and_member_have_distinct_stable_outputs() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Mint,
|
||||
base_fields: serde_json::json!({"supply":"1","decimals":0}),
|
||||
base_hex: "00".repeat(82),
|
||||
account_type: std::option::Option::Some(1),
|
||||
extensions: std::vec![
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 19,
|
||||
extension_name: "token_metadata",
|
||||
value_hex: "01".to_string(),
|
||||
value_fields: serde_json::json!({"name":"Token","symbol":"TOK","uri":"https://example.invalid/token.json"}),
|
||||
},
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 20,
|
||||
extension_name: "token_group",
|
||||
value_hex: "02".to_string(),
|
||||
value_fields: serde_json::json!({"mint":"mint111","size":"1","maxSize":"10"}),
|
||||
},
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 21,
|
||||
extension_name: "token_group_member",
|
||||
value_hex: "03".to_string(),
|
||||
value_fields: serde_json::json!({"mint":"mint111","group":"group111","memberNumber":"1"}),
|
||||
},
|
||||
],
|
||||
};
|
||||
let outputs =
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("mint111", 9, &state);
|
||||
assert_eq!(outputs.as_ref().map(|values| return values.len()), std::result::Result::Ok(3));
|
||||
assert_eq!(
|
||||
outputs
|
||||
.as_ref()
|
||||
.map(|values| return values[0].payload_json["metadataSource"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("token_2022_embedded_tlv"))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs
|
||||
.as_ref()
|
||||
.map(|values| return values[0].payload_json["externalUriFetched"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!(false))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs
|
||||
.as_ref()
|
||||
.map(|values| return values[1].payload_json["projectionKind"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!("token_group"))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs.as_ref().map(|values| {
|
||||
return values[0].payload_json["provenance"]["processorName"].clone();
|
||||
}),
|
||||
std::result::Result::Ok(serde_json::json!(
|
||||
crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
outputs
|
||||
.as_ref()
|
||||
.map(|values| return values[0].payload_json["projectionVersion"].clone()),
|
||||
std::result::Result::Ok(serde_json::json!(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_is_idempotent_filters_unrelated_extensions_and_requires_mint_identity() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Mint,
|
||||
base_fields: serde_json::json!({}),
|
||||
base_hex: "00".repeat(82),
|
||||
account_type: std::option::Option::Some(1),
|
||||
extensions: std::vec![crate::DcToken2022TlvEntry {
|
||||
extension_type: 1,
|
||||
extension_name: "transfer_fee_config",
|
||||
value_hex: "00".to_string(),
|
||||
value_fields: serde_json::json!({}),
|
||||
}],
|
||||
};
|
||||
let first =
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("mint111", 9, &state);
|
||||
let second =
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("mint111", 9, &state);
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(first.as_ref().map(|values| return values.len()), std::result::Result::Ok(0));
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot("", 9, &state).is_err()
|
||||
);
|
||||
let account_state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Account,
|
||||
base_fields: serde_json::json!({}),
|
||||
base_hex: "00".repeat(165),
|
||||
account_type: std::option::Option::None,
|
||||
extensions: std::vec::Vec::new(),
|
||||
};
|
||||
assert!(
|
||||
crate::materializer_metadata_materialize_token_2022_snapshot(
|
||||
"account111",
|
||||
9,
|
||||
&account_state
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/nft.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `nft` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved NFT materializer.
|
||||
pub use self::core::MtNftMaterializer;
|
||||
pub use self::materializer::MtNftMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_NFT_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `nft` materializer.
|
||||
pub(crate) use self::constants::MT_NFT_COMPONENT_NAME;
|
||||
/// Stable processor name for the `nft` materializer.
|
||||
pub(crate) use self::constants::MT_NFT_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/nft/constants.rs
Normal file
11
kb-lib/src/materializer/nft/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/nft/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the nft materializer.
|
||||
|
||||
/// Stable runtime component name for the nft materializer.
|
||||
pub(crate) const MT_NFT_COMPONENT_NAME: &str = "kb-lib.materializer.nft";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_NFT_PROCESSOR_NAME: &str = "materializer.nft";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_NFT_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/nft/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/nft/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved NFT materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtNftMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtNftMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.nft";
|
||||
return crate::MT_NFT_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtNftMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Nft,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtNftMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.nft".to_string(),
|
||||
name: crate::MT_NFT_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_NFT_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/oracle.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `oracle` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved oracle materializer.
|
||||
pub use self::core::MtOracleMaterializer;
|
||||
pub use self::materializer::MtOracleMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_ORACLE_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `oracle` materializer.
|
||||
pub(crate) use self::constants::MT_ORACLE_COMPONENT_NAME;
|
||||
/// Stable processor name for the `oracle` materializer.
|
||||
pub(crate) use self::constants::MT_ORACLE_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/oracle/constants.rs
Normal file
11
kb-lib/src/materializer/oracle/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/oracle/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the oracle materializer.
|
||||
|
||||
/// Stable runtime component name for the oracle materializer.
|
||||
pub(crate) const MT_ORACLE_COMPONENT_NAME: &str = "kb-lib.materializer.oracle";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_ORACLE_PROCESSOR_NAME: &str = "materializer.oracle";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_ORACLE_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/oracle/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/oracle/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved oracle materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtOracleMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtOracleMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.oracle";
|
||||
return crate::MT_ORACLE_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtOracleMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Oracle,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtOracleMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.oracle".to_string(),
|
||||
name: crate::MT_ORACLE_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_ORACLE_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/orderbook.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `orderbook` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved orderbook materializer.
|
||||
pub use self::core::MtOrderbookMaterializer;
|
||||
pub use self::materializer::MtOrderbookMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_ORDERBOOK_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `orderbook` materializer.
|
||||
pub(crate) use self::constants::MT_ORDERBOOK_COMPONENT_NAME;
|
||||
/// Stable processor name for the `orderbook` materializer.
|
||||
pub(crate) use self::constants::MT_ORDERBOOK_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/orderbook/constants.rs
Normal file
11
kb-lib/src/materializer/orderbook/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/orderbook/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the orderbook materializer.
|
||||
|
||||
/// Stable runtime component name for the orderbook materializer.
|
||||
pub(crate) const MT_ORDERBOOK_COMPONENT_NAME: &str = "kb-lib.materializer.orderbook";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_ORDERBOOK_PROCESSOR_NAME: &str = "materializer.orderbook";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_ORDERBOOK_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/orderbook/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/orderbook/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved orderbook materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtOrderbookMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtOrderbookMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.orderbook";
|
||||
return crate::MT_ORDERBOOK_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtOrderbookMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Orderbook,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtOrderbookMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.orderbook".to_string(),
|
||||
name: crate::MT_ORDERBOOK_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_ORDERBOOK_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/perpetuals.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `perpetuals` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved perpetuals materializer.
|
||||
pub use self::core::MtPerpetualsMaterializer;
|
||||
pub use self::materializer::MtPerpetualsMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_PERPETUALS_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `perpetuals` materializer.
|
||||
pub(crate) use self::constants::MT_PERPETUALS_COMPONENT_NAME;
|
||||
/// Stable processor name for the `perpetuals` materializer.
|
||||
pub(crate) use self::constants::MT_PERPETUALS_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/perpetuals/constants.rs
Normal file
11
kb-lib/src/materializer/perpetuals/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/perpetuals/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the perpetuals materializer.
|
||||
|
||||
/// Stable runtime component name for the perpetuals materializer.
|
||||
pub(crate) const MT_PERPETUALS_COMPONENT_NAME: &str = "kb-lib.materializer.perpetuals";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_PERPETUALS_PROCESSOR_NAME: &str = "materializer.perpetuals";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_PERPETUALS_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/perpetuals/core.rs
|
||||
// version: 3
|
||||
// file: kb-lib/src/materializer/perpetuals/materializer.rs
|
||||
// version: 4
|
||||
|
||||
//! Reserved perpetuals materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtPerpetualsMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtPerpetualsMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.perpetuals";
|
||||
return crate::MT_PERPETUALS_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtPerpetualsMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Perpetuals,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtPerpetualsMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.perpetuals".to_string(),
|
||||
name: crate::MT_PERPETUALS_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_PERPETUALS_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/pool.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `pool` materializer family.
|
||||
|
||||
@@ -7,3 +7,10 @@ mod state;
|
||||
|
||||
/// Reserved pool-state materializer.
|
||||
pub use self::state::MtPoolStateMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::state::MT_POOL_STATE_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the reserved pool-state materializer.
|
||||
pub(crate) use self::state::MT_POOL_STATE_COMPONENT_NAME;
|
||||
/// Stable processor name for the reserved pool-state materializer.
|
||||
pub(crate) use self::state::MT_POOL_STATE_PROCESSOR_NAME;
|
||||
|
||||
@@ -1,61 +1,17 @@
|
||||
// file: kb-lib/src/materializer/pool/state.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Reserved pool-state materializer.
|
||||
//! Pool-state materializer component facade.
|
||||
|
||||
/// Reserved materializer for pool-state observations.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtPoolStateMaterializer;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtPoolStateMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.pool.state";
|
||||
}
|
||||
/// Reserved pool-state materializer.
|
||||
pub use self::materializer::MtPoolStateMaterializer;
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, _event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::PoolState,
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtPoolStateMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.pool.state".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
_observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
}
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_POOL_STATE_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the reserved pool-state materializer.
|
||||
pub(crate) use self::constants::MT_POOL_STATE_COMPONENT_NAME;
|
||||
/// Stable processor name for the reserved pool-state materializer.
|
||||
pub(crate) use self::constants::MT_POOL_STATE_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/pool/state/constants.rs
Normal file
11
kb-lib/src/materializer/pool/state/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/pool/state/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the pool state materializer.
|
||||
|
||||
/// Stable runtime component name for the pool state materializer.
|
||||
pub(crate) const MT_POOL_STATE_COMPONENT_NAME: &str = "kb-lib.materializer.pool.state";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_POOL_STATE_PROCESSOR_NAME: &str = "materializer.pool.state";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_POOL_STATE_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
56
kb-lib/src/materializer/pool/state/materializer.rs
Normal file
56
kb-lib/src/materializer/pool/state/materializer.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// file: kb-lib/src/materializer/pool/state/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved pool-state materializer.
|
||||
|
||||
/// Reserved materializer for pool-state observations.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtPoolStateMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtPoolStateMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return crate::MT_POOL_STATE_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, _event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtPoolStateMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_POOL_STATE_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return crate::MT_POOL_STATE_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
_observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/rewards.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `rewards` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved rewards materializer.
|
||||
pub use self::core::MtRewardsMaterializer;
|
||||
pub use self::materializer::MtRewardsMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_REWARDS_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `rewards` materializer.
|
||||
pub(crate) use self::constants::MT_REWARDS_COMPONENT_NAME;
|
||||
/// Stable processor name for the `rewards` materializer.
|
||||
pub(crate) use self::constants::MT_REWARDS_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/rewards/constants.rs
Normal file
11
kb-lib/src/materializer/rewards/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/rewards/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the rewards materializer.
|
||||
|
||||
/// Stable runtime component name for the rewards materializer.
|
||||
pub(crate) const MT_REWARDS_COMPONENT_NAME: &str = "kb-lib.materializer.rewards";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_REWARDS_PROCESSOR_NAME: &str = "materializer.rewards";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_REWARDS_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,15 +1,15 @@
|
||||
// file: kb-lib/src/materializer/rewards/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/rewards/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved rewards materializer.
|
||||
|
||||
/// Reserved materializer for reward observations.
|
||||
/// Reserved materializer for rewards observations.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtRewardsMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtRewardsMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.rewards";
|
||||
return crate::MT_REWARDS_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtRewardsMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Reward,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtRewardsMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.rewards".to_string(),
|
||||
name: crate::MT_REWARDS_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_REWARDS_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,17 +1,23 @@
|
||||
// file: kb-lib/src/materializer/risk.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `risk` materializer family.
|
||||
|
||||
mod constants;
|
||||
mod core;
|
||||
mod materializer;
|
||||
|
||||
/// Stable SPL Token and ATA risk materializer.
|
||||
pub use self::core::MtRiskMaterializer;
|
||||
pub use self::materializer::MtRiskMaterializer;
|
||||
|
||||
/// Stable processor name for SPL Token and ATA risk facts.
|
||||
/// Crate-visible `MT_RISK_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_RISK_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `risk` materializer.
|
||||
pub(crate) use self::constants::MT_RISK_COMPONENT_NAME;
|
||||
/// Stable processor name for the `risk` materializer.
|
||||
pub(crate) use self::constants::MT_RISK_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for SPL Token and ATA risk facts.
|
||||
/// Stable projection version for the `risk` materializer.
|
||||
pub(crate) use self::constants::MT_RISK_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for SPL Token and ATA risk facts.
|
||||
/// Crate-visible `MT_RISK_RISK_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_RISK_RISK_ENTRIES;
|
||||
/// Canonical tracing target for the `risk` materializer.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_RISK;
|
||||
|
||||
@@ -1,11 +1,48 @@
|
||||
// file: kb-lib/src/materializer/risk/constants.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! Local constants for fact-only risk projections.
|
||||
//! Local constants for the risk materializer.
|
||||
|
||||
/// Stable runtime component name for the risk materializer.
|
||||
pub(crate) const MT_RISK_COMPONENT_NAME: &str = "kb-lib.materializer.risk";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_RISK_PROCESSOR_NAME: &str = "materializer.risk";
|
||||
/// Stable projection payload contract version.
|
||||
/// Stable projection contract version for the risk materializer.
|
||||
pub(crate) const MT_RISK_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for this component.
|
||||
/// Canonical tracing target for the risk materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_RISK: &str = "kb-lib.materializer.risk";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_RISK_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::TokenAccount,
|
||||
crate::MdEventFamily::TokenMint,
|
||||
crate::MdEventFamily::Admin,
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
crate::MdEventFamily::Audit,
|
||||
];
|
||||
/// Component contract for `risk_entries`.
|
||||
pub(crate) const MT_RISK_RISK_ENTRIES: &[&str] = &[
|
||||
"approve",
|
||||
"approve_checked",
|
||||
"revoke",
|
||||
"freeze_account",
|
||||
"thaw_account",
|
||||
"set_authority",
|
||||
"initialize_multisig",
|
||||
"initialize_multisig2",
|
||||
"initialize_default_account_state",
|
||||
"update_default_account_state",
|
||||
"enable_required_transfer_memos",
|
||||
"disable_required_transfer_memos",
|
||||
"enable_cpi_guard",
|
||||
"disable_cpi_guard",
|
||||
"initialize_non_transferable_mint",
|
||||
"initialize_pausable_config",
|
||||
"pause",
|
||||
"resume",
|
||||
"initialize_permissioned_burn",
|
||||
"enable_confidential_credits",
|
||||
"disable_confidential_credits",
|
||||
"enable_non_confidential_credits",
|
||||
"disable_non_confidential_credits",
|
||||
];
|
||||
|
||||
@@ -1,48 +1,15 @@
|
||||
// file: kb-lib/src/materializer/risk/core.rs
|
||||
// version: 6
|
||||
// file: kb-lib/src/materializer/risk/materializer.rs
|
||||
// version: 8
|
||||
|
||||
//! Stable fact-only risk projections for committed SPL Token and ATA observations.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::TokenAccount,
|
||||
crate::MdEventFamily::TokenMint,
|
||||
crate::MdEventFamily::Admin,
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
crate::MdEventFamily::Audit,
|
||||
];
|
||||
const RISK_ENTRIES: &[&str] = &[
|
||||
"approve",
|
||||
"approve_checked",
|
||||
"revoke",
|
||||
"freeze_account",
|
||||
"thaw_account",
|
||||
"set_authority",
|
||||
"initialize_multisig",
|
||||
"initialize_multisig2",
|
||||
"initialize_default_account_state",
|
||||
"update_default_account_state",
|
||||
"enable_required_transfer_memos",
|
||||
"disable_required_transfer_memos",
|
||||
"enable_cpi_guard",
|
||||
"disable_cpi_guard",
|
||||
"initialize_non_transferable_mint",
|
||||
"initialize_pausable_config",
|
||||
"pause",
|
||||
"resume",
|
||||
"initialize_permissioned_burn",
|
||||
"enable_confidential_credits",
|
||||
"disable_confidential_credits",
|
||||
"enable_non_confidential_credits",
|
||||
"disable_non_confidential_credits",
|
||||
];
|
||||
|
||||
/// Stable fact-only classic SPL Token risk materializer.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtRiskMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtRiskMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.risk";
|
||||
return crate::MT_RISK_COMPONENT_NAME;
|
||||
}
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
@@ -66,7 +33,7 @@ impl crate::MtApiEventMaterializer for crate::MtRiskMaterializer {
|
||||
};
|
||||
}
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
return crate::MT_RISK_ACCEPTED_FAMILIES;
|
||||
}
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return accepts_event(&observation.event);
|
||||
@@ -171,8 +138,9 @@ fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
&& event.surface_code.0 == "spl.token")
|
||||
|| (event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.token_2022");
|
||||
let token_risk = token_program && RISK_ENTRIES.contains(&event.event_name.0.as_str());
|
||||
return ACCEPTED_FAMILIES.contains(&event.event_family)
|
||||
let token_risk =
|
||||
token_program && crate::MT_RISK_RISK_ENTRIES.contains(&event.event_name.0.as_str());
|
||||
return crate::MT_RISK_ACCEPTED_FAMILIES.contains(&event.event_family)
|
||||
&& (token_risk || is_ata_recovery(event));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/routing.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `routing` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved routing materializer.
|
||||
pub use self::core::MtRoutingMaterializer;
|
||||
pub use self::materializer::MtRoutingMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_ROUTING_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `routing` materializer.
|
||||
pub(crate) use self::constants::MT_ROUTING_COMPONENT_NAME;
|
||||
/// Stable processor name for the `routing` materializer.
|
||||
pub(crate) use self::constants::MT_ROUTING_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/routing/constants.rs
Normal file
11
kb-lib/src/materializer/routing/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/routing/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the routing materializer.
|
||||
|
||||
/// Stable runtime component name for the routing materializer.
|
||||
pub(crate) const MT_ROUTING_COMPONENT_NAME: &str = "kb-lib.materializer.routing";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_ROUTING_PROCESSOR_NAME: &str = "materializer.routing";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_ROUTING_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/materializer/routing/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/routing/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved routing materializer.
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct MtRoutingMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtRoutingMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.routing";
|
||||
return crate::MT_ROUTING_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtRoutingMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Routing,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtRoutingMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.routing".to_string(),
|
||||
name: crate::MT_ROUTING_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_ROUTING_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,13 +1,45 @@
|
||||
// file: kb-lib/src/materializer/staking.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! `staking` materializer family.
|
||||
|
||||
mod constants;
|
||||
mod core;
|
||||
mod materializer;
|
||||
|
||||
/// Stable native stake and vote materializer.
|
||||
pub use self::core::MtStakingMaterializer;
|
||||
pub use self::materializer::MtStakingMaterializer;
|
||||
|
||||
/// Canonical tracing target for the native stake and vote materializer.
|
||||
/// Crate-visible `MT_STAKING_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `staking` materializer.
|
||||
pub(crate) use self::constants::MT_STAKING_COMPONENT_NAME;
|
||||
/// Stable processor name for the `staking` materializer.
|
||||
pub(crate) use self::constants::MT_STAKING_PROCESSOR_NAME;
|
||||
/// Stable projection version for the `staking` materializer.
|
||||
pub(crate) use self::constants::MT_STAKING_PROJECTION_VERSION;
|
||||
/// Crate-visible `MT_STAKING_STAKE_ACCOUNT_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_STAKE_ACCOUNT_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_STAKE_AUTHORITY_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_STAKE_AUTHORITY_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_STAKE_LOCKUP_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_STAKE_LOCKUP_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_STAKE_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_STAKE_SURFACE;
|
||||
/// Crate-visible `MT_STAKING_STAKE_VALUE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_STAKE_VALUE_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_ACCOUNT_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_VOTE_ACCOUNT_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_ADMIN_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_VOTE_ADMIN_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_AUTHORITY_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_VOTE_AUTHORITY_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_REWARD_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_VOTE_REWARD_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_STATE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_VOTE_STATE_ENTRIES;
|
||||
/// Crate-visible `MT_STAKING_VOTE_SURFACE` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_VOTE_SURFACE;
|
||||
/// Crate-visible `MT_STAKING_VOTE_VALUE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_STAKING_VOTE_VALUE_ENTRIES;
|
||||
/// Canonical tracing target for the `staking` materializer.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_STAKING;
|
||||
|
||||
@@ -1,7 +1,77 @@
|
||||
// file: kb-lib/src/materializer/staking/constants.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
//! Local constants for the native stake and vote materializer.
|
||||
//! Local constants for the staking materializer.
|
||||
|
||||
/// Canonical tracing target for this component.
|
||||
/// Stable runtime component name for the staking materializer.
|
||||
pub(crate) const MT_STAKING_COMPONENT_NAME: &str = "kb-lib.materializer.staking";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_STAKING_PROCESSOR_NAME: &str = "materializer.staking";
|
||||
/// Stable projection contract version for the staking materializer.
|
||||
pub(crate) const MT_STAKING_PROJECTION_VERSION: u32 = 2;
|
||||
/// Canonical tracing target for the staking materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_STAKING: &str = "kb-lib.materializer.staking";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_STAKING_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
crate::MdEventFamily::Admin,
|
||||
crate::MdEventFamily::Staking,
|
||||
crate::MdEventFamily::Reward,
|
||||
];
|
||||
/// Component contract for `stake_surface`.
|
||||
pub(crate) const MT_STAKING_STAKE_SURFACE: &str = "solana.core.stake";
|
||||
/// Component contract for `vote_surface`.
|
||||
pub(crate) const MT_STAKING_VOTE_SURFACE: &str = "solana.core.vote";
|
||||
/// Component contract for `stake_account_entries`.
|
||||
pub(crate) const MT_STAKING_STAKE_ACCOUNT_ENTRIES: &[&str] = &[
|
||||
"initialize",
|
||||
"initialize_checked",
|
||||
"delegate_stake",
|
||||
"deactivate",
|
||||
"deactivate_delinquent",
|
||||
];
|
||||
/// Component contract for `stake_value_entries`.
|
||||
pub(crate) const MT_STAKING_STAKE_VALUE_ENTRIES: &[&str] =
|
||||
&["split", "withdraw", "merge", "move_stake", "move_lamports"];
|
||||
/// Component contract for `stake_authority_entries`.
|
||||
pub(crate) const MT_STAKING_STAKE_AUTHORITY_ENTRIES: &[&str] = &[
|
||||
"authorize",
|
||||
"authorize_with_seed",
|
||||
"authorize_checked",
|
||||
"authorize_checked_with_seed",
|
||||
];
|
||||
/// Component contract for `stake_lockup_entries`.
|
||||
pub(crate) const MT_STAKING_STAKE_LOCKUP_ENTRIES: &[&str] = &["set_lockup", "set_lockup_checked"];
|
||||
/// Component contract for `vote_account_entries`.
|
||||
pub(crate) const MT_STAKING_VOTE_ACCOUNT_ENTRIES: &[&str] =
|
||||
&["initialize_account", "initialize_account_v2"];
|
||||
/// Component contract for `vote_authority_entries`.
|
||||
pub(crate) const MT_STAKING_VOTE_AUTHORITY_ENTRIES: &[&str] = &[
|
||||
"authorize",
|
||||
"authorize_checked",
|
||||
"authorize_with_seed",
|
||||
"authorize_checked_with_seed",
|
||||
];
|
||||
/// Component contract for `vote_admin_entries`.
|
||||
pub(crate) const MT_STAKING_VOTE_ADMIN_ENTRIES: &[&str] = &[
|
||||
"update_validator_identity",
|
||||
"update_commission",
|
||||
"update_commission_collector",
|
||||
"update_commission_bps",
|
||||
];
|
||||
/// Component contract for `vote_state_entries`.
|
||||
pub(crate) const MT_STAKING_VOTE_STATE_ENTRIES: &[&str] = &[
|
||||
"vote",
|
||||
"vote_switch",
|
||||
"update_vote_state",
|
||||
"update_vote_state_switch",
|
||||
"compact_update_vote_state",
|
||||
"compact_update_vote_state_switch",
|
||||
"tower_sync",
|
||||
"tower_sync_switch",
|
||||
];
|
||||
/// Component contract for `vote_value_entries`.
|
||||
pub(crate) const MT_STAKING_VOTE_VALUE_ENTRIES: &[&str] = &["withdraw"];
|
||||
/// Component contract for `vote_reward_entries`.
|
||||
pub(crate) const MT_STAKING_VOTE_REWARD_ENTRIES: &[&str] = &["deposit_delegator_rewards"];
|
||||
|
||||
@@ -1,57 +1,8 @@
|
||||
// file: kb-lib/src/materializer/staking/core.rs
|
||||
// version: 10
|
||||
// file: kb-lib/src/materializer/staking/materializer.rs
|
||||
// version: 12
|
||||
|
||||
//! Instruction-level staking projections for native Stake and Vote observations.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
crate::MdEventFamily::Admin,
|
||||
crate::MdEventFamily::Staking,
|
||||
crate::MdEventFamily::Reward,
|
||||
];
|
||||
const STAKE_SURFACE: &str = "solana.core.stake";
|
||||
const VOTE_SURFACE: &str = "solana.core.vote";
|
||||
const STAKE_ACCOUNT_ENTRIES: &[&str] = &[
|
||||
"initialize",
|
||||
"initialize_checked",
|
||||
"delegate_stake",
|
||||
"deactivate",
|
||||
"deactivate_delinquent",
|
||||
];
|
||||
const STAKE_VALUE_ENTRIES: &[&str] = &["split", "withdraw", "merge", "move_stake", "move_lamports"];
|
||||
const STAKE_AUTHORITY_ENTRIES: &[&str] = &[
|
||||
"authorize",
|
||||
"authorize_with_seed",
|
||||
"authorize_checked",
|
||||
"authorize_checked_with_seed",
|
||||
];
|
||||
const STAKE_LOCKUP_ENTRIES: &[&str] = &["set_lockup", "set_lockup_checked"];
|
||||
const VOTE_ACCOUNT_ENTRIES: &[&str] = &["initialize_account", "initialize_account_v2"];
|
||||
const VOTE_AUTHORITY_ENTRIES: &[&str] = &[
|
||||
"authorize",
|
||||
"authorize_checked",
|
||||
"authorize_with_seed",
|
||||
"authorize_checked_with_seed",
|
||||
];
|
||||
const VOTE_ADMIN_ENTRIES: &[&str] = &[
|
||||
"update_validator_identity",
|
||||
"update_commission",
|
||||
"update_commission_collector",
|
||||
"update_commission_bps",
|
||||
];
|
||||
const VOTE_STATE_ENTRIES: &[&str] = &[
|
||||
"vote",
|
||||
"vote_switch",
|
||||
"update_vote_state",
|
||||
"update_vote_state_switch",
|
||||
"compact_update_vote_state",
|
||||
"compact_update_vote_state_switch",
|
||||
"tower_sync",
|
||||
"tower_sync_switch",
|
||||
];
|
||||
const VOTE_VALUE_ENTRIES: &[&str] = &["withdraw"];
|
||||
const VOTE_REWARD_ENTRIES: &[&str] = &["deposit_delegator_rewards"];
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ProjectionKind {
|
||||
StakeAccount,
|
||||
@@ -100,7 +51,7 @@ pub struct MtStakingMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtStakingMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.staking";
|
||||
return crate::MT_STAKING_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -126,13 +77,13 @@ impl crate::MtMaterializer for crate::MtStakingMaterializer {
|
||||
impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "materializer.staking".to_string(),
|
||||
name: crate::MT_STAKING_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
return crate::MT_STAKING_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
@@ -205,7 +156,7 @@ impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer {
|
||||
output_key: projection.output_key(operation.as_str()),
|
||||
family: projection.family(),
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"projectionVersion": crate::MT_STAKING_PROJECTION_VERSION,
|
||||
"projectionSemantics": "committed_instruction_staking_event",
|
||||
"domain": projection.domain(),
|
||||
"operation": operation,
|
||||
@@ -214,6 +165,10 @@ impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer {
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"transactionSucceeded": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_STAKING_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
},
|
||||
"accounts": accounts,
|
||||
"parameters": parameters,
|
||||
"projection": projection_details(
|
||||
@@ -242,52 +197,54 @@ impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer {
|
||||
}
|
||||
|
||||
fn family_is_accepted(family: crate::MdEventFamily) -> bool {
|
||||
return ACCEPTED_FAMILIES.iter().any(|accepted| return *accepted == family);
|
||||
return crate::MT_STAKING_ACCEPTED_FAMILIES
|
||||
.iter()
|
||||
.any(|accepted| return *accepted == family);
|
||||
}
|
||||
|
||||
fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<ProjectionKind> {
|
||||
if surface_code == STAKE_SURFACE {
|
||||
if surface_code == crate::MT_STAKING_STAKE_SURFACE {
|
||||
return stake_projection(entry_code);
|
||||
}
|
||||
if surface_code == VOTE_SURFACE {
|
||||
if surface_code == crate::MT_STAKING_VOTE_SURFACE {
|
||||
return vote_projection(entry_code);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn stake_projection(entry_code: &str) -> std::option::Option<ProjectionKind> {
|
||||
if contains(STAKE_ACCOUNT_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_STAKE_ACCOUNT_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::StakeAccount);
|
||||
}
|
||||
if contains(STAKE_VALUE_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_STAKE_VALUE_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::StakeValue);
|
||||
}
|
||||
if contains(STAKE_AUTHORITY_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_STAKE_AUTHORITY_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::StakeAuthority);
|
||||
}
|
||||
if contains(STAKE_LOCKUP_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_STAKE_LOCKUP_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::StakeLockup);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn vote_projection(entry_code: &str) -> std::option::Option<ProjectionKind> {
|
||||
if contains(VOTE_ACCOUNT_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_VOTE_ACCOUNT_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::VoteAccount);
|
||||
}
|
||||
if contains(VOTE_AUTHORITY_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_VOTE_AUTHORITY_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::VoteAuthority);
|
||||
}
|
||||
if contains(VOTE_ADMIN_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_VOTE_ADMIN_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::VoteAdmin);
|
||||
}
|
||||
if contains(VOTE_STATE_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_VOTE_STATE_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::VoteState);
|
||||
}
|
||||
if contains(VOTE_VALUE_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_VOTE_VALUE_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::VoteValue);
|
||||
}
|
||||
if contains(VOTE_REWARD_ENTRIES, entry_code) {
|
||||
if contains(crate::MT_STAKING_VOTE_REWARD_ENTRIES, entry_code) {
|
||||
return std::option::Option::Some(ProjectionKind::VoteReward);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
@@ -637,7 +594,7 @@ mod tests {
|
||||
fn stake_initialize_creates_instructional_staking_projection() {
|
||||
let materializer = crate::MtStakingMaterializer;
|
||||
let observation = observation(
|
||||
super::STAKE_SURFACE,
|
||||
crate::MT_STAKING_STAKE_SURFACE,
|
||||
"initialize",
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
false,
|
||||
@@ -659,7 +616,7 @@ mod tests {
|
||||
fn stake_authority_and_lockup_events_are_projected() {
|
||||
let materializer = crate::MtStakingMaterializer;
|
||||
let authority = observation(
|
||||
super::STAKE_SURFACE,
|
||||
crate::MT_STAKING_STAKE_SURFACE,
|
||||
"authorize_with_seed",
|
||||
crate::MdEventFamily::Admin,
|
||||
false,
|
||||
@@ -672,7 +629,7 @@ mod tests {
|
||||
}),
|
||||
);
|
||||
let lockup = observation(
|
||||
super::STAKE_SURFACE,
|
||||
crate::MT_STAKING_STAKE_SURFACE,
|
||||
"set_lockup_checked",
|
||||
crate::MdEventFamily::Admin,
|
||||
false,
|
||||
@@ -701,7 +658,7 @@ mod tests {
|
||||
fn stake_value_projection_does_not_duplicate_lamport_changes() {
|
||||
let materializer = crate::MtStakingMaterializer;
|
||||
let observation = observation(
|
||||
super::STAKE_SURFACE,
|
||||
crate::MT_STAKING_STAKE_SURFACE,
|
||||
"withdraw",
|
||||
crate::MdEventFamily::Staking,
|
||||
false,
|
||||
@@ -724,7 +681,7 @@ mod tests {
|
||||
fn vote_account_and_authority_events_are_projected() {
|
||||
let materializer = crate::MtStakingMaterializer;
|
||||
let initialize = observation(
|
||||
super::VOTE_SURFACE,
|
||||
crate::MT_STAKING_VOTE_SURFACE,
|
||||
"initialize_account_v2",
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
false,
|
||||
@@ -739,7 +696,7 @@ mod tests {
|
||||
}),
|
||||
);
|
||||
let authorize = observation(
|
||||
super::VOTE_SURFACE,
|
||||
crate::MT_STAKING_VOTE_SURFACE,
|
||||
"authorize_checked",
|
||||
crate::MdEventFamily::Admin,
|
||||
false,
|
||||
@@ -769,7 +726,7 @@ mod tests {
|
||||
fn vote_state_and_reward_outputs_are_distinguished() {
|
||||
let materializer = crate::MtStakingMaterializer;
|
||||
let vote_state = observation(
|
||||
super::VOTE_SURFACE,
|
||||
crate::MT_STAKING_VOTE_SURFACE,
|
||||
"tower_sync_switch",
|
||||
crate::MdEventFamily::Staking,
|
||||
false,
|
||||
@@ -780,7 +737,7 @@ mod tests {
|
||||
serde_json::json!({"root": 10, "hash": "hash111", "switchProofHash": "switch111"}),
|
||||
);
|
||||
let reward = observation(
|
||||
super::VOTE_SURFACE,
|
||||
crate::MT_STAKING_VOTE_SURFACE,
|
||||
"deposit_delegator_rewards",
|
||||
crate::MdEventFamily::Reward,
|
||||
false,
|
||||
@@ -805,7 +762,7 @@ mod tests {
|
||||
fn failed_staking_mutation_is_refused() {
|
||||
let materializer = crate::MtStakingMaterializer;
|
||||
let observation = observation(
|
||||
super::STAKE_SURFACE,
|
||||
crate::MT_STAKING_STAKE_SURFACE,
|
||||
"delegate_stake",
|
||||
crate::MdEventFamily::Staking,
|
||||
true,
|
||||
@@ -829,7 +786,7 @@ mod tests {
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let redelegate = observation(
|
||||
super::STAKE_SURFACE,
|
||||
crate::MT_STAKING_STAKE_SURFACE,
|
||||
"redelegate",
|
||||
crate::MdEventFamily::Staking,
|
||||
false,
|
||||
@@ -848,7 +805,7 @@ mod tests {
|
||||
fn output_serialization_is_deterministic() {
|
||||
let materializer = crate::MtStakingMaterializer;
|
||||
let observation = observation(
|
||||
super::VOTE_SURFACE,
|
||||
crate::MT_STAKING_VOTE_SURFACE,
|
||||
"update_commission_bps",
|
||||
crate::MdEventFamily::Admin,
|
||||
false,
|
||||
@@ -1,10 +1,9 @@
|
||||
// file: kb-lib/src/materializer/token.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! `token` materializer family.
|
||||
|
||||
mod accounts;
|
||||
mod constants;
|
||||
mod metadata_risk;
|
||||
|
||||
/// Stable Token, ATA and Token-2022 account materializer.
|
||||
@@ -14,9 +13,23 @@ pub use self::accounts::materializer_token_materialize_token_2022_state_snapshot
|
||||
/// Reserved token-metadata risk materializer.
|
||||
pub use self::metadata_risk::MtTokenMetadataRiskMaterializer;
|
||||
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::accounts::MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES` component constant.
|
||||
pub(crate) use self::accounts::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES;
|
||||
/// Stable runtime component name for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::accounts::MT_TOKEN_ACCOUNTS_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES` component constant.
|
||||
pub(crate) use self::accounts::MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES;
|
||||
/// Stable processor name for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME;
|
||||
pub(crate) use self::accounts::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION;
|
||||
pub(crate) use self::accounts::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS;
|
||||
pub(crate) use self::accounts::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS;
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::metadata_risk::MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the reserved token-metadata risk materializer.
|
||||
pub(crate) use self::metadata_risk::MT_TOKEN_METADATA_RISK_COMPONENT_NAME;
|
||||
/// Stable processor name for the reserved token-metadata risk materializer.
|
||||
pub(crate) use self::metadata_risk::MT_TOKEN_METADATA_RISK_PROCESSOR_NAME;
|
||||
|
||||
@@ -1,842 +1,27 @@
|
||||
// file: kb-lib/src/materializer/token/accounts.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
//! Stable committed SPL Token mutation and ATA lifecycle projections.
|
||||
//! Token, ATA and Token-2022 account materializer component.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::TokenAccount,
|
||||
crate::MdEventFamily::TokenMint,
|
||||
crate::MdEventFamily::TokenBurn,
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
];
|
||||
const MUTATION_ENTRIES: &[&str] = &[
|
||||
"initialize_mint",
|
||||
"initialize_mint2",
|
||||
"initialize_account",
|
||||
"initialize_account2",
|
||||
"initialize_account3",
|
||||
"transfer",
|
||||
"transfer_checked",
|
||||
"approve",
|
||||
"approve_checked",
|
||||
"revoke",
|
||||
"mint_to",
|
||||
"mint_to_checked",
|
||||
"burn",
|
||||
"burn_checked",
|
||||
"close_account",
|
||||
"freeze_account",
|
||||
"thaw_account",
|
||||
"sync_native",
|
||||
"initialize_immutable_owner",
|
||||
"withdraw_excess_lamports",
|
||||
"unwrap_lamports",
|
||||
"initialize_non_transferable_mint",
|
||||
"reallocate",
|
||||
];
|
||||
const ATA_LIFECYCLE_ENTRIES: &[(&str, &str)] = &[
|
||||
("create", "ata_created"),
|
||||
("create_idempotent", "ata_created_or_reused_idempotently"),
|
||||
("recover_nested", "nested_ata_recovered"),
|
||||
];
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Stable committed classic SPL Token account and mint mutation materializer.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtTokenAccountsMaterializer;
|
||||
/// Stable Token, ATA and Token-2022 account materializer.
|
||||
pub use self::materializer::MtTokenAccountsMaterializer;
|
||||
/// Materializes one bounded Token-2022 account state snapshot.
|
||||
pub use self::materializer::materializer_token_materialize_token_2022_state_snapshot;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtTokenAccountsMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.token.accounts";
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return accepts_event(event);
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtTokenAccountsMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return accepts_event(&observation.event);
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
if !accepts_event(&observation.event) {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if observation.transaction_failed || !observation.observation_committed {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"uncommitted_token_account_projection_refused",
|
||||
"failed or uncommitted SPL Token or ATA observations cannot create mutable outputs",
|
||||
);
|
||||
}
|
||||
if is_ata_event(&observation.event) {
|
||||
return materialize_ata(observation);
|
||||
}
|
||||
let parameters = observation
|
||||
.payload_json
|
||||
.get("parameters")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let accounts = observation
|
||||
.payload_json
|
||||
.get("accounts")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let authority = observation
|
||||
.payload_json
|
||||
.get("authority")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let operation = observation.event.event_name.0.clone();
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: format!("token_account_mutation:{operation}:0"),
|
||||
family: crate::MdMaterializedEventFamily::TokenAccount,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION,
|
||||
"domain": "spl_token_account_mutation",
|
||||
"projectionSemantics": "committed_instruction_mutation_not_final_account_snapshot",
|
||||
"idempotenceKey": format!(
|
||||
"spl_token:{}:{}:{}",
|
||||
observation.event.signature.0,
|
||||
observation.event.instruction_path.0,
|
||||
operation
|
||||
),
|
||||
"operation": operation,
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"mint": account_key_for_role(&accounts, "mint"),
|
||||
"amountRaw": parameters.get("amountRaw").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"decimals": parameters.get("decimals").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"parameters": parameters,
|
||||
"accounts": accounts,
|
||||
"authority": authority,
|
||||
"committed": true,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
"coreBalanceChangesDuplicated": false,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"sourceSurface": observation.event.surface_code.0.clone(),
|
||||
"sourceEventCode": observation.event.event_code.0.clone(),
|
||||
"sourceEventKey": observation.event_key.clone()
|
||||
}
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS,
|
||||
action = "materialize_token_account_mutation",
|
||||
signature = %observation.event.signature.0,
|
||||
instruction_path = %observation.event.instruction_path.0,
|
||||
operation = %observation.event.event_name.0,
|
||||
"materialize committed classic SPL Token mutation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
let token_program = (event.program_id.0 == kb_program_ids::SPL_TOKEN_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.token")
|
||||
|| (event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.token_2022");
|
||||
let token_mutation = token_program && MUTATION_ENTRIES.contains(&event.event_name.0.as_str());
|
||||
let ata = is_ata_event(event);
|
||||
return ACCEPTED_FAMILIES.contains(&event.event_family) && (token_mutation || ata);
|
||||
}
|
||||
|
||||
fn is_ata_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.program_id.0 == kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.associated_token_account"
|
||||
&& event.event_family == crate::MdEventFamily::Lifecycle
|
||||
&& ATA_LIFECYCLE_ENTRIES
|
||||
.iter()
|
||||
.any(|(entry, _)| return *entry == event.event_name.0);
|
||||
}
|
||||
|
||||
fn materialize_ata(
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
let operation = observation.event.event_name.0.as_str();
|
||||
let addresses = match observation.payload_json.get("addresses") {
|
||||
std::option::Option::Some(value) if value.is_object() => value,
|
||||
_ => {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"ata_addresses_missing",
|
||||
"committed ATA lifecycle projection requires the decoded address contract",
|
||||
);
|
||||
},
|
||||
};
|
||||
let validation = validate_ata_addresses(operation, addresses);
|
||||
if let std::result::Result::Err((code, message)) = validation {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(code, message);
|
||||
}
|
||||
let lifecycle_kind =
|
||||
match ATA_LIFECYCLE_ENTRIES.iter().find(|(entry, _)| return *entry == operation) {
|
||||
std::option::Option::Some((_, value)) => *value,
|
||||
std::option::Option::None => {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
},
|
||||
};
|
||||
let associated_token_account = if operation == "recover_nested" {
|
||||
observed_address(addresses, "nestedAssociatedTokenAccount")
|
||||
} else {
|
||||
observed_address(addresses, "associatedTokenAccount")
|
||||
};
|
||||
let token_program = addresses
|
||||
.get("tokenProgram")
|
||||
.and_then(|value| return value.get("programId"))
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let wallet = addresses.get("walletOwner").cloned().unwrap_or(serde_json::Value::Null);
|
||||
let mint = if operation == "recover_nested" {
|
||||
addresses.get("nestedMint").cloned().unwrap_or(serde_json::Value::Null)
|
||||
} else {
|
||||
addresses.get("mint").cloned().unwrap_or(serde_json::Value::Null)
|
||||
};
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: format!("ata_lifecycle:{lifecycle_kind}:0"),
|
||||
family: crate::MdMaterializedEventFamily::TokenAccount,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION,
|
||||
"domain": "spl_associated_token_account_lifecycle",
|
||||
"projectionSemantics": "committed_associated_address_lifecycle_not_token_state_snapshot",
|
||||
"idempotenceKey": format!(
|
||||
"ata:{}:{}:{}:{}:{}",
|
||||
observation.event.signature.0,
|
||||
observation.event.instruction_path.0,
|
||||
observation.event.program_id.0,
|
||||
lifecycle_kind,
|
||||
associated_token_account.as_str().unwrap_or("")
|
||||
),
|
||||
"operation": operation,
|
||||
"lifecycleKind": lifecycle_kind,
|
||||
"associatedTokenAccount": associated_token_account,
|
||||
"walletOwner": wallet,
|
||||
"mint": mint,
|
||||
"tokenProgramId": token_program,
|
||||
"fundingAccount": addresses.get("fundingAccount").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"ownerMint": addresses.get("ownerMint").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"nestedMint": addresses.get("nestedMint").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"ownerAssociatedTokenAccount": observed_address(addresses, "ownerAssociatedTokenAccount"),
|
||||
"nestedAssociatedTokenAccount": observed_address(addresses, "nestedAssociatedTokenAccount"),
|
||||
"walletNestedMintAssociatedTokenAccount": observed_address(addresses, "walletNestedMintAssociatedTokenAccount"),
|
||||
"idempotentOutcome": if operation == "create_idempotent" {
|
||||
"created_or_reused_not_distinguishable_from_parent_instruction_alone"
|
||||
} else {
|
||||
"not_applicable"
|
||||
},
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"committed": true,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
"tokenBalanceReconstructed": false,
|
||||
"splTokenCpiMutationsDuplicated": false,
|
||||
"ownership": {
|
||||
"ataParent": "associated_address_lifecycle_only",
|
||||
"splTokenCpi": "initialize_transfer_and_close_mutations"
|
||||
},
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"sourceSurface": observation.event.surface_code.0.clone(),
|
||||
"sourceEventCode": observation.event.event_code.0.clone(),
|
||||
"sourceEventKey": observation.event_key.clone()
|
||||
}
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS,
|
||||
action = "materialize_ata_lifecycle",
|
||||
signature = %observation.event.signature.0,
|
||||
instruction_path = %observation.event.instruction_path.0,
|
||||
operation,
|
||||
lifecycle_kind,
|
||||
"materialize committed ATA lifecycle without duplicating SPL Token CPI mutations"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_ata_addresses(
|
||||
operation: &str,
|
||||
addresses: &serde_json::Value,
|
||||
) -> std::result::Result<(), (&'static str, &'static str)> {
|
||||
let token_supported = addresses
|
||||
.get("tokenProgram")
|
||||
.and_then(|value| return value.get("supported"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== std::option::Option::Some(true);
|
||||
let token_program_id = addresses
|
||||
.get("tokenProgram")
|
||||
.and_then(|value| return value.get("programId"))
|
||||
.and_then(serde_json::Value::as_str);
|
||||
let token_program_exact = matches!(
|
||||
token_program_id,
|
||||
std::option::Option::Some(kb_program_ids::SPL_TOKEN_PROGRAM_ID)
|
||||
| std::option::Option::Some(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)
|
||||
);
|
||||
if !token_supported || !token_program_exact {
|
||||
return std::result::Result::Err((
|
||||
"ata_token_program_not_supported",
|
||||
"ATA lifecycle projection requires classic SPL Token or Token-2022",
|
||||
));
|
||||
}
|
||||
let fields: &[&str] = if operation == "recover_nested" {
|
||||
&[
|
||||
"ownerAssociatedTokenAccount",
|
||||
"nestedAssociatedTokenAccount",
|
||||
"walletNestedMintAssociatedTokenAccount",
|
||||
]
|
||||
} else {
|
||||
&["associatedTokenAccount"]
|
||||
};
|
||||
for field in fields {
|
||||
let valid = addresses
|
||||
.get(*field)
|
||||
.and_then(|value| return value.get("valid"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== std::option::Option::Some(true);
|
||||
let observed = observed_address(addresses, field);
|
||||
if !valid || observed.as_str().is_none_or(|value| return value.is_empty()) {
|
||||
return std::result::Result::Err((
|
||||
"ata_derived_address_invalid",
|
||||
"ATA lifecycle projection requires every canonical address validation to pass",
|
||||
));
|
||||
}
|
||||
}
|
||||
let identity_fields: &[&str] = if operation == "recover_nested" {
|
||||
&["walletOwner", "ownerMint", "nestedMint"]
|
||||
} else {
|
||||
&["walletOwner", "mint"]
|
||||
};
|
||||
if identity_fields.iter().any(|field| {
|
||||
return addresses
|
||||
.get(*field)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_none_or(|value| return value.is_empty());
|
||||
}) {
|
||||
return std::result::Result::Err((
|
||||
"ata_identity_field_missing",
|
||||
"ATA lifecycle projection requires wallet and mint identities",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn observed_address(addresses: &serde_json::Value, field: &str) -> serde_json::Value {
|
||||
return addresses
|
||||
.get(field)
|
||||
.and_then(|value| return value.get("observed"))
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
fn account_key_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
|
||||
let value = accounts.as_array().and_then(|rows| {
|
||||
return rows.iter().find_map(|row| {
|
||||
if row.get("role").and_then(serde_json::Value::as_str)
|
||||
!= std::option::Option::Some(role)
|
||||
{
|
||||
return std::option::Option::None;
|
||||
}
|
||||
return row.get("accountKey").and_then(serde_json::Value::as_str);
|
||||
});
|
||||
});
|
||||
return match value {
|
||||
std::option::Option::Some(account_key) => {
|
||||
serde_json::Value::String(account_key.to_string())
|
||||
},
|
||||
std::option::Option::None => serde_json::Value::Null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Materialize one already parsed Token-2022 account snapshot without inferring hidden state.
|
||||
pub fn materializer_token_materialize_token_2022_state_snapshot(
|
||||
account_key: &str,
|
||||
slot: u64,
|
||||
state: &crate::DcToken2022State,
|
||||
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
|
||||
if account_key.is_empty() {
|
||||
return std::result::Result::Err(
|
||||
"Token-2022 state snapshot requires a non-empty account key".to_string(),
|
||||
);
|
||||
}
|
||||
let kind = match state.kind {
|
||||
crate::DcToken2022StateKind::Mint => "mint",
|
||||
crate::DcToken2022StateKind::Account => "account",
|
||||
crate::DcToken2022StateKind::Multisig => "multisig",
|
||||
};
|
||||
let extensions = state
|
||||
.extensions
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
return serde_json::json!({
|
||||
"extensionType": entry.extension_type,
|
||||
"extensionName": entry.extension_name,
|
||||
"valueHex": entry.value_hex,
|
||||
"valueFields": entry.value_fields,
|
||||
});
|
||||
})
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let extension_names = state
|
||||
.extensions
|
||||
.iter()
|
||||
.map(|entry| return entry.extension_name)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let contains_confidential_state = state.extensions.iter().any(|entry| {
|
||||
return entry.extension_name.starts_with("confidential_")
|
||||
|| entry.extension_name == "confidential_mint_burn";
|
||||
});
|
||||
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
|
||||
output_key: format!("token_2022_state_snapshot:{account_key}:{slot}"),
|
||||
family: crate::MdMaterializedEventFamily::TokenAccount,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION,
|
||||
"domain": "spl_token_2022_account_state",
|
||||
"projectionSemantics": "authoritative_bounded_account_snapshot",
|
||||
"idempotenceKey": format!("token_2022_state:{account_key}:{slot}"),
|
||||
"accountKey": account_key,
|
||||
"slot": slot,
|
||||
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"stateKind": kind,
|
||||
"baseFields": state.base_fields,
|
||||
"baseHex": state.base_hex,
|
||||
"accountType": state.account_type,
|
||||
"extensionCount": state.extensions.len(),
|
||||
"extensionNames": extension_names,
|
||||
"extensions": extensions,
|
||||
"containsConfidentialState": contains_confidential_state,
|
||||
"confidentialValuesDecrypted": false,
|
||||
"finalAccountStateCaptured": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"source": "bounded_token_2022_state_parser"
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn observation(
|
||||
entry: &str,
|
||||
family: crate::MdEventFamily,
|
||||
failed: bool,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: format!("token:{entry}:0"),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("1/0".to_string()),
|
||||
program_id: crate::MdProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string()),
|
||||
protocol_code: crate::MdProtocolCode("spl.token".to_string()),
|
||||
surface_code: crate::MdSurfaceCode("spl.token".to_string()),
|
||||
event_code: crate::MdEventCode(format!("spl.token.{entry}")),
|
||||
event_name: crate::MdEventName(entry.to_string()),
|
||||
event_family: family,
|
||||
source_kind: crate::MdEventSourceKind::InnerInstruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({
|
||||
"parameters": {"amountRaw":"18446744073709551615","decimals":9},
|
||||
"accounts": [{"role":"mint","accountKey":"mint111"}],
|
||||
"authority": {"form":"single"}
|
||||
}),
|
||||
transaction_failed: failed,
|
||||
transaction_error: if failed {
|
||||
std::option::Option::Some(serde_json::json!({"error":"failed"}))
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
observation_committed: !failed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["fixture".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn token_2022_observation(
|
||||
entry: &str,
|
||||
family: crate::MdEventFamily,
|
||||
failed: bool,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
let mut observation = observation(entry, family, failed);
|
||||
observation.event.program_id =
|
||||
crate::MdProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
|
||||
observation.event.protocol_code = crate::MdProtocolCode("spl.token_2022".to_string());
|
||||
observation.event.surface_code = crate::MdSurfaceCode("spl.token_2022".to_string());
|
||||
observation.event.event_code = crate::MdEventCode(format!("spl.token_2022.{entry}"));
|
||||
observation.event_key = format!("token_2022:{entry}:0");
|
||||
return observation;
|
||||
}
|
||||
|
||||
fn ata_observation(entry: &str, failed: bool, valid: bool) -> crate::DcApiDecodedObservation {
|
||||
let addresses = if entry == "recover_nested" {
|
||||
serde_json::json!({
|
||||
"tokenProgram": {
|
||||
"programId": kb_program_ids::SPL_TOKEN_PROGRAM_ID,
|
||||
"kind": "spl_token_classic",
|
||||
"supported": true
|
||||
},
|
||||
"ownerAssociatedTokenAccount": {"expected":"ownerAta","observed":"ownerAta","valid":valid},
|
||||
"nestedAssociatedTokenAccount": {"expected":"nestedAta","observed":"nestedAta","valid":valid},
|
||||
"walletNestedMintAssociatedTokenAccount": {"expected":"destinationAta","observed":"destinationAta","valid":valid},
|
||||
"walletOwner": "wallet111",
|
||||
"ownerMint": "ownerMint111",
|
||||
"nestedMint": "nestedMint111"
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"tokenProgram": {
|
||||
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"kind": "token_2022",
|
||||
"supported": true
|
||||
},
|
||||
"associatedTokenAccount": {"expected":"ata111","observed":"ata111","valid":valid},
|
||||
"fundingAccount": "payer111",
|
||||
"walletOwner": "wallet111",
|
||||
"mint": "mint111"
|
||||
})
|
||||
};
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: format!("ata:{entry}:0"),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("0".to_string()),
|
||||
program_id: crate::MdProgramId(
|
||||
kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string(),
|
||||
),
|
||||
protocol_code: crate::MdProtocolCode("spl.associated_token_account".to_string()),
|
||||
surface_code: crate::MdSurfaceCode("spl.associated_token_account".to_string()),
|
||||
event_code: crate::MdEventCode(format!("spl.associated_token_account.{entry}")),
|
||||
event_name: crate::MdEventName(entry.to_string()),
|
||||
event_family: crate::MdEventFamily::Lifecycle,
|
||||
source_kind: crate::MdEventSourceKind::Instruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({"addresses":addresses}),
|
||||
transaction_failed: failed,
|
||||
transaction_error: std::option::Option::None,
|
||||
observation_committed: !failed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["fixture".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_checked_transfer_preserves_exact_amount_mint_and_path() {
|
||||
let observation =
|
||||
observation("transfer_checked", crate::MdEventFamily::TokenAccount, false);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs[0].payload_json["amountRaw"], "18446744073709551615");
|
||||
assert_eq!(result.outputs[0].payload_json["mint"], "mint111");
|
||||
assert_eq!(result.outputs[0].payload_json["instructionPath"], "1/0");
|
||||
assert_eq!(result.outputs[0].payload_json["transactionFinalAccountStateCaptured"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_token_2022_transfer_has_the_same_single_projection_owner() {
|
||||
let observation =
|
||||
token_2022_observation("transfer_checked", crate::MdEventFamily::TokenAccount, false);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs.len(), 1);
|
||||
assert_eq!(result.outputs[0].payload_json["operation"], "transfer_checked");
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["programId"],
|
||||
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_mutation_is_refused() {
|
||||
let observation = observation("burn", crate::MdEventFamily::TokenBurn, true);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_parent_admin_and_conversion_events_are_not_duplicated() {
|
||||
for (entry, family) in [
|
||||
("batch", crate::MdEventFamily::Audit),
|
||||
("set_authority", crate::MdEventFamily::Admin),
|
||||
("amount_to_ui_amount", crate::MdEventFamily::Audit),
|
||||
] {
|
||||
let observation = observation(entry, family, false);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_ata_variant_has_one_exact_lifecycle_projection() {
|
||||
for (entry, expected_kind, expected_ata, expected_token_program) in [
|
||||
("create", "ata_created", "ata111", kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID),
|
||||
(
|
||||
"create_idempotent",
|
||||
"ata_created_or_reused_idempotently",
|
||||
"ata111",
|
||||
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
),
|
||||
(
|
||||
"recover_nested",
|
||||
"nested_ata_recovered",
|
||||
"nestedAta",
|
||||
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
|
||||
),
|
||||
] {
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&ata_observation(entry, false, true),
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs.len(), 1);
|
||||
assert_eq!(result.outputs[0].payload_json["lifecycleKind"], expected_kind);
|
||||
assert_eq!(result.outputs[0].payload_json["associatedTokenAccount"], expected_ata);
|
||||
assert_eq!(result.outputs[0].payload_json["tokenProgramId"], expected_token_program);
|
||||
assert_eq!(result.outputs[0].payload_json["tokenBalanceReconstructed"], false);
|
||||
assert_eq!(result.outputs[0].payload_json["splTokenCpiMutationsDuplicated"], false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idempotent_parent_does_not_invent_created_or_reused_branch() {
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&ata_observation("create_idempotent", false, true),
|
||||
);
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["idempotentOutcome"],
|
||||
"created_or_reused_not_distinguishable_from_parent_instruction_alone"
|
||||
);
|
||||
assert!(result.outputs[0].payload_json.get("reused").is_none());
|
||||
assert!(result.outputs[0].payload_json.get("created").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncommitted_or_invalid_ata_lifecycle_is_refused() {
|
||||
for observation in [
|
||||
ata_observation("create", true, true),
|
||||
ata_observation("recover_nested", false, false),
|
||||
] {
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_parent_and_classic_token_cpi_children_own_distinct_facts() {
|
||||
let parent = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&ata_observation("recover_nested", false, true),
|
||||
);
|
||||
let transfer = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation("transfer", crate::MdEventFamily::TokenAccount, false),
|
||||
);
|
||||
let close = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation("close_account", crate::MdEventFamily::TokenAccount, false),
|
||||
);
|
||||
assert_eq!(parent.outputs[0].payload_json["lifecycleKind"], "nested_ata_recovered");
|
||||
assert_eq!(
|
||||
parent.outputs[0].payload_json["ownership"]["ataParent"],
|
||||
"associated_address_lifecycle_only"
|
||||
);
|
||||
assert!(parent.outputs[0].payload_json.get("amountRaw").is_none());
|
||||
assert_eq!(transfer.outputs[0].payload_json["operation"], "transfer");
|
||||
assert_eq!(close.outputs[0].payload_json["operation"], "close_account");
|
||||
assert_eq!(transfer.outputs[0].payload_json["domain"], "spl_token_account_mutation");
|
||||
assert_eq!(close.outputs[0].payload_json["domain"], "spl_token_account_mutation");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_matrix_and_compiled_token_account_projection_ownership_are_equal() {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(include_str!(
|
||||
"../../../../test-fixtures/contract-matrices/SPL_ASSOCIATED_TOKEN_ACCOUNT_MATRIX.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("ATA matrix is invalid: {error}"),
|
||||
};
|
||||
let instructions = match matrix.get("instructions").and_then(serde_json::Value::as_array) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("ATA matrix instructions are absent"),
|
||||
};
|
||||
for (entry, fact) in super::ATA_LIFECYCLE_ENTRIES {
|
||||
let row = match instructions.iter().find(|row| {
|
||||
return row.get("name").and_then(serde_json::Value::as_str)
|
||||
== std::option::Option::Some(*entry);
|
||||
}) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("ATA matrix entry {entry} is absent"),
|
||||
};
|
||||
let expected = format!("token_accounts:{fact}");
|
||||
assert!(row["authorizedProjections"].as_array().is_some_and(|values| {
|
||||
return values.iter().any(|value| return value == expected.as_str());
|
||||
}));
|
||||
}
|
||||
let owner = &matrix["materializationContract"]["projectionOwners"][0];
|
||||
let matrix_facts = match owner["facts"].as_array() {
|
||||
std::option::Option::Some(values) => values
|
||||
.iter()
|
||||
.filter_map(|value| return value.as_str())
|
||||
.collect::<std::vec::Vec<_>>(),
|
||||
std::option::Option::None => panic!("ATA token-account owner facts are absent"),
|
||||
};
|
||||
let compiled_facts = super::ATA_LIFECYCLE_ENTRIES
|
||||
.iter()
|
||||
.map(|(_, fact)| return *fact)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
assert_eq!(matrix_facts, compiled_facts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_ata_materialization_is_byte_for_byte_idempotent() {
|
||||
let observation = ata_observation("create_idempotent", false, true);
|
||||
let first = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
let second = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(first.outputs[0].output_key, second.outputs[0].output_key);
|
||||
assert_eq!(
|
||||
first.outputs[0].payload_json["idempotenceKey"],
|
||||
second.outputs[0].payload_json["idempotenceKey"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_2022_state_snapshot_preserves_base_and_ordered_extensions() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Mint,
|
||||
base_fields: serde_json::json!({"supply":"42","decimals":9,"initialized":true}),
|
||||
base_hex: "00".repeat(82),
|
||||
account_type: std::option::Option::Some(1),
|
||||
extensions: std::vec![
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 1,
|
||||
extension_name: "transfer_fee_config",
|
||||
value_hex: "11".repeat(108),
|
||||
value_fields: serde_json::json!({"withheldAmount":"7"}),
|
||||
},
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 4,
|
||||
extension_name: "confidential_transfer_mint",
|
||||
value_hex: "22".repeat(65),
|
||||
value_fields: serde_json::json!({"autoApproveNewAccounts":true}),
|
||||
},
|
||||
],
|
||||
};
|
||||
let output = match crate::materializer_token_materialize_token_2022_state_snapshot(
|
||||
"mint111", 99, &state,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("snapshot failed: {error}"),
|
||||
};
|
||||
assert_eq!(output.payload_json["stateKind"], "mint");
|
||||
assert_eq!(output.payload_json["extensionCount"], 2);
|
||||
assert_eq!(output.payload_json["extensionNames"][0], "transfer_fee_config");
|
||||
assert_eq!(output.payload_json["extensionNames"][1], "confidential_transfer_mint");
|
||||
assert_eq!(output.payload_json["containsConfidentialState"], true);
|
||||
assert_eq!(output.payload_json["confidentialValuesDecrypted"], false);
|
||||
assert_eq!(output.payload_json["baseFields"]["supply"], "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_2022_state_snapshot_is_idempotent_and_rejects_missing_identity() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Account,
|
||||
base_fields: serde_json::json!({"mint":"mint111","owner":"wallet111","amount":"5"}),
|
||||
base_hex: "00".repeat(165),
|
||||
account_type: std::option::Option::None,
|
||||
extensions: std::vec::Vec::new(),
|
||||
};
|
||||
let first = crate::materializer_token_materialize_token_2022_state_snapshot(
|
||||
"account111",
|
||||
7,
|
||||
&state,
|
||||
);
|
||||
let second = crate::materializer_token_materialize_token_2022_state_snapshot(
|
||||
"account111",
|
||||
7,
|
||||
&state,
|
||||
);
|
||||
assert_eq!(first, second);
|
||||
assert!(
|
||||
crate::materializer_token_materialize_token_2022_state_snapshot("", 7, &state).is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES;
|
||||
/// Stable runtime component name for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_COMPONENT_NAME;
|
||||
/// Crate-visible `MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES` component constant.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES;
|
||||
/// Stable processor name for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for Token, ATA and Token-2022 account projections.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS;
|
||||
|
||||
54
kb-lib/src/materializer/token/accounts/constants.rs
Normal file
54
kb-lib/src/materializer/token/accounts/constants.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// file: kb-lib/src/materializer/token/accounts/constants.rs
|
||||
// version: 4
|
||||
|
||||
//! Local constants for the token accounts materializer.
|
||||
|
||||
/// Stable runtime component name for the token accounts materializer.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_COMPONENT_NAME: &str = "kb-lib.materializer.token.accounts";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_PROCESSOR_NAME: &str = "materializer.token.accounts";
|
||||
/// Stable projection contract version for the token accounts materializer.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_PROJECTION_VERSION: u32 = 3;
|
||||
/// Canonical tracing target for the token accounts materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS: &str =
|
||||
"kb-lib.materializer.token.accounts";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
|
||||
crate::MdEventFamily::TokenAccount,
|
||||
crate::MdEventFamily::TokenMint,
|
||||
crate::MdEventFamily::TokenBurn,
|
||||
crate::MdEventFamily::Lifecycle,
|
||||
];
|
||||
/// Component contract for `mutation_entries`.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES: &[&str] = &[
|
||||
"initialize_mint",
|
||||
"initialize_mint2",
|
||||
"initialize_account",
|
||||
"initialize_account2",
|
||||
"initialize_account3",
|
||||
"transfer",
|
||||
"transfer_checked",
|
||||
"approve",
|
||||
"approve_checked",
|
||||
"revoke",
|
||||
"mint_to",
|
||||
"mint_to_checked",
|
||||
"burn",
|
||||
"burn_checked",
|
||||
"close_account",
|
||||
"freeze_account",
|
||||
"thaw_account",
|
||||
"sync_native",
|
||||
"initialize_immutable_owner",
|
||||
"withdraw_excess_lamports",
|
||||
"unwrap_lamports",
|
||||
"initialize_non_transferable_mint",
|
||||
"reallocate",
|
||||
];
|
||||
/// Component contract for `ata_lifecycle_entries`.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES: &[(&str, &str)] = &[
|
||||
("create", "ata_created"),
|
||||
("create_idempotent", "ata_created_or_reused_idempotently"),
|
||||
("recover_nested", "nested_ata_recovered"),
|
||||
];
|
||||
809
kb-lib/src/materializer/token/accounts/materializer.rs
Normal file
809
kb-lib/src/materializer/token/accounts/materializer.rs
Normal file
@@ -0,0 +1,809 @@
|
||||
// file: kb-lib/src/materializer/token/accounts/materializer.rs
|
||||
// version: 9
|
||||
|
||||
//! Stable committed SPL Token mutation and ATA lifecycle projections.
|
||||
|
||||
/// Stable committed classic SPL Token account and mint mutation materializer.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtTokenAccountsMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtTokenAccountsMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return crate::MT_TOKEN_ACCOUNTS_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return accepts_event(event);
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtTokenAccountsMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return crate::MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return accepts_event(&observation.event);
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
if !accepts_event(&observation.event) {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if observation.transaction_failed || !observation.observation_committed {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"uncommitted_token_account_projection_refused",
|
||||
"failed or uncommitted SPL Token or ATA observations cannot create mutable outputs",
|
||||
);
|
||||
}
|
||||
if is_ata_event(&observation.event) {
|
||||
return materialize_ata(observation);
|
||||
}
|
||||
let parameters = observation
|
||||
.payload_json
|
||||
.get("parameters")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let accounts = observation
|
||||
.payload_json
|
||||
.get("accounts")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let authority = observation
|
||||
.payload_json
|
||||
.get("authority")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let operation = observation.event.event_name.0.clone();
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: format!("token_account_mutation:{operation}:0"),
|
||||
family: crate::MdMaterializedEventFamily::TokenAccount,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION,
|
||||
"domain": "spl_token_account_mutation",
|
||||
"projectionSemantics": "committed_instruction_mutation_not_final_account_snapshot",
|
||||
"idempotenceKey": format!(
|
||||
"spl_token:{}:{}:{}",
|
||||
observation.event.signature.0,
|
||||
observation.event.instruction_path.0,
|
||||
operation
|
||||
),
|
||||
"operation": operation,
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"mint": account_key_for_role(&accounts, "mint"),
|
||||
"amountRaw": parameters.get("amountRaw").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"decimals": parameters.get("decimals").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"parameters": parameters,
|
||||
"accounts": accounts,
|
||||
"authority": authority,
|
||||
"committed": true,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
"coreBalanceChangesDuplicated": false,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"sourceSurface": observation.event.surface_code.0.clone(),
|
||||
"sourceEventCode": observation.event.event_code.0.clone(),
|
||||
"sourceEventKey": observation.event_key.clone()
|
||||
}
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS,
|
||||
action = "materialize_token_account_mutation",
|
||||
signature = %observation.event.signature.0,
|
||||
instruction_path = %observation.event.instruction_path.0,
|
||||
operation = %observation.event.event_name.0,
|
||||
"materialize committed classic SPL Token mutation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
let token_program = (event.program_id.0 == kb_program_ids::SPL_TOKEN_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.token")
|
||||
|| (event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.token_2022");
|
||||
let token_mutation = token_program
|
||||
&& crate::MT_TOKEN_ACCOUNTS_MUTATION_ENTRIES.contains(&event.event_name.0.as_str());
|
||||
let ata = is_ata_event(event);
|
||||
return crate::MT_TOKEN_ACCOUNTS_ACCEPTED_FAMILIES.contains(&event.event_family)
|
||||
&& (token_mutation || ata);
|
||||
}
|
||||
|
||||
fn is_ata_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.program_id.0 == kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.associated_token_account"
|
||||
&& event.event_family == crate::MdEventFamily::Lifecycle
|
||||
&& crate::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES
|
||||
.iter()
|
||||
.any(|(entry, _)| return *entry == event.event_name.0);
|
||||
}
|
||||
|
||||
fn materialize_ata(
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
let operation = observation.event.event_name.0.as_str();
|
||||
let addresses = match observation.payload_json.get("addresses") {
|
||||
std::option::Option::Some(value) if value.is_object() => value,
|
||||
_ => {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"ata_addresses_missing",
|
||||
"committed ATA lifecycle projection requires the decoded address contract",
|
||||
);
|
||||
},
|
||||
};
|
||||
let validation = validate_ata_addresses(operation, addresses);
|
||||
if let std::result::Result::Err((code, message)) = validation {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(code, message);
|
||||
}
|
||||
let lifecycle_kind = match crate::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES
|
||||
.iter()
|
||||
.find(|(entry, _)| return *entry == operation)
|
||||
{
|
||||
std::option::Option::Some((_, value)) => *value,
|
||||
std::option::Option::None => {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
},
|
||||
};
|
||||
let associated_token_account = if operation == "recover_nested" {
|
||||
observed_address(addresses, "nestedAssociatedTokenAccount")
|
||||
} else {
|
||||
observed_address(addresses, "associatedTokenAccount")
|
||||
};
|
||||
let token_program = addresses
|
||||
.get("tokenProgram")
|
||||
.and_then(|value| return value.get("programId"))
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let wallet = addresses.get("walletOwner").cloned().unwrap_or(serde_json::Value::Null);
|
||||
let mint = if operation == "recover_nested" {
|
||||
addresses.get("nestedMint").cloned().unwrap_or(serde_json::Value::Null)
|
||||
} else {
|
||||
addresses.get("mint").cloned().unwrap_or(serde_json::Value::Null)
|
||||
};
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: format!("ata_lifecycle:{lifecycle_kind}:0"),
|
||||
family: crate::MdMaterializedEventFamily::TokenAccount,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION,
|
||||
"domain": "spl_associated_token_account_lifecycle",
|
||||
"projectionSemantics": "committed_associated_address_lifecycle_not_token_state_snapshot",
|
||||
"idempotenceKey": format!(
|
||||
"ata:{}:{}:{}:{}:{}",
|
||||
observation.event.signature.0,
|
||||
observation.event.instruction_path.0,
|
||||
observation.event.program_id.0,
|
||||
lifecycle_kind,
|
||||
associated_token_account.as_str().unwrap_or("")
|
||||
),
|
||||
"operation": operation,
|
||||
"lifecycleKind": lifecycle_kind,
|
||||
"associatedTokenAccount": associated_token_account,
|
||||
"walletOwner": wallet,
|
||||
"mint": mint,
|
||||
"tokenProgramId": token_program,
|
||||
"fundingAccount": addresses.get("fundingAccount").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"ownerMint": addresses.get("ownerMint").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"nestedMint": addresses.get("nestedMint").cloned().unwrap_or(serde_json::Value::Null),
|
||||
"ownerAssociatedTokenAccount": observed_address(addresses, "ownerAssociatedTokenAccount"),
|
||||
"nestedAssociatedTokenAccount": observed_address(addresses, "nestedAssociatedTokenAccount"),
|
||||
"walletNestedMintAssociatedTokenAccount": observed_address(addresses, "walletNestedMintAssociatedTokenAccount"),
|
||||
"idempotentOutcome": if operation == "create_idempotent" {
|
||||
"created_or_reused_not_distinguishable_from_parent_instruction_alone"
|
||||
} else {
|
||||
"not_applicable"
|
||||
},
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"committed": true,
|
||||
"transactionFinalAccountStateCaptured": false,
|
||||
"tokenBalanceReconstructed": false,
|
||||
"splTokenCpiMutationsDuplicated": false,
|
||||
"ownership": {
|
||||
"ataParent": "associated_address_lifecycle_only",
|
||||
"splTokenCpi": "initialize_transfer_and_close_mutations"
|
||||
},
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"sourceSurface": observation.event.surface_code.0.clone(),
|
||||
"sourceEventCode": observation.event.event_code.0.clone(),
|
||||
"sourceEventKey": observation.event_key.clone()
|
||||
}
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS,
|
||||
action = "materialize_ata_lifecycle",
|
||||
signature = %observation.event.signature.0,
|
||||
instruction_path = %observation.event.instruction_path.0,
|
||||
operation,
|
||||
lifecycle_kind,
|
||||
"materialize committed ATA lifecycle without duplicating SPL Token CPI mutations"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_ata_addresses(
|
||||
operation: &str,
|
||||
addresses: &serde_json::Value,
|
||||
) -> std::result::Result<(), (&'static str, &'static str)> {
|
||||
let token_supported = addresses
|
||||
.get("tokenProgram")
|
||||
.and_then(|value| return value.get("supported"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== std::option::Option::Some(true);
|
||||
let token_program_id = addresses
|
||||
.get("tokenProgram")
|
||||
.and_then(|value| return value.get("programId"))
|
||||
.and_then(serde_json::Value::as_str);
|
||||
let token_program_exact = matches!(
|
||||
token_program_id,
|
||||
std::option::Option::Some(kb_program_ids::SPL_TOKEN_PROGRAM_ID)
|
||||
| std::option::Option::Some(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)
|
||||
);
|
||||
if !token_supported || !token_program_exact {
|
||||
return std::result::Result::Err((
|
||||
"ata_token_program_not_supported",
|
||||
"ATA lifecycle projection requires classic SPL Token or Token-2022",
|
||||
));
|
||||
}
|
||||
let fields: &[&str] = if operation == "recover_nested" {
|
||||
&[
|
||||
"ownerAssociatedTokenAccount",
|
||||
"nestedAssociatedTokenAccount",
|
||||
"walletNestedMintAssociatedTokenAccount",
|
||||
]
|
||||
} else {
|
||||
&["associatedTokenAccount"]
|
||||
};
|
||||
for field in fields {
|
||||
let valid = addresses
|
||||
.get(*field)
|
||||
.and_then(|value| return value.get("valid"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== std::option::Option::Some(true);
|
||||
let observed = observed_address(addresses, field);
|
||||
if !valid || observed.as_str().is_none_or(|value| return value.is_empty()) {
|
||||
return std::result::Result::Err((
|
||||
"ata_derived_address_invalid",
|
||||
"ATA lifecycle projection requires every canonical address validation to pass",
|
||||
));
|
||||
}
|
||||
}
|
||||
let identity_fields: &[&str] = if operation == "recover_nested" {
|
||||
&["walletOwner", "ownerMint", "nestedMint"]
|
||||
} else {
|
||||
&["walletOwner", "mint"]
|
||||
};
|
||||
if identity_fields.iter().any(|field| {
|
||||
return addresses
|
||||
.get(*field)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_none_or(|value| return value.is_empty());
|
||||
}) {
|
||||
return std::result::Result::Err((
|
||||
"ata_identity_field_missing",
|
||||
"ATA lifecycle projection requires wallet and mint identities",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn observed_address(addresses: &serde_json::Value, field: &str) -> serde_json::Value {
|
||||
return addresses
|
||||
.get(field)
|
||||
.and_then(|value| return value.get("observed"))
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
fn account_key_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
|
||||
let value = accounts.as_array().and_then(|rows| {
|
||||
return rows.iter().find_map(|row| {
|
||||
if row.get("role").and_then(serde_json::Value::as_str)
|
||||
!= std::option::Option::Some(role)
|
||||
{
|
||||
return std::option::Option::None;
|
||||
}
|
||||
return row.get("accountKey").and_then(serde_json::Value::as_str);
|
||||
});
|
||||
});
|
||||
return match value {
|
||||
std::option::Option::Some(account_key) => {
|
||||
serde_json::Value::String(account_key.to_string())
|
||||
},
|
||||
std::option::Option::None => serde_json::Value::Null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Materialize one already parsed Token-2022 account snapshot without inferring hidden state.
|
||||
pub fn materializer_token_materialize_token_2022_state_snapshot(
|
||||
account_key: &str,
|
||||
slot: u64,
|
||||
state: &crate::DcToken2022State,
|
||||
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
|
||||
if account_key.is_empty() {
|
||||
return std::result::Result::Err(
|
||||
"Token-2022 state snapshot requires a non-empty account key".to_string(),
|
||||
);
|
||||
}
|
||||
let kind = match state.kind {
|
||||
crate::DcToken2022StateKind::Mint => "mint",
|
||||
crate::DcToken2022StateKind::Account => "account",
|
||||
crate::DcToken2022StateKind::Multisig => "multisig",
|
||||
};
|
||||
let extensions = state
|
||||
.extensions
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
return serde_json::json!({
|
||||
"extensionType": entry.extension_type,
|
||||
"extensionName": entry.extension_name,
|
||||
"valueHex": entry.value_hex,
|
||||
"valueFields": entry.value_fields,
|
||||
});
|
||||
})
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let extension_names = state
|
||||
.extensions
|
||||
.iter()
|
||||
.map(|entry| return entry.extension_name)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let contains_confidential_state = state.extensions.iter().any(|entry| {
|
||||
return entry.extension_name.starts_with("confidential_")
|
||||
|| entry.extension_name == "confidential_mint_burn";
|
||||
});
|
||||
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
|
||||
output_key: format!("token_2022_state_snapshot:{account_key}:{slot}"),
|
||||
family: crate::MdMaterializedEventFamily::TokenAccount,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION,
|
||||
"domain": "spl_token_2022_account_state",
|
||||
"projectionSemantics": "authoritative_bounded_account_snapshot",
|
||||
"idempotenceKey": format!("token_2022_state:{account_key}:{slot}"),
|
||||
"accountKey": account_key,
|
||||
"slot": slot,
|
||||
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"stateKind": kind,
|
||||
"baseFields": state.base_fields,
|
||||
"baseHex": state.base_hex,
|
||||
"accountType": state.account_type,
|
||||
"extensionCount": state.extensions.len(),
|
||||
"extensionNames": extension_names,
|
||||
"extensions": extensions,
|
||||
"containsConfidentialState": contains_confidential_state,
|
||||
"confidentialValuesDecrypted": false,
|
||||
"finalAccountStateCaptured": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"source": "bounded_token_2022_state_parser"
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn observation(
|
||||
entry: &str,
|
||||
family: crate::MdEventFamily,
|
||||
failed: bool,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: format!("token:{entry}:0"),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("1/0".to_string()),
|
||||
program_id: crate::MdProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string()),
|
||||
protocol_code: crate::MdProtocolCode("spl.token".to_string()),
|
||||
surface_code: crate::MdSurfaceCode("spl.token".to_string()),
|
||||
event_code: crate::MdEventCode(format!("spl.token.{entry}")),
|
||||
event_name: crate::MdEventName(entry.to_string()),
|
||||
event_family: family,
|
||||
source_kind: crate::MdEventSourceKind::InnerInstruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({
|
||||
"parameters": {"amountRaw":"18446744073709551615","decimals":9},
|
||||
"accounts": [{"role":"mint","accountKey":"mint111"}],
|
||||
"authority": {"form":"single"}
|
||||
}),
|
||||
transaction_failed: failed,
|
||||
transaction_error: if failed {
|
||||
std::option::Option::Some(serde_json::json!({"error":"failed"}))
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
observation_committed: !failed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["fixture".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn token_2022_observation(
|
||||
entry: &str,
|
||||
family: crate::MdEventFamily,
|
||||
failed: bool,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
let mut observation = observation(entry, family, failed);
|
||||
observation.event.program_id =
|
||||
crate::MdProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
|
||||
observation.event.protocol_code = crate::MdProtocolCode("spl.token_2022".to_string());
|
||||
observation.event.surface_code = crate::MdSurfaceCode("spl.token_2022".to_string());
|
||||
observation.event.event_code = crate::MdEventCode(format!("spl.token_2022.{entry}"));
|
||||
observation.event_key = format!("token_2022:{entry}:0");
|
||||
return observation;
|
||||
}
|
||||
|
||||
fn ata_observation(entry: &str, failed: bool, valid: bool) -> crate::DcApiDecodedObservation {
|
||||
let addresses = if entry == "recover_nested" {
|
||||
serde_json::json!({
|
||||
"tokenProgram": {
|
||||
"programId": kb_program_ids::SPL_TOKEN_PROGRAM_ID,
|
||||
"kind": "spl_token_classic",
|
||||
"supported": true
|
||||
},
|
||||
"ownerAssociatedTokenAccount": {"expected":"ownerAta","observed":"ownerAta","valid":valid},
|
||||
"nestedAssociatedTokenAccount": {"expected":"nestedAta","observed":"nestedAta","valid":valid},
|
||||
"walletNestedMintAssociatedTokenAccount": {"expected":"destinationAta","observed":"destinationAta","valid":valid},
|
||||
"walletOwner": "wallet111",
|
||||
"ownerMint": "ownerMint111",
|
||||
"nestedMint": "nestedMint111"
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"tokenProgram": {
|
||||
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"kind": "token_2022",
|
||||
"supported": true
|
||||
},
|
||||
"associatedTokenAccount": {"expected":"ata111","observed":"ata111","valid":valid},
|
||||
"fundingAccount": "payer111",
|
||||
"walletOwner": "wallet111",
|
||||
"mint": "mint111"
|
||||
})
|
||||
};
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: format!("ata:{entry}:0"),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("0".to_string()),
|
||||
program_id: crate::MdProgramId(
|
||||
kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string(),
|
||||
),
|
||||
protocol_code: crate::MdProtocolCode("spl.associated_token_account".to_string()),
|
||||
surface_code: crate::MdSurfaceCode("spl.associated_token_account".to_string()),
|
||||
event_code: crate::MdEventCode(format!("spl.associated_token_account.{entry}")),
|
||||
event_name: crate::MdEventName(entry.to_string()),
|
||||
event_family: crate::MdEventFamily::Lifecycle,
|
||||
source_kind: crate::MdEventSourceKind::Instruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({"addresses":addresses}),
|
||||
transaction_failed: failed,
|
||||
transaction_error: std::option::Option::None,
|
||||
observation_committed: !failed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["fixture".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_checked_transfer_preserves_exact_amount_mint_and_path() {
|
||||
let observation =
|
||||
observation("transfer_checked", crate::MdEventFamily::TokenAccount, false);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs[0].payload_json["amountRaw"], "18446744073709551615");
|
||||
assert_eq!(result.outputs[0].payload_json["mint"], "mint111");
|
||||
assert_eq!(result.outputs[0].payload_json["instructionPath"], "1/0");
|
||||
assert_eq!(result.outputs[0].payload_json["transactionFinalAccountStateCaptured"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_token_2022_transfer_has_the_same_single_projection_owner() {
|
||||
let observation =
|
||||
token_2022_observation("transfer_checked", crate::MdEventFamily::TokenAccount, false);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs.len(), 1);
|
||||
assert_eq!(result.outputs[0].payload_json["operation"], "transfer_checked");
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["programId"],
|
||||
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_mutation_is_refused() {
|
||||
let observation = observation("burn", crate::MdEventFamily::TokenBurn, true);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_parent_admin_and_conversion_events_are_not_duplicated() {
|
||||
for (entry, family) in [
|
||||
("batch", crate::MdEventFamily::Audit),
|
||||
("set_authority", crate::MdEventFamily::Admin),
|
||||
("amount_to_ui_amount", crate::MdEventFamily::Audit),
|
||||
] {
|
||||
let observation = observation(entry, family, false);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_ata_variant_has_one_exact_lifecycle_projection() {
|
||||
for (entry, expected_kind, expected_ata, expected_token_program) in [
|
||||
("create", "ata_created", "ata111", kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID),
|
||||
(
|
||||
"create_idempotent",
|
||||
"ata_created_or_reused_idempotently",
|
||||
"ata111",
|
||||
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
),
|
||||
(
|
||||
"recover_nested",
|
||||
"nested_ata_recovered",
|
||||
"nestedAta",
|
||||
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
|
||||
),
|
||||
] {
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&ata_observation(entry, false, true),
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(result.outputs.len(), 1);
|
||||
assert_eq!(result.outputs[0].payload_json["lifecycleKind"], expected_kind);
|
||||
assert_eq!(result.outputs[0].payload_json["associatedTokenAccount"], expected_ata);
|
||||
assert_eq!(result.outputs[0].payload_json["tokenProgramId"], expected_token_program);
|
||||
assert_eq!(result.outputs[0].payload_json["tokenBalanceReconstructed"], false);
|
||||
assert_eq!(result.outputs[0].payload_json["splTokenCpiMutationsDuplicated"], false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idempotent_parent_does_not_invent_created_or_reused_branch() {
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&ata_observation("create_idempotent", false, true),
|
||||
);
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["idempotentOutcome"],
|
||||
"created_or_reused_not_distinguishable_from_parent_instruction_alone"
|
||||
);
|
||||
assert!(result.outputs[0].payload_json.get("reused").is_none());
|
||||
assert!(result.outputs[0].payload_json.get("created").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uncommitted_or_invalid_ata_lifecycle_is_refused() {
|
||||
for observation in [
|
||||
ata_observation("create", true, true),
|
||||
ata_observation("recover_nested", false, false),
|
||||
] {
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_parent_and_classic_token_cpi_children_own_distinct_facts() {
|
||||
let parent = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&ata_observation("recover_nested", false, true),
|
||||
);
|
||||
let transfer = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation("transfer", crate::MdEventFamily::TokenAccount, false),
|
||||
);
|
||||
let close = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation("close_account", crate::MdEventFamily::TokenAccount, false),
|
||||
);
|
||||
assert_eq!(parent.outputs[0].payload_json["lifecycleKind"], "nested_ata_recovered");
|
||||
assert_eq!(
|
||||
parent.outputs[0].payload_json["ownership"]["ataParent"],
|
||||
"associated_address_lifecycle_only"
|
||||
);
|
||||
assert!(parent.outputs[0].payload_json.get("amountRaw").is_none());
|
||||
assert_eq!(transfer.outputs[0].payload_json["operation"], "transfer");
|
||||
assert_eq!(close.outputs[0].payload_json["operation"], "close_account");
|
||||
assert_eq!(transfer.outputs[0].payload_json["domain"], "spl_token_account_mutation");
|
||||
assert_eq!(close.outputs[0].payload_json["domain"], "spl_token_account_mutation");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ata_matrix_and_compiled_token_account_projection_ownership_are_equal() {
|
||||
let matrix: serde_json::Value = match serde_json::from_str(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/SPL_ASSOCIATED_TOKEN_ACCOUNT_MATRIX.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("ATA matrix is invalid: {error}"),
|
||||
};
|
||||
let instructions = match matrix.get("instructions").and_then(serde_json::Value::as_array) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("ATA matrix instructions are absent"),
|
||||
};
|
||||
for (entry, fact) in crate::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES {
|
||||
let row = match instructions.iter().find(|row| {
|
||||
return row.get("name").and_then(serde_json::Value::as_str)
|
||||
== std::option::Option::Some(*entry);
|
||||
}) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("ATA matrix entry {entry} is absent"),
|
||||
};
|
||||
let expected = format!("token_accounts:{fact}");
|
||||
assert!(row["authorizedProjections"].as_array().is_some_and(|values| {
|
||||
return values.iter().any(|value| return value == expected.as_str());
|
||||
}));
|
||||
}
|
||||
let owner = &matrix["materializationContract"]["projectionOwners"][0];
|
||||
let matrix_facts = match owner["facts"].as_array() {
|
||||
std::option::Option::Some(values) => values
|
||||
.iter()
|
||||
.filter_map(|value| return value.as_str())
|
||||
.collect::<std::vec::Vec<_>>(),
|
||||
std::option::Option::None => panic!("ATA token-account owner facts are absent"),
|
||||
};
|
||||
let compiled_facts = crate::MT_TOKEN_ACCOUNTS_ATA_LIFECYCLE_ENTRIES
|
||||
.iter()
|
||||
.map(|(_, fact)| return *fact)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
assert_eq!(matrix_facts, compiled_facts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_ata_materialization_is_byte_for_byte_idempotent() {
|
||||
let observation = ata_observation("create_idempotent", false, true);
|
||||
let first = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
let second = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTokenAccountsMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(first.outputs[0].output_key, second.outputs[0].output_key);
|
||||
assert_eq!(
|
||||
first.outputs[0].payload_json["idempotenceKey"],
|
||||
second.outputs[0].payload_json["idempotenceKey"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_2022_state_snapshot_preserves_base_and_ordered_extensions() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Mint,
|
||||
base_fields: serde_json::json!({"supply":"42","decimals":9,"initialized":true}),
|
||||
base_hex: "00".repeat(82),
|
||||
account_type: std::option::Option::Some(1),
|
||||
extensions: std::vec![
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 1,
|
||||
extension_name: "transfer_fee_config",
|
||||
value_hex: "11".repeat(108),
|
||||
value_fields: serde_json::json!({"withheldAmount":"7"}),
|
||||
},
|
||||
crate::DcToken2022TlvEntry {
|
||||
extension_type: 4,
|
||||
extension_name: "confidential_transfer_mint",
|
||||
value_hex: "22".repeat(65),
|
||||
value_fields: serde_json::json!({"autoApproveNewAccounts":true}),
|
||||
},
|
||||
],
|
||||
};
|
||||
let output = match crate::materializer_token_materialize_token_2022_state_snapshot(
|
||||
"mint111", 99, &state,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("snapshot failed: {error}"),
|
||||
};
|
||||
assert_eq!(output.payload_json["stateKind"], "mint");
|
||||
assert_eq!(output.payload_json["extensionCount"], 2);
|
||||
assert_eq!(output.payload_json["extensionNames"][0], "transfer_fee_config");
|
||||
assert_eq!(output.payload_json["extensionNames"][1], "confidential_transfer_mint");
|
||||
assert_eq!(output.payload_json["containsConfidentialState"], true);
|
||||
assert_eq!(output.payload_json["confidentialValuesDecrypted"], false);
|
||||
assert_eq!(output.payload_json["baseFields"]["supply"], "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_2022_state_snapshot_is_idempotent_and_rejects_missing_identity() {
|
||||
let state = crate::DcToken2022State {
|
||||
kind: crate::DcToken2022StateKind::Account,
|
||||
base_fields: serde_json::json!({"mint":"mint111","owner":"wallet111","amount":"5"}),
|
||||
base_hex: "00".repeat(165),
|
||||
account_type: std::option::Option::None,
|
||||
extensions: std::vec::Vec::new(),
|
||||
};
|
||||
let first = crate::materializer_token_materialize_token_2022_state_snapshot(
|
||||
"account111",
|
||||
7,
|
||||
&state,
|
||||
);
|
||||
let second = crate::materializer_token_materialize_token_2022_state_snapshot(
|
||||
"account111",
|
||||
7,
|
||||
&state,
|
||||
);
|
||||
assert_eq!(first, second);
|
||||
assert!(
|
||||
crate::materializer_token_materialize_token_2022_state_snapshot("", 7, &state).is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// file: kb-lib/src/materializer/token/constants.rs
|
||||
// version: 3
|
||||
|
||||
//! Local constants for classic SPL Token account projections.
|
||||
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_PROCESSOR_NAME: &str = "materializer.token.accounts";
|
||||
/// Stable projection payload contract version.
|
||||
pub(crate) const MT_TOKEN_ACCOUNTS_PROJECTION_VERSION: u32 = 3;
|
||||
/// Canonical tracing target for this component.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_TOKEN_ACCOUNTS: &str = "kb-lib.materializer.token";
|
||||
@@ -1,61 +1,17 @@
|
||||
// file: kb-lib/src/materializer/token/metadata_risk.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Reserved token-metadata risk materializer.
|
||||
//! Token-metadata risk materializer component facade.
|
||||
|
||||
/// Reserved materializer for token-metadata risk observations.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtTokenMetadataRiskMaterializer;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtTokenMetadataRiskMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.token.metadata_risk";
|
||||
}
|
||||
/// Reserved token-metadata risk materializer.
|
||||
pub use self::materializer::MtTokenMetadataRiskMaterializer;
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, _event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::TokenMetadataRisk,
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtTokenMetadataRiskMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.token.metadata_risk".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
_observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
}
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the reserved token-metadata risk materializer.
|
||||
pub(crate) use self::constants::MT_TOKEN_METADATA_RISK_COMPONENT_NAME;
|
||||
/// Stable processor name for the reserved token-metadata risk materializer.
|
||||
pub(crate) use self::constants::MT_TOKEN_METADATA_RISK_PROCESSOR_NAME;
|
||||
|
||||
12
kb-lib/src/materializer/token/metadata_risk/constants.rs
Normal file
12
kb-lib/src/materializer/token/metadata_risk/constants.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
// file: kb-lib/src/materializer/token/metadata_risk/constants.rs
|
||||
// version: 2
|
||||
|
||||
//! Local constants for the token metadata_risk materializer.
|
||||
|
||||
/// Stable runtime component name for the token metadata_risk materializer.
|
||||
pub(crate) const MT_TOKEN_METADATA_RISK_COMPONENT_NAME: &str =
|
||||
"kb-lib.materializer.token.metadata_risk";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_TOKEN_METADATA_RISK_PROCESSOR_NAME: &str = "materializer.token.metadata_risk";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
56
kb-lib/src/materializer/token/metadata_risk/materializer.rs
Normal file
56
kb-lib/src/materializer/token/metadata_risk/materializer.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// file: kb-lib/src/materializer/token/metadata_risk/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved token-metadata risk materializer.
|
||||
|
||||
/// Reserved materializer for token-metadata risk observations.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtTokenMetadataRiskMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtTokenMetadataRiskMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return crate::MT_TOKEN_METADATA_RISK_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, _event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtTokenMetadataRiskMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_TOKEN_METADATA_RISK_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return crate::MT_TOKEN_METADATA_RISK_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
_observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/trades.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `trades` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved trades materializer.
|
||||
pub use self::core::MtTradesMaterializer;
|
||||
pub use self::materializer::MtTradesMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_TRADES_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `trades` materializer.
|
||||
pub(crate) use self::constants::MT_TRADES_COMPONENT_NAME;
|
||||
/// Stable processor name for the `trades` materializer.
|
||||
pub(crate) use self::constants::MT_TRADES_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/trades/constants.rs
Normal file
11
kb-lib/src/materializer/trades/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/trades/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the trades materializer.
|
||||
|
||||
/// Stable runtime component name for the trades materializer.
|
||||
pub(crate) const MT_TRADES_COMPONENT_NAME: &str = "kb-lib.materializer.trades";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_TRADES_PROCESSOR_NAME: &str = "materializer.trades";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_TRADES_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
@@ -1,15 +1,15 @@
|
||||
// file: kb-lib/src/materializer/trades/core.rs
|
||||
// version: 4
|
||||
// file: kb-lib/src/materializer/trades/materializer.rs
|
||||
// version: 5
|
||||
|
||||
//! Reserved trades materializer.
|
||||
|
||||
/// Reserved materializer for trade observations.
|
||||
/// Reserved materializer for trades observations.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtTradesMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtTradesMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.trades";
|
||||
return crate::MT_TRADES_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
@@ -22,27 +22,22 @@ impl crate::MtMaterializer for crate::MtTradesMaterializer {
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &crate::MdDecodedProtocolEvent,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: crate::MdMaterializedEventFamily::Trade,
|
||||
}]);
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtTradesMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: "kb-lib.materializer.trades".to_string(),
|
||||
name: crate::MT_TRADES_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return &[];
|
||||
return crate::MT_TRADES_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
@@ -1,19 +1,24 @@
|
||||
// file: kb-lib/src/materializer/transaction.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `transaction` materializer family.
|
||||
|
||||
mod annotations;
|
||||
mod constants;
|
||||
|
||||
/// Stable committed Memo transaction annotation materializer.
|
||||
pub use self::annotations::MtTransactionAnnotationMaterializer;
|
||||
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::annotations::MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for transaction annotations.
|
||||
pub(crate) use self::annotations::MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME;
|
||||
/// Maximum Memo payload accepted by transaction annotations.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES;
|
||||
pub(crate) use self::annotations::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES;
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES` component constant.
|
||||
pub(crate) use self::annotations::MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES;
|
||||
/// Stable processor name for transaction annotations.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME;
|
||||
pub(crate) use self::annotations::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for transaction annotations.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION;
|
||||
pub(crate) use self::annotations::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for transaction annotations.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS;
|
||||
pub(crate) use self::annotations::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS;
|
||||
|
||||
@@ -1,611 +1,25 @@
|
||||
// file: kb-lib/src/materializer/transaction/annotations.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Exact committed SPL Memo transaction annotation projection.
|
||||
//! Committed Memo transaction annotation materializer component.
|
||||
|
||||
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[crate::MdEventFamily::Audit];
|
||||
const MEMO_ENTRY_NAMES: &[&str] = &["add_memo", "memo_intent", "invalid_memo_attempt"];
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum MemoGeneration {
|
||||
V1,
|
||||
V3,
|
||||
V4,
|
||||
}
|
||||
/// Stable committed Memo transaction annotation materializer.
|
||||
pub use self::materializer::MtTransactionAnnotationMaterializer;
|
||||
|
||||
impl MemoGeneration {
|
||||
fn from_event(event: &crate::MdDecodedProtocolEvent) -> std::option::Option<Self> {
|
||||
if event.program_id.0 == kb_program_ids::SPL_MEMO_V1_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.memo.v1"
|
||||
{
|
||||
return std::option::Option::Some(Self::V1);
|
||||
}
|
||||
if event.program_id.0 == kb_program_ids::SPL_MEMO_V3_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.memo.v3"
|
||||
{
|
||||
return std::option::Option::Some(Self::V3);
|
||||
}
|
||||
if event.program_id.0 == kb_program_ids::SPL_MEMO_V4_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.memo.v4"
|
||||
{
|
||||
return std::option::Option::Some(Self::V4);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn code(self) -> &'static str {
|
||||
return match self {
|
||||
Self::V1 => "v1",
|
||||
Self::V3 => "v3",
|
||||
Self::V4 => "v4",
|
||||
};
|
||||
}
|
||||
|
||||
fn signer_status(self) -> &'static str {
|
||||
return match self {
|
||||
Self::V1 => "not_applicable",
|
||||
Self::V3 | Self::V4 => "valid",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
struct MemoProjection {
|
||||
generation: MemoGeneration,
|
||||
memo_text: std::string::String,
|
||||
payload_length_bytes: u64,
|
||||
payload_sha256: std::string::String,
|
||||
required_signers: std::vec::Vec<std::string::String>,
|
||||
observed_signers: std::vec::Vec<std::string::String>,
|
||||
verified_signers: std::vec::Vec<std::string::String>,
|
||||
signer_validation_status: std::string::String,
|
||||
}
|
||||
|
||||
/// Stable transaction annotation materializer.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtTransactionAnnotationMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtTransactionAnnotationMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return "kb-lib.materializer.transaction.annotations";
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return accepts_event(event);
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtTransactionAnnotationMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return accepts_event(&observation.event);
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
if !accepts_event(&observation.event) {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if observation.transaction_failed || !observation.observation_committed {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"uncommitted_transaction_annotation_refused",
|
||||
"failed or uncommitted Memo observations cannot create transaction annotations",
|
||||
);
|
||||
}
|
||||
if observation.event.event_name.0 != "add_memo" {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"invalid_transaction_annotation_source_refused",
|
||||
"only a committed add_memo observation can create a transaction annotation",
|
||||
);
|
||||
}
|
||||
let projection_result = parse_projection(observation);
|
||||
let projection = match projection_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => {
|
||||
return failed("memo_annotation_payload_invalid", message);
|
||||
},
|
||||
};
|
||||
let idempotence_key = format!(
|
||||
"spl_memo:{}:{}:{}:{}",
|
||||
observation.event.signature.0,
|
||||
observation.event.instruction_path.0,
|
||||
observation.event.program_id.0,
|
||||
projection.payload_sha256
|
||||
);
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: "transaction_annotation:spl_memo:0".to_string(),
|
||||
family: crate::MdMaterializedEventFamily::TransactionAnnotation,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION,
|
||||
"domain": "transaction_annotation",
|
||||
"annotationKind": "spl.memo",
|
||||
"idempotenceKey": idempotence_key,
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"sourceKind": match observation.event.source_kind {
|
||||
crate::MdEventSourceKind::InnerInstruction => "inner_instruction",
|
||||
_ => "instruction"
|
||||
},
|
||||
"generation": projection.generation.code(),
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"text": projection.memo_text,
|
||||
"payloadLengthBytes": projection.payload_length_bytes,
|
||||
"payloadSha256": projection.payload_sha256,
|
||||
"signers": {
|
||||
"validationStatus": projection.signer_validation_status,
|
||||
"required": projection.required_signers,
|
||||
"observed": projection.observed_signers,
|
||||
"verified": projection.verified_signers
|
||||
},
|
||||
"committed": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"sourceProtocol": observation.event.protocol_code.0.clone(),
|
||||
"sourceSurface": observation.event.surface_code.0.clone(),
|
||||
"sourceEventCode": observation.event.event_code.0.clone(),
|
||||
"sourceEventKey": observation.event_key.clone()
|
||||
}
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS,
|
||||
action = "materialize_transaction_annotation",
|
||||
signature = %observation.event.signature.0,
|
||||
instruction_path = %observation.event.instruction_path.0,
|
||||
program_id = %observation.event.program_id.0,
|
||||
generation = projection.generation.code(),
|
||||
payload_length_bytes = projection.payload_length_bytes,
|
||||
output_count = 1_usize,
|
||||
"materialize committed SPL Memo transaction annotation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.event_family == crate::MdEventFamily::Audit
|
||||
&& event.protocol_code.0 == "spl.memo"
|
||||
&& (event.source_kind == crate::MdEventSourceKind::Instruction
|
||||
|| event.source_kind == crate::MdEventSourceKind::InnerInstruction)
|
||||
&& MemoGeneration::from_event(event).is_some()
|
||||
&& MEMO_ENTRY_NAMES.contains(&event.event_name.0.as_str());
|
||||
}
|
||||
|
||||
fn parse_projection(
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> std::result::Result<MemoProjection, std::string::String> {
|
||||
let generation = match MemoGeneration::from_event(&observation.event) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
"Memo generation and Program ID do not match".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let payload = &observation.payload_json;
|
||||
if payload.get("committed").and_then(serde_json::Value::as_bool)
|
||||
!= std::option::Option::Some(true)
|
||||
{
|
||||
return std::result::Result::Err("Memo payload is not marked committed".to_string());
|
||||
}
|
||||
if payload.get("transactionSucceeded").and_then(serde_json::Value::as_bool)
|
||||
!= std::option::Option::Some(true)
|
||||
|| payload.get("payloadComplete").and_then(serde_json::Value::as_bool)
|
||||
!= std::option::Option::Some(true)
|
||||
|| string_field(payload, "runtimeValidation")
|
||||
!= std::option::Option::Some("proven_by_successful_transaction")
|
||||
|| payload
|
||||
.get("utf8Validation")
|
||||
.and_then(|value| return string_field(value, "status"))
|
||||
!= std::option::Option::Some("valid")
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Memo successful runtime validation proof is incomplete".to_string(),
|
||||
);
|
||||
}
|
||||
if string_field(payload, "generation") != std::option::Option::Some(generation.code()) {
|
||||
return std::result::Result::Err(
|
||||
"Memo payload generation does not match its surface".to_string(),
|
||||
);
|
||||
}
|
||||
if string_field(payload, "programId")
|
||||
!= std::option::Option::Some(observation.event.program_id.0.as_str())
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Memo payload Program ID does not match its event".to_string(),
|
||||
);
|
||||
}
|
||||
if string_field(payload, "instructionPath")
|
||||
!= std::option::Option::Some(observation.event.instruction_path.0.as_str())
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Memo payload instruction path does not match its event".to_string(),
|
||||
);
|
||||
}
|
||||
let memo_text = match string_field(payload, "memoText") {
|
||||
std::option::Option::Some(value) => value.to_string(),
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("committed Memo has no UTF-8 text".to_string());
|
||||
},
|
||||
};
|
||||
let payload_length_bytes =
|
||||
match payload.get("payloadLengthBytes").and_then(serde_json::Value::as_u64) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("committed Memo has no byte length".to_string());
|
||||
},
|
||||
};
|
||||
let measured_length = match u64::try_from(memo_text.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!(
|
||||
"Memo text length conversion failed: {error}"
|
||||
));
|
||||
},
|
||||
};
|
||||
if measured_length != payload_length_bytes {
|
||||
return std::result::Result::Err(
|
||||
"Memo byte length does not match its UTF-8 text".to_string(),
|
||||
);
|
||||
}
|
||||
if payload_length_bytes > crate::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES {
|
||||
return std::result::Result::Err(
|
||||
"Memo byte length exceeds the projection limit".to_string(),
|
||||
);
|
||||
}
|
||||
let payload_sha256 = match string_field(payload, "payloadSha256") {
|
||||
std::option::Option::Some(value) if is_sha256(value) => value.to_string(),
|
||||
_ => return std::result::Result::Err("Memo payload SHA-256 is invalid".to_string()),
|
||||
};
|
||||
let signer_validation = match payload.get("signerValidation") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("Memo signer validation is absent".to_string());
|
||||
},
|
||||
};
|
||||
let signer_validation_status = match string_field(signer_validation, "status") {
|
||||
std::option::Option::Some(value) if value == generation.signer_status() => {
|
||||
value.to_string()
|
||||
},
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
"Memo signer validation status is inconsistent".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let missing_signers = match string_array(signer_validation, "missingRequiredSigners") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => return std::result::Result::Err(message),
|
||||
};
|
||||
if !missing_signers.is_empty() {
|
||||
return std::result::Result::Err("committed Memo reports missing signers".to_string());
|
||||
}
|
||||
let required_signers = match string_array(signer_validation, "orderedRequiredSigners") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => return std::result::Result::Err(message),
|
||||
};
|
||||
let observed_signers = match string_array(signer_validation, "orderedObservedSigners") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => return std::result::Result::Err(message),
|
||||
};
|
||||
let verified_signers = if generation == MemoGeneration::V1 {
|
||||
if !required_signers.is_empty() {
|
||||
return std::result::Result::Err("Memo v1 cannot require signers".to_string());
|
||||
}
|
||||
std::vec::Vec::new()
|
||||
} else {
|
||||
if required_signers != observed_signers {
|
||||
return std::result::Result::Err(
|
||||
"committed Memo required and observed signers differ".to_string(),
|
||||
);
|
||||
}
|
||||
observed_signers.clone()
|
||||
};
|
||||
return std::result::Result::Ok(MemoProjection {
|
||||
generation,
|
||||
memo_text,
|
||||
payload_length_bytes,
|
||||
payload_sha256,
|
||||
required_signers,
|
||||
observed_signers,
|
||||
verified_signers,
|
||||
signer_validation_status,
|
||||
});
|
||||
}
|
||||
|
||||
fn string_field<'a>(value: &'a serde_json::Value, key: &str) -> std::option::Option<&'a str> {
|
||||
return value.get(key).and_then(serde_json::Value::as_str);
|
||||
}
|
||||
|
||||
fn string_array(
|
||||
value: &serde_json::Value,
|
||||
key: &str,
|
||||
) -> std::result::Result<std::vec::Vec<std::string::String>, std::string::String> {
|
||||
let values = match value.get(key).and_then(serde_json::Value::as_array) {
|
||||
std::option::Option::Some(values) => values,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(format!("Memo signer field {key} is not an array"));
|
||||
},
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(values.len());
|
||||
for item in values {
|
||||
let text = match item.as_str() {
|
||||
std::option::Option::Some(text) if !text.trim().is_empty() => text,
|
||||
_ => {
|
||||
return std::result::Result::Err(format!(
|
||||
"Memo signer field {key} contains an invalid pubkey"
|
||||
));
|
||||
},
|
||||
};
|
||||
output.push(text.to_string());
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn is_sha256(value: &str) -> bool {
|
||||
return value.len() == 64
|
||||
&& value
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.all(|byte| return byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase());
|
||||
}
|
||||
|
||||
fn failed(code: &str, message: std::string::String) -> crate::MtApiMaterializerExecutionResult {
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Failed,
|
||||
outputs: std::vec::Vec::new(),
|
||||
diagnostics: std::vec![crate::MtApiMaterializerDiagnostic {
|
||||
code: code.to_string(),
|
||||
message,
|
||||
retriable: false,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn observation(
|
||||
program_id: &str,
|
||||
surface_code: &str,
|
||||
generation: &str,
|
||||
event_name: &str,
|
||||
failed: bool,
|
||||
committed: bool,
|
||||
required_signers: serde_json::Value,
|
||||
observed_signers: serde_json::Value,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: "memo:0".to_string(),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("1/0".to_string()),
|
||||
program_id: crate::MdProgramId(program_id.to_string()),
|
||||
protocol_code: crate::MdProtocolCode("spl.memo".to_string()),
|
||||
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
|
||||
event_code: crate::MdEventCode(format!("{surface_code}.{event_name}")),
|
||||
event_name: crate::MdEventName(event_name.to_string()),
|
||||
event_family: crate::MdEventFamily::Audit,
|
||||
source_kind: crate::MdEventSourceKind::InnerInstruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({
|
||||
"generation": generation,
|
||||
"programId": program_id,
|
||||
"instructionPath": "1/0",
|
||||
"transactionSucceeded": !failed,
|
||||
"committed": committed,
|
||||
"memoText": "memo",
|
||||
"payloadLengthBytes": 4,
|
||||
"payloadSha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"payloadComplete": true,
|
||||
"utf8Validation": {"status":"valid","invalidFromByte":null},
|
||||
"signerValidation": {
|
||||
"status": if generation == "v1" { "not_applicable" } else { "valid" },
|
||||
"orderedRequiredSigners": required_signers,
|
||||
"orderedObservedSigners": observed_signers,
|
||||
"missingRequiredSigners": []
|
||||
},
|
||||
"runtimeValidation": if failed {
|
||||
"not_proven_transaction_failed"
|
||||
} else {
|
||||
"proven_by_successful_transaction"
|
||||
}
|
||||
}),
|
||||
transaction_failed: failed,
|
||||
transaction_error: if failed {
|
||||
std::option::Option::Some(serde_json::json!({"InstructionError":[0,"Custom"]}))
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
observation_committed: committed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["memo".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn v4() -> crate::DcApiDecodedObservation {
|
||||
return observation(
|
||||
kb_program_ids::SPL_MEMO_V4_PROGRAM_ID,
|
||||
"spl.memo.v4",
|
||||
"v4",
|
||||
"add_memo",
|
||||
false,
|
||||
true,
|
||||
serde_json::json!(["signer", "signer"]),
|
||||
serde_json::json!(["signer", "signer"]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_v4_memo_becomes_deterministic_transaction_annotation() {
|
||||
let materializer = crate::MtTransactionAnnotationMaterializer;
|
||||
let first = crate::MtApiEventMaterializer::materialize(&materializer, &v4());
|
||||
let second = crate::MtApiEventMaterializer::materialize(&materializer, &v4());
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(first.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(first.outputs.len(), 1);
|
||||
assert_eq!(
|
||||
first.outputs[0].family,
|
||||
crate::MdMaterializedEventFamily::TransactionAnnotation
|
||||
);
|
||||
assert_eq!(
|
||||
first.outputs[0].payload_json["signers"]["verified"],
|
||||
serde_json::json!(["signer", "signer"])
|
||||
);
|
||||
assert_eq!(first.outputs[0].payload_json["committed"], true);
|
||||
assert!(first.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_v1_v3_and_v4_surfaces_are_accepted() {
|
||||
for (program_id, surface, generation) in [
|
||||
(kb_program_ids::SPL_MEMO_V1_PROGRAM_ID, "spl.memo.v1", "v1"),
|
||||
(kb_program_ids::SPL_MEMO_V3_PROGRAM_ID, "spl.memo.v3", "v3"),
|
||||
(kb_program_ids::SPL_MEMO_V4_PROGRAM_ID, "spl.memo.v4", "v4"),
|
||||
] {
|
||||
let observation = observation(
|
||||
program_id,
|
||||
surface,
|
||||
generation,
|
||||
"add_memo",
|
||||
false,
|
||||
true,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
);
|
||||
assert!(crate::MtApiEventMaterializer::accepts_observation(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_observed_accounts_are_not_claimed_as_runtime_verified() {
|
||||
let observation = observation(
|
||||
kb_program_ids::SPL_MEMO_V1_PROGRAM_ID,
|
||||
"spl.memo.v1",
|
||||
"v1",
|
||||
"add_memo",
|
||||
false,
|
||||
true,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!(["observed_only"]),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.outputs[0].payload_json["signers"]["verified"], serde_json::json!([]));
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["signers"]["observed"],
|
||||
serde_json::json!(["observed_only"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_or_uncommitted_memo_is_refused_without_output() {
|
||||
let observation = observation(
|
||||
kb_program_ids::SPL_MEMO_V4_PROGRAM_ID,
|
||||
"spl.memo.v4",
|
||||
"v4",
|
||||
"memo_intent",
|
||||
true,
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
);
|
||||
let materializer = crate::MtTransactionAnnotationMaterializer;
|
||||
let policy =
|
||||
crate::materializer_api_validate_materialization_policy(&materializer, &observation);
|
||||
assert!(policy.is_err());
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_committed_payload_fails_closed() {
|
||||
let mut observation = v4();
|
||||
observation.payload_json["payloadSha256"] = serde_json::Value::String("bad".to_string());
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Failed);
|
||||
assert!(result.outputs.is_empty());
|
||||
assert_eq!(result.diagnostics[0].code, "memo_annotation_payload_invalid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreign_audit_observation_is_not_accepted() {
|
||||
let mut observation = v4();
|
||||
observation.event.protocol_code = crate::MdProtocolCode("foreign".to_string());
|
||||
assert!(!crate::MtApiEventMaterializer::accepts_observation(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
));
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn machine_readable_matrix_declares_transaction_annotation_policy() {
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(include_str!(
|
||||
"../../../../test-fixtures/contract-matrices/SPL_MEMO_MATRIX.json"
|
||||
));
|
||||
let matrix = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("memo matrix is invalid JSON: {error}"),
|
||||
};
|
||||
assert_eq!(matrix["matrixVersion"], 4);
|
||||
assert_eq!(matrix["materializationContract"]["crate"], "kb-lib");
|
||||
assert_eq!(
|
||||
matrix["materializationContract"]["transactionPolicy"],
|
||||
"SuccessfulCommittedOnly"
|
||||
);
|
||||
assert_eq!(matrix["materializationContract"]["failedOrUncommittedOutput"], false);
|
||||
}
|
||||
}
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES` component constant.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for transaction annotations.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME;
|
||||
/// Maximum Memo payload accepted by transaction annotations.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES;
|
||||
/// Crate-visible `MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES` component constant.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES;
|
||||
/// Stable processor name for transaction annotations.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME;
|
||||
/// Stable projection contract version for transaction annotations.
|
||||
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION;
|
||||
/// Canonical tracing target for transaction annotations.
|
||||
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS;
|
||||
|
||||
25
kb-lib/src/materializer/transaction/annotations/constants.rs
Normal file
25
kb-lib/src/materializer/transaction/annotations/constants.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
// file: kb-lib/src/materializer/transaction/annotations/constants.rs
|
||||
// version: 5
|
||||
|
||||
//! Local constants for the transaction annotations materializer.
|
||||
|
||||
/// Stable runtime component name for the transaction annotations materializer.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME: &str =
|
||||
"kb-lib.materializer.transaction.annotations";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME: &str =
|
||||
"materializer.transaction.annotations";
|
||||
/// Maximum Memo payload accepted by transaction annotations.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES: u64 = 566;
|
||||
/// Stable projection contract version for the transaction annotations materializer.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION: u32 = 1;
|
||||
/// Canonical tracing target for the transaction annotations materializer.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS: &str =
|
||||
"kb-lib.materializer.transaction.annotations";
|
||||
|
||||
/// Component contract for `accepted_families`.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
|
||||
&[crate::MdEventFamily::Audit];
|
||||
/// Component contract for `memo_entry_names`.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES: &[&str] =
|
||||
&["add_memo", "memo_intent", "invalid_memo_attempt"];
|
||||
609
kb-lib/src/materializer/transaction/annotations/materializer.rs
Normal file
609
kb-lib/src/materializer/transaction/annotations/materializer.rs
Normal file
@@ -0,0 +1,609 @@
|
||||
// file: kb-lib/src/materializer/transaction/annotations/materializer.rs
|
||||
// version: 7
|
||||
|
||||
//! Exact committed SPL Memo transaction annotation projection.
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum MemoGeneration {
|
||||
V1,
|
||||
V3,
|
||||
V4,
|
||||
}
|
||||
|
||||
impl MemoGeneration {
|
||||
fn from_event(event: &crate::MdDecodedProtocolEvent) -> std::option::Option<Self> {
|
||||
if event.program_id.0 == kb_program_ids::SPL_MEMO_V1_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.memo.v1"
|
||||
{
|
||||
return std::option::Option::Some(Self::V1);
|
||||
}
|
||||
if event.program_id.0 == kb_program_ids::SPL_MEMO_V3_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.memo.v3"
|
||||
{
|
||||
return std::option::Option::Some(Self::V3);
|
||||
}
|
||||
if event.program_id.0 == kb_program_ids::SPL_MEMO_V4_PROGRAM_ID
|
||||
&& event.surface_code.0 == "spl.memo.v4"
|
||||
{
|
||||
return std::option::Option::Some(Self::V4);
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn code(self) -> &'static str {
|
||||
return match self {
|
||||
Self::V1 => "v1",
|
||||
Self::V3 => "v3",
|
||||
Self::V4 => "v4",
|
||||
};
|
||||
}
|
||||
|
||||
fn signer_status(self) -> &'static str {
|
||||
return match self {
|
||||
Self::V1 => "not_applicable",
|
||||
Self::V3 | Self::V4 => "valid",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
struct MemoProjection {
|
||||
generation: MemoGeneration,
|
||||
memo_text: std::string::String,
|
||||
payload_length_bytes: u64,
|
||||
payload_sha256: std::string::String,
|
||||
required_signers: std::vec::Vec<std::string::String>,
|
||||
observed_signers: std::vec::Vec<std::string::String>,
|
||||
verified_signers: std::vec::Vec<std::string::String>,
|
||||
signer_validation_status: std::string::String,
|
||||
}
|
||||
|
||||
/// Stable transaction annotation materializer.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MtTransactionAnnotationMaterializer;
|
||||
|
||||
impl crate::MtMaterializer for crate::MtTransactionAnnotationMaterializer {
|
||||
fn materializer_name(&self) -> &'static str {
|
||||
return crate::MT_TRANSACTION_ANNOTATIONS_COMPONENT_NAME;
|
||||
}
|
||||
|
||||
fn materializer_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return accepts_event(event);
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
_event: &crate::MdDecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::MtApiEventMaterializer for crate::MtTransactionAnnotationMaterializer {
|
||||
fn identity(&self) -> crate::MtApiMaterializerIdentity {
|
||||
return crate::MtApiMaterializerIdentity {
|
||||
name: crate::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
|
||||
return crate::MT_TRANSACTION_ANNOTATIONS_ACCEPTED_FAMILIES;
|
||||
}
|
||||
|
||||
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
|
||||
return accepts_event(&observation.event);
|
||||
}
|
||||
|
||||
fn transaction_policy(
|
||||
&self,
|
||||
_family: crate::MdEventFamily,
|
||||
) -> crate::MtApiMaterializationTransactionPolicy {
|
||||
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
||||
}
|
||||
|
||||
fn materialize(
|
||||
&self,
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> crate::MtApiMaterializerExecutionResult {
|
||||
if !accepts_event(&observation.event) {
|
||||
return crate::MtApiMaterializerExecutionResult::ignored();
|
||||
}
|
||||
if observation.transaction_failed || !observation.observation_committed {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"uncommitted_transaction_annotation_refused",
|
||||
"failed or uncommitted Memo observations cannot create transaction annotations",
|
||||
);
|
||||
}
|
||||
if observation.event.event_name.0 != "add_memo" {
|
||||
return crate::MtApiMaterializerExecutionResult::refused(
|
||||
"invalid_transaction_annotation_source_refused",
|
||||
"only a committed add_memo observation can create a transaction annotation",
|
||||
);
|
||||
}
|
||||
let projection_result = parse_projection(observation);
|
||||
let projection = match projection_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => {
|
||||
return failed("memo_annotation_payload_invalid", message);
|
||||
},
|
||||
};
|
||||
let idempotence_key = format!(
|
||||
"spl_memo:{}:{}:{}:{}",
|
||||
observation.event.signature.0,
|
||||
observation.event.instruction_path.0,
|
||||
observation.event.program_id.0,
|
||||
projection.payload_sha256
|
||||
);
|
||||
let output = crate::MtApiMaterializedOutput {
|
||||
output_key: "transaction_annotation:spl_memo:0".to_string(),
|
||||
family: crate::MdMaterializedEventFamily::TransactionAnnotation,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": crate::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION,
|
||||
"domain": "transaction_annotation",
|
||||
"annotationKind": "spl.memo",
|
||||
"idempotenceKey": idempotence_key,
|
||||
"signature": observation.event.signature.0.clone(),
|
||||
"slot": observation.event.slot.0,
|
||||
"instructionPath": observation.event.instruction_path.0.clone(),
|
||||
"sourceKind": match observation.event.source_kind {
|
||||
crate::MdEventSourceKind::InnerInstruction => "inner_instruction",
|
||||
_ => "instruction"
|
||||
},
|
||||
"generation": projection.generation.code(),
|
||||
"programId": observation.event.program_id.0.clone(),
|
||||
"text": projection.memo_text,
|
||||
"payloadLengthBytes": projection.payload_length_bytes,
|
||||
"payloadSha256": projection.payload_sha256,
|
||||
"signers": {
|
||||
"validationStatus": projection.signer_validation_status,
|
||||
"required": projection.required_signers,
|
||||
"observed": projection.observed_signers,
|
||||
"verified": projection.verified_signers
|
||||
},
|
||||
"committed": true,
|
||||
"provenance": {
|
||||
"processorName": crate::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME,
|
||||
"processorVersion": env!("CARGO_PKG_VERSION"),
|
||||
"sourceProtocol": observation.event.protocol_code.0.clone(),
|
||||
"sourceSurface": observation.event.surface_code.0.clone(),
|
||||
"sourceEventCode": observation.event.event_code.0.clone(),
|
||||
"sourceEventKey": observation.event_key.clone()
|
||||
}
|
||||
}),
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS,
|
||||
action = "materialize_transaction_annotation",
|
||||
signature = %observation.event.signature.0,
|
||||
instruction_path = %observation.event.instruction_path.0,
|
||||
program_id = %observation.event.program_id.0,
|
||||
generation = projection.generation.code(),
|
||||
payload_length_bytes = projection.payload_length_bytes,
|
||||
output_count = 1_usize,
|
||||
"materialize committed SPL Memo transaction annotation"
|
||||
);
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
|
||||
outputs: std::vec![output],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
|
||||
return event.event_family == crate::MdEventFamily::Audit
|
||||
&& event.protocol_code.0 == "spl.memo"
|
||||
&& (event.source_kind == crate::MdEventSourceKind::Instruction
|
||||
|| event.source_kind == crate::MdEventSourceKind::InnerInstruction)
|
||||
&& MemoGeneration::from_event(event).is_some()
|
||||
&& crate::MT_TRANSACTION_ANNOTATIONS_MEMO_ENTRY_NAMES
|
||||
.contains(&event.event_name.0.as_str());
|
||||
}
|
||||
|
||||
fn parse_projection(
|
||||
observation: &crate::DcApiDecodedObservation,
|
||||
) -> std::result::Result<MemoProjection, std::string::String> {
|
||||
let generation = match MemoGeneration::from_event(&observation.event) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
"Memo generation and Program ID do not match".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let payload = &observation.payload_json;
|
||||
if payload.get("committed").and_then(serde_json::Value::as_bool)
|
||||
!= std::option::Option::Some(true)
|
||||
{
|
||||
return std::result::Result::Err("Memo payload is not marked committed".to_string());
|
||||
}
|
||||
if payload.get("transactionSucceeded").and_then(serde_json::Value::as_bool)
|
||||
!= std::option::Option::Some(true)
|
||||
|| payload.get("payloadComplete").and_then(serde_json::Value::as_bool)
|
||||
!= std::option::Option::Some(true)
|
||||
|| string_field(payload, "runtimeValidation")
|
||||
!= std::option::Option::Some("proven_by_successful_transaction")
|
||||
|| payload
|
||||
.get("utf8Validation")
|
||||
.and_then(|value| return string_field(value, "status"))
|
||||
!= std::option::Option::Some("valid")
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Memo successful runtime validation proof is incomplete".to_string(),
|
||||
);
|
||||
}
|
||||
if string_field(payload, "generation") != std::option::Option::Some(generation.code()) {
|
||||
return std::result::Result::Err(
|
||||
"Memo payload generation does not match its surface".to_string(),
|
||||
);
|
||||
}
|
||||
if string_field(payload, "programId")
|
||||
!= std::option::Option::Some(observation.event.program_id.0.as_str())
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Memo payload Program ID does not match its event".to_string(),
|
||||
);
|
||||
}
|
||||
if string_field(payload, "instructionPath")
|
||||
!= std::option::Option::Some(observation.event.instruction_path.0.as_str())
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Memo payload instruction path does not match its event".to_string(),
|
||||
);
|
||||
}
|
||||
let memo_text = match string_field(payload, "memoText") {
|
||||
std::option::Option::Some(value) => value.to_string(),
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("committed Memo has no UTF-8 text".to_string());
|
||||
},
|
||||
};
|
||||
let payload_length_bytes =
|
||||
match payload.get("payloadLengthBytes").and_then(serde_json::Value::as_u64) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("committed Memo has no byte length".to_string());
|
||||
},
|
||||
};
|
||||
let measured_length = match u64::try_from(memo_text.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!(
|
||||
"Memo text length conversion failed: {error}"
|
||||
));
|
||||
},
|
||||
};
|
||||
if measured_length != payload_length_bytes {
|
||||
return std::result::Result::Err(
|
||||
"Memo byte length does not match its UTF-8 text".to_string(),
|
||||
);
|
||||
}
|
||||
if payload_length_bytes > crate::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES {
|
||||
return std::result::Result::Err(
|
||||
"Memo byte length exceeds the projection limit".to_string(),
|
||||
);
|
||||
}
|
||||
let payload_sha256 = match string_field(payload, "payloadSha256") {
|
||||
std::option::Option::Some(value) if is_sha256(value) => value.to_string(),
|
||||
_ => return std::result::Result::Err("Memo payload SHA-256 is invalid".to_string()),
|
||||
};
|
||||
let signer_validation = match payload.get("signerValidation") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err("Memo signer validation is absent".to_string());
|
||||
},
|
||||
};
|
||||
let signer_validation_status = match string_field(signer_validation, "status") {
|
||||
std::option::Option::Some(value) if value == generation.signer_status() => {
|
||||
value.to_string()
|
||||
},
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
"Memo signer validation status is inconsistent".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let missing_signers = match string_array(signer_validation, "missingRequiredSigners") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => return std::result::Result::Err(message),
|
||||
};
|
||||
if !missing_signers.is_empty() {
|
||||
return std::result::Result::Err("committed Memo reports missing signers".to_string());
|
||||
}
|
||||
let required_signers = match string_array(signer_validation, "orderedRequiredSigners") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => return std::result::Result::Err(message),
|
||||
};
|
||||
let observed_signers = match string_array(signer_validation, "orderedObservedSigners") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => return std::result::Result::Err(message),
|
||||
};
|
||||
let verified_signers = if generation == MemoGeneration::V1 {
|
||||
if !required_signers.is_empty() {
|
||||
return std::result::Result::Err("Memo v1 cannot require signers".to_string());
|
||||
}
|
||||
std::vec::Vec::new()
|
||||
} else {
|
||||
if required_signers != observed_signers {
|
||||
return std::result::Result::Err(
|
||||
"committed Memo required and observed signers differ".to_string(),
|
||||
);
|
||||
}
|
||||
observed_signers.clone()
|
||||
};
|
||||
return std::result::Result::Ok(MemoProjection {
|
||||
generation,
|
||||
memo_text,
|
||||
payload_length_bytes,
|
||||
payload_sha256,
|
||||
required_signers,
|
||||
observed_signers,
|
||||
verified_signers,
|
||||
signer_validation_status,
|
||||
});
|
||||
}
|
||||
|
||||
fn string_field<'a>(value: &'a serde_json::Value, key: &str) -> std::option::Option<&'a str> {
|
||||
return value.get(key).and_then(serde_json::Value::as_str);
|
||||
}
|
||||
|
||||
fn string_array(
|
||||
value: &serde_json::Value,
|
||||
key: &str,
|
||||
) -> std::result::Result<std::vec::Vec<std::string::String>, std::string::String> {
|
||||
let values = match value.get(key).and_then(serde_json::Value::as_array) {
|
||||
std::option::Option::Some(values) => values,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(format!("Memo signer field {key} is not an array"));
|
||||
},
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(values.len());
|
||||
for item in values {
|
||||
let text = match item.as_str() {
|
||||
std::option::Option::Some(text) if !text.trim().is_empty() => text,
|
||||
_ => {
|
||||
return std::result::Result::Err(format!(
|
||||
"Memo signer field {key} contains an invalid pubkey"
|
||||
));
|
||||
},
|
||||
};
|
||||
output.push(text.to_string());
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn is_sha256(value: &str) -> bool {
|
||||
return value.len() == 64
|
||||
&& value
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.all(|byte| return byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase());
|
||||
}
|
||||
|
||||
fn failed(code: &str, message: std::string::String) -> crate::MtApiMaterializerExecutionResult {
|
||||
return crate::MtApiMaterializerExecutionResult {
|
||||
status: crate::MtApiMaterializerOutcomeStatus::Failed,
|
||||
outputs: std::vec::Vec::new(),
|
||||
diagnostics: std::vec![crate::MtApiMaterializerDiagnostic {
|
||||
code: code.to_string(),
|
||||
message,
|
||||
retriable: false,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn observation(
|
||||
program_id: &str,
|
||||
surface_code: &str,
|
||||
generation: &str,
|
||||
event_name: &str,
|
||||
failed: bool,
|
||||
committed: bool,
|
||||
required_signers: serde_json::Value,
|
||||
observed_signers: serde_json::Value,
|
||||
) -> crate::DcApiDecodedObservation {
|
||||
return crate::DcApiDecodedObservation {
|
||||
event_key: "memo:0".to_string(),
|
||||
event: crate::MdDecodedProtocolEvent {
|
||||
signature: crate::MdSignature("signature".to_string()),
|
||||
slot: crate::MdSlot(42),
|
||||
instruction_path: crate::MdInstructionPath("1/0".to_string()),
|
||||
program_id: crate::MdProgramId(program_id.to_string()),
|
||||
protocol_code: crate::MdProtocolCode("spl.memo".to_string()),
|
||||
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
|
||||
event_code: crate::MdEventCode(format!("{surface_code}.{event_name}")),
|
||||
event_name: crate::MdEventName(event_name.to_string()),
|
||||
event_family: crate::MdEventFamily::Audit,
|
||||
source_kind: crate::MdEventSourceKind::InnerInstruction,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
},
|
||||
payload_json: serde_json::json!({
|
||||
"generation": generation,
|
||||
"programId": program_id,
|
||||
"instructionPath": "1/0",
|
||||
"transactionSucceeded": !failed,
|
||||
"committed": committed,
|
||||
"memoText": "memo",
|
||||
"payloadLengthBytes": 4,
|
||||
"payloadSha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"payloadComplete": true,
|
||||
"utf8Validation": {"status":"valid","invalidFromByte":null},
|
||||
"signerValidation": {
|
||||
"status": if generation == "v1" { "not_applicable" } else { "valid" },
|
||||
"orderedRequiredSigners": required_signers,
|
||||
"orderedObservedSigners": observed_signers,
|
||||
"missingRequiredSigners": []
|
||||
},
|
||||
"runtimeValidation": if failed {
|
||||
"not_proven_transaction_failed"
|
||||
} else {
|
||||
"proven_by_successful_transaction"
|
||||
}
|
||||
}),
|
||||
transaction_failed: failed,
|
||||
transaction_error: if failed {
|
||||
std::option::Option::Some(serde_json::json!({"InstructionError":[0,"Custom"]}))
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
observation_committed: committed,
|
||||
proof: crate::DcApiDecoderProof {
|
||||
kind: crate::DcApiDecoderProofKind::Manual,
|
||||
confidence: crate::MdDecoderConfidence::ManualExact,
|
||||
evidence: std::vec!["memo".to_string()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn v4() -> crate::DcApiDecodedObservation {
|
||||
return observation(
|
||||
kb_program_ids::SPL_MEMO_V4_PROGRAM_ID,
|
||||
"spl.memo.v4",
|
||||
"v4",
|
||||
"add_memo",
|
||||
false,
|
||||
true,
|
||||
serde_json::json!(["signer", "signer"]),
|
||||
serde_json::json!(["signer", "signer"]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_v4_memo_becomes_deterministic_transaction_annotation() {
|
||||
let materializer = crate::MtTransactionAnnotationMaterializer;
|
||||
let first = crate::MtApiEventMaterializer::materialize(&materializer, &v4());
|
||||
let second = crate::MtApiEventMaterializer::materialize(&materializer, &v4());
|
||||
assert_eq!(first, second);
|
||||
assert_eq!(first.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
|
||||
assert_eq!(first.outputs.len(), 1);
|
||||
assert_eq!(
|
||||
first.outputs[0].family,
|
||||
crate::MdMaterializedEventFamily::TransactionAnnotation
|
||||
);
|
||||
assert_eq!(
|
||||
first.outputs[0].payload_json["signers"]["verified"],
|
||||
serde_json::json!(["signer", "signer"])
|
||||
);
|
||||
assert_eq!(first.outputs[0].payload_json["committed"], true);
|
||||
assert!(first.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_v1_v3_and_v4_surfaces_are_accepted() {
|
||||
for (program_id, surface, generation) in [
|
||||
(kb_program_ids::SPL_MEMO_V1_PROGRAM_ID, "spl.memo.v1", "v1"),
|
||||
(kb_program_ids::SPL_MEMO_V3_PROGRAM_ID, "spl.memo.v3", "v3"),
|
||||
(kb_program_ids::SPL_MEMO_V4_PROGRAM_ID, "spl.memo.v4", "v4"),
|
||||
] {
|
||||
let observation = observation(
|
||||
program_id,
|
||||
surface,
|
||||
generation,
|
||||
"add_memo",
|
||||
false,
|
||||
true,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
);
|
||||
assert!(crate::MtApiEventMaterializer::accepts_observation(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_observed_accounts_are_not_claimed_as_runtime_verified() {
|
||||
let observation = observation(
|
||||
kb_program_ids::SPL_MEMO_V1_PROGRAM_ID,
|
||||
"spl.memo.v1",
|
||||
"v1",
|
||||
"add_memo",
|
||||
false,
|
||||
true,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!(["observed_only"]),
|
||||
);
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.outputs[0].payload_json["signers"]["verified"], serde_json::json!([]));
|
||||
assert_eq!(
|
||||
result.outputs[0].payload_json["signers"]["observed"],
|
||||
serde_json::json!(["observed_only"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_or_uncommitted_memo_is_refused_without_output() {
|
||||
let observation = observation(
|
||||
kb_program_ids::SPL_MEMO_V4_PROGRAM_ID,
|
||||
"spl.memo.v4",
|
||||
"v4",
|
||||
"memo_intent",
|
||||
true,
|
||||
false,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
);
|
||||
let materializer = crate::MtTransactionAnnotationMaterializer;
|
||||
let policy =
|
||||
crate::materializer_api_validate_materialization_policy(&materializer, &observation);
|
||||
assert!(policy.is_err());
|
||||
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
|
||||
assert!(result.outputs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_committed_payload_fails_closed() {
|
||||
let mut observation = v4();
|
||||
observation.payload_json["payloadSha256"] = serde_json::Value::String("bad".to_string());
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Failed);
|
||||
assert!(result.outputs.is_empty());
|
||||
assert_eq!(result.diagnostics[0].code, "memo_annotation_payload_invalid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreign_audit_observation_is_not_accepted() {
|
||||
let mut observation = v4();
|
||||
observation.event.protocol_code = crate::MdProtocolCode("foreign".to_string());
|
||||
assert!(!crate::MtApiEventMaterializer::accepts_observation(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
));
|
||||
let result = crate::MtApiEventMaterializer::materialize(
|
||||
&crate::MtTransactionAnnotationMaterializer,
|
||||
&observation,
|
||||
);
|
||||
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn machine_readable_matrix_declares_transaction_annotation_policy() {
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(include_str!(
|
||||
"../../../../../test-fixtures/contract-matrices/SPL_MEMO_MATRIX.json"
|
||||
));
|
||||
let matrix = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("memo matrix is invalid JSON: {error}"),
|
||||
};
|
||||
assert_eq!(matrix["matrixVersion"], 4);
|
||||
assert_eq!(matrix["materializationContract"]["crate"], "kb-lib");
|
||||
assert_eq!(
|
||||
matrix["materializationContract"]["transactionPolicy"],
|
||||
"SuccessfulCommittedOnly"
|
||||
);
|
||||
assert_eq!(matrix["materializationContract"]["failedOrUncommittedOutput"], false);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// file: kb-lib/src/materializer/transaction/constants.rs
|
||||
// version: 3
|
||||
|
||||
//! Local constants for the transaction annotation materializer.
|
||||
|
||||
/// Maximum Memo payload accepted from the decoded contract.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES: u64 = 4_096;
|
||||
/// Stable materializer processor name.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME: &str =
|
||||
"materializer.transaction.annotations";
|
||||
/// Stable projection contract version.
|
||||
pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION: u32 = 1;
|
||||
/// Canonical tracing target for this component.
|
||||
pub(crate) const TRACING_TARGET_MATERIALIZER_TRANSACTION_ANNOTATIONS: &str =
|
||||
"kb-lib.materializer.transaction";
|
||||
@@ -1,9 +1,17 @@
|
||||
// file: kb-lib/src/materializer/vault.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! `vault` materializer family.
|
||||
|
||||
mod core;
|
||||
mod constants;
|
||||
mod materializer;
|
||||
|
||||
/// Reserved vault materializer.
|
||||
pub use self::core::MtVaultMaterializer;
|
||||
pub use self::materializer::MtVaultMaterializer;
|
||||
|
||||
/// Empty accepted-family boundary for the reserved component.
|
||||
pub(crate) use self::constants::MT_VAULT_ACCEPTED_FAMILIES;
|
||||
/// Stable runtime component name for the `vault` materializer.
|
||||
pub(crate) use self::constants::MT_VAULT_COMPONENT_NAME;
|
||||
/// Stable processor name for the `vault` materializer.
|
||||
pub(crate) use self::constants::MT_VAULT_PROCESSOR_NAME;
|
||||
|
||||
11
kb-lib/src/materializer/vault/constants.rs
Normal file
11
kb-lib/src/materializer/vault/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// file: kb-lib/src/materializer/vault/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Local constants for the vault materializer.
|
||||
|
||||
/// Stable runtime component name for the vault materializer.
|
||||
pub(crate) const MT_VAULT_COMPONENT_NAME: &str = "kb-lib.materializer.vault";
|
||||
/// Stable processor name persisted by the replay ledger.
|
||||
pub(crate) const MT_VAULT_PROCESSOR_NAME: &str = "materializer.vault";
|
||||
/// Empty family boundary retained while the materializer is reserved.
|
||||
pub(crate) const MT_VAULT_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user