Skip to content

Commit

Permalink
Format rune supply using divisibility (#2509)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey committed Oct 10, 2023
1 parent c811d48 commit 3408de4
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/index/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ impl Entry for BlockHash {

#[derive(Debug, PartialEq, Copy, Clone)]
pub(crate) struct RuneEntry {
pub(crate) divisibility: u128,
pub(crate) divisibility: u8,
pub(crate) rarity: Rarity,
pub(crate) rune: Rune,
pub(crate) supply: u128,
}

pub(super) type RuneEntryValue = (u128, u8, u128, u128);
pub(super) type RuneEntryValue = (u8, u8, u128, u128);

impl Entry for RuneEntry {
type Value = RuneEntryValue;
Expand Down
2 changes: 1 addition & 1 deletion src/index/updater/rune_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use {

struct Allocation {
balance: u128,
divisibility: u128,
divisibility: u8,
id: u128,
rune: Rune,
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use {
options::Options,
outgoing::Outgoing,
representation::Representation,
runes::RuneId,
runes::{Pile, RuneId},
subcommand::{Subcommand, SubcommandResult},
tally::Tally,
},
Expand Down
5 changes: 4 additions & 1 deletion src/runes.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use {self::error::Error, super::*};

pub(crate) use {
edict::Edict, etching::Etching, rune::Rune, rune_id::RuneId, runestone::Runestone,
edict::Edict, etching::Etching, pile::Pile, rune::Rune, rune_id::RuneId, runestone::Runestone,
};

const MAX_DIVISIBILITY: u8 = 38;

mod edict;
mod error;
mod etching;
mod pile;
mod rune;
mod rune_id;
mod runestone;
Expand Down
2 changes: 1 addition & 1 deletion src/runes/etching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ use super::*;

#[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)]
pub struct Etching {
pub(crate) divisibility: u128,
pub(crate) divisibility: u8,
pub(crate) rune: Rune,
}
124 changes: 124 additions & 0 deletions src/runes/pile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use super::*;

pub(crate) struct Pile {
pub(crate) amount: u128,
pub(crate) divisibility: u8,
}

impl Display for Pile {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let cutoff = 10u128.pow(self.divisibility.into());

let whole = self.amount / cutoff;
let mut fractional = self.amount % cutoff;

if fractional == 0 {
return write!(f, "{whole}");
}

let mut width = usize::from(self.divisibility);
while fractional % 10 == 0 {
fractional /= 10;
width -= 1;
}

write!(f, "{whole}.{fractional:0>width$}")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn display() {
assert_eq!(
Pile {
amount: 0,
divisibility: 0
}
.to_string(),
"0"
);
assert_eq!(
Pile {
amount: 25,
divisibility: 0
}
.to_string(),
"25"
);
assert_eq!(
Pile {
amount: 0,
divisibility: 1,
}
.to_string(),
"0"
);
assert_eq!(
Pile {
amount: 1,
divisibility: 1,
}
.to_string(),
"0.1"
);
assert_eq!(
Pile {
amount: 1,
divisibility: 2,
}
.to_string(),
"0.01"
);
assert_eq!(
Pile {
amount: 10,
divisibility: 2,
}
.to_string(),
"0.1"
);
assert_eq!(
Pile {
amount: 1100,
divisibility: 3,
}
.to_string(),
"1.1"
);
assert_eq!(
Pile {
amount: 100,
divisibility: 2,
}
.to_string(),
"1"
);
assert_eq!(
Pile {
amount: 101,
divisibility: 2,
}
.to_string(),
"1.01"
);
assert_eq!(
Pile {
amount: u128::max_value(),
divisibility: 18,
}
.to_string(),
"340282366920938463463.374607431768211455"
);
assert_eq!(
Pile {
amount: u128::max_value(),
divisibility: MAX_DIVISIBILITY,
}
.to_string(),
"3.40282366920938463463374607431768211455"
);
}
}
76 changes: 73 additions & 3 deletions src/runes/runestone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ impl Runestone {
rune: Rune(rune),
})
}
[rune, divisibility] => {
[rune, parameters] => {
etching = Some(Etching {
divisibility,
divisibility: u8::try_from(parameters & 0b11_1111)
.unwrap()
.min(MAX_DIVISIBILITY),
rune: Rune(rune),
})
}
Expand All @@ -62,7 +64,7 @@ impl Runestone {

if let Some(etching) = self.etching {
varint::encode_to_vec(etching.rune.0, &mut payload);
varint::encode_to_vec(etching.divisibility, &mut payload);
varint::encode_to_vec(etching.divisibility.into(), &mut payload);
}

let mut builder = script::Builder::new()
Expand Down Expand Up @@ -454,6 +456,74 @@ mod tests {
);
}

#[test]
fn divisibility_above_max_is_clamped() {
let payload = payload(&[1, 2, 3, 4, (MAX_DIVISIBILITY + 1).into()]);

let payload: &PushBytes = payload.as_slice().try_into().unwrap();

assert_eq!(
Runestone::decipher(&Transaction {
input: Vec::new(),
output: vec![TxOut {
script_pubkey: script::Builder::new()
.push_opcode(opcodes::all::OP_RETURN)
.push_slice(b"RUNE_TEST")
.push_slice(payload)
.into_script(),
value: 0
}],
lock_time: locktime::absolute::LockTime::ZERO,
version: 0,
}),
Ok(Some(Runestone {
edicts: vec![Edict {
id: 1,
amount: 2,
output: 3,
}],
etching: Some(Etching {
rune: Rune(4),
divisibility: MAX_DIVISIBILITY,
}),
}))
);
}

#[test]
fn divisibility_is_taken_from_lower_six_bits_of_parameter() {
let payload = payload(&[1, 2, 3, 4, 0b110_0000]);

let payload: &PushBytes = payload.as_slice().try_into().unwrap();

assert_eq!(
Runestone::decipher(&Transaction {
input: Vec::new(),
output: vec![TxOut {
script_pubkey: script::Builder::new()
.push_opcode(opcodes::all::OP_RETURN)
.push_slice(b"RUNE_TEST")
.push_slice(payload)
.into_script(),
value: 0
}],
lock_time: locktime::absolute::LockTime::ZERO,
version: 0,
}),
Ok(Some(Runestone {
edicts: vec![Edict {
id: 1,
amount: 2,
output: 3,
}],
etching: Some(Etching {
rune: Rune(4),
divisibility: 0b10_0000,
}),
}))
);
}

#[test]
fn runestone_may_contain_multiple_edicts() {
let payload = payload(&[1, 2, 3, 4, 5, 6]);
Expand Down
36 changes: 36 additions & 0 deletions src/templates/rune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,39 @@ impl PageContent for RuneHtml {
format!("Rune {}", self.entry.rune)
}
}

#[cfg(test)]
mod tests {
use {super::*, crate::runes::Rune};

#[test]
fn supply_is_displayed_using_divisibility() {
assert_eq!(
RuneHtml {
entry: RuneEntry {
divisibility: 9,
rarity: Rarity::Uncommon,
rune: Rune(u128::max_value()),
supply: 123456789123456789,
},
id: RuneId {
height: 10,
index: 9,
},
}
.to_string(),
"<h1>Rune BCGDENLQRQWDSLRUGSNLBTMFIJAV</h1>
<dl>
<dt>id</dt>
<dd>10/9</dd>
<dt>supply</dt>
<dd>123456789.123456789</dd>
<dt>divisibility</dt>
<dd>9</dd>
<dt>rarity</dt>
<dd><span class=uncommon>uncommon</span></dd>
</dl>
"
);
}
}
2 changes: 1 addition & 1 deletion templates/rune.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ <h1>Rune {{ self.entry.rune }}</h1>
<dt>id</dt>
<dd>{{ self.id }}</dd>
<dt>supply</dt>
<dd>{{ self.entry.supply }}</dd>
<dd>{{ Pile{ amount: self.entry.supply, divisibility: self.entry.divisibility } }}</dd>
<dt>divisibility</dt>
<dd>{{ self.entry.divisibility }}</dd>
<dt>rarity</dt>
Expand Down

0 comments on commit 3408de4

Please sign in to comment.