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 odra attributes #273

Merged
merged 14 commits into from
Dec 5, 2023
2 changes: 2 additions & 0 deletions core/src/contract_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ pub trait ContractContext {
fn transfer_tokens(&self, to: &Address, amount: &U512);
fn revert(&self, error: OdraError) -> !;
fn get_named_arg_bytes(&self, name: &str) -> Bytes;
fn handle_attached_value(&self);
fn clear_attached_value(&self);
}
111 changes: 69 additions & 42 deletions core/src/contract_env.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::call_def::CallDef;
use crate::{prelude::*, UnwrapOrRevert};
pub use crate::ContractContext;
use crate::{key_maker, UnwrapOrRevert};
use crate::{prelude::*, ExecutionError};
use crate::{Address, Bytes, CLTyped, FromBytes, OdraError, ToBytes, U512};
use casper_types::crypto::PublicKey;

use crate::key_maker;
pub use crate::ContractContext;
use crate::ExecutionError::CouldntDeserializeSignature;

#[derive(Clone)]
pub struct ContractEnv {
index: u32,
mapping_data: Vec<u8>,
Expand All @@ -22,31 +21,7 @@ impl ContractEnv {
}
}

pub fn duplicate(&self) -> Self {
Self {
index: self.index,
mapping_data: self.mapping_data.clone(),
backend: self.backend.clone()
}
}

pub fn get_named_arg<T: FromBytes>(&self, name: &str) -> T {
let bytes = self.backend.borrow().get_named_arg_bytes(name);

let opt_result = match T::from_bytes(&bytes) {
Ok((value, remainder)) => {
if remainder.is_empty() {
Some(value)
} else {
None
}
}
Err(_) => None
};
UnwrapOrRevert::unwrap_or_revert(opt_result, self)
}

