Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/versioned contract #155

Merged
merged 5 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading