Skip to content

Commit

Permalink
Feat/versioned contract (#155)
Browse files Browse the repository at this point in the history
* feat: Add support for versioned smart contracts

* bump app version and update snapshots

* remove support for stax device

* remove linker scripts

* update Ledger guidelines enforcer
  • Loading branch information
neithanmo authored Jan 12, 2024
1 parent ac58037 commit 6e45b07
Show file tree
Hide file tree
Showing 96 changed files with 121 additions and 361 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
name: Analyse
strategy:
matrix:
sdk: ["$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK", "$STAX_SDK"]
sdk: ["$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK"]
runs-on: ubuntu-latest
container:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/guidelines_enforcer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,3 @@ jobs:
guidelines_enforcer:
name: Call Ledger guidelines_enforcer
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1
with:
relative_app_directory: 'app'
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ ifeq ($(BOLOS_SDK),)
# In this case, there is not predefined SDK and we run dockerized
# When not using the SDK, we override and build the XL complete app

ZXLIB_COMPILE_STAX ?= 1
SUBSTRATE_PARSER_FULL ?= 1
include $(CURDIR)/deps/ledger-zxlib/dockerized_build.mk

Expand Down
2 changes: 1 addition & 1 deletion app/Makefile.version
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
APPVERSION_M=0
APPVERSION_N=23
APPVERSION_P=11
APPVERSION_P=12
9 changes: 9 additions & 0 deletions app/rust/src/parser/parsed_obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,4 +784,13 @@ mod test {
msg.read(&bytes).unwrap();
ParsedObj::validate(&mut msg).unwrap();
}

#[test]
fn parse_versioned_contract() {
let input = "8080000000040060dbb32efe0c56e1d418c020f4cb71c556b6a60d0000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000301000000000602107468656e2d677265656e2d6d61636177000004cf3b3b2068656c6c6f2d776f726c6420636f6e74726163740a0a28646566696e652d636f6e7374616e742073656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b51394836445052290a28646566696e652d636f6e7374616e7420726563697069656e742027534d324a365a593438475631455a35563256355242394d5036365357383650594b4b51565838583047290a0a28646566696e652d66756e6769626c652d746f6b656e206e6f76656c2d746f6b656e2d3139290a2866742d6d696e743f206e6f76656c2d746f6b656e2d3139207531322073656e646572290a2866742d7472616e736665723f206e6f76656c2d746f6b656e2d31392075322073656e64657220726563697069656e74290a0a28646566696e652d6e6f6e2d66756e6769626c652d746f6b656e2068656c6c6f2d6e66742075696e74290a0a286e66742d6d696e743f2068656c6c6f2d6e66742075312073656e646572290a286e66742d6d696e743f2068656c6c6f2d6e66742075322073656e646572290a286e66742d7472616e736665723f2068656c6c6f2d6e66742075312073656e64657220726563697069656e74290a0a28646566696e652d7075626c69632028746573742d656d69742d6576656e74290a202028626567696e0a20202020287072696e7420224576656e74212048656c6c6f20776f726c64220a20202020286f6b207531290a2020290a290a0a28626567696e2028746573742d656d69742d6576656e7429290a0a28646566696e652d7075626c69632028746573742d6576656e742d7479706573290a202028626567696e0a2020202028756e777261702d70616e6963202866742d6d696e743f206e6f76656c2d746f6b656e2d313920753320726563697069656e7429290a2020202028756e777261702d70616e696320286e66742d6d696e743f2068656c6c6f2d6e667420753220726563697069656e7429290a2020202028756e777261702d70616e696320287374782d7472616e736665723f207536302074782d73656e6465722027535a324a365a593438475631455a35563256355242394d5036365357383650594b4b5139483644505229290a2020202028756e777261702d70616e696320287374782d6275726e3f207532302074782d73656e64657229290a20202020286f6b207531290a2020290a290a0a28646566696e652d6d61702073746f7265207b206b65793a20286275666620333229207d207b2076616c75653a20286275666620333229207d290a0a28646566696e652d7075626c696320286765742d76616c756520286b65792028627566662033322929290a202028626567696e0a20202020286d6174636820286d61702d6765743f2073746f7265207b206b65793a206b6579207d290a202020202020656e74727920286f6b20286765742076616c756520656e74727929290a202020202020286572722030290a20202020290a2020290a290a0a28646566696e652d7075626c696320287365742d76616c756520286b65792028627566662033322929202876616c75652028627566662033322929290a202028626567696e0a20202020286d61702d7365742073746f7265207b206b65793a206b6579207d207b2076616c75653a2076616c7565207d290a20202020286f6b207531290a2020290a290a";
let bytes = hex::decode(input).unwrap();
let mut msg = ParsedObj::from_bytes(&bytes).unwrap();
msg.read(&bytes).unwrap();
ParsedObj::validate(&mut msg).unwrap();
}
}
108 changes: 101 additions & 7 deletions app/rust/src/parser/transaction_payload.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use core::fmt::Write;
use nom::{
bytes::complete::take,
number::complete::{be_u32, be_u64, le_u8},
branch::alt,
bytes::complete::{tag, take},
combinator::{flat_map, map},
number::complete::{be_u32, be_u64, be_u8, le_u8},
sequence::tuple,
};

Expand All @@ -19,6 +21,9 @@ use crate::parser::c32;
use super::value::{Value, ValueId};
use crate::{check_canary, is_expert_mode, zxformat};

// The number of contract call arguments we can handle.
// this can be adjusted, but keep in mind that higher values could
// hit stack overflows issues.
pub const MAX_NUM_ARGS: u32 = 10;

// The items in contract_call transactions are
Expand Down Expand Up @@ -477,6 +482,69 @@ impl<'a> TransactionContractCall<'a> {
}
}

/// A transaction that deploys a versioned smart contract
#[repr(C)]
#[derive(Clone, PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub struct VersionedSmartContract<'a>(&'a [u8]);

impl<'a> VersionedSmartContract<'a> {
#[inline(never)]
fn from_bytes(input: &'a [u8]) -> Result<(&[u8], Self), ParserError> {
check_canary!();

// clarity version
// len prefixed contract name
// len prefixed contract code
let parse_tag = alt((tag(&[0x01]), tag(&[0x02])));
let parse_length_1_byte = map(be_u8, |length| std::cmp::min(length, 128u8) as usize);
let parse_length_4_bytes = flat_map(be_u32, take);

let parser = tuple((
parse_tag,
flat_map(parse_length_1_byte, take),
parse_length_4_bytes,
));
let (_, (_, name, code)) = parser(input)?;

// 1-byte tag, 1-byte name_len, name, 4-byte code_len, code
let total_length = 1 + 1 + name.len() + 4 + code.len();
let (rem, res) = take(total_length)(input)?;

Ok((rem, Self(res)))
}

pub fn contract_name(&'a self) -> Result<ContractName<'a>, ParserError> {
// skip the tag. safe ecause this was checked during parsing
ContractName::from_bytes(&self.0[1..])
.map(|(_, res)| res)
.map_err(|e| e.into())
}

#[inline(never)]
fn get_contract_items(
&self,
display_idx: u8,
out_key: &mut [u8],
out_value: &mut [u8],
page_idx: u8,
) -> Result<u8, ParserError> {
let mut writer_key = zxformat::Writer::new(out_key);

match display_idx {
0 => {
writer_key
.write_str("Contract Name")
.map_err(|_| ParserError::parser_unexpected_buffer_end)?;
check_canary!();
let name = self.contract_name()?;
zxformat::pageString(out_value, name.name(), page_idx)
}
_ => Err(ParserError::parser_value_out_of_range),
}
}
}

/// A transaction that instantiates a smart contract
#[repr(C)]
#[derive(Clone, PartialEq)]
Expand All @@ -485,11 +553,22 @@ pub struct TransactionSmartContract<'a>(&'a [u8]);

