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

allow_reentrancy attribute #1783

Closed
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock

.idea

# Ignore history files.
**/.history/**
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ In a module annotated with `#[ink::contract]` these attributes are available:
| `#[ink(anonymous)]` | Applicable to ink! events. | Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting. Very similar to anonymous events in Solidity. |
| `#[ink(topic)]` | Applicable on ink! event field. | Tells the ink! codegen to provide a topic hash for the given field. Every ink! event can only have a limited number of such topic fields. Similar semantics as to indexed event arguments in Solidity. |
| `#[ink(payable)]` | Applicable to ink! messages. | Allows receiving value as part of the call of the ink! message. ink! constructors are implicitly payable. |
| `#[ink(allow_reentrancy)]` | Applicable to ink! messages. | Allows the ink! message to be called reentrantly. |
| `#[ink(selector = S:u32)]` | Applicable to ink! messages and ink! constructors. | Specifies a concrete dispatch selector for the flagged entity. This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage. |
| `#[ink(selector = _)]` | Applicable to ink! messages. | Specifies a fallback message that is invoked if no other ink! message matches a selector. |
| `#[ink(namespace = N:string)]` | Applicable to ink! trait implementation blocks. | Changes the resulting selectors of all the ink! messages and ink! constructors within the trait implementation. Allows to disambiguate between trait implementations with overlapping message or constructor names. Use only with great care and consideration! |
Expand Down
10 changes: 10 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,13 @@ where
TypedEnvBackend::call_runtime::<E, _>(instance, call)
})
}

/// Returns how many times caller exists on call stack.
pub fn reentrance_count<E>() -> u32
where
E: Environment,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::reentrance_count::<E>(instance)
})
}
29 changes: 22 additions & 7 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,25 @@ impl ReturnFlags {

/// The flags used to change the behavior of a contract call.
#[must_use]
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug)]
pub struct CallFlags {
forward_input: bool,
clone_input: bool,
tail_call: bool,
allow_reentry: bool,
}

impl Default for CallFlags {
fn default() -> Self {
Self {
forward_input: false,
clone_input: false,
tail_call: false,
allow_reentry: true,
}
}
}

impl CallFlags {
/// Forwards the input for the current function to the callee.
///
Expand Down Expand Up @@ -109,13 +120,13 @@ impl CallFlags {
self
}

/// Allow the callee to reenter into the current contract.
/// Disallow the callee to reenter into the current contract.
///
/// Without this flag any reentrancy into the current contract that originates from
/// the callee (or any of its callees) is denied. This includes the first callee:
/// the callee (or any of its callees) is allowed. This includes the first callee:
/// You cannot call into yourself with this flag set.
pub const fn set_allow_reentry(mut self, allow_reentry: bool) -> Self {
self.allow_reentry = allow_reentry;
pub const fn set_deny_reentry(mut self, deny_reentry: bool) -> Self {
self.allow_reentry = !deny_reentry;
self
}

Expand Down Expand Up @@ -162,8 +173,8 @@ impl CallFlags {
/// # Note
///
/// See [`Self::set_allow_reentry`] for more information.
pub const fn allow_reentry(&self) -> bool {
self.allow_reentry
pub const fn deny_reentry(&self) -> bool {
SkymanOne marked this conversation as resolved.
Show resolved Hide resolved
!self.allow_reentry
}
}

Expand Down Expand Up @@ -519,4 +530,8 @@ pub trait TypedEnvBackend: EnvBackend {
where
E: Environment,
Call: scale::Encode;

fn reentrance_count<E>(&mut self) -> u32
where
E: Environment;
}
7 changes: 7 additions & 0 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,11 @@ impl TypedEnvBackend for EnvInstance {
{
unimplemented!("off-chain environment does not support `call_runtime`")
}

fn reentrance_count<E>(&mut self) -> u32
where
E: Environment,
{
unimplemented!("off-chain environment does not support `reentrance_count`")
Copy link

Choose a reason for hiding this comment

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

Why not?

Copy link
Author

Choose a reason for hiding this comment

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

I am not sure, that we should implement it in this PR, along with the fact, that we need to expand ExecContext in off-chain engine, so it looks for me as a point for another PR, maybe something like this: #1589

}
}
5 changes: 5 additions & 0 deletions crates/env/src/engine/on_chain/ext/riscv32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result
ret_code.into()
}

pub fn reentrance_count() -> u32 {
let ret_val = sys::call0(FUNC_ID);
ret_val.into_u32()
}

pub fn is_contract(account_id: &[u8]) -> bool {
let ret_val = sys::call(FUNC_ID, Ptr32::from_slice(account_id));
ret_val.into_bool()
Expand Down
7 changes: 7 additions & 0 deletions crates/env/src/engine/on_chain/ext/wasm32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ mod sys {
) -> ReturnCode;

pub fn call_runtime(call_ptr: Ptr32<[u8]>, call_len: u32) -> ReturnCode;

pub fn reentrance_count() -> ReturnCode;
}

#[link(wasm_import_module = "seal1")]
Expand Down Expand Up @@ -595,6 +597,11 @@ pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result
ret_code.into()
}

pub fn reentrance_count() -> u32 {
let ret_code = unsafe { sys::reentrance_count() };
ret_code.into_u32()
}

pub fn is_contract(account_id: &[u8]) -> bool {
let ret_val = unsafe { sys::is_contract(Ptr32::from_slice(account_id)) };
ret_val.into_bool()
Expand Down
7 changes: 7 additions & 0 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,4 +587,11 @@ impl TypedEnvBackend for EnvInstance {
let enc_call = scope.take_encoded(call);
ext::call_runtime(enc_call).map_err(Into::into)
}

fn reentrance_count<E>(&mut self) -> u32
where
E: Environment,
{
ext::reentrance_count()
}
}
24 changes: 12 additions & 12 deletions crates/env/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,34 +70,34 @@ fn test_call_flags() {
// enable each flag one after the other
let flags = flags.set_forward_input(true);
assert!(flags.forward_input());
assert_eq!(flags.into_u32(), 0b0000_0001);
assert_eq!(flags.into_u32(), 0b0000_1001);

let flags = flags.set_clone_input(true);
assert!(flags.clone_input());
assert_eq!(flags.into_u32(), 0b0000_0011);
assert_eq!(flags.into_u32(), 0b0000_1011);

let flags = flags.set_tail_call(true);
assert!(flags.tail_call());
assert_eq!(flags.into_u32(), 0b0000_0111);

let flags = flags.set_allow_reentry(true);
assert!(flags.allow_reentry());
assert_eq!(flags.into_u32(), 0b0000_1111);

// disable each flag one after the other
let flags = flags.set_allow_reentry(false);
assert!(!flags.allow_reentry());
let flags = flags.set_deny_reentry(true);
assert!(flags.deny_reentry());
assert_eq!(flags.into_u32(), 0b0000_0111);

// disable each flag one after the other
let flags = flags.set_deny_reentry(false);
assert!(!flags.deny_reentry());
assert_eq!(flags.into_u32(), 0b0000_1111);

let flags = flags.set_tail_call(false);
assert!(!flags.tail_call());
assert_eq!(flags.into_u32(), 0b0000_0011);
assert_eq!(flags.into_u32(), 0b0000_1011);

let flags = flags.set_clone_input(false);
assert!(!flags.clone_input());
assert_eq!(flags.into_u32(), 0b0000_0001);
assert_eq!(flags.into_u32(), 0b0000_1001);

let flags = flags.set_forward_input(false);
assert!(!flags.forward_input());
assert_eq!(flags.into_u32(), 0b0000_0000);
assert_eq!(flags.into_u32(), 0b0000_1000);
}
Loading