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

Add contract deployment transaction #164

Closed
wants to merge 3 commits into from
Closed
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
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add contract deployment transaction [#163]
- Add `Transaction::exec` function and field [#163]

### Removed

- Remove `Transaction::call` function [#163]

## [0.27.0] - 2024-04-24

### Added
Expand All @@ -22,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update `bls12_381-bls` to v0.3.0
- Update `jubjub-schnorr` to v0.3.0

### Removed
### Removed

- Remove the `Message` module.
- Remove `StealthAddress::address` method [#156]
Expand Down Expand Up @@ -284,6 +293,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Canonical implementation shielded by feature.

<!-- ISSUES -->
[#163]: https://github.com/dusk-network/phoenix-core/issues/163
[#156]: https://github.com/dusk-network/phoenix-core/issues/156
[#155]: https://github.com/dusk-network/phoenix-core/issues/155
[#152]: https://github.com/dusk-network/phoenix-core/issues/152
Expand Down
180 changes: 138 additions & 42 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub use stake::*;
mod transfer;
pub use transfer::*;

use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;

Expand All @@ -21,7 +22,7 @@ use rkyv::{Archive, Deserialize, Serialize};
use dusk_bls12_381::BlsScalar;
use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable};

use crate::{Crossover, Fee, Note};
use crate::{Crossover, Fee, Note, PublicKey};

/// Type alias for the ID of a module.
pub type ModuleId = [u8; 32];
Expand All @@ -48,10 +49,40 @@ pub struct Transaction {
pub crossover: Option<Crossover>,
/// Serialized proof of the `Execute` circuit for this transaction.
pub proof: Vec<u8>,
/// An optional execution carried by the transaction.
pub exec: Option<Execution>,
}

/// An execution is either a call to a contract, or a contract deployment.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "rkyv-impl",
derive(Archive, Serialize, Deserialize),
archive_attr(derive(bytecheck::CheckBytes))
)]
pub enum Execution {
/// A call to a contract. The `Vec<u8>` must be an `rkyv`ed representation
/// of the data the contract expects, and the `String` the name of the
/// function to call.
pub call: Option<(ModuleId, String, Vec<u8>)>,
/// contract function to call.
Call {
/// The contract to be called
contract: ModuleId,
/// The function to call in the contract
fn_name: String,
/// The argument to the function to call
fn_arg: Vec<u8>,
},
/// A contract deployment.
Deploy {
/// The owner of the contract
owner: Box<PublicKey>, /* boxed due to a large size difference
* between variants */
moCello marked this conversation as resolved.
Show resolved Hide resolved
/// The contract's bytecode
bytecode: Vec<u8>,
/// Arguments to the constructor of the contract. If the contract has
/// no constructor this should be empty.
constructor_args: Vec<u8>,
},
}

impl Transaction {
Expand All @@ -64,7 +95,7 @@ impl Transaction {
anchor: &BlsScalar,
fee: &Fee,
crossover: &Option<Crossover>,
call: &Option<(ModuleId, String, Vec<u8>)>,
call: &Option<Execution>,
) -> Vec<u8> {
let mut bytes = Vec::new();

Expand All @@ -82,10 +113,27 @@ impl Transaction {
bytes.extend(crossover.to_bytes());
}

if let Some((module, fn_name, call_data)) = call {
bytes.extend(module);
bytes.extend(fn_name.as_bytes());
bytes.extend(call_data);
if let Some(execution) = call {
match execution {
Execution::Call {
contract,
fn_name,
fn_arg,
} => {
bytes.extend(contract);
bytes.extend(fn_name.as_bytes());
bytes.extend(fn_arg);
}
Execution::Deploy {
owner,
bytecode,
constructor_args,
} => {
bytes.extend(owner.to_bytes());
bytes.extend(bytecode);
bytes.extend(constructor_args);
}
}
}

bytes
Expand All @@ -100,7 +148,7 @@ impl Transaction {
&self.anchor,
&self.fee,
&self.crossover,
&self.call,
&self.exec,
)
}

Expand Down Expand Up @@ -137,17 +185,42 @@ impl Transaction {
bytes.extend(proof_len.to_bytes());
bytes.extend(&self.proof);

if let Some((module, fn_name, call_data)) = &self.call {
bytes.push(1);
bytes.extend(module);

let size = fn_name.len() as u64;
bytes.extend(size.to_bytes());
bytes.extend(fn_name.as_bytes());
bytes.extend(call_data);
} else {
bytes.push(0);
match &self.exec {
None => bytes.push(0),
Some(execution) => match execution {
Execution::Call {
contract,
fn_name,
fn_arg,
} => {
bytes.push(1);

bytes.extend(contract);

let fn_name_size = fn_name.len() as u64;
bytes.extend(fn_name_size.to_bytes());
bytes.extend(fn_name.as_bytes());

bytes.extend(fn_arg);
}
Execution::Deploy {
owner,
bytecode,
constructor_args,
} => {
bytes.push(2);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the encodings 0,1,2 are also used below for deserialisation, could we maybe introduce some implicit constants for them? Also, case 0 would be easier to understand if it had a name.


bytes.extend(owner.to_bytes());

let bytecode_size = bytecode.len() as u64;
bytes.extend(bytecode_size.to_bytes());
bytes.extend(bytecode);

bytes.extend(constructor_args);
}
},
}

bytes
}

Expand Down Expand Up @@ -183,31 +256,54 @@ impl Transaction {
let proof = buffer[..proof_size].to_vec();
let buffer = &buffer[proof_size..];

let has_call = buffer[0] != 0;
let exec_id = buffer[0];
let mut buffer = &buffer[1..];

let call = if has_call {
let buffer_len = buffer.len();
if buffer.len() < 32 {
return Err(BytesError::BadLength {
found: buffer_len,
expected: 32,
});
let exec = match exec_id {
0 => None,
1 => {
let buffer_len = buffer.len();
if buffer.len() < 32 {
return Err(BytesError::BadLength {
found: buffer_len,
expected: 32,
});
}

let (module_buf, buf) = buffer.split_at(32);
buffer = buf;

let mut contract = [0u8; 32];
contract.copy_from_slice(module_buf);

let fn_name_size = u64::from_reader(&mut buffer)? as usize;
let fn_name =
String::from_utf8(buffer[..fn_name_size].to_vec())
.map_err(|_err| BytesError::InvalidData)?;
let fn_arg = buffer[fn_name_size..].to_vec();

Some(Execution::Call {
contract,
fn_name,
fn_arg,
})
}
2 => {
let owner = PublicKey::from_reader(&mut buffer)?;
let owner = Box::new(owner);

let (module_buf, buf) = buffer.split_at(32);
buffer = buf;
let bytecode_size = u64::from_reader(&mut buffer)? as usize;
let bytecode = buffer[..bytecode_size].to_vec();

let mut module = [0u8; 32];
module.copy_from_slice(module_buf);
let constructor_args = buffer[bytecode_size..].to_vec();

let fn_name_size = u64::from_reader(&mut buffer)? as usize;
let fn_name = String::from_utf8(buffer[..fn_name_size].to_vec())
.map_err(|_err| BytesError::InvalidData)?;
let call_data = buffer[fn_name_size..].to_vec();
Some((module, fn_name, call_data))
} else {
None
Some(Execution::Deploy {
owner,
bytecode,
constructor_args,
})
}
_ => return Err(BytesError::InvalidData),
};

Ok(Self {
Expand All @@ -217,7 +313,7 @@ impl Transaction {
fee,
crossover,
proof,
call,
exec,
})
}

Expand All @@ -241,8 +337,8 @@ impl Transaction {
self.crossover.as_ref()
}

/// Returns the call of the transaction.
pub fn call(&self) -> Option<&(ModuleId, String, Vec<u8>)> {
self.call.as_ref()
/// Returns the execution of the transaction.
pub fn exec(&self) -> Option<&Execution> {
self.exec.as_ref()
}
}
48 changes: 41 additions & 7 deletions tests/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ use core::convert::TryInto;
use dusk_bls12_381::BlsScalar;
use dusk_jubjub::JubJubScalar;
use ff::Field;
use phoenix_core::transaction::Execution;
use phoenix_core::{Error, Note, PublicKey, SecretKey, Transaction};
use rand_core::OsRng;

#[test]
fn transaction_parse() -> Result<(), Error> {
let mut rng = OsRng;
use rand_core::{CryptoRng, OsRng, RngCore};

fn tx_parse<Rng: RngCore + CryptoRng>(
mut rng: Rng,
exec: Option<Execution>,
) -> Result<(), Error> {
let sk = SecretKey::random(&mut rng);
let pk = PublicKey::from(&sk);

Expand All @@ -30,15 +31,14 @@ fn transaction_parse() -> Result<(), Error> {
let nullifiers = vec![BlsScalar::from(456), BlsScalar::from(789)];
let outputs = vec![note];
let proof = vec![23, 45, 67];
let call = Some(([0; 32], "TestString".to_string(), vec![4, 5, 6]));
let transaction = Transaction {
anchor,
nullifiers,
outputs,
fee,
crossover: Some(crossover),
proof,
call,
exec,
};
let bytes_of_transaction = transaction.to_var_bytes();
assert_eq!(
Expand All @@ -47,3 +47,37 @@ fn transaction_parse() -> Result<(), Error> {
);
Ok(())
}

#[test]
fn transfer_parse() -> Result<(), Error> {
tx_parse(OsRng, None)
}

#[test]
fn call_parse() -> Result<(), Error> {
tx_parse(
OsRng,
Some(Execution::Call {
contract: [0; 32],
fn_name: "TestString".to_string(),
fn_arg: vec![4, 5, 6],
}),
)
}

#[test]
fn deploy_parse() -> Result<(), Error> {
let mut rng = OsRng;

let sk = SecretKey::random(&mut rng);
let pk = PublicKey::from(&sk);

tx_parse(
rng,
Some(Execution::Deploy {
owner: Box::new(pk),
bytecode: b"some-strange-bytecode.wasm".to_vec(),
constructor_args: b"some-strange-ctor".to_vec(),
}),
)
}