pub fn current_key(&self) -> Vec<u8> {
pub(crate) fn current_key(&self) -> Vec<u8> {
kpob marked this conversation as resolved.
Show resolved Hide resolved
let index_bytes = key_maker::u32_to_hex(self.index);
let mapping_data_bytes = key_maker::bytes_to_hex(&self.mapping_data);
let mut key = Vec::new();
Expand All @@ -55,11 +30,11 @@ impl ContractEnv {
key
}

pub fn add_to_mapping_data(&mut self, data: &[u8]) {
pub(crate) fn add_to_mapping_data(&mut self, data: &[u8]) {
kpob marked this conversation as resolved.
Show resolved Hide resolved
self.mapping_data.extend_from_slice(data);
}

pub fn child(&self, index: u8) -> Self {
pub(crate) fn child(&self, index: u8) -> Self {
kpob marked this conversation as resolved.
Show resolved Hide resolved
Self {
index: (self.index << 4) + index as u32,
mapping_data: self.mapping_data.clone(),
Expand All @@ -71,14 +46,13 @@ impl ContractEnv {
self.backend
.borrow()
.get_value(key)
// TODO: remove unwrap
.map(|bytes| T::from_bytes(&bytes).unwrap().0)
.map(|bytes| deserialize_bytes(bytes, self))
}

pub fn set_value<T: ToBytes + CLTyped>(&self, key: &[u8], value: T) {
// TODO: remove unwrap
let bytes = value.to_bytes().unwrap();
self.backend.borrow().set_value(key, Bytes::from(bytes));
let result = value.to_bytes().map_err(ExecutionError::from);
let bytes = result.unwrap_or_revert(self);
self.backend.borrow().set_value(key, bytes.into());
}

pub fn caller(&self) -> Address {
Expand All @@ -89,8 +63,7 @@ impl ContractEnv {
pub fn call_contract<T: FromBytes>(&self, address: Address, call: CallDef) -> T {
let backend = self.backend.borrow();
let bytes = backend.call_contract(address, call);
// TODO: remove unwrap
T::from_bytes(&bytes).unwrap().0
deserialize_bytes(bytes, self)
}

pub fn self_address(&self) -> Address {
Expand Down Expand Up @@ -120,8 +93,9 @@ impl ContractEnv {

pub fn emit_event<T: ToBytes>(&self, event: T) {
let backend = self.backend.borrow();
// TODO: remove unwrap
backend.emit_event(&event.to_bytes().unwrap().into())
let result = event.to_bytes().map_err(ExecutionError::from);
let bytes = result.unwrap_or_revert(self);
backend.emit_event(&bytes.into())
}

pub fn verify_signature(
Expand All @@ -131,7 +105,60 @@ impl ContractEnv {
public_key: &PublicKey
) -> bool {
let (signature, _) = casper_types::crypto::Signature::from_bytes(signature.as_slice())
.unwrap_or_else(|_| self.revert(CouldntDeserializeSignature));
.unwrap_or_else(|_| self.revert(ExecutionError::CouldNotDeserializeSignature));
casper_types::crypto::verify(message.as_slice(), &signature, public_key).is_ok()
}
}

pub struct ExecutionEnv {
kpob marked this conversation as resolved.
Show resolved Hide resolved
env: Rc<ContractEnv>
}

impl ExecutionEnv {
pub fn new(env: Rc<ContractEnv>) -> Self {
Self { env }
}

pub fn non_reentrant_before(&self) {
let status: bool = self
kpob marked this conversation as resolved.
Show resolved Hide resolved
.env
.get_value(crate::consts::REENTRANCY_GUARD.as_slice())
.unwrap_or_default();
if status {
self.env.revert(ExecutionError::ReentrantCall);
}
self.env
.set_value(crate::consts::REENTRANCY_GUARD.as_slice(), true);
}

pub fn non_reentrant_after(&self) {
self.env
.set_value(crate::consts::REENTRANCY_GUARD.as_slice(), false);
}

pub fn handle_attached_value(&self) {
self.env.backend.borrow().handle_attached_value();
}

pub fn clear_attached_value(&self) {
self.env.backend.borrow().clear_attached_value();
}

pub fn get_named_arg<T: FromBytes>(&self, name: &str) -> T {
kpob marked this conversation as resolved.
Show resolved Hide resolved
let bytes = self.env.backend.borrow().get_named_arg_bytes(name);
deserialize_bytes(bytes, &self.env)
}
}

fn deserialize_bytes<T: FromBytes>(bytes: Bytes, env: &ContractEnv) -> T {
match T::from_bytes(&bytes) {
Ok((value, remainder)) => {
kpob marked this conversation as resolved.
Show resolved Hide resolved
if remainder.is_empty() {
value
} else {
env.revert(ExecutionError::LeftOverBytes)
}
}
Err(err) => env.revert(ExecutionError::from(err))
}
}
23 changes: 18 additions & 5 deletions core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,16 @@ impl From<Box<dyn Any + Send>> for OdraError {
}

impl From<casper_types::bytesrepr::Error> for ExecutionError {
fn from(_: casper_types::bytesrepr::Error) -> Self {
Self::SerializationFailed
fn from(error: casper_types::bytesrepr::Error) -> Self {
match error {
casper_types::bytesrepr::Error::EarlyEndOfStream => Self::EarlyEndOfStream,
casper_types::bytesrepr::Error::Formatting => Self::Formatting,
casper_types::bytesrepr::Error::LeftOverBytes => Self::LeftOverBytes,
casper_types::bytesrepr::Error::OutOfMemory => Self::OutOfMemory,
casper_types::bytesrepr::Error::NotRepresentable => Self::NotRepresentable,
casper_types::bytesrepr::Error::ExceededRecursionDepth => Self::ExceededRecursionDepth,
_ => Self::Formatting
}
}
}

Expand Down Expand Up @@ -79,9 +87,14 @@ pub enum ExecutionError {
IndexOutOfBounds = 108,
ZeroAddress = 109,
AddressCreationFailed = 110,
SerializationFailed = 111,
KeyNotFound = 112,
CouldntDeserializeSignature = 113,
EarlyEndOfStream = 111,
Formatting = 112,
LeftOverBytes = 113,
OutOfMemory = 114,
NotRepresentable = 115,
ExceededRecursionDepth = 116,
KeyNotFound = 117,
CouldNotDeserializeSignature = 118,
MaxUserError = 32767,
/// User error too high. The code should be in range 0..32767.
UserErrorTooHigh = 32768,
Expand Down
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub use address::{Address, OdraAddress};
pub use call_def::CallDef;
pub use casper_event_standard;
pub use contract_context::ContractContext;
pub use contract_env::ContractEnv;
pub use contract_env::{ContractEnv, ExecutionEnv};
pub use entry_point_callback::EntryPointsCaller;
pub use error::{AddressError, CollectionError, ExecutionError, OdraError, VmError};
pub use host_context::HostContext;
Expand Down
2 changes: 1 addition & 1 deletion core/src/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl<K: ToBytes, V> Mapping<K, V> {

impl<K: ToBytes, V> Mapping<K, V> {
fn env_for_key(&self, key: K) -> ContractEnv {
let mut env = self.parent_env.duplicate();
let mut env = (*self.parent_env).clone();
let key = key.to_bytes().unwrap_or_default();
env.add_to_mapping_data(&key);
env
Expand Down
2 changes: 1 addition & 1 deletion examples2/src/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ impl Erc20 {
self.total_supply() + other_erc20.total_supply()
}

#[odra(payable)]
pub fn pay_to_mint(&mut self) {
let attached_value = self.env().attached_value();
let caller = self.env().caller();
Expand Down Expand Up @@ -208,7 +209,6 @@ mod tests {

env.set_caller(alice);
pobcoin.with_tokens(100.into()).pay_to_mint();
// TODO: fails cause the generated code does not handle the attached value - more specifically the `payable` attribute
assert_eq!(env.balance_of(&pobcoin.address), 100.into());
assert_eq!(pobcoin.total_supply(), 200.into());
assert_eq!(pobcoin.balance_of(alice), 100.into());
Expand Down
1 change: 1 addition & 0 deletions examples2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ extern crate alloc;
pub mod counter;
pub mod counter_pack;
pub mod erc20;
pub mod reentrancy_guard;
78 changes: 78 additions & 0 deletions examples2/src/reentrancy_guard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use odra::{prelude::*, ContractEnv, Variable};

#[odra::module]
pub struct ReentrancyMock {
env: Rc<ContractEnv>,
counter: Variable<u32>
}

#[odra::module]
impl ReentrancyMock {
#[odra(non_reentrant)]
pub fn count_local_recursive(&mut self, n: u32) {
if n > 0 {
self.count();
self.count_local_recursive(n - 1);
}
}

#[odra(non_reentrant)]
pub fn count_ref_recursive(&mut self, n: u32) {
if n > 0 {
self.count();
let other_erc20 = ReentrancyMockContractRef {
address: self.env.self_address(),
env: self.env.clone()
}
.count_ref_recursive(n - 1);
}
}

#[odra(non_reentrant)]
pub fn non_reentrant_count(&mut self) {
self.count();
}

pub fn get_value(&self) -> u32 {
self.counter.get_or_default()
}
}

impl ReentrancyMock {
fn count(&mut self) {
let c = self.counter.get_or_default();
self.counter.set(c + 1);
}
}

#[cfg(test)]
mod test {
use odra::test_env;

use super::ReentrancyMockDeployer;

#[test]
fn non_reentrant_function_can_be_called() {
let env = odra::test_env();

let mut contract = ReentrancyMockDeployer::init(&env);
assert_eq!(contract.get_value(), 0);
contract.non_reentrant_count();
assert_eq!(contract.get_value(), 1);
}

#[test]
fn ref_recursion_not_allowed() {
let env = odra::test_env();
let mut contract = ReentrancyMockDeployer::init(&env);
assert!(contract.try_count_ref_recursive(11).is_err());
}

#[test]
fn local_recursion_allowed() {
let env = odra::test_env();
let mut contract = ReentrancyMockDeployer::init(&env);
contract.count_local_recursive(11);
assert_eq!(contract.get_value(), 11);
}
}
8 changes: 8 additions & 0 deletions odra-casper/wasm-env/src/wasm_contract_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ impl ContractContext for WasmContractEnv {
fn get_named_arg_bytes(&self, name: &str) -> Bytes {
host_functions::get_named_arg(name).into()
}

fn handle_attached_value(&self) {
host_functions::handle_attached_value();
}

fn clear_attached_value(&self) {
host_functions::clear_attached_value();
}
}

impl WasmContractEnv {
Expand Down
3 changes: 2 additions & 1 deletion odra-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ quote = "1.0.33"
syn = { version = "2.0.29", features = ["full"] }
syn_derive = "0.1.8"
convert_case = { version = "0.5.0" }
derive_convert = "0.4.0"
derive-try-from ={ path = "../try-from-macro" }
derive_more = "0.99.17"
itertools = "0.12.0"

[dev-dependencies]
pretty_assertions = "1.4.0"
Expand Down
8 changes: 8 additions & 0 deletions odra-macros/src/ast/deployer_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ mod deployer_impl {
let result = execute_total_supply(contract_env);
odra::ToBytes::to_bytes(&result).map(Into::into).unwrap()
}
"pay_to_mint" => {
let result = execute_pay_to_mint(contract_env);
odra::ToBytes::to_bytes(&result).map(Into::into).unwrap()
}
"approve" => {
let result = execute_approve(contract_env);
odra::ToBytes::to_bytes(&result).map(Into::into).unwrap()
}
_ => panic!("Unknown method")
}
});
Expand Down
Loading