Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
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
3 changes: 2 additions & 1 deletion contracts/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use primitives::Address;
use primitives::hash::H256;
use state_machine::StaticExternalities;

use error::Result;
use executor::RustExecutor;

/// Data and some sort of Authentication Data
type DataAndAuth = (Vec<u8>, Vec<u8>);
type DataAndAuth = (H256, Vec<u8>);

/// Authentication contract rust implementation.
#[derive(Debug, Default)]
Expand Down
212 changes: 200 additions & 12 deletions contracts/src/balances.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use primitives::Address;
use primitives::contract::CallData;
use primitives::uint::U256;
use state_machine::{Externalities, StaticExternalities};
use primitives::{hash, Address};
use serializer;
use state_machine::{self, Externalities, StaticExternalities};

use error::Result;
use error::{Error, ErrorKind, Result, ResultExt};
use executor::RustExecutor;

#[derive(Debug, Serialize, Deserialize)]
/// Account entry
#[derive(Default, Debug, Serialize, Deserialize)]
struct Account {
/// Account balance
balance: U256,
/// Account nonce
nonce: U256,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transfer {
/// Transfer value
value: U256,
Expand All @@ -33,32 +44,209 @@ pub struct Transfer {
authentication_data: Vec<u8>,
}

fn externalities_error<E: state_machine::Error>(e: E) -> Error {
ErrorKind::Externalities(Box::new(e)).into()
}

fn address(x: u8) -> Address {
[x].as_ref().into()
}

fn account<E: StaticExternalities<RustExecutor>>(ext: &E, address: Address) -> Result<Account> {
ext.storage(&address.into())
.map_err(externalities_error)
.and_then(|x| if x.is_empty() {
Ok(Account::default())
} else {
serializer::from_slice(x).chain_err(|| "Invalid internal structure.")
})
}

/// Balances contract rust implementation.
#[derive(Debug, Default)]
pub struct Contract;
impl Contract {
/// Returns a balance of given address.
pub fn balance_of<E: StaticExternalities<RustExecutor>>(&self, _ext: &E, _data: Address) -> Result<U256> {
unimplemented!()
pub fn balance_of<E: StaticExternalities<RustExecutor>>(&self, ext: &E, data: Address) -> Result<U256> {
account(ext, data).map(|acc| acc.balance)
}

/// Returns the next nonce to authorize the transfer from given address.
pub fn next_nonce<E: StaticExternalities<RustExecutor>>(&self, _ext: &E, _data: Address) -> Result<U256> {
unimplemented!()
pub fn next_nonce<E: StaticExternalities<RustExecutor>>(&self, ext: &E, data: Address) -> Result<U256> {
account(ext, data).map(|acc| acc.nonce)
}

/// Checks preconditions for transfer.
/// Should verify:
/// - signature
/// - replay protection
/// - enough balance
pub fn transfer_preconditions<E: StaticExternalities<RustExecutor>>(&self, _db: &E, _data: Transfer) -> Result<bool> {
unimplemented!()
pub fn transfer_preconditions<E: StaticExternalities<RustExecutor>>(&self, ext: &E, data: Transfer) -> Result<Option<Address>> {
// Check the caller:
let sender = ext.sender();
if ![
address(RustExecutor::TOP_LEVEL),
address(RustExecutor::BALANCES),
].contains(sender) {
return Ok(None)
}

// check the signature
let mut bytes = [0u8; 32 + 20 + 32];
data.value.to_big_endian(&mut bytes[0..32]);
bytes[32..52].clone_from_slice(&*data.to);
data.nonce.to_big_endian(&mut bytes[52..84]);
let hash = hash(&bytes);

let sender = serializer::from_slice(&ext.call_static(
&address(RustExecutor::TOP_LEVEL),
"check_auth",
&CallData(serializer::to_vec(&(hash, data.authentication_data))),
).map_err(externalities_error)?.0)
.chain_err(|| "Invalid auth contract response.")?;

let account = account(ext, sender)?;

// check nonce
if account.nonce != data.nonce {
return Ok(None)
}

// check balance
if account.balance < data.value {
return Ok(None)
}

// return sender and account
Ok(Some(sender))
}

/// Perform a transfer.
/// This should first make sure that precondtions are satisfied and later perform the transfer.
pub fn transfer<E: Externalities<RustExecutor>>(&self, _ext: &mut E, _data: Transfer) -> Result<bool> {
unimplemented!()
pub fn transfer<E: Externalities<RustExecutor>>(&self, ext: &mut E, data: Transfer) -> Result<bool> {
let from = match self.transfer_preconditions(ext, data.clone())? {
None => return Ok(false),
Some(address) => address,
};

let mut sender = account(ext, from)?;
let mut recipient = account(ext, data.to)?;

// update nonce
sender.nonce = sender.nonce.checked_add(&1.into())
.ok_or_else(|| ErrorKind::OperationOverflow)?;

// update balances
sender.balance = sender.balance.checked_sub(&data.value)
.ok_or_else(|| ErrorKind::OperationOverflow)?;
recipient.balance = recipient.balance.checked_add(&data.value)
.ok_or_else(|| ErrorKind::OperationOverflow)?;

// save changes to the storage
ext.set_storage(data.to.into(), serializer::to_vec(&recipient));
ext.set_storage(from.into(), serializer::to_vec(&sender));

Ok(true)
}
}

#[cfg(test)]
mod tests {
use super::*;
use test_helpers::TestExternalities;

#[test]
fn should_return_balance_of_and_do_a_transfer() {
// given
let mut ext = TestExternalities::default();
ext.call_result = Some(serializer::to_vec(&Address::from(5)));
ext.storage.insert(5.into(), serializer::to_vec(&Account {
nonce: 0.into(),
balance: 10.into(),
}));

let balances = Contract::default();
let transfer = Transfer {
value: 5.into(),
to: 1.into(),
nonce: 0.into(),
authentication_data: vec![5],
};
assert_eq!(balances.balance_of(&ext, 5.into()).unwrap(), 10.into());
assert_eq!(balances.balance_of(&ext, 1.into()).unwrap(), 0.into());
assert_eq!(balances.next_nonce(&ext, 5.into()).unwrap(), 0.into());

// when
assert!(balances.transfer_preconditions(&ext, transfer.clone()).unwrap().is_some());
assert_eq!(balances.transfer(&mut ext, transfer).unwrap(), true);

// then
assert_eq!(balances.balance_of(&ext, 5.into()).unwrap(), 5.into());
assert_eq!(balances.balance_of(&ext, 1.into()).unwrap(), 5.into());
assert_eq!(balances.next_nonce(&ext, 5.into()).unwrap(), 1.into());
}

#[test]
fn should_reject_on_invalid_nonce() {
// given
let mut ext = TestExternalities::default();
ext.call_result = Some(serializer::to_vec(&Address::from(5)));
ext.storage.insert(5.into(), serializer::to_vec(&Account {
nonce: 0.into(),
balance: 10.into(),
}));

let balances = Contract::default();
let transfer = Transfer {
value: 5.into(),
to: 1.into(),
nonce: 1.into(),
authentication_data: vec![5],
};

assert!(balances.transfer_preconditions(&ext, transfer.clone()).unwrap().is_none());
}

#[test]
fn should_reject_on_insufficient_balance() {
// given
let mut ext = TestExternalities::default();
ext.call_result = Some(serializer::to_vec(&Address::from(5)));
ext.storage.insert(5.into(), serializer::to_vec(&Account {
nonce: 0.into(),
balance: 4.into(),
}));

let balances = Contract::default();
let transfer = Transfer {
value: 5.into(),
to: 1.into(),
nonce: 0.into(),
authentication_data: vec![5],
};

assert!(balances.transfer_preconditions(&ext, transfer.clone()).unwrap().is_none());
}

#[test]
fn should_reject_on_non_top_level_call() {
// given
let mut ext = TestExternalities::default();
ext.sender = 1.into();
ext.call_result = Some(serializer::to_vec(&Address::from(5)));
ext.storage.insert(5.into(), serializer::to_vec(&Account {
nonce: 0.into(),
balance: 10.into(),
}));

let balances = Contract::default();
let transfer = Transfer {
value: 5.into(),
to: 1.into(),
nonce: 0.into(),
authentication_data: vec![5],
};

assert!(balances.transfer_preconditions(&ext, transfer.clone()).unwrap().is_none());
}
}
6 changes: 6 additions & 0 deletions contracts/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,11 @@ error_chain! {
description("externalities failure"),
display("Externalities error: {}", e),
}

/// Operation overflow
OperationOverflow {
description("overflow"),
display("Operation overflow"),
}
}
}
38 changes: 9 additions & 29 deletions contracts/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ pub struct RustExecutor {
}

impl RustExecutor {
const AUTH: u8 = 1;
const BALANCES: u8 = 2;
const VALIDATOR_SET: u8 = 3;
/// Call initiated by a transaction.
pub const TOP_LEVEL: u8 = 0;
/// Authentication contract.
pub const AUTH: u8 = 1;
/// Balances contract.
pub const BALANCES: u8 = 2;
/// Validator set contract.
pub const VALIDATOR_SET: u8 = 3;
}

impl CodeExecutor for RustExecutor {
Expand Down Expand Up @@ -95,32 +100,7 @@ impl CodeExecutor for RustExecutor {
#[cfg(test)]
mod tests {
use super::*;
use primitives::Address;
use primitives::hash::H256;

#[derive(Debug, Default)]
struct TestExternalities;
impl Externalities<RustExecutor> for TestExternalities {
fn set_storage(&mut self, _key: H256, _value: Vec<u8>) {
unimplemented!()
}

fn call(&mut self, _address: &Address, _method: &str, _data: &CallData) -> Result<OutData> {
unimplemented!()
}
}

impl StaticExternalities<RustExecutor> for TestExternalities {
type Error = Error;

fn storage(&self, _key: &H256) -> Result<&[u8]> {
unimplemented!()
}

fn call_static(&self, _address: &Address, _method: &str, _data: &CallData) -> Result<OutData> {
unimplemented!()
}
}
use test_helpers::TestExternalities;

#[test]
fn should_fail_for_empty_or_unknown_code() {
Expand Down
3 changes: 3 additions & 0 deletions contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ mod validator_set;
pub mod error;
pub mod executor;

#[cfg(test)]
mod test_helpers;

/// Creates new RustExecutor for contracts.
pub fn executor() -> executor::RustExecutor {
executor::RustExecutor::default()
Expand Down
Loading