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 example which uses ext_transfer + ext_terminate #554

Merged
merged 20 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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 crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ pub fn restore_contract<T>(
/// This function never returns. Either the termination was successful and the
/// execution of the destroyed contract is halted. Or it failed during the termination
/// which is considered fatal and results in a trap + rollback.
pub fn terminate_contract<T>(beneficiary: T::AccountId) -> !
pub fn terminate_contract<T>(beneficiary: T::AccountId) -> Result<()>
where
T: Environment,
{
Expand Down
2 changes: 1 addition & 1 deletion crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ pub trait TypedEnvBackend: EnvBackend {
/// # Note
///
/// For more details visit: [`ink_env::terminate_contract`]
fn terminate_contract<T>(&mut self, beneficiary: T::AccountId) -> !
fn terminate_contract<T>(&mut self, beneficiary: T::AccountId) -> Result<()>
where
T: Environment;

Expand Down
8 changes: 8 additions & 0 deletions crates/env/src/engine/off_chain/db/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ impl AccountsDb {
},
);
}

/// Removes an account.
pub fn remove_account<T>(&mut self, account_id: T::AccountId)
where
T: Environment,
{
self.accounts.remove(&OffAccountId::new(&account_id));
}
}

/// An account within the chain.
Expand Down
20 changes: 17 additions & 3 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ impl EnvInstance {
.set_balance::<T>(dst_value + value)?;
Ok(())
}

fn terminate_contract_impl<T>(&mut self, beneficiary: T::AccountId) -> Result<()>
where
T: Environment,
{
// Send the remaining balance to the beneficiary
let all: T::Balance = self.balance::<T>().expect("could not decode balance");
self.transfer_impl::<T>(beneficiary, all)?;

let contract_id = self.account_id::<T>().expect("could not decode account id");
self.accounts.remove_account::<T>(contract_id);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What the on-chain impl would do here is set a tombstone with a code hash and remove storage. AFAIU both is not easily achievable with our current off-chain env, hence I left it out here for the moment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you maybe provide a small dev. note comment explaining exactly this?

Ok(())
}
}