impl<'a> TransactionSmartContract<'a> {
#[inline(never)]
fn from_bytes(bytes: &'a [u8]) -> nom::IResult<&[u8], Self, ParserError> {
fn from_bytes(bytes: &'a [u8]) -> Result<(&[u8], Self), ParserError> {
check_canary!();
// we take "ownership" of bytes here because
// it should only contain the contract information and body
Ok((Default::default(), Self(bytes)))

// len prefixed contract name
// len prefixed contract code
let parse_length_1_byte = map(be_u8, |length| std::cmp::min(length, 128u8) as usize);
let parse_length_4_bytes = flat_map(be_u32, take);

let parser = tuple((flat_map(parse_length_1_byte, take), parse_length_4_bytes));
let (_, (name, code)) = parser(bytes)?;

// 1-byte name_len, name, 4-byte code_len, code
let total_length = 1 + name.len() + 4 + code.len();
let (rem, res) = take(total_length)(bytes)?;

Ok((rem, Self(res)))
}

pub fn contract_name(&'a self) -> Result<ContractName<'a>, ParserError> {
Expand Down Expand Up @@ -529,6 +608,7 @@ pub enum TransactionPayloadId {
TokenTransfer = 0,
SmartContract = 1,
ContractCall = 2,
VersionedSmartContract = 6,
}

impl TransactionPayloadId {
Expand All @@ -537,6 +617,7 @@ impl TransactionPayloadId {
0 => Ok(Self::TokenTransfer),
1 => Ok(Self::SmartContract),
2 => Ok(Self::ContractCall),
6 => Ok(Self::VersionedSmartContract),
_ => Err(ParserError::parser_invalid_transaction_payload),
}
}
Expand All @@ -549,6 +630,7 @@ pub enum TransactionPayload<'a> {
TokenTransfer(StxTokenTransfer<'a>),
SmartContract(TransactionSmartContract<'a>),
ContractCall(TransactionContractCall<'a>),
VersionedSmartContract(VersionedSmartContract<'a>),
}

impl<'a> TransactionPayload<'a> {
Expand All @@ -568,6 +650,10 @@ impl<'a> TransactionPayload<'a> {
let call = TransactionContractCall::from_bytes(id.0)?;
(call.0, Self::ContractCall(call.1))
}
TransactionPayloadId::VersionedSmartContract => {
let call = VersionedSmartContract::from_bytes(id.0)?;
(call.0, Self::VersionedSmartContract(call.1))
}
};
Ok(res)
}
Expand All @@ -583,10 +669,15 @@ impl<'a> TransactionPayload<'a> {
matches!(self, &Self::ContractCall(_))
}

pub fn is_contract_deploy_payload(&self) -> bool {
matches!(self, &Self::VersionedSmartContract(_))
}

pub fn contract_name(&'a self) -> Option<ContractName<'a>> {
match self {
Self::SmartContract(contract) => contract.contract_name().ok(),
Self::ContractCall(contract) => contract.contract_name().ok(),
Self::VersionedSmartContract(contract) => contract.contract_name().ok(),
_ => None,
}
}
Expand Down Expand Up @@ -635,7 +726,7 @@ impl<'a> TransactionPayload<'a> {
pub fn num_items(&self) -> u8 {
match self {
Self::TokenTransfer(_) => 3,
Self::SmartContract(_) => 1,
Self::SmartContract(_) | Self::VersionedSmartContract(_) => 1,
Self::ContractCall(ref call) => call.num_items().unwrap_or(CONTRACT_CALL_BASE_ITEMS),
}
}
Expand All @@ -659,6 +750,9 @@ impl<'a> TransactionPayload<'a> {
Self::ContractCall(ref call) => {
call.get_contract_call_items(idx, out_key, out_value, page_idx)
}
Self::VersionedSmartContract(ref deploy) => {
deploy.get_contract_items(idx, out_key, out_value, page_idx)
}
}
}
}
Expand Down
Loading

0 comments on commit 6e45b07

Please sign in to comment.