Skip to content

Commit

Permalink
feat brc20 self issuance (#35)
Browse files Browse the repository at this point in the history
* feat: support proposal for brc20 self issuance

* add part 2 for burned supply
  • Loading branch information
wanyvic authored Mar 31, 2024
1 parent 3eab454 commit 7a379ac
Show file tree
Hide file tree
Showing 34 changed files with 439 additions and 179 deletions.
2 changes: 1 addition & 1 deletion src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl Chain {
Self::Mainnet => 779832,
Self::Regtest => 0,
Self::Signet => 0,
Self::Testnet => 0,
Self::Testnet => 2413343,
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ impl Index {
})
}

pub(crate) fn get_chain_network(&self) -> Network {
self.options.chain().network()
pub(crate) fn get_chain(&self) -> Chain {
self.options.chain()
}

#[cfg(test)]
Expand Down
13 changes: 7 additions & 6 deletions src/index/extend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ impl Index {
txid: Txid,
rtx: &Rtx,
client: &Client,
network: Network,
chain: Chain,
index_transactions: bool,
) -> Result<Option<Transaction>> {
let genesis_block = bitcoin::blockdata::constants::genesis_block(network);
let genesis_block = chain.genesis_block();
let genesis_block_coinbase_transaction = genesis_block.coinbase().unwrap();

if txid == genesis_block_coinbase_transaction.txid() {
Expand Down Expand Up @@ -248,7 +248,7 @@ impl Index {
rtx: &Rtx,
client: &Client,
outpoint: OutPoint,
network: Network,
chain: Chain,
index_transactions: bool,
) -> Result<Option<TxOut>> {
// Try to get the txout from the database store at first.
Expand All @@ -257,13 +257,14 @@ impl Index {
} else {
// Try to get the txout from the transaction table or the RPC request.
Ok(
Self::get_transaction_with_rtx(outpoint.txid, rtx, client, network, index_transactions)?
.map(|tx| {
Self::get_transaction_with_rtx(outpoint.txid, rtx, client, chain, index_transactions)?.map(
|tx| {
tx.output
.get(usize::try_from(outpoint.vout).unwrap())
.unwrap()
.to_owned()
}),
},
),
)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/index/updater.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::okx::protocol::{context::Context, BlockContext, ProtocolConfig, ProtocolManager};
use crate::okx::protocol::{context::Context, ChainContext, ProtocolConfig, ProtocolManager};
use std::sync::atomic::{AtomicUsize, Ordering};
use {
self::{inscription_updater::InscriptionUpdater, rune_updater::RuneUpdater},
Expand Down Expand Up @@ -621,8 +621,8 @@ impl<'index> Updater<'_> {
inscription_updater.flush_cache()?;

let mut context = Context {
chain: BlockContext {
network: index.get_chain_network(),
chain_conf: ChainContext {
chain: self.index.options.chain(),
blockheight: self.height,
blocktime: block.header.time,
},
Expand Down
9 changes: 9 additions & 0 deletions src/okx/datastore/brc20/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ pub enum BRC20Error {
#[error("transferable owner not match {0}")]
TransferableOwnerNotMatch(InscriptionId),

#[error("self issuance not activated")]
SelfIssuanceNotActivated,

#[error("'self_mint' must be set to 'true', when deploying 5 bytes tick")]
SelfIssuanceCheckedFailed,

#[error("self mint permission denied")]
SelfMintPermissionDenied,

/// an InternalError is an error that happens exceed our expect
/// and should not happen under normal circumstances
#[error("internal error: {0}")]
Expand Down
1 change: 1 addition & 0 deletions src/okx/datastore/brc20/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct DeployEvent {
pub limit_per_mint: u128,
pub decimal: u8,
pub tick: Tick,
pub self_mint: bool,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
Expand Down
2 changes: 2 additions & 0 deletions src/okx/datastore/brc20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub trait Brc20ReaderWriter: Brc20Reader {
minted_block_number: u32,
) -> Result<(), Self::Error>;

fn update_burned_token_info(&mut self, tick: &Tick, burned_amt: u128) -> Result<(), Self::Error>;

fn save_transaction_receipts(
&mut self,
txid: &Txid,
Expand Down
15 changes: 15 additions & 0 deletions src/okx/datastore/brc20/redb/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,21 @@ pub fn update_mint_token_info(
Ok(())
}

pub fn update_burned_token_info(
table: &mut Table<'_, '_, &'static str, &'static [u8]>,
tick: &Tick,
burned_amt: u128,
) -> Result<()> {
let mut info =
get_token_info(table, tick)?.unwrap_or_else(|| panic!("token {} not exist", tick.as_str()));
info.burned_supply = burned_amt;
table.insert(
tick.to_lowercase().hex().as_str(),
rmp_serde::to_vec(&info).unwrap().as_slice(),
)?;
Ok(())
}

// BRC20_EVENTS
pub fn save_transaction_receipts(
table: &mut Table<'_, '_, &'static TxidValue, &'static [u8]>,
Expand Down
34 changes: 20 additions & 14 deletions src/okx/datastore/brc20/tick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,41 @@ use super::*;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt::Formatter, str::FromStr};

pub const TICK_BYTE_COUNT: usize = 4;
pub const ORIGINAL_TICK_LENGTH: usize = 4;
pub const SELF_ISSUANCE_TICK_LENGTH: usize = 5;
pub const MAX_TICK_BYTE_COUNT: usize = SELF_ISSUANCE_TICK_LENGTH;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Tick([u8; TICK_BYTE_COUNT]);
pub struct Tick(Box<[u8]>);

impl FromStr for Tick {
type Err = BRC20Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.as_bytes();

if bytes.len() != TICK_BYTE_COUNT {
if bytes.len() < ORIGINAL_TICK_LENGTH || bytes.len() > SELF_ISSUANCE_TICK_LENGTH {
return Err(BRC20Error::InvalidTickLen(s.to_string()));
}

Ok(Self(bytes.try_into().unwrap()))
Ok(Self(bytes.into()))
}
}

impl Tick {
pub fn as_str(&self) -> &str {
pub fn as_str(&self) -> String {
// NOTE: Tick comes from &str by from_str,
// so it could be calling unwrap when convert to str
std::str::from_utf8(self.0.as_slice()).unwrap()
String::from_utf8(self.0.to_vec()).unwrap()
}

pub fn to_lowercase(&self) -> LowerTick {
LowerTick::new(&self.as_str().to_lowercase())
}

pub fn self_issuance_tick(&self) -> bool {
self.0.len() == SELF_ISSUANCE_TICK_LENGTH
}
}

impl Serialize for Tick {
Expand Down Expand Up @@ -71,17 +77,17 @@ impl LowerTick {
}

pub fn hex(&self) -> String {
let mut data = [0u8; TICK_BYTE_COUNT * 4];
let mut data = [0u8; MAX_TICK_BYTE_COUNT * 4];
data[..self.0.len()].copy_from_slice(&self.0);
hex::encode(data)
}

pub fn min_hex() -> String {
hex::encode([0u8; TICK_BYTE_COUNT * 4])
hex::encode([0u8; MAX_TICK_BYTE_COUNT * 4])
}

pub fn max_hex() -> String {
hex::encode([0xffu8; TICK_BYTE_COUNT * 4])
hex::encode([0xffu8; MAX_TICK_BYTE_COUNT * 4])
}
}

Expand All @@ -106,22 +112,22 @@ mod tests {
assert!(Tick::from_str("aBc1").is_ok());
assert!("aBc1".parse::<Tick>().is_ok());
assert!("ατ".parse::<Tick>().is_ok());
assert!("∑ii".parse::<Tick>().is_err());
assert!("∑ii".parse::<Tick>().is_ok()); // when self issuance is enabled
assert!("∑i".parse::<Tick>().is_ok());
assert!("⊢i".parse::<Tick>().is_ok());
assert!("⊢ii".parse::<Tick>().is_err());
assert!("⊢ii".parse::<Tick>().is_ok()); // when self issuance is enabled
assert!("≯a".parse::<Tick>().is_ok());
assert!("a≯a".parse::<Tick>().is_err());
assert!("a≯a".parse::<Tick>().is_ok()); // when self issuance is enabled
}
#[test]
fn test_tick_hex() {
assert_eq!(
Tick::from_str("XAİ").unwrap().to_lowercase().hex(),
"786169cc870000000000000000000000"
"786169cc87000000000000000000000000000000"
);
assert_eq!(
Tick::from_str("aBc1").unwrap().to_lowercase().hex(),
"61626331000000000000000000000000"
"6162633100000000000000000000000000000000"
);
}

Expand Down
2 changes: 2 additions & 0 deletions src/okx/datastore/brc20/token_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ pub struct TokenInfo {
pub inscription_id: InscriptionId,
pub inscription_number: i32,
pub supply: u128,
pub burned_supply: u128,
pub minted: u128,
pub limit_per_mint: u128,
pub decimal: u8,
pub deploy_by: ScriptKey,
pub is_self_mint: bool,
pub deployed_number: u32,
pub deployed_timestamp: u32,
pub latest_mint_number: u32,
Expand Down
7 changes: 2 additions & 5 deletions src/okx/datastore/ord/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
pub use self::operation::{Action, InscriptionOp};
use bitcoin::Network;

use crate::okx::datastore::ScriptKey;
use crate::SatPoint;
use {
crate::{InscriptionId, Result},
crate::{okx::datastore::ScriptKey, Chain, InscriptionId, Result, SatPoint},
bitcoin::Txid,
collections::CollectionKind,
std::fmt::{Debug, Display},
Expand All @@ -25,7 +22,7 @@ pub trait OrdReader {
fn get_script_key_on_satpoint(
&mut self,
satpoint: &SatPoint,
network: Network,
chain: Chain,
) -> Result<ScriptKey, Self::Error>;

fn get_transaction_operations(
Expand Down
73 changes: 21 additions & 52 deletions src/okx/datastore/script_key.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
use bitcoin::{address, Address, Network, Script, ScriptHash};
use crate::Chain;
use bitcoin::{address, Address, Script, ScriptHash};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};

#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum ScriptKey {
Address(Address<address::NetworkUnchecked>),
ScriptHash(ScriptHash),
ScriptHash {
script_hash: ScriptHash,
is_op_return: bool,
},
}

impl ScriptKey {
#[allow(unused)]
pub fn from_address(address: Address) -> Self {
ScriptKey::Address(Address::new(address.network, address.payload))
}
pub fn from_script(script: &Script, network: Network) -> Self {
match Address::from_script(script, network) {
Ok(address) => ScriptKey::Address(Address::new(address.network, address.payload)),
Err(_) => ScriptKey::ScriptHash(script.script_hash()),
}
pub fn from_script(script: &Script, chain: Chain) -> Self {
chain
.address_from_script(script)
.map(|address| Self::Address(Address::new(address.network, address.payload)))
.unwrap_or(ScriptKey::ScriptHash {
script_hash: script.script_hash(),
is_op_return: script.is_op_return(),
})
}
}

Expand All @@ -28,7 +35,7 @@ impl Display for ScriptKey {
"{}",
match self {
ScriptKey::Address(address) => address.clone().assume_checked().to_string(),
ScriptKey::ScriptHash(script_hash) => script_hash.to_string(),
ScriptKey::ScriptHash { script_hash, .. } => script_hash.to_string(),
}
)
}
Expand Down Expand Up @@ -57,7 +64,7 @@ mod tests {
.payload
.script_pubkey();
assert_eq!(
ScriptKey::from_script(&script, Network::Bitcoin),
ScriptKey::from_script(&script, Chain::Mainnet),
ScriptKey::Address(Address::from_str("bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4").unwrap())
);
let binding = hex::decode(
Expand All @@ -66,49 +73,11 @@ mod tests {
.unwrap();
let script = Script::from_bytes(binding.as_slice());
assert_eq!(
ScriptKey::from_script(script, Network::Bitcoin),
ScriptKey::ScriptHash(
ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap()
)
);
}
#[test]
fn test_script_key_serialize() {
let script_key =
ScriptKey::Address(Address::from_str("bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4").unwrap());
assert_eq!(
serde_json::to_string(&script_key).unwrap(),
r#"{"Address":"bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4"}"#
);
let script_key = ScriptKey::ScriptHash(
ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap(),
);
assert_eq!(
serde_json::to_string(&script_key).unwrap(),
r#"{"ScriptHash":"df65c8a338dce7900824e7bd18c336656ca19e57"}"#
);
}

#[test]
fn test_script_key_deserialize() {
let script_key =
ScriptKey::Address(Address::from_str("bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4").unwrap());
assert_eq!(
script_key,
serde_json::from_str::<ScriptKey>(
r#"{"Address":"bc1qhvd6suvqzjcu9pxjhrwhtrlj85ny3n2mqql5w4"}"#
)
.unwrap()
);
let script_key = ScriptKey::ScriptHash(
ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap(),
);
assert_eq!(
serde_json::from_str::<ScriptKey>(
r#"{"ScriptHash":"df65c8a338dce7900824e7bd18c336656ca19e57"}"#
)
.unwrap(),
script_key
ScriptKey::from_script(script, Chain::Mainnet),
ScriptKey::ScriptHash {
script_hash: ScriptHash::from_str("df65c8a338dce7900824e7bd18c336656ca19e57").unwrap(),
is_op_return: false,
},
);
}
}
1 change: 1 addition & 0 deletions src/okx/protocol/brc20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod msg_resolver;
mod num;
mod operation;
mod params;
mod policies;

use self::error::Error;
pub(crate) use self::{
Expand Down
Loading

0 comments on commit 7a379ac

Please sign in to comment.