Skip to content

Commit

Permalink
Make inscription type more flexible
Browse files Browse the repository at this point in the history
  • Loading branch information
casey committed Dec 6, 2022
1 parent b37a0f6 commit 65b1cee
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 82 deletions.
4 changes: 4 additions & 0 deletions src/content_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub(crate) enum ContentType {
Text,
Png,
}
116 changes: 56 additions & 60 deletions src/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,18 @@ use {
util::taproot::TAPROOT_ANNEX_PREFIX,
Script, Witness,
},
std::{
iter::Peekable,
str::{self, Utf8Error},
},
std::iter::Peekable,
};

const PROTOCOL_ID: &[u8] = b"ord";
const RESOURCE_TAG: &[u8] = &[];
const TYPE_TAG: &[u8] = &[1];

const CONTENT_TAG: &[u8] = &[];
const CONTENT_TYPE_TAG: &[u8] = &[1];

#[derive(Debug, PartialEq)]
pub(crate) enum Inscription {
Text(Vec<u8>),
Png(Vec<u8>),
pub(crate) struct Inscription {
pub(crate) content_type: Vec<u8>,
pub(crate) content: Vec<u8>,
}

impl Inscription {
Expand All @@ -30,51 +28,57 @@ impl Inscription {
}

pub(crate) fn from_file(path: PathBuf) -> Result<Self, Error> {
let file = fs::read(&path).with_context(|| format!("io error reading {}", path.display()))?;
let content =
fs::read(&path).with_context(|| format!("io error reading {}", path.display()))?;

if file.len() > 520 {
if content.len() > 520 {
bail!("file size exceeds 520 bytes");
}

match path
let content_type = match path
.extension()
.ok_or_else(|| anyhow!("file must have extension"))?
.to_str()
.ok_or_else(|| anyhow!("unrecognized extension"))?
{
"txt" => Ok(Inscription::Text(file)),
"png" => Ok(Inscription::Png(file)),
other => Err(anyhow!(
"unrecognized file extension `.{other}`, only .txt and .png accepted"
)),
}
"txt" => "text/plain;charset=utf-8",
"png" => "image/png",
other => {
return Err(anyhow!(
"unrecognized file extension `.{other}`, only .txt and .png accepted"
))
}
};

Ok(Self {
content_type: content_type.into(),
content,
})
}

pub(crate) fn append_reveal_script(&self, builder: script::Builder) -> Script {
builder
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(PROTOCOL_ID)
.push_slice(TYPE_TAG)
.push_slice(self.media_type().as_bytes())
.push_slice(RESOURCE_TAG)
.push_slice(self.resource())
.push_slice(CONTENT_TYPE_TAG)
.push_slice(&self.content_type)
.push_slice(CONTENT_TAG)
.push_slice(&self.content)
.push_opcode(opcodes::all::OP_ENDIF)
.into_script()
}

fn media_type(&self) -> &str {
match self {
Inscription::Text(_) => "text/plain;charset=utf-8",
Inscription::Png(_) => "image/png",
pub(crate) fn content_type(&self) -> Option<ContentType> {
match self.content_type.as_slice() {
b"text/plain;charset=utf-8" => Some(ContentType::Text),
b"image/png" => Some(ContentType::Png),
_ => None,
}
}

fn resource(&self) -> &[u8] {
match self {
Inscription::Text(text) => text.as_ref(),
Inscription::Png(png) => png.as_ref(),
}
pub(crate) fn content(&self) -> &[u8] {
&self.content
}
}

Expand All @@ -84,7 +88,6 @@ enum InscriptionError {
KeyPathSpend,
Script(script::Error),
NoInscription,
Utf8Decode(Utf8Error),
InvalidInscription,
}

Expand Down Expand Up @@ -154,17 +157,13 @@ impl<'a> InscriptionParser<'a> {
return Err(InscriptionError::NoInscription);
}

if !self.accept(Instruction::PushBytes(TYPE_TAG))? {
if !self.accept(Instruction::PushBytes(CONTENT_TYPE_TAG))? {
return Err(InscriptionError::InvalidInscription);
}

let media_type = if let Instruction::PushBytes(bytes) = self.advance()? {
str::from_utf8(bytes).map_err(InscriptionError::Utf8Decode)?
} else {
return Err(InscriptionError::InvalidInscription);
};
let content_type = self.expect_push()?;

if !self.accept(Instruction::PushBytes(RESOURCE_TAG))? {
if !self.accept(Instruction::PushBytes(CONTENT_TAG))? {
return Err(InscriptionError::InvalidInscription);
}

Expand All @@ -173,19 +172,16 @@ impl<'a> InscriptionParser<'a> {
content.extend_from_slice(self.expect_push()?);
}

let inscription = match media_type {
"text/plain;charset=utf-8" => Some(Inscription::Text(content)),
"image/png" => Some(Inscription::Png(content)),
_ => None,
};

return Ok(inscription);
return Ok(Some(Inscription {
content,
content_type: content_type.into(),
}));
}

Ok(None)
}

fn expect_push(&mut self) -> Result<&[u8]> {
fn expect_push(&mut self) -> Result<&'a [u8]> {
match self.advance()? {
Instruction::PushBytes(bytes) => Ok(bytes),
_ => Err(InscriptionError::InvalidInscription),
Expand Down Expand Up @@ -276,12 +272,12 @@ mod tests {
&[],
b"ord",
])),
Ok(Inscription::Text("ord".into()))
Ok(inscription("text/plain;charset=utf-8", "ord")),
);
}

#[test]
fn valid_resource_in_multiple_pushes() {
fn valid_content_in_multiple_pushes() {
assert_eq!(
InscriptionParser::parse(&container(&[
b"ord",
Expand All @@ -291,20 +287,20 @@ mod tests {
b"foo",
b"bar"
])),
Ok(Inscription::Text("foobar".into()))
Ok(inscription("text/plain;charset=utf-8", "foobar")),
);
}

#[test]
fn valid_resource_in_zero_pushes() {
fn valid_content_in_zero_pushes() {
assert_eq!(
InscriptionParser::parse(&container(&[
b"ord",
&[1],
b"text/plain;charset=utf-8",
&[]
])),
Ok(Inscription::Text("".into()))
Ok(inscription("text/plain;charset=utf-8", "")),
);
}

Expand All @@ -324,7 +320,7 @@ mod tests {

assert_eq!(
InscriptionParser::parse(&Witness::from_vec(vec![script.into_bytes(), vec![]])),
Ok(Inscription::Text("ord".into()))
Ok(inscription("text/plain;charset=utf-8", "ord")),
);
}

Expand All @@ -344,7 +340,7 @@ mod tests {

assert_eq!(
InscriptionParser::parse(&Witness::from_vec(vec![script.into_bytes(), vec![]])),
Ok(Inscription::Text("ord".into()))
Ok(inscription("text/plain;charset=utf-8", "ord")),
);
}

Expand All @@ -371,22 +367,22 @@ mod tests {

assert_eq!(
InscriptionParser::parse(&Witness::from_vec(vec![script.into_bytes(), vec![]])),
Ok(Inscription::Text("foo".into()))
Ok(inscription("text/plain;charset=utf-8", "foo")),
);
}

#[test]
fn invalid_utf8_is_allowed() {
assert!(matches!(
assert_eq!(
InscriptionParser::parse(&container(&[
b"ord",
&[1],
b"text/plain;charset=utf-8",
&[],
&[0b10000000]
])),
Ok(Inscription::Text(bytes)) if bytes == [0b10000000],
));
Ok(inscription("text/plain;charset=utf-8", [0b10000000])),
);
}

#[test]
Expand Down Expand Up @@ -451,7 +447,7 @@ mod tests {

assert_eq!(
Inscription::from_transaction(&tx),
Some(Inscription::Text("ord".into())),
Some(inscription("text/plain;charset=utf-8", "ord")),
);
}

Expand Down Expand Up @@ -515,7 +511,7 @@ mod tests {
fn inscribe_png() {
assert_eq!(
InscriptionParser::parse(&container(&[b"ord", &[1], b"image/png", &[], &[1; 100]])),
Ok(Inscription::Png(vec![1; 100]))
Ok(inscription("image/png", [1; 100])),
);
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use {
self::{
arguments::Arguments,
blocktime::Blocktime,
content_type::ContentType,
decimal::Decimal,
degree::Degree,
epoch::Epoch,
Expand Down Expand Up @@ -68,6 +69,7 @@ use self::test::*;
mod arguments;
mod blocktime;
mod chain;
mod content_type;
mod decimal;
mod degree;
mod epoch;
Expand Down
4 changes: 2 additions & 2 deletions src/subcommand/server/templates/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod tests {
InscriptionHtml {
txid: Txid::from_str("ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc")
.unwrap(),
inscription: Inscription::Text("HELLOWORLD".into()),
inscription: inscription("text/plain;charset=utf-8", "HELLOWORLD"),
satpoint: satpoint(1, 0),
}
.to_string(),
Expand All @@ -44,7 +44,7 @@ mod tests {
pretty_assert_eq!(
InscriptionHtml {
txid: Txid::from_str("ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc").unwrap(),
inscription: Inscription::Png(vec![1; 100]),
inscription: inscription("image/png", [1; 100]),
satpoint: satpoint(1, 0),
}
.to_string(),
Expand Down
13 changes: 8 additions & 5 deletions src/subcommand/server/templates/ordinal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ mod tests {
OrdinalHtml {
ordinal: Ordinal(0),
blocktime: Blocktime::Confirmed(0),
inscription: Some(Inscription::Text("HELLOWORLD".into())),
inscription: Some(inscription("text/plain;charset=utf-8", "HELLOWORLD")),
}
.to_string(),
"
Expand All @@ -102,7 +102,8 @@ mod tests {
<dt>offset</dt><dd>0</dd>
<dt>rarity</dt><dd><span class=mythic>mythic</span></dd>
<dt>time</dt><dd>1970-01-01 00:00:00</dd>
<dt>inscription</dt><dd>HELLOWORLD</dd>
<dt>inscription</dt>
<dd>HELLOWORLD</dd>
</dl>
prev
<a href=/ordinal/1>next</a>
Expand All @@ -117,8 +118,9 @@ mod tests {
OrdinalHtml {
ordinal: Ordinal(0),
blocktime: Blocktime::Confirmed(0),
inscription: Some(Inscription::Text(
"<script>alert('HELLOWORLD');</script>".into()
inscription: Some(inscription(
"text/plain;charset=utf-8",
"<script>alert('HELLOWORLD');</script>",
)),
}
.to_string(),
Expand All @@ -136,7 +138,8 @@ mod tests {
<dt>offset</dt><dd>0</dd>
<dt>rarity</dt><dd><span class=mythic>mythic</span></dd>
<dt>time</dt><dd>1970-01-01 00:00:00</dd>
<dt>inscription</dt><dd>&lt;script&gt;alert(&apos;HELLOWORLD&apos;);&lt;/script&gt;</dd>
<dt>inscription</dt>
<dd>&lt;script&gt;alert(&apos;HELLOWORLD&apos;);&lt;/script&gt;</dd>
</dl>
prev
<a href=/ordinal/1>next</a>
Expand Down
6 changes: 3 additions & 3 deletions src/subcommand/wallet/inscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ mod tests {
#[test]
fn reveal_transaction_pays_fee() {
let utxos = vec![(outpoint(1), Amount::from_sat(5000))];
let inscription = Inscription::Text("ord".into());
let inscription = inscription("text/plain", "ord");
let commit_address = change(0);
let reveal_address = recipient();

Expand All @@ -226,7 +226,7 @@ mod tests {
fn reveal_transaction_value_insufficient_to_pay_fee() {
let utxos = vec![(outpoint(1), Amount::from_sat(1000))];
let satpoint = satpoint(1, 0);
let inscription = Inscription::Png([1; 10_000].to_vec());
let inscription = inscription("image/png", [1; 10_000]);
let commit_address = change(0);
let reveal_address = recipient();

Expand All @@ -247,7 +247,7 @@ mod tests {
#[test]
fn reveal_transaction_would_create_dust() {
let utxos = vec![(outpoint(1), Amount::from_sat(600))];
let inscription = Inscription::Text("ord".into());
let inscription = inscription("text/plain", "ord");
let satpoint = satpoint(1, 0);
let commit_address = change(0);
let reveal_address = recipient();
Expand Down
7 changes: 7 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,10 @@ pub(crate) fn tx_out(value: u64, address: Address) -> TxOut {
script_pubkey: address.script_pubkey(),
}
}

pub(crate) fn inscription(content_type: &str, content: impl AsRef<[u8]>) -> Inscription {
Inscription {
content_type: content_type.into(),
content: content.as_ref().into(),
}
}
Loading

0 comments on commit 65b1cee

Please sign in to comment.