Skip to content
This repository was archived by the owner on Apr 8, 2022. It is now read-only.
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
44 changes: 29 additions & 15 deletions pallets/pp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

use codec::Encode;
use codec::{Decode, Encode};
pub use frame_support::{
construct_runtime,
dispatch::Vec,
Expand Down Expand Up @@ -144,7 +144,12 @@ fn send_p2pk_tx<T: Config>(
ensure!(fund_info.funds >= value, "Caller doesn't have enough funds");
let outpoints = fund_info.utxos.iter().map(|x| x.0).collect::<Vec<H256>>();

T::Utxo::send_conscrit_p2pk(caller, dest, value, &outpoints)
T::Utxo::submit_c2pk_tx(caller, dest, value, &outpoints).map(|_| {
<ContractBalances<T>>::mutate(&caller, |info| {
info.as_mut().unwrap().utxos = Vec::new();
info.as_mut().unwrap().funds = 0;
});
})
}

/// Create Contract-to-Contract transfer that allows smart contracts to
Expand All @@ -169,7 +174,12 @@ fn send_c2c_tx<T: Config>(
))?;
let outpoints = fund_info.utxos.iter().map(|x| x.0).collect::<Vec<H256>>();

T::Utxo::send_conscrit_c2c(caller, dest, fund_info.funds, data, &outpoints)
T::Utxo::submit_c2c_tx(caller, dest, fund_info.funds, data, &outpoints).map(|_| {
<ContractBalances<T>>::mutate(&caller, |info| {
info.as_mut().unwrap().utxos = Vec::new();
info.as_mut().unwrap().funds = 0;
});
})
}

impl<T: Config> ProgrammablePoolApi for Pallet<T>
Expand Down Expand Up @@ -267,10 +277,18 @@ impl<T: pallet_contracts::Config + pallet::Config> ChainExtension<T> for Pallet<
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
// Fetch AccountId of the caller from the ChainExtension's memory
// This way the progrmmable pool can force the caller of the ChainExtension
// to only spend their own funds as `ContractBalances` will be queried
// using `acc_id` and user cannot control the value of this variable
let mut env = env.buf_in_buf_out();
let acc_id = env.ext().address().encode();
let acc_id: T::AccountId = T::AccountId::decode(&mut &acc_id[..])
.map_err(|_| "Failed to get smart contract's AccountId")?;