impl TypedEnvBackend for EnvInstance {
Expand Down Expand Up @@ -349,7 +363,7 @@ impl TypedEnvBackend for EnvInstance {
T: Environment,
Args: scale::Encode,
{
unimplemented!("off-chain environment does not support contract invokation")
unimplemented!("off-chain environment does not support contract invocation")
}

fn eval_contract<T, Args, R>(
Expand All @@ -375,11 +389,11 @@ impl TypedEnvBackend for EnvInstance {
unimplemented!("off-chain environment does not support contract instantiation")
}

fn terminate_contract<T>(&mut self, _beneficiary: T::AccountId) -> !
fn terminate_contract<T>(&mut self, beneficiary: T::AccountId) -> Result<()>
where
T: Environment,
{
unimplemented!("off-chain environment does not support contract termination")
self.terminate_contract_impl::<T>(beneficiary)
}

fn restore_contract<T>(
Expand Down
2 changes: 1 addition & 1 deletion crates/env/src/engine/off_chain/typed_encoded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl<M> TypedEncoded<M> {
}
}

/// Creates a new typed-encoded ininitialized by `value` of type `T`.
/// Creates a new typed-encoded initialized by `value` of type `T`.
pub fn new<T>(value: &T) -> Self
where
T: scale::Encode + 'static,
Expand Down
2 changes: 1 addition & 1 deletion crates/lang/ir/src/ir/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ where
{
let (ink_attrs, other_attrs) = ir::partition_attributes(attrs)?;
let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
err.into_combine(format_err!(parent_span, "at this invokation",))
err.into_combine(format_err!(parent_span, "at this invocation",))
})?;
normalized.ensure_first(is_valid_first).map_err(|err| {
err.into_combine(format_err!(
Expand Down
2 changes: 1 addition & 1 deletion crates/lang/ir/src/ir/item/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl TryFrom<syn::ItemStruct> for Event {
}
let normalized =
ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
err.into_combine(format_err!(field_span, "at this invokation",))
err.into_combine(format_err!(field_span, "at this invocation",))
})?;
if !matches!(normalized.first().kind(), ir::AttributeArgKind::Topic) {
return Err(format_err!(
Expand Down
4 changes: 2 additions & 2 deletions crates/lang/ir/src/ir/item_impl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ impl ItemImpl {
if !ink_attrs.is_empty() {
let normalized =
ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
err.into_combine(format_err!(impl_block_span, "at this invokation",))
err.into_combine(format_err!(impl_block_span, "at this invocation",))
})?;
if normalized
.ensure_first(&ir::AttributeArgKind::Implementation)
Expand Down Expand Up @@ -295,7 +295,7 @@ impl TryFrom<syn::ItemImpl> for ItemImpl {
if !ink_attrs.is_empty() {
let normalized =
ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
err.into_combine(format_err!(impl_block_span, "at this invokation",))
err.into_combine(format_err!(impl_block_span, "at this invocation",))
})?;
normalized.ensure_no_conflicts(|arg| {
!matches!(arg.kind(), ir::AttributeArgKind::Implementation | ir::AttributeArgKind::Namespace(_))
Expand Down
2 changes: 1 addition & 1 deletion crates/lang/ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
//! parse, analyze and generate code for ink! smart contracts.
//!
//! The entry point for every ink! smart contract is the [`Contract`](`crate::ir::Contract`)
//! with its [`Config`](`crate::ir::Config`) provided in the initial invokation at
//! with its [`Config`](`crate::ir::Config`) provided in the initial invocation at
//! `#[ink::contract(... configuration ...)]`.
//!
//! The ink! IR tries to stay close to the original Rust syntactic structure.
Expand Down
4 changes: 2 additions & 2 deletions crates/lang/src/env_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ where
ink_env::gas_left::<T>().expect("couldn't decode gas left")
}

/// Returns the timstamp of the current block.
/// Returns the timestamp of the current block.
///
/// # Note
///
Expand Down Expand Up @@ -269,7 +269,7 @@ where
/// # Note
///
/// For more details visit: [`ink_env::terminate_contract`]
pub fn terminate_contract(self, beneficiary: T::AccountId) -> ! {
pub fn terminate_contract(self, beneficiary: T::AccountId) -> Result<()> {
ink_env::terminate_contract::<T>(beneficiary)
}

Expand Down
9 changes: 9 additions & 0 deletions examples/lock-until/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore build artifacts from the local tests sub-crate.
/target/

# Ignore backup files creates by cargo fmt.
**/*.rs.bk

# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
36 changes: 36 additions & 0 deletions examples/lock-until/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "lock_until"
version = "3.0.0-rc1"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"

[dependencies]
ink_primitives = { version = "3.0.0-rc1", path = "../../crates/primitives", default-features = false }
ink_metadata = { version = "3.0.0-rc1", path = "../../crates/metadata", default-features = false, features = ["derive"], optional = true }
ink_env = { version = "3.0.0-rc1", path = "../../crates/env", default-features = false }
ink_storage = { version = "3.0.0-rc1", path = "../../crates/storage", default-features = false }
ink_lang = { version = "3.0.0-rc1", path = "../../crates/lang", default-features = false }

scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] }
scale-info = { version = "0.4", default-features = false, features = ["derive"], optional = true }


[lib]
name = "lock_until"
path = "lib.rs"
crate-type = ["cdylib"]

[features]
default = ["std"]
std = [
"ink_primitives/std",
"ink_metadata",
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
"ink_lang/std",
"scale/std",
"scale-info",
"scale-info/std",
]
ink-as-dependency = []
184 changes: 184 additions & 0 deletions examples/lock-until/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Locks the tokens sent to this smart contract until a lock period
//! is reached. The lock period is set by the initial creator of the
//! contract.
//!
//! Whoever calls `unlock()` first gets the locked amount paid out.
//! The contract terminates itself subsequently.

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
pub mod lock_until {
/// Hold a timestamp denoting how long the balance in this
/// contract should be kept locked.
#[ink(storage)]
pub struct LockUntil {
lock_until: Timestamp,
}

/// The error types.
#[derive(Debug, PartialEq, Eq, scale::Encode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
/// Returned if the lock period is not yet over.
LockPeriodNotOver,
}

impl LockUntil {
/// Creates a new lock-until smart contract. The tokens sent to this contract
/// will be locked until the supplied `lock_until` timestamp has been reached.
#[ink(constructor)]
pub fn new(lock_until: Timestamp) -> Self {
Self { lock_until }
}

/// Locks the amount sent with this call.
///
/// If the contract already has some balance then the value send with
/// this call is just added to the existing balance.
#[ink(message, payable)]
pub fn lock(&mut self) {}

/// Tries to unlock the currently locked value.
///
/// # Errors
///
/// - Return `Error::LockPeriodNotOver` in case the timestamp of this
/// block is >= to the timestamp set when creating this contract.
#[ink(message)]
pub fn unlock(&mut self) -> Result<(), Error> {
let now = self.env().block_timestamp();
if now < self.lock_until {
return Err(Error::LockPeriodNotOver)
}
self.env()
.terminate_contract(self.env().caller())
.expect("failed at contract termination");
Ok(())
}
}

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

use ink_lang as ink;
use ink_env::{
call,
test,
};

#[ink::test]
fn unlocking_before_end_does_not_work() {
// given
let (mut lock_until, _contract_id) = create_contract();

// when
let maybe_unlocked = lock_until.unlock();

// then
// the amount has been locked until `now() + 1`, but since the
// block has not been advanced `now()` still returns the same
// block timestamp.
assert_eq!(maybe_unlocked, Err(Error::LockPeriodNotOver));
}

#[ink::test]
fn unlocking_works() {
// given
let accounts = default_accounts();
let (mut lock_until, contract_id) = create_contract();
assert_eq!(lock_until.unlock(), Err(Error::LockPeriodNotOver));
set_sender(accounts.eve);
let old_balance_contract = get_balance(contract_id);
let old_balance_eve = get_balance(accounts.eve);

// when
// in order to change the result of `now()` the block needs to
// be advanced.
ink_env::test::advance_block::<ink_env::DefaultEnvironment>()
.expect("Cannot advance block");

// then
assert_eq!(lock_until.unlock(), Ok(()));
assert!(
ink_env::test::get_account_balance::<ink_env::DefaultEnvironment>(
contract_id
)
.is_err()
);
assert_eq!(
old_balance_eve + old_balance_contract,
get_balance(accounts.eve)
);
}

/// Returns `(contract_instance, contract_id)`.
///
/// The created contract is set to lock until `now() + 1`.
fn create_contract() -> (LockUntil, AccountId) {
let accounts = default_accounts();
let contract_id = ink_env::test::get_current_contract_account_id::<
ink_env::DefaultEnvironment,
>()
.expect("Cannot get contract id");

set_sender(accounts.alice);
set_balance(contract_id, 100);
set_balance(accounts.eve, 200);

(LockUntil::new(now() + 1), contract_id)
}

fn set_sender(sender: AccountId) {
let callee = ink_env::account_id::<ink_env::DefaultEnvironment>()
.unwrap_or([0x0; 32].into());
test::push_execution_context::<Environment>(
sender,
callee,
1000000,
1000000,
test::CallData::new(call::Selector::new([0x00; 4])), // dummy
);
}

fn default_accounts(
) -> ink_env::test::DefaultAccounts<ink_env::DefaultEnvironment> {
ink_env::test::default_accounts::<ink_env::DefaultEnvironment>()
.expect("Off-chain environment should have been initialized already")
}

fn get_balance(account_id: AccountId) -> Balance {
ink_env::test::get_account_balance::<ink_env::DefaultEnvironment>(account_id)
.expect("Cannot get account balance")
}

fn set_balance(account_id: AccountId, balance: Balance) {
ink_env::test::set_account_balance::<ink_env::DefaultEnvironment>(
account_id, balance,
)
.expect("Cannot set account balance");
}

fn now() -> Timestamp {
ink_env::block_timestamp::<ink_env::DefaultEnvironment>()
.expect("Cannot get block timestamp")
}
}
}