SHOWING: First 60
(3, 0, 0, witness.clone()),
],
..Default::default()
});
let first = InscriptionId { txid, index: 0 }; // normal
let second = InscriptionId { txid, index: 1 }; // cursed reinscription
let fourth = InscriptionId { txid, index: 3 }; // cursed but bound
let ninth = InscriptionId { txid, index: 8 }; // cursed reinscription
context.mine_blocks(1);
context.index.assert_inscription_location(
first,
SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 0,
},
Some(50 * COIN_VALUE),
);
context.index.assert_inscription_location(
second,
SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 0,
},
Some(50 * COIN_VALUE),
);
context.index.assert_inscription_location(
fourth,
SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 50 * COIN_VALUE,
},
Some(100 * COIN_VALUE),
#44,987,311text first,
SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 0,
},
Some(50 * COIN_VALUE),
);
context.index.assert_inscription_location(
second,
SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 50 * COIN_VALUE,
},
Some(100 * COIN_VALUE),
);
context.index.assert_inscription_location(
third,
SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 100 * COIN_VALUE,
},
Some(150 * COIN_VALUE),
);
assert_eq!(
context
.index
.get_inscription_entry(first)
.unwrap()
.unwrap()
.inscription_number,
0
);
assert_eq!(
context
.index
.get_inscription_entry(second)
.unwrap()
.unwrap()
.inscription_number,
-1
);
assert_eq!(
context
.index
.get_inscription_entry(third)
#44,987,310text );
context.index.assert_inscription_location(
ninth,
SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 100 * COIN_VALUE,
},
Some(150 * COIN_VALUE),
);
assert_eq!(
context
.index
.get_inscription_entry(first)
.unwrap()
.unwrap()
.inscription_number,
0
);
assert_eq!(
context
.index
.get_inscription_id_by_inscription_number(-3)
.unwrap()
.unwrap(),
fourth
);
assert_eq!(
context
.index
.get_inscription_entry(fourth)
.unwrap()
.unwrap()
.inscription_number,
-3
);
assert_eq!(
context
.index
.get_inscription_entry(ninth)
.unwrap()
.unwrap()
.inscription_number,
-8
);
}
}
#[test]
fn genesis_fee_distributed_evenly() {
for context in Context::configurations() {
#44,987,296text context.mine_blocks(1);
let witness = envelope(&[
b"ord",
&[1],
b"text/plain;charset=utf-8",
&[2],
b"bar",
&[4],
b"ord",
]);
let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, witness)],
..Default::default()
});
let inscription_id = InscriptionId { txid, index: 0 };
context.mine_blocks(1);
context.index.assert_inscription_location(
inscription_id,
SatPoint {
outpoint: unbound_outpoint(),
offset: 0,
},
None,
);
assert_eq!(
context
.index
.get_inscription_entry(inscription_id)
.unwrap()
.unwrap()
.inscription_number,
-1
);
}
}
#[test]
fn duplicate_field_inscriptions_are_cursed() {
for context in Context::configurations() {
context.mine_blocks(1);
let witness = envelope(&[
b"ord",
&[1],
#44,987,291text .unwrap()
.unwrap()
.inscription_number,
-2
);
}
}
#[test]
fn multiple_inscriptions_same_input_are_cursed_reinscriptions() {
for context in Context::configurations() {
context.rpc_server.mine_blocks(1);
let script = script::Builder::new()
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_slice([1])
.push_slice(b"text/plain;charset=utf-8")
.push_slice([])
.push_slice(b"foo")
.push_opcode(opcodes::all::OP_ENDIF)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_slice([1])
.push_slice(b"text/plain;charset=utf-8")
.push_slice([])
.push_slice(b"bar")
.push_opcode(opcodes::all::OP_ENDIF)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_slice([1])
.push_slice(b"text/plain;charset=utf-8")
#44,987,266text ..Default::default()
});
let inscription_id = InscriptionId { txid, index: 0 };
context.mine_blocks(1);
context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(2, 1, 0, Default::default())],
fee: 50 * COIN_VALUE,
..Default::default()
});
let coinbase_tx = context.mine_blocks(1)[0].txdata[0].txid();
context.index.assert_inscription_location(
inscription_id,
SatPoint {
outpoint: OutPoint {
txid: coinbase_tx,
vout: 0,
},
offset: 50 * COIN_VALUE,
},
Some(50 * COIN_VALUE),
);
}
}
#[test]
fn two_input_fee_spent_inscriptions_are_tracked_correctly() {
for context in Context::configurations() {
context.mine_blocks(2);
let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())],
..Default::default()
});
let inscription_id = InscriptionId { txid, index: 0 };
#44,987,243textuse super::*;
#[derive(Deserialize, Default, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
pub(crate) struct Config {
pub(crate) hidden: HashSet<InscriptionId>,
pub(crate) bitcoin_rpc_pass: Option<String>,
pub(crate) bitcoin_rpc_user: Option<String>,
}
impl Config {
pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool {
self.hidden.contains(&inscription_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inscriptions_can_be_hidden() {
let a = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0"
.parse::<InscriptionId>()
.unwrap();
let b = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi1"
.parse::<InscriptionId>()
.unwrap();
let config = Config {
hidden: iter::once(a).collect(),
..Default::default()
};
assert!(config.is_hidden(a));
assert!(!config.is_hidden(b));
}
#[test]
fn example_config_file_is_valid() {
let _: Config = serde_yaml::from_reader(File::open("ord.yaml").unwrap()).unwrap();
#44,985,746textpub(crate) use {
super::*,
bitcoin::{
blockdata::{opcodes, script, script::PushBytesBuf},
ScriptBuf, Witness,
},
pretty_assertions::assert_eq as pretty_assert_eq,
std::iter,
test_bitcoincore_rpc::TransactionTemplate,
unindent::Unindent,
};
macro_rules! assert_regex_match {
($value:expr, $pattern:expr $(,)?) => {
let regex = Regex::new(&format!("^(?s){}$", $pattern)).unwrap();
let string = $value.to_string();
if !regex.is_match(string.as_ref()) {
panic!(
"Regex:\n\n{}\n\n…did not match string:\n\n{}",
regex, string
);
}
};
}
macro_rules! assert_matches {
($expression:expr, $( $pattern:pat_param )|+ $( if $guard:expr )? $(,)?) => {
match $expression {
$( $pattern )|+ $( if $guard )? => {}
left => panic!(
"assertion failed: (left ~= right)\n left: `{:?}`\n right: `{}`",
left,
stringify!($($pattern)|+ $(if $guard)?)
),
}
}
}
pub(crate) fn blockhash(n: u64) -> BlockHash {
let hex = format!("{n:x}");#44,985,565text
if hex.is_empty() || hex.len() > 1 {
panic!();
}
hex.repeat(64).parse().unwrap()
}
pub(crate) fn txid(n: u64) -> Txid {
let hex = format!("{n:x}");
if hex.is_empty() || hex.len() > 1 {
panic!();
}
hex.repeat(64).parse().unwrap()
}
pub(crate) fn outpoint(n: u64) -> OutPoint {
format!("{}:{}", txid(n), n).parse().unwrap()
}
pub(crate) fn satpoint(n: u64, offset: u64) -> SatPoint {
SatPoint {
outpoint: outpoint(n),
offset,
}
}
pub(crate) fn address() -> Address {
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
.parse::<Address<NetworkUnchecked>>()
.unwrap()
.assume_checked()
}
pub(crate) fn recipient() -> Address {
"tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz"
.parse::<Address<NetworkUnchecked>>()
.unwrap()
.assume_checked()
}
pub(crate) fn change(n: u64) -> Address {
match n {
0 => "tb1qjsv26lap3ffssj6hfy8mzn0lg5vte6a42j75ww",
1 => "tb1qakxxzv9n7706kc3xdcycrtfv8cqv62hnwexc0l",
2 => "tb1qxz9yk0td0yye009gt6ayn7jthz5p07a75luryg",
3 => "tb1qe62s57n77pfhlw2vtqlhm87dwj75l6fguavjjq",
#44,985,551texttransfer50,000.mice#43,873,689brc-20