match func_id {
x if x == ChainExtensionCall::Transfer as u32 => {
let mut env = env.buf_in_buf_out();
let (acc_id, dest, value): (T::AccountId, T::AccountId, u128) = env.read_as()?;
let (dest, value): (T::AccountId, u128) = env.read_as()?;

if !<ContractBalances<T>>::get(&dest).is_none() {
return Err(DispatchError::Other(
Expand All @@ -281,9 +299,6 @@ impl<T: pallet_contracts::Config + pallet::Config> ChainExtension<T> for Pallet<
send_p2pk_tx::<T>(&acc_id, &dest, value)?
}
x if x == ChainExtensionCall::Balance as u32 => {
let mut env = env.buf_in_buf_out();
let acc_id: T::AccountId = env.read_as()?;

let fund_info = <ContractBalances<T>>::get(&acc_id).ok_or(DispatchError::Other(
"Contract doesn't own any UTXO or it doesn't exist!",
))?;
Expand All @@ -294,18 +309,17 @@ impl<T: pallet_contracts::Config + pallet::Config> ChainExtension<T> for Pallet<
x if x == ChainExtensionCall::Call as u32 => {
// `read_as_unbounded()` has to be used here because the size of `data`
// is only known during runtime
let mut env = env.buf_in_buf_out();
let (acc_id, dest, selector, mut data): (
T::AccountId,
T::AccountId,
[u8; 4],
Vec<u8>,
) = env.read_as_unbounded(env.in_len())?;
let (dest, selector, mut data): (T::AccountId, [u8; 4], Vec<u8>) =
env.read_as_unbounded(env.in_len())?;

if <ContractBalances<T>>::get(&dest).is_none() {
return Err(DispatchError::Other("Destination doesn't exist"));
}

if acc_id == dest {
return Err(DispatchError::Other("Contract cannot call itself"));
}

// append data to the selector so the final data
// passed on to the contract is in correct format
let mut selector = selector.to_vec();
Expand Down
144 changes: 74 additions & 70 deletions pallets/utxo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,10 @@ pub mod pallet {
pub(super) type StakingCount<T: Config> =
StorageMap<_, Identity, T::AccountId, (u64, Value), OptionQuery>;

#[pallet::storage]
#[pallet::getter(fn ectl_store)]
pub(super) type EctlStore<T: Config> = StorageMap<_, Identity, H256, bool>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
#[pallet::metadata(T::AccountId = "AccountId")]
Expand Down Expand Up @@ -561,42 +565,36 @@ pub mod pallet {
}

pub fn create<T: Config>(
_caller: &T::AccountId,
_code: &Vec<u8>,
_utxo_hash: H256,
_utxo_value: u128,
_data: &Vec<u8>,
) {
// let weight: Weight = 6000000000;

// match T::ProgrammablePool::create(caller, weight, code, utxo_hash, utxo_value, data) {
// Ok(_) => log::info!("success!"),
// Err(e) => log::error!("failure: {:#?}", e),
// }
caller: &T::AccountId,
code: &Vec<u8>,
utxo_hash: H256,
utxo_value: u128,
data: &Vec<u8>,
) -> Result<(), &'static str> {
let weight: Weight = 6000000000;

T::ProgrammablePool::create(caller, weight, code, utxo_hash, utxo_value, data)
}

pub fn call<T: Config>(
_caller: &T::AccountId,
_dest: &T::AccountId,
_utxo_hash: H256,
_utxo_value: u128,
_fund_contract: bool,
_data: &Vec<u8>,
) {
// let weight: Weight = 6000000000;

// match T::ProgrammablePool::call(
// caller,
// dest,
// weight,
// utxo_hash,
// utxo_value,
// fund_contract,
// data,
// ) {
// Ok(_) => log::info!("success!"),
// Err(e) => log::error!("failure: {:#?}", e),
// }
caller: &T::AccountId,
dest: &T::AccountId,
utxo_hash: H256,
utxo_value: u128,
fund_contract: bool,
data: &Vec<u8>,
) -> Result<(), &'static str> {
let weight: Weight = 6000000000;

T::ProgrammablePool::call(
caller,
dest,
weight,
utxo_hash,
utxo_value,
fund_contract,
data,
)
}

pub fn validate_transaction<T: Config>(
Expand Down Expand Up @@ -1040,13 +1038,22 @@ pub mod pallet {
log::info!("TODO validate spending of OP_CREATE");
}
Destination::CallPP(_, _, _) => {
let spend =
u16::from_le_bytes(input.witness[1..].try_into().or_else(|_| {
Err(DispatchError::Other(
"Failed to convert witness to an opcode",
))
})?);
ensure!(spend == 0x1337, "OP_SPEND not found");
// 32-byte hash + 1 byte length
ensure!(
input.witness.len() == 33,
"Witness field doesn't contain valid data"
);

let hash: [u8; 32] = input.witness[1..]
.try_into()
.map_err(|_| DispatchError::Other("Failed to convert the slice"))?;

ensure!(
<EctlStore<T>>::get(&H256::from(hash)).is_some(),
"Transaction does not have access to smart contract outputs"
);

<EctlStore<T>>::remove(&H256::from(hash));
}
Destination::ScriptHash(_hash) => {
let witness = input.witness.clone();
Expand Down Expand Up @@ -1139,12 +1146,12 @@ pub mod pallet {
Destination::CreatePP(script, data) => {
log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash);
<UtxoStore<T>>::insert(hash, output);
create::<T>(caller, script, hash, output.value, &data);
create::<T>(caller, script, hash, output.value, &data)?;
}
Destination::CallPP(acct_id, fund, data) => {
log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash);
<UtxoStore<T>>::insert(hash, output);
call::<T>(caller, acct_id, hash, output.value, *fund, data);
call::<T>(caller, acct_id, hash, output.value, *fund, data)?;
}
Destination::LockForStaking { .. } => {
staking::lock_for_staking::<T>(hash, output)?;
Expand Down Expand Up @@ -1371,10 +1378,11 @@ impl<T: Config> crate::Pallet<T> {
// }
}

fn coin_picker<T: Config>(outpoints: &Vec<H256>) -> Result<Vec<TransactionInput>, DispatchError> {
fn construct_inputs<T: Config>(
outpoints: &Vec<H256>,
) -> Result<Vec<TransactionInput>, DispatchError> {
let mut inputs: Vec<TransactionInput> = Vec::new();

// consensus-critical sorting function...
let mut outpoints = outpoints.clone();
outpoints.sort();

Expand All @@ -1385,8 +1393,13 @@ fn coin_picker<T: Config>(outpoints: &Vec<H256>) -> Result<Vec<TransactionInput>
inputs.push(TransactionInput::new_script(
*outpoint,
Builder::new().into_script(),
Builder::new().push_int(0x1337).into_script(),
Builder::new().push_slice(&outpoint.encode()).into_script(),
));

// save the outpoint hash of the input UTXO to ECTL
// from which it can be fetched for validation when
// the node receives a TX that tries to spend OP_CALLs
<EctlStore<T>>::insert(outpoint, true);
}
_ => {
return Err(DispatchError::Other("Only CallPP vouts can be spent!"));
Expand Down Expand Up @@ -1430,7 +1443,7 @@ where
staking::withdraw::<T>(stash_account_caller.clone())
}

fn send_conscrit_p2pk(
fn submit_c2pk_tx(
caller: &T::AccountId,
dest: &T::AccountId,
value: u128,
Expand All @@ -1439,39 +1452,30 @@ where
let pubkey_raw: [u8; 32] =
dest.encode().try_into().map_err(|_| "Failed to get caller's public key")?;

spend::<T>(
caller,
&Transaction {
inputs: coin_picker::<T>(outpoints)?,
outputs: vec![TransactionOutput::new_pubkey(value, H256::from(pubkey_raw))],
time_lock: Default::default(),
},
)
.map_err(|_| "Failed to spend the transaction!")?;
let tx = Transaction {
inputs: construct_inputs::<T>(outpoints)?,
outputs: vec![TransactionOutput::new_pubkey(value, H256::from(pubkey_raw))],
time_lock: Default::default(),
};

spend::<T>(caller, &tx).map_err(|_| "Failed to spend the transaction!")?;
Ok(())
}

fn send_conscrit_c2c(
fn submit_c2c_tx(
caller: &Self::AccountId,
dest: &Self::AccountId,
value: u128,
data: &Vec<u8>,
outpoints: &Vec<H256>,
) -> Result<(), DispatchError> {
spend::<T>(
caller,
&Transaction {
inputs: coin_picker::<T>(outpoints)?,
outputs: vec![TransactionOutput::new_call_pp(
value,
dest.clone(),
true,
data.clone(),
)],
time_lock: Default::default(),
},
)
.map_err(|_| "Failed to spend the transaction!")?;
let tx = Transaction {
inputs: construct_inputs::<T>(outpoints)?,
outputs: vec![TransactionOutput::new_call_pp(value, dest.clone(), true, data.clone())],
time_lock: Default::default(),
};

spend::<T>(caller, &tx).map_err(|_| "Failed to spend the transaction!")?;
Ok(())
}
}
Loading