RGB in Rust
NB: Work in progress – the chapter is not fully written yet and lack
explanation details for the code.
Please contribute at
our GitHub.
We will start with showing how a simple RGB20 contract can be written in Rust using pre-existing schema and no advanced functionality. The rest of the chapter will guide you through adding more and more customization to it, which will require creating a custom state data types, writing validation scripts for them, packing them into a new schema – and providing a custom interface to this schema.
Sample project providing the source code from this chapter can be found in examples directory of RGB smart contract compiler repository.
Writing simple contract
let nominal = Nominal::new("TEST", "Test asset", Precision::CentiMicro);
let contract_text = ContractText::default();
let beneficiary = Outpoint::new(
Txid::from_hex("623554ac1dcd15496c105a27042c438921f2a82873579be88e74d7ef559a3d91").unwrap(),
0
);
let contract = ContractBuilder::with(
rgb20(),
schema(),
iface_impl()
).expect("schema fails to implement RGB20 interface")
.set_chain(Chain::Testnet3)
.add_global_state("Nominal", nominal)
.expect("invalid nominal")
.add_global_state("ContractText", contract_text)
.expect("invalid contract text")
.add_fungible_state("Assets", beneficiary, 1_000_000_0000_0000)
.expect("invalid asset amount")
.issue_contract()
.expect("contract doesn't fit schema requirements");
let contract_id = contract.contract_id();
let bindle = contract.bindle();
bindle.save("examples/rgb20-simplest.contract.rgb").expect("unable to save contract");
Creating custom state
Custom state can be added to a contract by defining a data type which will hold
it. Data type is a rust structure or enum, which implements strict_encoding
traits. The simplest way to add this implementation is through derive macros:
#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_CONTRACT)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Nominal {
ticker: Ticker,
name: ContractName,
details: Option<ContractDetails>,
precision: Precision,
}
impl StrictSerialize for Nominal {}
impl StrictDeserialize for Nominal {}
Once declared, the type can be compiled into a type library:
let lib = LibBuilder::new(libname!(LIB_NAME_RGB_CONTRACT))
.process::<Nominal>()?
.compile(none!())?;
let types = SystemBuilder::new()
.import(lib)?
.finalize()?;
Scripting
let code = [RgbIsa::Contract(ContractOp::PcVs(OS_ASSETS))];
let alu_lib = Lib::assemble(&code).unwrap();
let alu_id = alu_lib.id();
let code = aluasm! {
clr r1024[5] ;
put 5,a16[8] ;
putif 0xaf67937b5498dc,r256[1] ;
putif 13,a8[1] ;
swp a8[1],a8[2] ;
swp f256[8],f256[7] ;
dup a256[1],a256[7] ;
mov a16[1],a16[2] ;
mov r256[8],r256[7] ;
cpy a256[1],a256[7] ;
cnv f128[4],a128[3] ;
spy a1024[15],r1024[24] ;
gt.u a8[5],a64[9] ;
lt.s a8[5],a64[9] ;
gt.e f64[5],f64[9] ;
lt.r f64[5],f64[9] ;
gt r160[5],r256[9] ;
lt r160[5],r256[9] ;
eq.e a8[5],a8[9] ;
eq.n r160[5],r160[9] ;
eq.e f64[19],f64[29] ;
ifn a32[32] ;
ifz r2048[17] ;
inv st0 ;
st.s a8[1] ;
put 13,a32[12] ;
put 66,a32[13] ;
add.uc a32[12],a32[13] ;
add.sw a32[12],a32[13] ;
sub.sc a32[13],a32[12] ;
mul.uw a32[12],a32[13] ;
div.cu a32[12],a32[13] ;
put 2.13,f32[12] ;
put 5.18,f32[13] ;
add.z f32[12],f32[13] ;
sub.n f32[13],f32[12] ;
mul.c f32[12],f32[13] ;
div.f f32[12],f32[13] ;
rem a64[8],a8[2] ;
inc a16[3] ;
add 5,a16[4] ;
dec a16[8] ;
sub 82,a16[4] ;
neg a64[16] ;
abs f128[11] ;
and a32[5],a32[6],a32[5] ;
xor r128[5],r128[6],r128[5] ;
shr.u a256[12],a16[2] ;
shr.s a256[12],a16[2] ;
shl r256[24],a16[22] ;
shr r256[24],a16[22] ;
scr r256[24],a16[22] ;
scl r256[24],a16[22] ;
rev a512[28] ;
ripemd s16[9],r160[7] ;
sha2 s16[19],r256[2] ;
secpgen r256[1],r512[1] ;
dup r512[1],r512[22] ;
spy a512[1],r512[22] ;
secpmul r256[1],r512[1],r512[2] ;
secpadd r512[22],r512[1] ;
secpneg r512[1],r512[3] ;
ifz a16[8] ;
jif 190 ;
jmp 6 ;
call 56 @ alu1wnhusevxmdphv3dh8ada44k0xw66ahq9nzhkv39z07hmudhp380sq0dtml ;
ret ;
};
let lib = Lib::assemble(&code).expect("invalid program);
Creating custom schema
fn schema() -> SubSchema {
Schema {
ffv: zero!(),
subset_of: None,
type_system: types.type_system(),
global_types: tiny_bmap! {
GS_NOMINAL => GlobalStateSchema::once(types.get("RGBContract.Nominal")),
GS_CONTRACT => GlobalStateSchema::once(types.get("RGBContract.ContractText")),
},
owned_types: tiny_bmap! {
OS_ASSETS => StateSchema::Fungible(FungibleType::Unsigned64Bit),
},
valency_types: none!(),
genesis: GenesisSchema {
metadata: Ty::<SemId>::UNIT.id(None),
globals: tiny_bmap! {
GS_NOMINAL => Occurrences::Once,
GS_CONTRACT => Occurrences::Once,
},
assignments: tiny_bmap! {
OS_ASSETS => Occurrences::OnceOrMore,
},
valencies: none!(),
},
extensions: none!(),
transitions: tiny_bmap! {
TS_TRANSFER => TransitionSchema {
metadata: Ty::<SemId>::UNIT.id(None),
globals: none!(),
inputs: tiny_bmap! {
OS_ASSETS => Occurrences::OnceOrMore
},
assignments: tiny_bmap! {
OS_ASSETS => Occurrences::OnceOrMore
},
valencies: none!(),
}
},
script: Script::AluVM(AluScript {
libs: confined_bmap! { alu_id => alu_lib },
entry_points: confined_bmap! {
EntryPoint::ValidateOwnedState(OS_ASSETS) => LibSite::with(0, alu_id)
},
}),
}
}
Doing custom interface
pub fn rgb20() -> Iface {
let types = StandardTypes::new();
Iface {
name: tn!("RGB20"),
global_state: tiny_bmap! {
tn!("Nominal") => Req::require(types.get("RGBContract.Nominal")),
tn!("ContractText") => Req::require(types.get("RGBContract.ContractText")),
},
assignments: tiny_bmap! {
tn!("Assets") => AssignIface::private(OwnedIface::Amount),
},
valencies: none!(),
genesis: GenesisIface {
metadata: None,
global: tiny_bmap! {
tn!("Nominal") => Occurrences::Once,
tn!("ContractText") => Occurrences::Once,
},
assignments: tiny_bmap! {
tn!("Assets") => Occurrences::OnceOrMore
},
valencies: none!(),
},
transitions: tiny_bmap! {
tn!("Transfer") => TransitionIface {
metadata: None,
globals: none!(),
inputs: tiny_bmap! {
tn!("Assets") => Occurrences::OnceOrMore,
},
assignments: tiny_bmap! {
tn!("Assets") => Occurrences::OnceOrMore,
},
valencies: none!(),
default_assignment: Some(tn!("Assets")),
}
},
extensions: none!(),
default_operation: Some(tn!("Transfer")),
}
}
Implementing interface
fn iface_impl() -> IfaceImpl {
let schema = schema();
let iface = rgb20();
IfaceImpl {
schema_id: schema.schema_id(),
iface_id: iface.iface_id(),
global_state: tiny_bset! {
NamedType::with(GS_NOMINAL, tn!("Nominal")),
NamedType::with(GS_CONTRACT, tn!("ContractText")),
},
assignments: tiny_bset! {
NamedType::with(OS_ASSETS, tn!("Assets")),
},
valencies: none!(),
transitions: tiny_bset! {
NamedType::with(TS_TRANSFER, tn!("Transfer")),
},
extensions: none!(),
}
}