Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Balances contract implementation #23

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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 !&[
Copy link
Contributor

Choose a reason for hiding this comment

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

the & may not be necessary

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 @@ -44,5 +44,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 Executor for RustExecutor {
Expand Down Expand Up @@ -95,32 +100,7 @@ impl Executor 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 new() -> executor::RustExecutor {
executor::RustExecutor::default()
Expand Down
Loading