From 6fbda414fd38f1f6152fdb07d9d97a8d46e169f7 Mon Sep 17 00:00:00 2001 From: Varex Silver Date: Thu, 13 Jul 2023 14:38:55 +0300 Subject: [PATCH 01/17] Add traits for PSP22Permit --- Cargo.toml | 1 + .../src/token/psp22/extensions/permit.rs | 98 +++++++++++++++++++ contracts/src/token/psp22/mod.rs | 1 + .../src/traits/psp22/extensions/permit.rs | 55 +++++++++++ contracts/src/traits/psp22/mod.rs | 1 + examples/psp22_extensions/permit/.gitignore | 9 ++ examples/psp22_extensions/permit/Cargo.toml | 38 +++++++ examples/psp22_extensions/permit/README.md | 3 + examples/psp22_extensions/permit/lib.rs | 27 +++++ lang/codegen/src/implementation.rs | 30 ++---- lang/codegen/src/implementations.rs | 90 ++++++++++++++++- 11 files changed, 329 insertions(+), 24 deletions(-) create mode 100644 contracts/src/token/psp22/extensions/permit.rs create mode 100644 contracts/src/traits/psp22/extensions/permit.rs create mode 100644 examples/psp22_extensions/permit/.gitignore create mode 100644 examples/psp22_extensions/permit/Cargo.toml create mode 100644 examples/psp22_extensions/permit/README.md create mode 100644 examples/psp22_extensions/permit/lib.rs diff --git a/Cargo.toml b/Cargo.toml index b33c66347..2e6523b3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ scale-info = { version = "2.6", default-features = false, features = ["derive"] openbrush_contracts = { version = "~3.1.1", path = "contracts", default-features = false } openbrush_lang = { version = "~3.1.1", path = "lang", default-features = false } +#secp256k1 = { version = "0.14.1" } [lib] name = "openbrush" diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs new file mode 100644 index 000000000..c13b7170a --- /dev/null +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -0,0 +1,98 @@ +// Copyright (c) 2012-2022 Supercolony +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the"Software"), +// to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +pub use crate::{ + psp22, + psp22::extensions::permit, + traits::psp22::{extensions::permit::*, *}, +}; +use openbrush::storage::Mapping; +use openbrush::traits::{AccountId, Balance}; +pub use psp22::{Internal as _, InternalImpl as _, PSP22Impl}; + +#[derive(Default, Debug)] +#[openbrush::storage_item] +pub struct Data { + pub nonces: Mapping, +} + +pub trait PSP22PermitImpl: Internal { + fn permit( + &mut self, + owner: AccountId, + spender: AccountId, + amount: Balance, + deadline: u32, + v: u8, + r: [u8; 32], + s: [u8; 32], + ) -> Result<(), PSP22Error> { + self._permit(owner, spender, amount, deadline, v, r, s) + } + + fn nonces(&self, owner: AccountId) -> u32 { + self._nonces(owner) + } + + fn domain_separator(&self) -> [u8; 32] { + self._domain_separator() + } +} + +pub trait Internal { + fn _permit( + &mut self, + owner: AccountId, + spender: AccountId, + amount: Balance, + deadline: u32, + v: u8, + r: [u8; 32], + s: [u8; 32], + ) -> Result<(), PSP22Error>; + + fn _nonces(&self, owner: AccountId) -> u32; + + fn _domain_separator(&self) -> [u8; 32]; +} + +pub trait InternalImpl { + fn _permit( + &mut self, + owner: AccountId, + spender: AccountId, + amount: Balance, + deadline: u32, + v: u8, + r: [u8; 32], + s: [u8; 32], + ) -> Result<(), PSP22Error> { + Ok(()) + } + + fn _nonces(&self, owner: AccountId) -> u32 { + 0 + } + + fn _domain_separator(&self) -> [u8; 32] { + [0; 32] + } +} diff --git a/contracts/src/token/psp22/mod.rs b/contracts/src/token/psp22/mod.rs index dcea328ea..e2877aa58 100644 --- a/contracts/src/token/psp22/mod.rs +++ b/contracts/src/token/psp22/mod.rs @@ -29,6 +29,7 @@ pub mod extensions { pub mod flashmint; pub mod metadata; pub mod mintable; + pub mod permit; pub mod wrapper; } diff --git a/contracts/src/traits/psp22/extensions/permit.rs b/contracts/src/traits/psp22/extensions/permit.rs new file mode 100644 index 000000000..26ad4aae3 --- /dev/null +++ b/contracts/src/traits/psp22/extensions/permit.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2012-2022 Supercolony +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the"Software"), +// to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +/// Extension of [`PSP22`] that allows create `amount` tokens +/// and assigns them to `account`, increasing the total supply +pub use crate::traits::errors::PSP22Error; +use openbrush::traits::{AccountId, Balance}; + +#[openbrush::wrapper] +pub type PSP22PermitRef = dyn PSP22Permit; + +#[openbrush::trait_definition] +pub trait PSP22Permit { + /// Permit allows `spender` to spend `value` tokens on behalf of `owner` with a signature + /// + /// See [`PSP22::_approve`]. + #[ink(message)] + fn permit( + &mut self, + owner: AccountId, + spender: AccountId, + value: Balance, + deadline: u32, + v: u8, + r: [u8; 32], + s: [u8; 32], + ) -> Result<(), PSP22Error>; + + /// Returns the current permit nonce for `owner`. This value must be + /// + /// This value must be included whenever a signature is generated for + #[ink(message)] + fn nonces(&self, owner: AccountId) -> u32; + + #[ink(message)] + fn domain_separator(&self) -> [u8; 32]; +} diff --git a/contracts/src/traits/psp22/mod.rs b/contracts/src/traits/psp22/mod.rs index d1af12e8a..3e5c662d2 100644 --- a/contracts/src/traits/psp22/mod.rs +++ b/contracts/src/traits/psp22/mod.rs @@ -29,6 +29,7 @@ pub mod extensions { pub mod capped; pub mod metadata; pub mod mintable; + pub mod permit; pub mod wrapper; } diff --git a/examples/psp22_extensions/permit/.gitignore b/examples/psp22_extensions/permit/.gitignore new file mode 100644 index 000000000..8de8f877e --- /dev/null +++ b/examples/psp22_extensions/permit/.gitignore @@ -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 diff --git a/examples/psp22_extensions/permit/Cargo.toml b/examples/psp22_extensions/permit/Cargo.toml new file mode 100644 index 000000000..4447c103c --- /dev/null +++ b/examples/psp22_extensions/permit/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "my_psp22_permit" +version = "3.1.1" +authors = ["Brushfam "] +edition = "2021" + +[dependencies] +ink = { version = "4.2.1", default-features = false} + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } + +# These dependencies +openbrush = { path = "../../..", default-features = false, features = ["psp22"] } + +[dev-dependencies] +ink_e2e = "4.2.1" +test_helpers = { path = "../../test_helpers", default-features = false } + +[lib] +name = "my_psp22_permit" +path = "lib.rs" + + +[features] +default = ["std"] +std = [ + "ink/std", + "scale/std", + "scale-info/std", + # These dependencies + "openbrush/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[profile.dev] +codegen-units = 16 diff --git a/examples/psp22_extensions/permit/README.md b/examples/psp22_extensions/permit/README.md new file mode 100644 index 000000000..b3da81594 --- /dev/null +++ b/examples/psp22_extensions/permit/README.md @@ -0,0 +1,3 @@ +## PSP22 contract (ERC20 analogue) with 'Permit' extension + +TODO: add description \ No newline at end of file diff --git a/examples/psp22_extensions/permit/lib.rs b/examples/psp22_extensions/permit/lib.rs new file mode 100644 index 000000000..beea63d7c --- /dev/null +++ b/examples/psp22_extensions/permit/lib.rs @@ -0,0 +1,27 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[openbrush::implementation(PSP22, PSP22Permit)] +#[openbrush::contract] +pub mod my_psp22_burnable { + use openbrush::traits::Storage; + + #[ink(storage)] + #[derive(Default, Storage)] + pub struct Contract { + #[storage_field] + psp22: psp22::Data, + #[storage_field] + psp22_permit: psp22::extensions::permit::Data, + } + + impl Contract { + #[ink(constructor)] + pub fn new(total_supply: Balance) -> Self { + let mut instance = Self::default(); + + psp22::Internal::_mint_to(&mut instance, Self::env().caller(), total_supply).expect("Should mint"); + + instance + } + } +} diff --git a/lang/codegen/src/implementation.rs b/lang/codegen/src/implementation.rs index 6637675cf..db51a0617 100644 --- a/lang/codegen/src/implementation.rs +++ b/lang/codegen/src/implementation.rs @@ -19,25 +19,15 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use crate::{ - implementations::*, - internal, - internal::*, -}; +use crate::{implementations::*, internal, internal::*}; use proc_macro2::TokenStream; -use quote::{ - quote, - ToTokens, -}; +use quote::{quote, ToTokens}; use std::collections::HashMap; -use syn::{ - Item, - Path, -}; +use syn::{Item, Path}; pub fn generate(attrs: TokenStream, ink_module: TokenStream) -> TokenStream { if internal::skip() { - return quote! {} + return quote! {}; } let input: TokenStream = ink_module; @@ -45,11 +35,9 @@ pub fn generate(attrs: TokenStream, ink_module: TokenStream) -> TokenStream { let args = syn::parse2::(attrs) .expect("No default contracts to implement provided") .iter() - .map(|arg| { - match arg { - NestedMeta::Path(method) => method.to_token_stream().to_string().replace(' ', ""), - _ => panic!("Expected names of OpenBrush traits to implement in the contract!"), - } + .map(|arg| match arg { + NestedMeta::Path(method) => method.to_token_stream().to_string().replace(' ', ""), + _ => panic!("Expected names of OpenBrush traits to implement in the contract!"), }) .collect::>(); @@ -81,6 +69,7 @@ pub fn generate(attrs: TokenStream, ink_module: TokenStream) -> TokenStream { "PSP22" => impl_psp22(&mut impl_args), "PSP22Mintable" => impl_psp22_mintable(&mut impl_args), "PSP22Burnable" => impl_psp22_burnable(&mut impl_args), + "PSP22Permit" => impl_psp22_permit(&mut impl_args), "PSP22Metadata" => impl_psp22_metadata(&mut impl_args), "PSP22Capped" => impl_psp22_capped(&mut impl_args), "PSP22Wrapper" => impl_psp22_wrapper(&mut impl_args), @@ -142,6 +131,7 @@ fn cleanup_imports(imports: &mut HashMap<&str, syn::ItemUse>) { "PSP22Capped", "PSP22Metadata", "PSP22Wrapper", + "PSP22Permit", "Flashmint", ]; check_and_remove_import("PSP22", psp22_impls, imports); @@ -231,7 +221,7 @@ fn extract_storage_struct_name(items: &[syn::Item]) -> String { if let Some(ink_attr) = ink_attr_maybe { if let Ok(path) = ink_attr.parse_args::() { - return path.to_token_stream().to_string() == "storage" + return path.to_token_stream().to_string() == "storage"; } } false diff --git a/lang/codegen/src/implementations.rs b/lang/codegen/src/implementations.rs index 1cbc7bc30..12d234f54 100644 --- a/lang/codegen/src/implementations.rs +++ b/lang/codegen/src/implementations.rs @@ -1,7 +1,4 @@ -use quote::{ - format_ident, - quote, -}; +use quote::{format_ident, quote}; use std::collections::HashMap; use syn::Block; @@ -255,6 +252,91 @@ pub(crate) fn impl_psp22_burnable(impl_args: &mut ImplArgs) { impl_args.items.push(syn::Item::Impl(burnable)); } +pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { + let storage_struct_name = impl_args.contract_name(); + let permit_internal_impl = syn::parse2::(quote!( + impl permit::InternalImpl for #storage_struct_name {} + )) + .expect("Should parse"); + + let permit_internal = syn::parse2::(quote!( + impl permit::Internal for #storage_struct_name { + fn _permit( + &mut self, + owner: AccountId, + spender: AccountId, + amount: Balance, + deadline: u32, + v: u8, + r: [u8; 32], + s: [u8; 32], + ) -> Result<(), PSP22Error> { + permit::InternalImpl::_permit(self, owner, spender, amount, deadline, v, r, s) + } + + fn _nonces(&self, owner: AccountId) -> u32 { + permit::InternalImpl::_nonces(self, owner) + } + + fn _domain_separator(&self) -> [u8; 32] { + permit::InternalImpl::_domain_separator(self) + } + } + )) + .expect("Should parse"); + + let permit_impl = syn::parse2::(quote!( + impl permit::PSP22PermitImpl for #storage_struct_name {} + )) + .expect("Should parse"); + + let permit = syn::parse2::(quote!( + impl permit::PSP22Permit for #storage_struct_name { + #[ink(message)] + fn permit( + &mut self, + owner: AccountId, + spender: AccountId, + value: Balance, + deadline: u32, + v: u8, + r: [u8; 32], + s: [u8; 32], + ) -> Result<(), PSP22Error> { + permit::PSP22PermitImpl::permit(self, owner, spender, value, deadline, v, r, s) + } + + #[ink(message)] + fn nonces(&self, owner: AccountId) -> u32 { + permit::PSP22PermitImpl::nonces(self, owner) + } + + #[ink(message)] + fn domain_separator(&self) -> [u8; 32] { + permit::PSP22PermitImpl::domain_separator(self) + } + } + )) + .expect("Should parse"); + + let import = syn::parse2::(quote!( + use openbrush::contracts::psp22::extensions::permit::*; + )) + .expect("Should parse"); + + impl_args.imports.insert("PSP22Permit", import); + impl_args.vec_import(); + + // TODO + // override_functions("PSP22Permit", &mut burnable, impl_args.map); + + impl_args.items.push(syn::Item::Impl(permit_internal_impl)); + impl_args.items.push(syn::Item::Impl(permit_internal)); + + impl_args.items.push(syn::Item::Impl(permit_impl)); + impl_args.items.push(syn::Item::Impl(permit)); +} + pub(crate) fn impl_psp22_metadata(impl_args: &mut ImplArgs) { let storage_struct_name = impl_args.contract_name(); let metadata_impl = syn::parse2::(quote!( From bb821afdbfad7a28e724e9e3f589a732246cea6e Mon Sep 17 00:00:00 2001 From: Varex Silver Date: Fri, 14 Jul 2023 00:39:45 +0300 Subject: [PATCH 02/17] Add PSP22 Permit implementation and some tests --- Cargo.toml | 1 - contracts/Cargo.toml | 3 + .../src/token/psp22/extensions/permit.rs | 107 +++++++++++++---- contracts/src/traits/errors/flashloan.rs | 13 +- contracts/src/traits/errors/psp22.rs | 22 +--- .../src/traits/psp22/extensions/permit.rs | 12 +- examples/psp22_extensions/permit/Cargo.toml | 6 +- examples/psp22_extensions/permit/README.md | 7 +- examples/psp22_extensions/permit/lib.rs | 4 +- examples/psp22_extensions/permit/tests/e2e.rs | 111 ++++++++++++++++++ examples/test_helpers/lib.rs | 14 +++ lang/codegen/src/implementations.rs | 28 ++--- 12 files changed, 254 insertions(+), 74 deletions(-) create mode 100644 examples/psp22_extensions/permit/tests/e2e.rs diff --git a/Cargo.toml b/Cargo.toml index 2e6523b3f..b33c66347 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ scale-info = { version = "2.6", default-features = false, features = ["derive"] openbrush_contracts = { version = "~3.1.1", path = "contracts", default-features = false } openbrush_lang = { version = "~3.1.1", path = "lang", default-features = false } -#secp256k1 = { version = "0.14.1" } [lib] name = "openbrush" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 669db693d..7c83fc032 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -22,6 +22,8 @@ scale-info = { version = "2.6", default-features = false, features = ["derive"], openbrush = { version = "~3.1.1", package = "openbrush_lang", path = "../lang", default-features = false } pallet-assets-chain-extension = { git = "https://github.com/Brushfam/pallet-assets-chain-extension", branch = "polkadot-v0.9.37", default-features = false, features = ["ink-lang"] } +blake2 = { version = "0.10.6", default-features = false } +sp-core = { version = "21.0.0", default-features = false, features = ["full_crypto"] } [lib] name = "openbrush_contracts" @@ -38,6 +40,7 @@ std = [ "scale-info/std", "openbrush/std", "pallet-assets-chain-extension/ink-std", + "sp-core/std", ] psp22 = [] psp22_pallet = [] diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index c13b7170a..a787697ad 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -24,14 +24,25 @@ pub use crate::{ psp22::extensions::permit, traits::psp22::{extensions::permit::*, *}, }; + +use blake2::digest::Update; +use blake2::{Blake2s, Digest}; +use ink::blake2x256; use openbrush::storage::Mapping; -use openbrush::traits::{AccountId, Balance}; +use openbrush::traits::AccountId; +use openbrush::traits::{Balance, Storage}; pub use psp22::{Internal as _, InternalImpl as _, PSP22Impl}; +use sp_core::crypto::Pair; +use sp_core::sr25519::{Public, Signature}; +pub const PERMIT_TYPE_HASH: [u8; 32] = + blake2x256!("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); #[derive(Default, Debug)] #[openbrush::storage_item] pub struct Data { - pub nonces: Mapping, + pub nonces: Mapping, + #[lazy] + pub cached_domain_separator: [u8; 32], } pub trait PSP22PermitImpl: Internal { @@ -40,19 +51,17 @@ pub trait PSP22PermitImpl: Internal { owner: AccountId, spender: AccountId, amount: Balance, - deadline: u32, - v: u8, - r: [u8; 32], - s: [u8; 32], + deadline: u64, + signature: [u8; 64], ) -> Result<(), PSP22Error> { - self._permit(owner, spender, amount, deadline, v, r, s) + self._permit(owner, spender, amount, deadline, signature) } - fn nonces(&self, owner: AccountId) -> u32 { + fn nonces(&self, owner: AccountId) -> u64 { self._nonces(owner) } - fn domain_separator(&self) -> [u8; 32] { + fn domain_separator(&mut self) -> [u8; 32] { self._domain_separator() } } @@ -63,36 +72,84 @@ pub trait Internal { owner: AccountId, spender: AccountId, amount: Balance, - deadline: u32, - v: u8, - r: [u8; 32], - s: [u8; 32], + deadline: u64, + signature: [u8; 64], ) -> Result<(), PSP22Error>; - fn _nonces(&self, owner: AccountId) -> u32; + fn _nonces(&self, owner: AccountId) -> u64; + + fn _domain_separator(&mut self) -> [u8; 32]; - fn _domain_separator(&self) -> [u8; 32]; + fn _use_nonce(&mut self, owner: AccountId) -> u64; } -pub trait InternalImpl { +pub trait InternalImpl: Storage + psp22::Internal { fn _permit( &mut self, owner: AccountId, spender: AccountId, amount: Balance, - deadline: u32, - v: u8, - r: [u8; 32], - s: [u8; 32], + deadline: u64, + signature: [u8; 64], ) -> Result<(), PSP22Error> { - Ok(()) + let block_time = Self::env().block_timestamp(); + if deadline < block_time { + return Err(PSP22Error::PermitExpired); + } + + let nonce = self._use_nonce(owner); + let message_hash: [u8; 32] = Blake2s::new() + .chain(PERMIT_TYPE_HASH) + .chain(self._domain_separator()) + .chain(owner) + .chain(spender) + .chain(amount.to_le_bytes()) + .chain(nonce.to_le_bytes()) + .chain(deadline.to_le_bytes()) + .finalize() + .into(); + + let message = &message_hash[..]; + let sig = Signature::from_raw(signature); + + let owner_bytes: &[u8; 32] = owner.as_ref(); + let pubkey = Public::from_raw(owner_bytes.clone()); + + if sp_core::sr25519::Pair::verify(&sig, message, &pubkey) { + self._approve_from_to(owner, spender, amount)?; + Ok(()) + } else { + Err(PSP22Error::PermitInvalidSignature) + } + } + + fn _nonces(&self, owner: AccountId) -> u64 { + self.data().nonces.get(&owner).unwrap_or_default() + } + + fn _set_nonce(&mut self, owner: AccountId, nonce: u64) { + self.data().nonces.insert(&owner, &nonce); } - fn _nonces(&self, owner: AccountId) -> u32 { - 0 + fn _domain_separator(&mut self) -> [u8; 32] { + let cached = self.data().cached_domain_separator.get_or_default(); + + if self.data().cached_domain_separator.get().is_none() { + let account_id = &Self::env().account_id(); + + let result: [u8; 32] = Blake2s::new().chain(account_id).finalize().into(); + + self.data().cached_domain_separator.set(&result.clone()); + + result + } else { + cached + } } - fn _domain_separator(&self) -> [u8; 32] { - [0; 32] + fn _use_nonce(&mut self, owner: AccountId) -> u64 { + let nonce = self._nonces(owner); + self._set_nonce(owner, nonce.clone() + 1); + nonce } } diff --git a/contracts/src/traits/errors/flashloan.rs b/contracts/src/traits/errors/flashloan.rs index 4ea923ca9..37c7ab8c8 100644 --- a/contracts/src/traits/errors/flashloan.rs +++ b/contracts/src/traits/errors/flashloan.rs @@ -19,14 +19,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use super::{ - AccessControlError, - OwnableError, - PSP22Error, - PSP22ReceiverError, - PausableError, - ReentrancyGuardError, -}; +use super::{AccessControlError, OwnableError, PSP22Error, PSP22ReceiverError, PausableError, ReentrancyGuardError}; use openbrush::traits::String; #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] @@ -110,6 +103,10 @@ impl From for FlashLenderError { PSP22Error::ZeroRecipientAddress => FlashLenderError::Custom(String::from("PSP22: Zero Recipient Address")), PSP22Error::ZeroSenderAddress => FlashLenderError::Custom(String::from("PSP22: Zero Sender Address")), PSP22Error::SafeTransferCheckFailed(message) => FlashLenderError::Custom(message), + PSP22Error::PermitInvalidSignature => { + FlashLenderError::Custom(String::from("PSP22: Permit Invalid Signature")) + } + PSP22Error::PermitExpired => FlashLenderError::Custom(String::from("PSP22: Permit Expired")), } } } diff --git a/contracts/src/traits/errors/psp22.rs b/contracts/src/traits/errors/psp22.rs index e203c2603..4c559fec5 100644 --- a/contracts/src/traits/errors/psp22.rs +++ b/contracts/src/traits/errors/psp22.rs @@ -19,12 +19,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use super::{ - AccessControlError, - OwnableError, - PausableError, - ReentrancyGuardError, -}; +use super::{AccessControlError, OwnableError, PausableError, ReentrancyGuardError}; use openbrush::traits::String; /// The PSP22 error type. Contract will throw one of this errors. @@ -43,6 +38,10 @@ pub enum PSP22Error { ZeroSenderAddress, /// Returned if safe transfer check fails SafeTransferCheckFailed(String), + /// Returned if permit signature is invalid + PermitInvalidSignature, + /// Returned if permit deadline is expired + PermitExpired, } impl From for PSP22Error { @@ -113,16 +112,7 @@ pub enum PSP22TokenTimelockError { impl From for PSP22TokenTimelockError { fn from(error: PSP22Error) -> Self { - match error { - PSP22Error::Custom(message) => PSP22TokenTimelockError::PSP22Error(PSP22Error::Custom(message)), - PSP22Error::InsufficientBalance => PSP22TokenTimelockError::PSP22Error(PSP22Error::InsufficientBalance), - PSP22Error::InsufficientAllowance => PSP22TokenTimelockError::PSP22Error(PSP22Error::InsufficientAllowance), - PSP22Error::ZeroRecipientAddress => PSP22TokenTimelockError::PSP22Error(PSP22Error::ZeroRecipientAddress), - PSP22Error::ZeroSenderAddress => PSP22TokenTimelockError::PSP22Error(PSP22Error::ZeroSenderAddress), - PSP22Error::SafeTransferCheckFailed(message) => { - PSP22TokenTimelockError::PSP22Error(PSP22Error::SafeTransferCheckFailed(message)) - } - } + PSP22TokenTimelockError::PSP22Error(error) } } diff --git a/contracts/src/traits/psp22/extensions/permit.rs b/contracts/src/traits/psp22/extensions/permit.rs index 26ad4aae3..6ffa6f90c 100644 --- a/contracts/src/traits/psp22/extensions/permit.rs +++ b/contracts/src/traits/psp22/extensions/permit.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2022 Supercolony +// Copyright (c) 2012-2023 727-ventures // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the"Software"), @@ -38,18 +38,16 @@ pub trait PSP22Permit { owner: AccountId, spender: AccountId, value: Balance, - deadline: u32, - v: u8, - r: [u8; 32], - s: [u8; 32], + deadline: u64, + signature: [u8; 64], ) -> Result<(), PSP22Error>; /// Returns the current permit nonce for `owner`. This value must be /// /// This value must be included whenever a signature is generated for #[ink(message)] - fn nonces(&self, owner: AccountId) -> u32; + fn nonces(&self, owner: AccountId) -> u64; #[ink(message)] - fn domain_separator(&self) -> [u8; 32]; + fn domain_separator(&mut self) -> [u8; 32]; } diff --git a/examples/psp22_extensions/permit/Cargo.toml b/examples/psp22_extensions/permit/Cargo.toml index 4447c103c..afcadf945 100644 --- a/examples/psp22_extensions/permit/Cargo.toml +++ b/examples/psp22_extensions/permit/Cargo.toml @@ -12,10 +12,12 @@ scale-info = { version = "2.6", default-features = false, features = ["derive"], # These dependencies openbrush = { path = "../../..", default-features = false, features = ["psp22"] } +ecdsa = { version = "0.16.7", default-features = false, optional = true } [dev-dependencies] ink_e2e = "4.2.1" test_helpers = { path = "../../test_helpers", default-features = false } +blake2 = { version = "0.10.6", default-features = false } [lib] name = "my_psp22_permit" @@ -32,7 +34,9 @@ std = [ "openbrush/std", ] ink-as-dependency = [] -e2e-tests = [] +e2e-tests = [ + "ecdsa" +] [profile.dev] codegen-units = 16 diff --git a/examples/psp22_extensions/permit/README.md b/examples/psp22_extensions/permit/README.md index b3da81594..99babb8dc 100644 --- a/examples/psp22_extensions/permit/README.md +++ b/examples/psp22_extensions/permit/README.md @@ -1,3 +1,8 @@ ## PSP22 contract (ERC20 analogue) with 'Permit' extension -TODO: add description \ No newline at end of file +> If you are trying to run this on run, and getting errors, try to use +> +> `` +> AR=/opt/homebrew/opt/llvm/bin/llvm-ar CC=/opt/homebrew/opt/llvm/bin/clang cargo contract build +`` +> , and check [this](https://substrate.stackexchange.com/questions/1098/how-to-use-sp-core-in-libraries-that-target-wasm-for-the-web) \ No newline at end of file diff --git a/examples/psp22_extensions/permit/lib.rs b/examples/psp22_extensions/permit/lib.rs index beea63d7c..bfa2ae994 100644 --- a/examples/psp22_extensions/permit/lib.rs +++ b/examples/psp22_extensions/permit/lib.rs @@ -1,8 +1,10 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] +pub use crate::my_psp22_permit::*; + #[openbrush::implementation(PSP22, PSP22Permit)] #[openbrush::contract] -pub mod my_psp22_burnable { +pub mod my_psp22_permit { use openbrush::traits::Storage; #[ink(storage)] diff --git a/examples/psp22_extensions/permit/tests/e2e.rs b/examples/psp22_extensions/permit/tests/e2e.rs new file mode 100644 index 000000000..3429bb857 --- /dev/null +++ b/examples/psp22_extensions/permit/tests/e2e.rs @@ -0,0 +1,111 @@ +#![cfg(feature = "e2e-tests")] + +extern crate my_psp22_permit; + +use openbrush::contracts::psp22::extensions::permit::psp22permit_external::PSP22Permit; +use openbrush::contracts::psp22::psp22_external::PSP22; +#[rustfmt::skip] +use crate::my_psp22_permit::*; +#[rustfmt::skip] +use ink_e2e::{build_message, PolkadotConfig}; +use blake2::digest::Update; +use blake2::Blake2s; +use blake2::Digest; +use ink_e2e::subxt::ext::sp_core; +use ink_e2e::subxt::ext::sp_core::Pair; +use openbrush::contracts::psp22::extensions::permit::PERMIT_TYPE_HASH; +use openbrush::traits::Balance; + +use test_helpers::{address_of, balance_of, method_call_dry_run}; + +type E2EResult = Result>; + +#[ink_e2e::test] +async fn assigns_initial_balance(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = ContractRef::new(1000); + let address = client + .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + assert!(matches!(balance_of!(client, address, alice), 1000)); + + Ok(()) +} + +#[ink_e2e::test] +async fn nonce_should_be_equal_zero(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = ContractRef::new(1000); + let address = client + .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let nonce = method_call_dry_run!(client, address, nonces(address_of!(alice))); + + assert!(matches!(nonce, 0)); + + Ok(()) +} + +#[ink_e2e::test] +async fn check_domain_separator(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = ContractRef::new(1000); + let address = client + .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator()); + let real_domain_separator: [u8; 32] = Blake2s::new().chain(address).finalize().into(); + + assert_eq!(domain_separator, real_domain_separator); + + Ok(()) +} + +#[ink_e2e::test] +async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = ContractRef::new(1000); + let address = client + .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let nonce: u64 = method_call_dry_run!(client, address, nonces(address_of!(alice))); + let deadline: u64 = 30_000_000_000_000; + let amount: Balance = 1000; + + let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator); + + let permit_hash: [u8; 32] = Blake2s::new() + .chain(PERMIT_TYPE_HASH) + .chain(domain_separator) + .chain(address_of!(alice)) + .chain(address_of!(bob)) + .chain(amount.to_le_bytes()) + .chain(nonce.to_le_bytes()) + .chain(deadline.to_le_bytes()) + .finalize() + .into(); + + let signature: [u8; 64] = sp_core::sr25519::Pair::from_string("//Alice", None) + .expect("Should generate pair") + .sign(&permit_hash) + .0; + + let permit_signature = method_call_dry_run!( + client, + address, + permit(address_of!(alice), address_of!(bob), amount, deadline, signature) + ); + + println!("permit_signature: {:?}", permit_signature); + assert!(matches!(permit_signature, Ok(_))); + + Ok(()) +} diff --git a/examples/test_helpers/lib.rs b/examples/test_helpers/lib.rs index ee3a7f645..0df097f52 100644 --- a/examples/test_helpers/lib.rs +++ b/examples/test_helpers/lib.rs @@ -184,6 +184,13 @@ macro_rules! method_call_dry_run { .await .return_value() }}; + ($client:ident, $address:ident, $method:ident($($args:expr),*)) => {{ + let _msg = build_message::($address.clone()).call(|contract| contract.$method($($args),*)); + $client + .call_dry_run(&ink_e2e::alice(), &_msg, 0, None) + .await + .return_value() + }}; ($client:ident, $address:ident, $signer:ident, $method:ident) => {{ let _msg = build_message::($address.clone()).call(|contract| contract.$method()); $client @@ -191,4 +198,11 @@ macro_rules! method_call_dry_run { .await .return_value() }}; + ($client:ident, $address:ident, $signer:ident, $method:ident($($args:expr),*)) => {{ + let _msg = build_message::($address.clone()).call(|contract| contract.$method($($args),*)); + $client + .call_dry_run(&ink_e2e::$signer(), &_msg, 0, None) + .await + .return_value() + }}; } diff --git a/lang/codegen/src/implementations.rs b/lang/codegen/src/implementations.rs index 12d234f54..f28a08bf2 100644 --- a/lang/codegen/src/implementations.rs +++ b/lang/codegen/src/implementations.rs @@ -266,21 +266,23 @@ pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { owner: AccountId, spender: AccountId, amount: Balance, - deadline: u32, - v: u8, - r: [u8; 32], - s: [u8; 32], + deadline: u64, + signature: [u8; 64], ) -> Result<(), PSP22Error> { - permit::InternalImpl::_permit(self, owner, spender, amount, deadline, v, r, s) + permit::InternalImpl::_permit(self, owner, spender, amount, deadline, signature) } - fn _nonces(&self, owner: AccountId) -> u32 { + fn _nonces(&self, owner: AccountId) -> u64 { permit::InternalImpl::_nonces(self, owner) } - fn _domain_separator(&self) -> [u8; 32] { + fn _domain_separator(&mut self) -> [u8; 32] { permit::InternalImpl::_domain_separator(self) } + + fn _use_nonce(&mut self, owner: AccountId) -> u64 { + permit::InternalImpl::_use_nonce(self, owner) + } } )) .expect("Should parse"); @@ -298,21 +300,19 @@ pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { owner: AccountId, spender: AccountId, value: Balance, - deadline: u32, - v: u8, - r: [u8; 32], - s: [u8; 32], + deadline: u64, + signature: [u8; 64], ) -> Result<(), PSP22Error> { - permit::PSP22PermitImpl::permit(self, owner, spender, value, deadline, v, r, s) + permit::PSP22PermitImpl::permit(self, owner, spender, value, deadline, signature) } #[ink(message)] - fn nonces(&self, owner: AccountId) -> u32 { + fn nonces(&self, owner: AccountId) -> u64 { permit::PSP22PermitImpl::nonces(self, owner) } #[ink(message)] - fn domain_separator(&self) -> [u8; 32] { + fn domain_separator(&mut self) -> [u8; 32] { permit::PSP22PermitImpl::domain_separator(self) } } From 3da6a18ee82bc06bd0235dadd07c3099ba9b2f18 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Wed, 23 Aug 2023 19:46:39 +0300 Subject: [PATCH 03/17] update permit implementation, use specific ink! commit for sr25519 --- contracts/Cargo.toml | 7 +- .../src/token/psp22/extensions/permit.rs | 84 +++++++++++-------- examples/psp22_extensions/permit/Cargo.toml | 3 +- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 7c83fc032..4a1503294 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -15,15 +15,13 @@ categories = ["no-std", "embedded"] include = ["Cargo.toml", "src/**/*.rs"] [dependencies] -ink = { version = "4.2.1", default-features = false} +ink = { git = "https://github.com/paritytech/ink", rev = "a71990f", default-features = false} scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } -openbrush = { version = "~3.1.1", package = "openbrush_lang", path = "../lang", default-features = false } +openbrush = { package = "openbrush_lang", path = "../lang", default-features = false } pallet-assets-chain-extension = { git = "https://github.com/Brushfam/pallet-assets-chain-extension", branch = "polkadot-v0.9.37", default-features = false, features = ["ink-lang"] } -blake2 = { version = "0.10.6", default-features = false } -sp-core = { version = "21.0.0", default-features = false, features = ["full_crypto"] } [lib] name = "openbrush_contracts" @@ -40,7 +38,6 @@ std = [ "scale-info/std", "openbrush/std", "pallet-assets-chain-extension/ink-std", - "sp-core/std", ] psp22 = [] psp22_pallet = [] diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index a787697ad..7b0f2c737 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -22,20 +22,30 @@ pub use crate::{ psp22, psp22::extensions::permit, - traits::psp22::{extensions::permit::*, *}, + traits::psp22::{ + extensions::permit::*, + *, + }, }; -use blake2::digest::Update; -use blake2::{Blake2s, Digest}; -use ink::blake2x256; -use openbrush::storage::Mapping; -use openbrush::traits::AccountId; -use openbrush::traits::{Balance, Storage}; -pub use psp22::{Internal as _, InternalImpl as _, PSP22Impl}; -use sp_core::crypto::Pair; -use sp_core::sr25519::{Public, Signature}; -pub const PERMIT_TYPE_HASH: [u8; 32] = - blake2x256!("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); +use ink::env::hash::{ + Blake2x256, + HashOutput, +}; +use openbrush::{ + storage::Mapping, + traits::{ + AccountId, + Balance, + Storage, + }, +}; +pub use psp22::{ + Internal as _, + InternalImpl as _, + PSP22Impl, +}; +use scale::Encode; #[derive(Default, Debug)] #[openbrush::storage_item] @@ -45,6 +55,16 @@ pub struct Data { pub cached_domain_separator: [u8; 32], } +#[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] +pub struct PermitMessage { + domain_separator: [u8; 32], + owner: AccountId, + spender: AccountId, + amount: Balance, + deadline: u64, + nonce: u64, +} + pub trait PSP22PermitImpl: Internal { fn permit( &mut self, @@ -94,28 +114,22 @@ pub trait InternalImpl: Storage + psp22::Internal { ) -> Result<(), PSP22Error> { let block_time = Self::env().block_timestamp(); if deadline < block_time { - return Err(PSP22Error::PermitExpired); + return Err(PSP22Error::PermitExpired) } let nonce = self._use_nonce(owner); - let message_hash: [u8; 32] = Blake2s::new() - .chain(PERMIT_TYPE_HASH) - .chain(self._domain_separator()) - .chain(owner) - .chain(spender) - .chain(amount.to_le_bytes()) - .chain(nonce.to_le_bytes()) - .chain(deadline.to_le_bytes()) - .finalize() - .into(); - - let message = &message_hash[..]; - let sig = Signature::from_raw(signature); - - let owner_bytes: &[u8; 32] = owner.as_ref(); - let pubkey = Public::from_raw(owner_bytes.clone()); - - if sp_core::sr25519::Pair::verify(&sig, message, &pubkey) { + let domain_separator = self._domain_separator(); + + let message = &scale::Encode::encode(&PermitMessage { + domain_separator, + owner, + spender, + amount, + deadline, + nonce, + }); + + if ink::env::sr25519_verify(&signature, message, (&owner).as_ref()).is_ok() { self._approve_from_to(owner, spender, amount)?; Ok(()) } else { @@ -137,11 +151,13 @@ pub trait InternalImpl: Storage + psp22::Internal { if self.data().cached_domain_separator.get().is_none() { let account_id = &Self::env().account_id(); - let result: [u8; 32] = Blake2s::new().chain(account_id).finalize().into(); + let mut output = ::Type::default(); + + ink::env::hash_bytes::(&account_id.encode(), &mut output); - self.data().cached_domain_separator.set(&result.clone()); + self.data().cached_domain_separator.set(&output); - result + output } else { cached } diff --git a/examples/psp22_extensions/permit/Cargo.toml b/examples/psp22_extensions/permit/Cargo.toml index afcadf945..05bc68254 100644 --- a/examples/psp22_extensions/permit/Cargo.toml +++ b/examples/psp22_extensions/permit/Cargo.toml @@ -5,8 +5,7 @@ authors = ["Brushfam "] edition = "2021" [dependencies] -ink = { version = "4.2.1", default-features = false} - +ink = { git = "https://github.com/paritytech/ink", rev = "a71990f", default-features = false} scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } From 814ac9636e4bcd2b19841bfb216a67a823ac44ae Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 28 Aug 2023 11:23:58 +0300 Subject: [PATCH 04/17] create signature type --- .../src/token/psp22/extensions/permit.rs | 5 ++-- lang/Cargo.toml | 2 +- lang/src/utils.rs | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index 7b0f2c737..83324c252 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -39,6 +39,7 @@ use openbrush::{ Balance, Storage, }, + utils::Signature, }; pub use psp22::{ Internal as _, @@ -72,7 +73,7 @@ pub trait PSP22PermitImpl: Internal { spender: AccountId, amount: Balance, deadline: u64, - signature: [u8; 64], + signature: Signature, ) -> Result<(), PSP22Error> { self._permit(owner, spender, amount, deadline, signature) } @@ -93,7 +94,7 @@ pub trait Internal { spender: AccountId, amount: Balance, deadline: u64, - signature: [u8; 64], + signature: Signature, ) -> Result<(), PSP22Error>; fn _nonces(&self, owner: AccountId) -> u64; diff --git a/lang/Cargo.toml b/lang/Cargo.toml index ef482d3c1..67ba7f66f 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -16,7 +16,7 @@ include = ["Cargo.toml", "src/**/*.rs"] [dependencies] openbrush_lang_macro = { version = "~4.0.0-beta", path = "macro", default-features = false } -ink = { version = "4.2.1", default-features = false} +ink = { git = "https://github.com/paritytech/ink", rev = "a71990f", default-features = false} scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"] } diff --git a/lang/src/utils.rs b/lang/src/utils.rs index 32974687a..4586d158a 100644 --- a/lang/src/utils.rs +++ b/lang/src/utils.rs @@ -22,6 +22,7 @@ pub use const_format; pub use xxhash_rust; +use crate::traits::AccountId; use xxhash_rust::const_xxh32::xxh32; /// The value 0 is a valid seed. @@ -34,3 +35,27 @@ impl ConstHasher { xxh32(str.as_bytes(), XXH32_SEED) } } + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] +pub enum SignatureType { + #[default] + ECDSA, + SR25519, +} + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] +pub struct Signature { + pub signature_type: SignatureType, + pub raw_signature: [u8], +} + +impl Signature { + pub fn verify(&self, message: &[u8], pub_key: &AccountId) -> bool { + match self.signature_type { + SignatureType::ECDSA => ink::env::ecdsa_recover(&self.raw_signature.into(), message.into()).is_ok(), + SignatureType::SR25519 => { + ink::env::sr25519_verify(&self.raw_signature.into(), message, pub_key.as_ref()).is_ok() + } + } + } +} From ccd7fcb97c662f1989318f6948b12b35b055deac Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 28 Aug 2023 13:35:02 +0300 Subject: [PATCH 05/17] use Signature enum for signatures and verifying --- contracts/Cargo.toml | 2 +- .../src/token/psp22/extensions/permit.rs | 4 +-- .../src/traits/psp22/extensions/permit.rs | 8 ++++-- examples/psp22_extensions/permit/Cargo.toml | 5 ++-- lang/Cargo.toml | 4 ++- lang/codegen/src/implementations.rs | 9 ++++--- lang/src/utils.rs | 26 ++++++++----------- 7 files changed, 31 insertions(+), 27 deletions(-) diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 5dbcf8bf2..4430b3770 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -15,7 +15,7 @@ categories = ["no-std", "embedded"] include = ["Cargo.toml", "src/**/*.rs"] [dependencies] -ink = { git = "https://github.com/paritytech/ink", rev = "a71990f", default-features = false} +ink = { version = "4.2.1", default-features = false} scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index 83324c252..93b923eb8 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -111,7 +111,7 @@ pub trait InternalImpl: Storage + psp22::Internal { spender: AccountId, amount: Balance, deadline: u64, - signature: [u8; 64], + signature: Signature, ) -> Result<(), PSP22Error> { let block_time = Self::env().block_timestamp(); if deadline < block_time { @@ -130,7 +130,7 @@ pub trait InternalImpl: Storage + psp22::Internal { nonce, }); - if ink::env::sr25519_verify(&signature, message, (&owner).as_ref()).is_ok() { + if signature.verify(message, &(owner).as_ref()) { self._approve_from_to(owner, spender, amount)?; Ok(()) } else { diff --git a/contracts/src/traits/psp22/extensions/permit.rs b/contracts/src/traits/psp22/extensions/permit.rs index 6ffa6f90c..bddf48bfa 100644 --- a/contracts/src/traits/psp22/extensions/permit.rs +++ b/contracts/src/traits/psp22/extensions/permit.rs @@ -22,7 +22,11 @@ /// Extension of [`PSP22`] that allows create `amount` tokens /// and assigns them to `account`, increasing the total supply pub use crate::traits::errors::PSP22Error; -use openbrush::traits::{AccountId, Balance}; +use openbrush::traits::{ + AccountId, + Balance, +}; +pub use openbrush::utils::Signature; #[openbrush::wrapper] pub type PSP22PermitRef = dyn PSP22Permit; @@ -39,7 +43,7 @@ pub trait PSP22Permit { spender: AccountId, value: Balance, deadline: u64, - signature: [u8; 64], + signature: Signature, ) -> Result<(), PSP22Error>; /// Returns the current permit nonce for `owner`. This value must be diff --git a/examples/psp22_extensions/permit/Cargo.toml b/examples/psp22_extensions/permit/Cargo.toml index 05bc68254..7d8995c5f 100644 --- a/examples/psp22_extensions/permit/Cargo.toml +++ b/examples/psp22_extensions/permit/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "my_psp22_permit" version = "3.1.1" -authors = ["Brushfam "] +authors = ["Brushfam "] edition = "2021" [dependencies] -ink = { git = "https://github.com/paritytech/ink", rev = "a71990f", default-features = false} +ink = { version = "4.2.1", default-features = false} scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } @@ -16,7 +16,6 @@ ecdsa = { version = "0.16.7", default-features = false, optional = true } [dev-dependencies] ink_e2e = "4.2.1" test_helpers = { path = "../../test_helpers", default-features = false } -blake2 = { version = "0.10.6", default-features = false } [lib] name = "my_psp22_permit" diff --git a/lang/Cargo.toml b/lang/Cargo.toml index 67ba7f66f..98fa48e64 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -16,9 +16,11 @@ include = ["Cargo.toml", "src/**/*.rs"] [dependencies] openbrush_lang_macro = { version = "~4.0.0-beta", path = "macro", default-features = false } -ink = { git = "https://github.com/paritytech/ink", rev = "a71990f", default-features = false} +ink = { version = "4.2.1", default-features = false} +ink_ir = { version = "4.2.1", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"] } +blake2 = { version = "0.10.6", default-features = false } xxhash-rust = { version = "0.8", features = ["const_xxh32"] } const_format = "0.2.21" diff --git a/lang/codegen/src/implementations.rs b/lang/codegen/src/implementations.rs index edc6b73cf..3dc0ff0f4 100644 --- a/lang/codegen/src/implementations.rs +++ b/lang/codegen/src/implementations.rs @@ -1,4 +1,7 @@ -use quote::{format_ident, quote}; +use quote::{ + format_ident, + quote, +}; use std::collections::HashMap; use syn::Block; @@ -267,7 +270,7 @@ pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { spender: AccountId, amount: Balance, deadline: u64, - signature: [u8; 64], + signature: Signature, ) -> Result<(), PSP22Error> { permit::InternalImpl::_permit(self, owner, spender, amount, deadline, signature) } @@ -301,7 +304,7 @@ pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { spender: AccountId, value: Balance, deadline: u64, - signature: [u8; 64], + signature: Signature, ) -> Result<(), PSP22Error> { permit::PSP22PermitImpl::permit(self, owner, spender, value, deadline, signature) } diff --git a/lang/src/utils.rs b/lang/src/utils.rs index 4586d158a..48207172f 100644 --- a/lang/src/utils.rs +++ b/lang/src/utils.rs @@ -36,26 +36,22 @@ impl ConstHasher { } } -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] -pub enum SignatureType { - #[default] - ECDSA, - SR25519, -} - -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] -pub struct Signature { - pub signature_type: SignatureType, - pub raw_signature: [u8], +#[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] +pub enum Signature { + ECDSA([u8; 65]), } impl Signature { pub fn verify(&self, message: &[u8], pub_key: &AccountId) -> bool { - match self.signature_type { - SignatureType::ECDSA => ink::env::ecdsa_recover(&self.raw_signature.into(), message.into()).is_ok(), - SignatureType::SR25519 => { - ink::env::sr25519_verify(&self.raw_signature.into(), message, pub_key.as_ref()).is_ok() + match self { + Signature::ECDSA(sig) => { + let mut output: [u8; 33]; + let mut message_hash: [u8; 32]; + ink_ir::blake2b_256(message, &mut message_hash); + let result = ink::env::ecdsa_recover(sig, &message_hash, &mut output); + return result.is_ok() && output == pub_key.as_ref() } + _ => false, } } } From 2ad103d774c06f8940f7a8292d6be7b13355fb97 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 28 Aug 2023 13:46:51 +0300 Subject: [PATCH 06/17] move signature to traits --- .../src/token/psp22/extensions/permit.rs | 2 +- .../src/traits/psp22/extensions/permit.rs | 2 +- lang/Cargo.toml | 1 - lang/codegen/Cargo.toml | 6 +++--- lang/src/lib.rs | 2 +- lang/src/traits.rs | 20 +++++++++++++++++++ lang/src/utils.rs | 20 ------------------- 7 files changed, 26 insertions(+), 27 deletions(-) diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index 93b923eb8..3224ad3d7 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -37,9 +37,9 @@ use openbrush::{ traits::{ AccountId, Balance, + Signature, Storage, }, - utils::Signature, }; pub use psp22::{ Internal as _, diff --git a/contracts/src/traits/psp22/extensions/permit.rs b/contracts/src/traits/psp22/extensions/permit.rs index bddf48bfa..e167931f6 100644 --- a/contracts/src/traits/psp22/extensions/permit.rs +++ b/contracts/src/traits/psp22/extensions/permit.rs @@ -22,11 +22,11 @@ /// Extension of [`PSP22`] that allows create `amount` tokens /// and assigns them to `account`, increasing the total supply pub use crate::traits::errors::PSP22Error; +pub use openbrush::traits::Signature; use openbrush::traits::{ AccountId, Balance, }; -pub use openbrush::utils::Signature; #[openbrush::wrapper] pub type PSP22PermitRef = dyn PSP22Permit; diff --git a/lang/Cargo.toml b/lang/Cargo.toml index 98fa48e64..fbf1f4a0a 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -20,7 +20,6 @@ ink = { version = "4.2.1", default-features = false} ink_ir = { version = "4.2.1", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"] } -blake2 = { version = "0.10.6", default-features = false } xxhash-rust = { version = "0.8", features = ["const_xxh32"] } const_format = "0.2.21" diff --git a/lang/codegen/Cargo.toml b/lang/codegen/Cargo.toml index 1f8307d8b..bd09b68d2 100644 --- a/lang/codegen/Cargo.toml +++ b/lang/codegen/Cargo.toml @@ -22,10 +22,10 @@ fs2 = "0.4.3" serde = { version = "1.0", features = ["derive"] } cargo_metadata = "0.13.1" unwrap = "1.2.1" -blake2 = "0.9" +blake2 = "0.10.6" heck = "0.3.1" -ink_ir = { version = "4.1.0-beta", default-features = false } -ink_primitives = { version = "4.1.0-beta", default-features = false } +ink_ir = { version = "4.2.1", default-features = false } +ink_primitives = { version = "4.2.1", default-features = false } synstructure = "0.12" [lib] diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 64ce6c0dd..eb710792f 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -19,7 +19,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![cfg_attr(not(feature = "std"), no_std)] mod macros; pub mod storage; diff --git a/lang/src/traits.rs b/lang/src/traits.rs index acc118539..74f057c1e 100644 --- a/lang/src/traits.rs +++ b/lang/src/traits.rs @@ -123,3 +123,23 @@ pub trait Flush: Storable + Sized + StorageKey { } impl Flush for T {} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] +pub enum Signature { + ECDSA([u8; 65]), +} + +impl Signature { + pub fn verify(&self, message: &[u8], pub_key: &AccountId) -> bool { + match self { + Signature::ECDSA(sig) => { + let mut output: [u8; 33]; + let mut message_hash: [u8; 32]; + ink_ir::blake2b_256(message, &mut message_hash); + let result = ink::env::ecdsa_recover(sig, &message_hash, &mut output); + return result.is_ok() && output == pub_key.as_ref() + } + _ => false, + } + } +} diff --git a/lang/src/utils.rs b/lang/src/utils.rs index 48207172f..5bba179b4 100644 --- a/lang/src/utils.rs +++ b/lang/src/utils.rs @@ -35,23 +35,3 @@ impl ConstHasher { xxh32(str.as_bytes(), XXH32_SEED) } } - -#[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] -pub enum Signature { - ECDSA([u8; 65]), -} - -impl Signature { - pub fn verify(&self, message: &[u8], pub_key: &AccountId) -> bool { - match self { - Signature::ECDSA(sig) => { - let mut output: [u8; 33]; - let mut message_hash: [u8; 32]; - ink_ir::blake2b_256(message, &mut message_hash); - let result = ink::env::ecdsa_recover(sig, &message_hash, &mut output); - return result.is_ok() && output == pub_key.as_ref() - } - _ => false, - } - } -} From 4cea9d824883cc5d7e70059407f326aea86f8dc7 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 28 Aug 2023 14:18:30 +0300 Subject: [PATCH 07/17] fix hashing compilation --- contracts/src/token/psp22/extensions/permit.rs | 2 +- lang/Cargo.toml | 1 - lang/codegen/src/implementations.rs | 9 +++++++++ lang/src/traits.rs | 12 ++++++++---- lang/src/utils.rs | 8 +++++++- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index 3224ad3d7..24336f16e 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -130,7 +130,7 @@ pub trait InternalImpl: Storage + psp22::Internal { nonce, }); - if signature.verify(message, &(owner).as_ref()) { + if signature.verify(message, &owner) { self._approve_from_to(owner, spender, amount)?; Ok(()) } else { diff --git a/lang/Cargo.toml b/lang/Cargo.toml index fbf1f4a0a..ef482d3c1 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -17,7 +17,6 @@ include = ["Cargo.toml", "src/**/*.rs"] openbrush_lang_macro = { version = "~4.0.0-beta", path = "macro", default-features = false } ink = { version = "4.2.1", default-features = false} -ink_ir = { version = "4.2.1", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"] } diff --git a/lang/codegen/src/implementations.rs b/lang/codegen/src/implementations.rs index 3dc0ff0f4..7297a3c8c 100644 --- a/lang/codegen/src/implementations.rs +++ b/lang/codegen/src/implementations.rs @@ -44,6 +44,14 @@ impl<'a> ImplArgs<'a> { .expect("Should parse"); self.imports.insert("vec", vec_import); } + + fn signature_import(&mut self) { + let sig_import = syn::parse2::(quote!( + use openbrush::traits::Signature; + )) + .expect("Should parse"); + self.imports.insert("Signature", sig_import); + } } pub(crate) fn impl_psp22(impl_args: &mut ImplArgs) { @@ -328,6 +336,7 @@ pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { .expect("Should parse"); impl_args.imports.insert("PSP22Permit", import); + impl_args.signature_import(); impl_args.vec_import(); // TODO diff --git a/lang/src/traits.rs b/lang/src/traits.rs index 74f057c1e..7e4e8eaed 100644 --- a/lang/src/traits.rs +++ b/lang/src/traits.rs @@ -19,6 +19,8 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// use crate::utils::blake2b_256; +use crate::utils::hash_blake2b256; use ::ink::env::{ DefaultEnvironment, Environment, @@ -124,18 +126,20 @@ pub trait Flush: Storable + Sized + StorageKey { impl Flush for T {} -#[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] +#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum Signature { ECDSA([u8; 65]), } impl Signature { + #[allow(unreachable_patterns)] pub fn verify(&self, message: &[u8], pub_key: &AccountId) -> bool { match self { Signature::ECDSA(sig) => { - let mut output: [u8; 33]; - let mut message_hash: [u8; 32]; - ink_ir::blake2b_256(message, &mut message_hash); + let mut output: [u8; 33] = [0; 33]; + let message_hash = hash_blake2b256(message); + let result = ink::env::ecdsa_recover(sig, &message_hash, &mut output); return result.is_ok() && output == pub_key.as_ref() } diff --git a/lang/src/utils.rs b/lang/src/utils.rs index 5bba179b4..005fb858f 100644 --- a/lang/src/utils.rs +++ b/lang/src/utils.rs @@ -20,9 +20,9 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pub use const_format; +use ink::env::hash; pub use xxhash_rust; -use crate::traits::AccountId; use xxhash_rust::const_xxh32::xxh32; /// The value 0 is a valid seed. @@ -35,3 +35,9 @@ impl ConstHasher { xxh32(str.as_bytes(), XXH32_SEED) } } + +pub fn hash_blake2b256(input: &[u8]) -> [u8; 32] { + let mut output = ::Type::default(); + ink::env::hash_bytes::(input, &mut output); + output +} From c06f9dbec51b4baeb3b554dd8913623463591505 Mon Sep 17 00:00:00 2001 From: prxgr4mm3r Date: Mon, 28 Aug 2023 19:44:07 +0300 Subject: [PATCH 08/17] permit e2e-tests --- contracts/Cargo.toml | 2 +- examples/psp22_extensions/permit/Cargo.toml | 4 +- examples/psp22_extensions/permit/tests/e2e.rs | 66 +++++++++++-------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 866d011fd..836d6ed9a 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openbrush_contracts" -version= "4.0.0-beta" +version= "4.0.0-beta.1" authors = ["Brushfam "] edition = "2021" diff --git a/examples/psp22_extensions/permit/Cargo.toml b/examples/psp22_extensions/permit/Cargo.toml index 7d8995c5f..17530a611 100644 --- a/examples/psp22_extensions/permit/Cargo.toml +++ b/examples/psp22_extensions/permit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "my_psp22_permit" -version = "3.1.1" +version = "4.0.0-beta.1" authors = ["Brushfam "] edition = "2021" @@ -16,6 +16,8 @@ ecdsa = { version = "0.16.7", default-features = false, optional = true } [dev-dependencies] ink_e2e = "4.2.1" test_helpers = { path = "../../test_helpers", default-features = false } +secp256k1 = { version = "0.27.0", default-features = false } + [lib] name = "my_psp22_permit" diff --git a/examples/psp22_extensions/permit/tests/e2e.rs b/examples/psp22_extensions/permit/tests/e2e.rs index 3429bb857..1bd0c3872 100644 --- a/examples/psp22_extensions/permit/tests/e2e.rs +++ b/examples/psp22_extensions/permit/tests/e2e.rs @@ -7,14 +7,13 @@ use openbrush::contracts::psp22::psp22_external::PSP22; #[rustfmt::skip] use crate::my_psp22_permit::*; #[rustfmt::skip] -use ink_e2e::{build_message, PolkadotConfig}; -use blake2::digest::Update; -use blake2::Blake2s; -use blake2::Digest; -use ink_e2e::subxt::ext::sp_core; -use ink_e2e::subxt::ext::sp_core::Pair; -use openbrush::contracts::psp22::extensions::permit::PERMIT_TYPE_HASH; +use ink_e2e::{build_message}; +use ink::env::hash::{Blake2x256, HashOutput}; +// use openbrush::contracts::psp22::extensions::permit::PERMIT_TYPE_HASH; +use openbrush::contracts::psp22::extensions::permit::PermitMessage; use openbrush::traits::Balance; +use openbrush::utils::hash_blake2b256; +use scale::Encode; use test_helpers::{address_of, balance_of, method_call_dry_run}; @@ -29,7 +28,7 @@ async fn assigns_initial_balance(mut client: ink_e2e::Client) -> E2EResult .expect("instantiate failed") .account_id; - assert!(matches!(balance_of!(client, address, alice), 1000)); + assert!(matches!(balance_of!(client, address, Alice), 1000)); Ok(()) } @@ -43,7 +42,7 @@ async fn nonce_should_be_equal_zero(mut client: ink_e2e::Client) -> E2ERes .expect("instantiate failed") .account_id; - let nonce = method_call_dry_run!(client, address, nonces(address_of!(alice))); + let nonce = method_call_dry_run!(client, address, nonces(address_of!(Alice))); assert!(matches!(nonce, 0)); @@ -53,15 +52,16 @@ async fn nonce_should_be_equal_zero(mut client: ink_e2e::Client) -> E2ERes #[ink_e2e::test] async fn check_domain_separator(mut client: ink_e2e::Client) -> E2EResult<()> { let constructor = ContractRef::new(1000); - let address = client + let address = &client .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) .await .expect("instantiate failed") .account_id; + let mut output = ::Type::default(); + ink::env::hash_bytes::(&address.encode(), &mut output); let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator()); - let real_domain_separator: [u8; 32] = Blake2s::new().chain(address).finalize().into(); - + let real_domain_separator: [u8; 32] = output; assert_eq!(domain_separator, real_domain_separator); Ok(()) @@ -69,6 +69,11 @@ async fn check_domain_separator(mut client: ink_e2e::Client) -> E2EResult< #[ink_e2e::test] async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult<()> { + use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, SECP256K1, + }; + let constructor = ContractRef::new(1000); let address = client .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) @@ -76,32 +81,35 @@ async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult< .expect("instantiate failed") .account_id; - let nonce: u64 = method_call_dry_run!(client, address, nonces(address_of!(alice))); + let nonce: u64 = method_call_dry_run!(client, address, nonces(address_of!(Alice))); let deadline: u64 = 30_000_000_000_000; let amount: Balance = 1000; - let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator); + let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator()); - let permit_hash: [u8; 32] = Blake2s::new() - .chain(PERMIT_TYPE_HASH) - .chain(domain_separator) - .chain(address_of!(alice)) - .chain(address_of!(bob)) - .chain(amount.to_le_bytes()) - .chain(nonce.to_le_bytes()) - .chain(deadline.to_le_bytes()) - .finalize() - .into(); + let permit_message = PermitMessage { + domain_separator: domain_separator.clone(), + owner: address_of!(Alice), + spender: address_of!(Bob), + amount, + deadline, + nonce, + }; - let signature: [u8; 64] = sp_core::sr25519::Pair::from_string("//Alice", None) - .expect("Should generate pair") - .sign(&permit_hash) - .0; + let message = &scale::Encode::encode(&permit_message); + + let message_hash = hash_blake2b256(message); + + let signature = SECP256K1::sign_recoverable( + &SECP256K1, + &Message::parse_slice(&message_hash).unwrap(), + &ink_e2e::alice().secret_key, + ); let permit_signature = method_call_dry_run!( client, address, - permit(address_of!(alice), address_of!(bob), amount, deadline, signature) + permit(address_of!(Alice), address_of!(Bob), amount, deadline, signature) ); println!("permit_signature: {:?}", permit_signature); From b1dbc9cb0017d487021ed0e6bd017282e9ec2ccb Mon Sep 17 00:00:00 2001 From: prxgr4mm3r Date: Mon, 28 Aug 2023 22:43:34 +0300 Subject: [PATCH 09/17] e2e-tests --- .../src/token/psp22/extensions/permit.rs | 37 ++++------- examples/psp22_extensions/permit/Cargo.toml | 6 +- examples/psp22_extensions/permit/tests/e2e.rs | 62 +++++++++++++------ 3 files changed, 58 insertions(+), 47 deletions(-) diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index 24336f16e..bd006473f 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -22,30 +22,15 @@ pub use crate::{ psp22, psp22::extensions::permit, - traits::psp22::{ - extensions::permit::*, - *, - }, + traits::psp22::{extensions::permit::*, *}, }; -use ink::env::hash::{ - Blake2x256, - HashOutput, -}; +use ink::env::hash::{Blake2x256, HashOutput}; use openbrush::{ storage::Mapping, - traits::{ - AccountId, - Balance, - Signature, - Storage, - }, -}; -pub use psp22::{ - Internal as _, - InternalImpl as _, - PSP22Impl, + traits::{AccountId, Balance, Signature, Storage}, }; +pub use psp22::{Internal as _, InternalImpl as _, PSP22Impl}; use scale::Encode; #[derive(Default, Debug)] @@ -58,12 +43,12 @@ pub struct Data { #[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] pub struct PermitMessage { - domain_separator: [u8; 32], - owner: AccountId, - spender: AccountId, - amount: Balance, - deadline: u64, - nonce: u64, + pub domain_separator: [u8; 32], + pub owner: AccountId, + pub spender: AccountId, + pub amount: Balance, + pub deadline: u64, + pub nonce: u64, } pub trait PSP22PermitImpl: Internal { @@ -115,7 +100,7 @@ pub trait InternalImpl: Storage + psp22::Internal { ) -> Result<(), PSP22Error> { let block_time = Self::env().block_timestamp(); if deadline < block_time { - return Err(PSP22Error::PermitExpired) + return Err(PSP22Error::PermitExpired); } let nonce = self._use_nonce(owner); diff --git a/examples/psp22_extensions/permit/Cargo.toml b/examples/psp22_extensions/permit/Cargo.toml index 17530a611..b750335bd 100644 --- a/examples/psp22_extensions/permit/Cargo.toml +++ b/examples/psp22_extensions/permit/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Brushfam "] edition = "2021" [dependencies] -ink = { version = "4.2.1", default-features = false} +ink = { version = "4.3.0", default-features = false} scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } @@ -14,9 +14,11 @@ openbrush = { path = "../../..", default-features = false, features = ["psp22"] ecdsa = { version = "0.16.7", default-features = false, optional = true } [dev-dependencies] -ink_e2e = "4.2.1" +ink_e2e = "4.3.0" +ink_env = "4.3.0" test_helpers = { path = "../../test_helpers", default-features = false } secp256k1 = { version = "0.27.0", default-features = false } +subxt-signer = "0.31.0" [lib] diff --git a/examples/psp22_extensions/permit/tests/e2e.rs b/examples/psp22_extensions/permit/tests/e2e.rs index 1bd0c3872..0e04f5813 100644 --- a/examples/psp22_extensions/permit/tests/e2e.rs +++ b/examples/psp22_extensions/permit/tests/e2e.rs @@ -2,19 +2,18 @@ extern crate my_psp22_permit; +use ink::env::hash::{Blake2x256, HashOutput}; +#[rustfmt::skip] +use ink_e2e::build_message; use openbrush::contracts::psp22::extensions::permit::psp22permit_external::PSP22Permit; use openbrush::contracts::psp22::psp22_external::PSP22; #[rustfmt::skip] use crate::my_psp22_permit::*; -#[rustfmt::skip] -use ink_e2e::{build_message}; -use ink::env::hash::{Blake2x256, HashOutput}; // use openbrush::contracts::psp22::extensions::permit::PERMIT_TYPE_HASH; use openbrush::contracts::psp22::extensions::permit::PermitMessage; -use openbrush::traits::Balance; +use openbrush::traits::{AccountId, Balance, Signature}; use openbrush::utils::hash_blake2b256; use scale::Encode; - use test_helpers::{address_of, balance_of, method_call_dry_run}; type E2EResult = Result>; @@ -69,10 +68,8 @@ async fn check_domain_separator(mut client: ink_e2e::Client) -> E2EResult< #[ink_e2e::test] async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult<()> { - use secp256k1::{ - ecdsa::{RecoverableSignature, RecoveryId}, - Message, SECP256K1, - }; + use ink_e2e::Keypair; + use secp256k1::{ecdsa::RecoverableSignature, Message, PublicKey, SecretKey, SECP256K1}; let constructor = ContractRef::new(1000); let address = client @@ -87,10 +84,26 @@ async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult< let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator()); + let seckey = [ + 59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107, 94, 203, 174, 253, 102, 39, 170, 146, 46, + 252, 4, 143, 236, 12, 136, 28, + ]; + let pubkey = PublicKey::from_slice(&[ + 2, 29, 21, 35, 7, 198, 183, 43, 14, 208, 65, 139, 14, 112, 205, 128, 231, 245, 41, 91, 141, 134, 245, 114, 45, + 63, 82, 19, 251, 210, 57, 79, 54, + ]) + .expect("pubkey creation failed"); + + let owner = AccountId::from([ + 2, 29, 21, 35, 7, 198, 183, 43, 14, 208, 65, 139, 14, 112, 205, 128, 231, 245, 41, 91, 141, 134, 245, 114, 45, + 63, 82, 19, 251, 210, 57, 79, + ]); + let spender = address_of!(Bob); + let permit_message = PermitMessage { - domain_separator: domain_separator.clone(), - owner: address_of!(Alice), - spender: address_of!(Bob), + domain_separator, + owner, + spender, amount, deadline, nonce, @@ -98,18 +111,29 @@ async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult< let message = &scale::Encode::encode(&permit_message); - let message_hash = hash_blake2b256(message); + let msg_hash = hash_blake2b256(message); - let signature = SECP256K1::sign_recoverable( - &SECP256K1, - &Message::parse_slice(&message_hash).unwrap(), - &ink_e2e::alice().secret_key, - ); + let msg = Message::from_slice(&msg_hash).expect("message creation failed"); + let seckey = SecretKey::from_slice(&seckey).expect("secret key creation failed"); + let recoverable_signature: RecoverableSignature = SECP256K1.sign_ecdsa_recoverable(&msg, &seckey); + + let recovery_id = recoverable_signature.serialize_compact().0.to_i32() as u8; + let mut signature = recoverable_signature.serialize_compact().1.to_vec(); + signature.push(recovery_id); + let signature_with_recovery_id: [u8; 65] = signature + .try_into() + .expect("unable to create signature with recovery id"); let permit_signature = method_call_dry_run!( client, address, - permit(address_of!(Alice), address_of!(Bob), amount, deadline, signature) + permit( + owner, + spender, + amount, + deadline, + Signature::ECDSA(signature_with_recovery_id) + ) ); println!("permit_signature: {:?}", permit_signature); From 829a81aa29e764c245fa5c76db8520774deb385d Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Mon, 28 Aug 2023 23:16:34 +0300 Subject: [PATCH 10/17] fix tests --- examples/psp22_extensions/permit/tests/e2e.rs | 53 ++++++++++++------- lang/src/traits.rs | 5 +- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/examples/psp22_extensions/permit/tests/e2e.rs b/examples/psp22_extensions/permit/tests/e2e.rs index 0e04f5813..d4d7a90fc 100644 --- a/examples/psp22_extensions/permit/tests/e2e.rs +++ b/examples/psp22_extensions/permit/tests/e2e.rs @@ -2,19 +2,34 @@ extern crate my_psp22_permit; -use ink::env::hash::{Blake2x256, HashOutput}; +use ink::env::hash::{ + Blake2x256, + HashOutput, +}; #[rustfmt::skip] use ink_e2e::build_message; -use openbrush::contracts::psp22::extensions::permit::psp22permit_external::PSP22Permit; -use openbrush::contracts::psp22::psp22_external::PSP22; +use openbrush::contracts::psp22::{ + extensions::permit::psp22permit_external::PSP22Permit, + psp22_external::PSP22, +}; #[rustfmt::skip] use crate::my_psp22_permit::*; // use openbrush::contracts::psp22::extensions::permit::PERMIT_TYPE_HASH; -use openbrush::contracts::psp22::extensions::permit::PermitMessage; -use openbrush::traits::{AccountId, Balance, Signature}; -use openbrush::utils::hash_blake2b256; +use openbrush::{ + contracts::psp22::extensions::permit::PermitMessage, + traits::{ + AccountId, + Balance, + Signature, + }, + utils::hash_blake2b256, +}; use scale::Encode; -use test_helpers::{address_of, balance_of, method_call_dry_run}; +use test_helpers::{ + address_of, + balance_of, + method_call_dry_run, +}; type E2EResult = Result>; @@ -69,7 +84,13 @@ async fn check_domain_separator(mut client: ink_e2e::Client) -> E2EResult< #[ink_e2e::test] async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult<()> { use ink_e2e::Keypair; - use secp256k1::{ecdsa::RecoverableSignature, Message, PublicKey, SecretKey, SECP256K1}; + use secp256k1::{ + ecdsa::RecoverableSignature, + Message, + PublicKey, + SecretKey, + SECP256K1, + }; let constructor = ContractRef::new(1000); let address = client @@ -88,16 +109,12 @@ async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult< 59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107, 94, 203, 174, 253, 102, 39, 170, 146, 46, 252, 4, 143, 236, 12, 136, 28, ]; - let pubkey = PublicKey::from_slice(&[ - 2, 29, 21, 35, 7, 198, 183, 43, 14, 208, 65, 139, 14, 112, 205, 128, 231, 245, 41, 91, 141, 134, 245, 114, 45, - 63, 82, 19, 251, 210, 57, 79, 54, - ]) - .expect("pubkey creation failed"); - - let owner = AccountId::from([ - 2, 29, 21, 35, 7, 198, 183, 43, 14, 208, 65, 139, 14, 112, 205, 128, 231, 245, 41, 91, 141, 134, 245, 114, 45, - 63, 82, 19, 251, 210, 57, 79, - ]); + let pubkey = PublicKey::from_secret_key( + &SECP256K1, + &SecretKey::from_slice(&seckey).expect("seckey creation failed"), + ); + + let owner = AccountId::from(hash_blake2b256(&pubkey.serialize().to_vec())); let spender = address_of!(Bob); let permit_message = PermitMessage { diff --git a/lang/src/traits.rs b/lang/src/traits.rs index 7e4e8eaed..f03f5d50c 100644 --- a/lang/src/traits.rs +++ b/lang/src/traits.rs @@ -141,7 +141,10 @@ impl Signature { let message_hash = hash_blake2b256(message); let result = ink::env::ecdsa_recover(sig, &message_hash, &mut output); - return result.is_ok() && output == pub_key.as_ref() + + let address = hash_blake2b256(&output); + + return result.is_ok() && address == pub_key.as_ref() } _ => false, } From 78d96dd8083269910164c41933a15446773c871a Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 29 Aug 2023 09:07:11 +0300 Subject: [PATCH 11/17] apply fmt --- .../src/token/psp22/extensions/permit.rs | 25 +++++++++++++---- contracts/src/traits/errors/flashloan.rs | 9 +++++- contracts/src/traits/errors/psp22.rs | 7 ++++- lang/codegen/src/implementation.rs | 28 +++++++++++++------ 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index bd006473f..74393f40c 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -22,15 +22,30 @@ pub use crate::{ psp22, psp22::extensions::permit, - traits::psp22::{extensions::permit::*, *}, + traits::psp22::{ + extensions::permit::*, + *, + }, }; -use ink::env::hash::{Blake2x256, HashOutput}; +use ink::env::hash::{ + Blake2x256, + HashOutput, +}; use openbrush::{ storage::Mapping, - traits::{AccountId, Balance, Signature, Storage}, + traits::{ + AccountId, + Balance, + Signature, + Storage, + }, +}; +pub use psp22::{ + Internal as _, + InternalImpl as _, + PSP22Impl, }; -pub use psp22::{Internal as _, InternalImpl as _, PSP22Impl}; use scale::Encode; #[derive(Default, Debug)] @@ -100,7 +115,7 @@ pub trait InternalImpl: Storage + psp22::Internal { ) -> Result<(), PSP22Error> { let block_time = Self::env().block_timestamp(); if deadline < block_time { - return Err(PSP22Error::PermitExpired); + return Err(PSP22Error::PermitExpired) } let nonce = self._use_nonce(owner); diff --git a/contracts/src/traits/errors/flashloan.rs b/contracts/src/traits/errors/flashloan.rs index 37c7ab8c8..27c433061 100644 --- a/contracts/src/traits/errors/flashloan.rs +++ b/contracts/src/traits/errors/flashloan.rs @@ -19,7 +19,14 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use super::{AccessControlError, OwnableError, PSP22Error, PSP22ReceiverError, PausableError, ReentrancyGuardError}; +use super::{ + AccessControlError, + OwnableError, + PSP22Error, + PSP22ReceiverError, + PausableError, + ReentrancyGuardError, +}; use openbrush::traits::String; #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] diff --git a/contracts/src/traits/errors/psp22.rs b/contracts/src/traits/errors/psp22.rs index 4c559fec5..4967af0e8 100644 --- a/contracts/src/traits/errors/psp22.rs +++ b/contracts/src/traits/errors/psp22.rs @@ -19,7 +19,12 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use super::{AccessControlError, OwnableError, PausableError, ReentrancyGuardError}; +use super::{ + AccessControlError, + OwnableError, + PausableError, + ReentrancyGuardError, +}; use openbrush::traits::String; /// The PSP22 error type. Contract will throw one of this errors. diff --git a/lang/codegen/src/implementation.rs b/lang/codegen/src/implementation.rs index c454bc202..6371bba60 100644 --- a/lang/codegen/src/implementation.rs +++ b/lang/codegen/src/implementation.rs @@ -19,15 +19,25 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use crate::{implementations::*, internal, internal::*}; +use crate::{ + implementations::*, + internal, + internal::*, +}; use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::{ + quote, + ToTokens, +}; use std::collections::HashMap; -use syn::{Item, Path}; +use syn::{ + Item, + Path, +}; pub fn generate(attrs: TokenStream, ink_module: TokenStream) -> TokenStream { if internal::skip() { - return quote! {}; + return quote! {} } let input: TokenStream = ink_module; @@ -35,9 +45,11 @@ pub fn generate(attrs: TokenStream, ink_module: TokenStream) -> TokenStream { let args = syn::parse2::(attrs) .expect("No default contracts to implement provided") .iter() - .map(|arg| match arg { - NestedMeta::Path(method) => method.to_token_stream().to_string().replace(' ', ""), - _ => panic!("Expected names of OpenBrush traits to implement in the contract!"), + .map(|arg| { + match arg { + NestedMeta::Path(method) => method.to_token_stream().to_string().replace(' ', ""), + _ => panic!("Expected names of OpenBrush traits to implement in the contract!"), + } }) .collect::>(); @@ -228,7 +240,7 @@ fn extract_storage_struct_name(items: &[syn::Item]) -> String { if let Some(ink_attr) = ink_attr_maybe { if let Ok(path) = ink_attr.parse_args::() { - return path.to_token_stream().to_string() == "storage"; + return path.to_token_stream().to_string() == "storage" } } false From 7b75b990fbcecd9178f8f130390ea042419588b1 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 29 Aug 2023 09:08:54 +0300 Subject: [PATCH 12/17] fix warning --- contracts/src/token/psp22/extensions/permit.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index 74393f40c..bfec20345 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -37,7 +37,6 @@ use openbrush::{ traits::{ AccountId, Balance, - Signature, Storage, }, }; From 753726838e222bd04cf5eac998298db3d1bfe6c7 Mon Sep 17 00:00:00 2001 From: prxgr4mm3r Date: Tue, 29 Aug 2023 11:56:54 +0300 Subject: [PATCH 13/17] add some e2e-tests --- examples/psp22_extensions/permit/tests/e2e.rs | 265 ++++++++++++++++-- examples/test_helpers/lib.rs | 16 ++ 2 files changed, 253 insertions(+), 28 deletions(-) diff --git a/examples/psp22_extensions/permit/tests/e2e.rs b/examples/psp22_extensions/permit/tests/e2e.rs index d4d7a90fc..a732baf5a 100644 --- a/examples/psp22_extensions/permit/tests/e2e.rs +++ b/examples/psp22_extensions/permit/tests/e2e.rs @@ -2,34 +2,21 @@ extern crate my_psp22_permit; -use ink::env::hash::{ - Blake2x256, - HashOutput, -}; +use ink::env::hash::{Blake2x256, HashOutput}; #[rustfmt::skip] use ink_e2e::build_message; -use openbrush::contracts::psp22::{ - extensions::permit::psp22permit_external::PSP22Permit, - psp22_external::PSP22, -}; +use openbrush::contracts::psp22::{extensions::permit::psp22permit_external::PSP22Permit, psp22_external::PSP22}; #[rustfmt::skip] use crate::my_psp22_permit::*; // use openbrush::contracts::psp22::extensions::permit::PERMIT_TYPE_HASH; use openbrush::{ contracts::psp22::extensions::permit::PermitMessage, - traits::{ - AccountId, - Balance, - Signature, - }, + traits::{AccountId, Balance, Signature}, utils::hash_blake2b256, }; use scale::Encode; -use test_helpers::{ - address_of, - balance_of, - method_call_dry_run, -}; +use secp256k1::{ecdsa::RecoverableSignature, Message, PublicKey, SecretKey, SECP256K1}; +use test_helpers::{address_of, balance_of, method_call, method_call_dry_run}; type E2EResult = Result>; @@ -82,16 +69,7 @@ async fn check_domain_separator(mut client: ink_e2e::Client) -> E2EResult< } #[ink_e2e::test] -async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult<()> { - use ink_e2e::Keypair; - use secp256k1::{ - ecdsa::RecoverableSignature, - Message, - PublicKey, - SecretKey, - SECP256K1, - }; - +async fn permit_accepts_owner_signature(mut client: ink_e2e::Client) -> E2EResult<()> { let constructor = ContractRef::new(1000); let address = client .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) @@ -158,3 +136,234 @@ async fn check_permit_signature(mut client: ink_e2e::Client) -> E2EResult< Ok(()) } + +#[ink_e2e::test] +async fn permit_rejects_reused_signature(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = ContractRef::new(1000); + let address = client + .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let nonce: u64 = method_call_dry_run!(client, address, nonces(address_of!(Alice))); + let deadline: u64 = 30_000_000_000_000; + let amount: Balance = 1000; + + let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator()); + + let seckey = [ + 59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107, 94, 203, 174, 253, 102, 39, 170, 146, 46, + 252, 4, 143, 236, 12, 136, 28, + ]; + let pubkey = PublicKey::from_secret_key( + &SECP256K1, + &SecretKey::from_slice(&seckey).expect("seckey creation failed"), + ); + + let owner = AccountId::from(hash_blake2b256(&pubkey.serialize().to_vec())); + let spender = address_of!(Bob); + + let permit_message = PermitMessage { + domain_separator, + owner, + spender, + amount, + deadline, + nonce, + }; + + let message = &scale::Encode::encode(&permit_message); + + let msg_hash = hash_blake2b256(message); + + let msg = Message::from_slice(&msg_hash).expect("message creation failed"); + let seckey = SecretKey::from_slice(&seckey).expect("secret key creation failed"); + let recoverable_signature: RecoverableSignature = SECP256K1.sign_ecdsa_recoverable(&msg, &seckey); + + let recovery_id = recoverable_signature.serialize_compact().0.to_i32() as u8; + let mut signature = recoverable_signature.serialize_compact().1.to_vec(); + signature.push(recovery_id); + let signature_with_recovery_id: [u8; 65] = signature + .try_into() + .expect("unable to create signature with recovery id"); + + let first_permit_result = method_call_dry_run!( + client, + address, + permit( + owner, + spender, + amount, + deadline, + Signature::ECDSA(signature_with_recovery_id) + ) + ); + let _call_permit = method_call!( + client, + address, + permit( + owner, + spender, + amount, + deadline, + Signature::ECDSA(signature_with_recovery_id) + ) + ); + + assert!(matches!(first_permit_result, Ok(_))); + + let second_permit_result = method_call_dry_run!( + client, + address, + permit( + owner, + spender, + amount, + deadline, + Signature::ECDSA(signature_with_recovery_id) + ) + ); + + println!("second_permit_result: {:?}", second_permit_result); + + assert!(matches!(second_permit_result, Err(_))); + + Ok(()) +} + +#[ink_e2e::test] +async fn permit_rejects_other_signature(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = ContractRef::new(1000); + let address = client + .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let nonce: u64 = method_call_dry_run!(client, address, nonces(address_of!(Alice))); + let deadline: u64 = 30_000_000_000_000; + let amount: Balance = 1000; + + let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator()); + + let seckey = [ + 59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107, 94, 203, 174, 253, 102, 39, 170, 146, 46, + 252, 4, 143, 236, 12, 136, 28, + ]; + let _pubkey = PublicKey::from_secret_key( + &SECP256K1, + &SecretKey::from_slice(&seckey).expect("seckey creation failed"), + ); + + let owner = address_of!(Alice); + let spender = address_of!(Bob); + + let permit_message = PermitMessage { + domain_separator, + owner, + spender, + amount, + deadline, + nonce, + }; + + let message = &scale::Encode::encode(&permit_message); + + let msg_hash = hash_blake2b256(message); + + let msg = Message::from_slice(&msg_hash).expect("message creation failed"); + let seckey = SecretKey::from_slice(&seckey).expect("secret key creation failed"); + let recoverable_signature: RecoverableSignature = SECP256K1.sign_ecdsa_recoverable(&msg, &seckey); + + let recovery_id = recoverable_signature.serialize_compact().0.to_i32() as u8; + let mut signature = recoverable_signature.serialize_compact().1.to_vec(); + signature.push(recovery_id); + let signature_with_recovery_id: [u8; 65] = signature + .try_into() + .expect("unable to create signature with recovery id"); + + let first_permit_result = method_call_dry_run!( + client, + address, + permit( + owner, + spender, + amount, + deadline, + Signature::ECDSA(signature_with_recovery_id) + ) + ); + + assert!(matches!(first_permit_result, Err(_))); + + Ok(()) +} + +#[ink_e2e::test] +async fn permit_rejects_expired_permit(mut client: ink_e2e::Client) -> E2EResult<()> { + let constructor = ContractRef::new(1000); + let address = client + .instantiate("my_psp22_permit", &ink_e2e::alice(), constructor, 0, None) + .await + .expect("instantiate failed") + .account_id; + + let nonce: u64 = method_call_dry_run!(client, address, nonces(address_of!(Alice))); + let deadline: u64 = 1; + let amount: Balance = 1000; + + let domain_separator: [u8; 32] = method_call_dry_run!(client, address, domain_separator()); + + let seckey = [ + 59, 148, 11, 85, 134, 130, 61, 253, 2, 174, 59, 70, 27, 180, 51, 107, 94, 203, 174, 253, 102, 39, 170, 146, 46, + 252, 4, 143, 236, 12, 136, 28, + ]; + let pubkey = PublicKey::from_secret_key( + &SECP256K1, + &SecretKey::from_slice(&seckey).expect("seckey creation failed"), + ); + + let owner = AccountId::from(hash_blake2b256(&pubkey.serialize().to_vec())); + let spender = address_of!(Bob); + + let permit_message = PermitMessage { + domain_separator, + owner, + spender, + amount, + deadline, + nonce, + }; + + let message = &scale::Encode::encode(&permit_message); + + let msg_hash = hash_blake2b256(message); + + let msg = Message::from_slice(&msg_hash).expect("message creation failed"); + let seckey = SecretKey::from_slice(&seckey).expect("secret key creation failed"); + let recoverable_signature: RecoverableSignature = SECP256K1.sign_ecdsa_recoverable(&msg, &seckey); + + let recovery_id = recoverable_signature.serialize_compact().0.to_i32() as u8; + let mut signature = recoverable_signature.serialize_compact().1.to_vec(); + signature.push(recovery_id); + let signature_with_recovery_id: [u8; 65] = signature + .try_into() + .expect("unable to create signature with recovery id"); + + let permit_signature = method_call_dry_run!( + client, + address, + permit( + owner, + spender, + amount, + deadline, + Signature::ECDSA(signature_with_recovery_id) + ) + ); + + assert!(matches!(permit_signature, Err(_))); + + Ok(()) +} diff --git a/examples/test_helpers/lib.rs b/examples/test_helpers/lib.rs index 2462857bd..59a4b2fcf 100644 --- a/examples/test_helpers/lib.rs +++ b/examples/test_helpers/lib.rs @@ -173,6 +173,22 @@ macro_rules! method_call { .expect("method_call failed") .return_value() }}; + ($client:ident, $address:ident, $method:ident($($args:expr),*)) => {{ + let _msg = build_message::($address.clone()).call(|contract| contract.$method($($args),*)); + $client + .call(&ink_e2e::alice(), _msg, 0, None) + .await + .expect("method_call failed") + .return_value() + }}; + ($client:ident, $address:ident, $signer:ident, $method:ident($($args:expr),*)) => {{ + let _msg = build_message::($address.clone()).call(|contract| contract.$method($($args),*)); + $client + .call(&ink_e2e::$signer(), _msg, 0, None) + .await + .expect("method_call failed") + .return_value() + }}; } #[macro_export] From d6a2fe5c7f16345cd4b16a73cc31d41f36a3cfa1 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 29 Aug 2023 15:41:48 +0300 Subject: [PATCH 14/17] refactor utils, use nonces and crypto in permit --- Cargo.toml | 10 +- contracts/Cargo.toml | 15 +-- .../extensions/governor_quorum/data.rs | 2 +- .../extensions/governor_quorum/impls.rs | 5 +- contracts/src/governance/governor/impls.rs | 73 ++++++-------- contracts/src/governance/governor/internal.rs | 22 +++-- contracts/src/governance/utils/votes/data.rs | 2 +- contracts/src/governance/utils/votes/impls.rs | 27 ++--- .../src/governance/utils/votes/internal.rs | 18 ++-- contracts/src/lib.rs | 4 +- .../src/token/psp22/extensions/permit.rs | 52 +++------- contracts/src/token/psp22/extensions/votes.rs | 2 +- contracts/src/traits/errors/flashloan.rs | 1 + contracts/src/traits/errors/governance.rs | 18 ++-- contracts/src/traits/errors/mod.rs | 6 +- contracts/src/traits/errors/nonces.rs | 4 +- contracts/src/traits/errors/psp22.rs | 9 ++ .../src/traits/governance/governor/mod.rs | 6 +- contracts/src/traits/governance/mod.rs | 2 + .../src/traits/governance/utils/votes.rs | 6 +- contracts/src/traits/mod.rs | 2 +- .../{errors/crypto.rs => nonces/mod.rs} | 15 +-- .../src/traits/psp22/extensions/permit.rs | 8 +- .../src/traits/psp22/extensions/votes.rs | 10 +- contracts/src/traits/types.rs | 2 +- contracts/src/traits/utils/mod.rs | 1 - contracts/src/traits/utils/nonces.rs | 9 -- contracts/src/utils/crypto/mod.rs | 69 ------------- contracts/src/utils/mod.rs | 6 +- contracts/src/utils/nonces/mod.rs | 50 +--------- contracts/src/utils/nonces/nonces.rs | 68 +++++++++++++ examples/governance/governor/Cargo.toml | 4 +- examples/psp22_extensions/permit/lib.rs | 4 +- examples/psp22_extensions/votes/Cargo.toml | 2 +- examples/utils/nonces/Cargo.toml | 2 +- lang/Cargo.toml | 4 +- lang/codegen/src/implementations.rs | 28 ++---- lang/src/macros.rs | 6 +- lang/src/traits.rs | 30 ++---- lang/src/utils.rs | 43 -------- .../mod.rs => lang/src/utils/checkpoints.rs | 8 +- lang/src/utils/crypto.rs | 98 +++++++++++++++++++ .../checkpoints.rs => lang/src/utils/mod.rs | 9 +- package.json | 2 +- 44 files changed, 351 insertions(+), 413 deletions(-) rename contracts/src/traits/{errors/crypto.rs => nonces/mod.rs} (78%) delete mode 100644 contracts/src/traits/utils/mod.rs delete mode 100644 contracts/src/traits/utils/nonces.rs delete mode 100644 contracts/src/utils/crypto/mod.rs create mode 100644 contracts/src/utils/nonces/nonces.rs delete mode 100644 lang/src/utils.rs rename contracts/src/utils/checkpoint/mod.rs => lang/src/utils/checkpoints.rs (97%) create mode 100644 lang/src/utils/crypto.rs rename contracts/src/traits/errors/checkpoints.rs => lang/src/utils/mod.rs (86%) diff --git a/Cargo.toml b/Cargo.toml index 0d458b960..47f7e56c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,8 +68,10 @@ timelock_controller = ["openbrush_contracts/timelock_controller"] proxy = ["openbrush_contracts/proxy"] diamond = ["openbrush_contracts/diamond"] upgradeable = ["openbrush_contracts/upgradeable"] -governance = ["openbrush_contracts/governance"] -utils = ["openbrush_contracts/utils"] +governance = ["openbrush_contracts/governance", "openbrush_contracts/checkpoints"] +crypto = ["openbrush_contracts/crypto"] +nonces = ["openbrush_contracts/nonces"] +checkpoints = ["openbrush_contracts/checkpoints"] test-all = [ "psp22", @@ -85,7 +87,9 @@ test-all = [ "proxy", "diamond", "governance", - "utils", + "crypto", + "nonces", + "checkpoints" ] [profile.release] diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 9d122d68f..ff456408e 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -20,7 +20,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -openbrush = { version = "~4.0.0-beta.1", package = "openbrush_lang", path = "../lang", default-features = false } +openbrush = { version = "~4.0.0-beta.1", package = "openbrush_lang", path = "../lang", default-features = false, features = ["crypto", "checkpoints"] } pallet-assets-chain-extension = { git = "https://github.com/Brushfam/pallet-assets-chain-extension", branch = "polkadot-v0.9.37", default-features = false, features = ["ink-lang"] } @@ -40,7 +40,7 @@ std = [ "openbrush/std", "pallet-assets-chain-extension/ink-std", ] -psp22 = [] +psp22 = ["nonces", "crypto"] psp22_pallet = [] psp34 = [] psp37 = [] @@ -60,10 +60,14 @@ diamond = [ ] governance = [ "timelock_controller", - "utils" + "crypto", + "nonces", + "checkpoints" ] -utils = [] upgradeable = ["ownable"] +crypto = ["openbrush/crypto"] +nonces = [] +checkpoints = ["openbrush/checkpoints"] test-all = [ "psp22", "psp34", @@ -75,7 +79,6 @@ test-all = [ "pausable", "timelock_controller", "proxy", - "utils", "diamond", "governance", -] \ No newline at end of file +] diff --git a/contracts/src/governance/extensions/governor_quorum/data.rs b/contracts/src/governance/extensions/governor_quorum/data.rs index 793f72bff..32e983b0b 100644 --- a/contracts/src/governance/extensions/governor_quorum/data.rs +++ b/contracts/src/governance/extensions/governor_quorum/data.rs @@ -20,7 +20,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -pub use crate::utils::checkpoint::Checkpoints; +pub use openbrush::utils::checkpoints::Checkpoints; #[derive(Debug, Default)] #[openbrush::storage_item] diff --git a/contracts/src/governance/extensions/governor_quorum/impls.rs b/contracts/src/governance/extensions/governor_quorum/impls.rs index 49dc2ba27..80532884b 100644 --- a/contracts/src/governance/extensions/governor_quorum/impls.rs +++ b/contracts/src/governance/extensions/governor_quorum/impls.rs @@ -59,7 +59,10 @@ pub trait QuorumImpl: /// Returns the current quorum numerator fn quorum_numerator(&self) -> u128 { - let history = self.data::().quorum_numerator_history.get_or_default(); + let history = self + .data::() + .quorum_numerator_history + .get_or_default(); let (exist, _, last_value) = history.latest_checkpoint(); diff --git a/contracts/src/governance/governor/impls.rs b/contracts/src/governance/governor/impls.rs index 672b0c088..6d5ddc005 100644 --- a/contracts/src/governance/governor/impls.rs +++ b/contracts/src/governance/governor/impls.rs @@ -19,32 +19,29 @@ // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +use crate::governance::{ + extensions::{ + governor_settings::{ + GovernorSettingsImpl, + GovernorSettingsInternal, + }, + governor_votes::GovernorVotesInternal, + }, + governor::{ + Data, + GovernorEvents, + GovernorInternal, + GovernorStorageGetters, + TimestampProvider, + }, +}; pub use crate::{ governance::governor, traits::{ - errors::governance::GovernanceError, + errors::GovernanceError, governance::*, - types::SignatureType, - }, -}; -use crate::{ - governance::{ - extensions::{ - governor_settings::{ - GovernorSettingsImpl, - GovernorSettingsInternal, - }, - governor_votes::GovernorVotesInternal, - }, - governor::{ - Data, - GovernorEvents, - GovernorInternal, - GovernorStorageGetters, - TimestampProvider, - }, + types::Signature, }, - utils::crypto, }; use ink::{ env::{ @@ -326,22 +323,15 @@ pub trait GovernorImpl: proposal_id: ProposalId, support: VoteType, reason: String, - signature: SignatureType, + signature: Signature, ) -> Result { - let message = crypto::hash_message( - (proposal_id.clone(), support.clone(), reason.clone(), Vec::::new()) - .encode() - .as_slice(), - )?; - - let voter = Self::env().caller(); - let valid = crypto::verify_signature(&message, &voter.clone(), &signature)?; + let message = (proposal_id.clone(), support.clone(), reason.clone(), Vec::::new()).encode(); - if !valid { - return Err(GovernanceError::InvalidSignature(voter.clone())) + if !signature.verify(&message, &Self::env().caller()) { + return Err(GovernanceError::InvalidSignature(Self::env().caller())) } - self._cast_vote(proposal_id, voter, support, reason) + self._cast_vote(proposal_id, Self::env().caller(), support, reason) } /// Casts a vote with signature and parameters for a proposal from a message sender. Returns the number of votes already casted for the proposal by the sender @@ -350,23 +340,16 @@ pub trait GovernorImpl: proposal_id: ProposalId, support: VoteType, reason: String, - signature: SignatureType, + signature: Signature, params: Vec, ) -> Result { - let message = crypto::hash_message( - (proposal_id.clone(), support.clone(), reason.clone(), params.clone()) - .encode() - .as_slice(), - )?; + let message = (proposal_id.clone(), support.clone(), reason.clone(), params.clone()).encode(); - let voter = Self::env().caller(); - let valid = crypto::verify_signature(&message, &voter.clone(), &signature)?; - - if !valid { - return Err(GovernanceError::InvalidSignature(voter.clone())) + if !signature.verify(&message, &Self::env().caller()) { + return Err(GovernanceError::InvalidSignature(Self::env().caller())) } - self._cast_vote_with_params(proposal_id, voter, support, reason, params) + self._cast_vote_with_params(proposal_id, Self::env().caller(), support, reason, params) } /// Relays a transaction or function call to an arbitrary target. In cases where the governance executor diff --git a/contracts/src/governance/governor/internal.rs b/contracts/src/governance/governor/internal.rs index 65698f084..74d663ce4 100644 --- a/contracts/src/governance/governor/internal.rs +++ b/contracts/src/governance/governor/internal.rs @@ -33,7 +33,7 @@ use crate::{ }, }, traits::{ - errors::governance::GovernanceError, + errors::GovernanceError, governance::{ CancelationStatus, ExecutionStatus, @@ -46,7 +46,6 @@ use crate::{ ALL_PROPOSAL_STATES, }, }, - utils::crypto, }; use ink::{ env::{ @@ -65,12 +64,15 @@ use ink::{ vec::Vec, }, }; -use openbrush::traits::{ - AccountId, - Balance, - DefaultEnv, - Storage, - String, +use openbrush::{ + traits::{ + AccountId, + Balance, + DefaultEnv, + Storage, + String, + }, + utils::crypto, }; use scale::Encode; @@ -85,7 +87,7 @@ pub trait GovernorInternal: ) -> Result { let message = (transactions, description_hash).encode(); - crypto::hash_message(message.as_slice()).map_err(|err| err.into()) + Ok(crypto::hash_blake2b256(message.as_slice())) } /// Current state of a proposal, following Compound's convention @@ -326,7 +328,7 @@ pub trait GovernorInternal: /// Return the hash of the description. fn _hash_description(&self, description: String) -> Result { - Ok(crypto::hash_message(description.as_bytes())?) + Ok(crypto::hash_blake2b256(description.as_bytes())) } } diff --git a/contracts/src/governance/utils/votes/data.rs b/contracts/src/governance/utils/votes/data.rs index 5c98b6880..6278cb1a6 100644 --- a/contracts/src/governance/utils/votes/data.rs +++ b/contracts/src/governance/utils/votes/data.rs @@ -20,10 +20,10 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use crate::utils::checkpoint::Checkpoints; use openbrush::{ storage::Mapping, traits::AccountId, + utils::checkpoints::Checkpoints, }; #[derive(Default, Debug)] diff --git a/contracts/src/governance/utils/votes/impls.rs b/contracts/src/governance/utils/votes/impls.rs index f63f70a46..34a88db99 100644 --- a/contracts/src/governance/utils/votes/impls.rs +++ b/contracts/src/governance/utils/votes/impls.rs @@ -21,15 +21,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. use crate::{ - governance::utils::votes::{ - Data, - VotesEvents, - VotesInternal, - }, - utils::{ - crypto, - nonces::NoncesImpl, - }, + governance::utils::votes::*, + nonces, }; pub use crate::{ governance::{ @@ -39,9 +32,8 @@ pub use crate::{ traits::{ errors::GovernanceError, governance::utils::votes::*, - types::SignatureType, + types::Signature, }, - utils::checkpoint::*, }; use openbrush::traits::{ AccountId, @@ -52,7 +44,7 @@ use openbrush::traits::{ use scale::Encode; /// Common interface for `PSP22Votes`, and other `Votes`-enabled contracts. -pub trait VotesImpl: Storage + VotesInternal + NoncesImpl + VotesEvents + TimestampProvider { +pub trait VotesImpl: Storage + VotesInternal + nonces::NoncesImpl + VotesEvents + TimestampProvider { /// The amount of votes owned by `account`. fn get_votes(&self, account: AccountId) -> Balance { self.data::() @@ -110,16 +102,17 @@ pub trait VotesImpl: Storage + VotesInternal + NoncesImpl + VotesEvents + &mut self, signer: AccountId, delegatee: AccountId, - nonce: u128, + nonce: u64, expiry: Timestamp, - signature: SignatureType, + signature: Signature, ) -> Result<(), GovernanceError> { if TimestampProvider::block_timestamp(self) > expiry { return Err(GovernanceError::ExpiredSignature(expiry)) } - let message_hash = crypto::hash_message(Encode::encode(&(&delegatee, &nonce, &expiry)).as_slice())?; - let verify_result = crypto::verify_signature(&message_hash, &signer, &signature)?; - if !verify_result { + + let message = (&delegatee, &nonce, &expiry).encode(); + + if !signature.verify(&message, &signer) { return Err(GovernanceError::InvalidSignature(signer)) } else { self._use_checked_nonce(&signer, nonce)?; diff --git a/contracts/src/governance/utils/votes/internal.rs b/contracts/src/governance/utils/votes/internal.rs index 9ae8af834..843208fa1 100644 --- a/contracts/src/governance/utils/votes/internal.rs +++ b/contracts/src/governance/utils/votes/internal.rs @@ -28,20 +28,20 @@ use crate::{ VotesEvents, }, }, - traits::errors::{ - CheckpointsError, - GovernanceError, + traits::errors::GovernanceError, +}; +use openbrush::{ + traits::{ + AccountId, + Balance, + Storage, }, - utils::checkpoint::{ + utils::checkpoints::{ Checkpoint, Checkpoints, + CheckpointsError, }, }; -use openbrush::traits::{ - AccountId, - Balance, - Storage, -}; pub trait VotesInternal: Storage + VotesEvents + TimestampProvider { /// Returns the total number of votes. diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 86d915227..62a5b4ee8 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -61,5 +61,5 @@ pub use upgradeability::diamond; pub use upgradeability::proxy; #[cfg(feature = "upgradeable")] pub use upgradeability::upgradeable; -#[cfg(feature = "utils")] -pub use utils::*; +#[cfg(feature = "nonces")] +pub use utils::nonces; diff --git a/contracts/src/token/psp22/extensions/permit.rs b/contracts/src/token/psp22/extensions/permit.rs index bfec20345..5be379028 100644 --- a/contracts/src/token/psp22/extensions/permit.rs +++ b/contracts/src/token/psp22/extensions/permit.rs @@ -20,6 +20,7 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pub use crate::{ + nonces::*, psp22, psp22::extensions::permit, traits::psp22::{ @@ -27,19 +28,17 @@ pub use crate::{ *, }, }; - -use ink::env::hash::{ - Blake2x256, - HashOutput, -}; use openbrush::{ - storage::Mapping, traits::{ AccountId, Balance, Storage, }, + utils::crypto::hash_blake2b256, }; + +pub use openbrush::utils::crypto::Signature; + pub use psp22::{ Internal as _, InternalImpl as _, @@ -50,7 +49,6 @@ use scale::Encode; #[derive(Default, Debug)] #[openbrush::storage_item] pub struct Data { - pub nonces: Mapping, #[lazy] pub cached_domain_separator: [u8; 32], } @@ -77,10 +75,6 @@ pub trait PSP22PermitImpl: Internal { self._permit(owner, spender, amount, deadline, signature) } - fn nonces(&self, owner: AccountId) -> u64 { - self._nonces(owner) - } - fn domain_separator(&mut self) -> [u8; 32] { self._domain_separator() } @@ -96,14 +90,10 @@ pub trait Internal { signature: Signature, ) -> Result<(), PSP22Error>; - fn _nonces(&self, owner: AccountId) -> u64; - fn _domain_separator(&mut self) -> [u8; 32]; - - fn _use_nonce(&mut self, owner: AccountId) -> u64; } -pub trait InternalImpl: Storage + psp22::Internal { +pub trait InternalImpl: Storage + psp22::Internal + NoncesImpl { fn _permit( &mut self, owner: AccountId, @@ -117,7 +107,7 @@ pub trait InternalImpl: Storage + psp22::Internal { return Err(PSP22Error::PermitExpired) } - let nonce = self._use_nonce(owner); + let nonce = self._use_nonce(&owner)?; let domain_separator = self._domain_separator(); let message = &scale::Encode::encode(&PermitMessage { @@ -137,35 +127,17 @@ pub trait InternalImpl: Storage + psp22::Internal { } } - fn _nonces(&self, owner: AccountId) -> u64 { - self.data().nonces.get(&owner).unwrap_or_default() - } - - fn _set_nonce(&mut self, owner: AccountId, nonce: u64) { - self.data().nonces.insert(&owner, &nonce); - } - fn _domain_separator(&mut self) -> [u8; 32] { - let cached = self.data().cached_domain_separator.get_or_default(); + let cached = self.data::().cached_domain_separator.get_or_default(); - if self.data().cached_domain_separator.get().is_none() { - let account_id = &Self::env().account_id(); + if self.data::().cached_domain_separator.get().is_none() { + let account_hash = hash_blake2b256(&Self::env().account_id().encode()); - let mut output = ::Type::default(); + self.data::().cached_domain_separator.set(&account_hash); - ink::env::hash_bytes::(&account_id.encode(), &mut output); - - self.data().cached_domain_separator.set(&output); - - output + account_hash } else { cached } } - - fn _use_nonce(&mut self, owner: AccountId) -> u64 { - let nonce = self._nonces(owner); - self._set_nonce(owner, nonce.clone() + 1); - nonce - } } diff --git a/contracts/src/token/psp22/extensions/votes.rs b/contracts/src/token/psp22/extensions/votes.rs index c1cd6d27d..1c82b83d3 100644 --- a/contracts/src/token/psp22/extensions/votes.rs +++ b/contracts/src/token/psp22/extensions/votes.rs @@ -26,10 +26,10 @@ use crate::{ psp22, psp22::PSP22Error, traits::errors::GovernanceError, - utils::checkpoint::Checkpoint, }; use ink::prelude::vec; use openbrush::traits::AccountId; +pub use openbrush::utils::checkpoints::Checkpoint; /// Extension of ERC20 to support Compound-like voting and delegation. /// diff --git a/contracts/src/traits/errors/flashloan.rs b/contracts/src/traits/errors/flashloan.rs index 27c433061..01688ae40 100644 --- a/contracts/src/traits/errors/flashloan.rs +++ b/contracts/src/traits/errors/flashloan.rs @@ -114,6 +114,7 @@ impl From for FlashLenderError { FlashLenderError::Custom(String::from("PSP22: Permit Invalid Signature")) } PSP22Error::PermitExpired => FlashLenderError::Custom(String::from("PSP22: Permit Expired")), + PSP22Error::NoncesError(_) => FlashLenderError::Custom(String::from("PSP22: Nonces Error")), } } } diff --git a/contracts/src/traits/errors/governance.rs b/contracts/src/traits/errors/governance.rs index 360b10b95..41814a053 100644 --- a/contracts/src/traits/errors/governance.rs +++ b/contracts/src/traits/errors/governance.rs @@ -21,20 +21,22 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. use crate::traits::{ - errors::{ - CheckpointsError, - CryptoError, - NoncesError, - }, + errors::NoncesError, governance::{ ProposalId, ProposalState, Transaction, }, }; -use openbrush::traits::{ - AccountId, - Timestamp, +use openbrush::{ + traits::{ + AccountId, + Timestamp, + }, + utils::{ + checkpoints::CheckpointsError, + crypto::CryptoError, + }, }; /// The Governor error type. Contract will throw one of this errors. diff --git a/contracts/src/traits/errors/mod.rs b/contracts/src/traits/errors/mod.rs index 94d882278..df817bcdc 100644 --- a/contracts/src/traits/errors/mod.rs +++ b/contracts/src/traits/errors/mod.rs @@ -21,11 +21,9 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mod access_control; -mod checkpoints; -mod crypto; mod diamond; mod flashloan; -pub(crate) mod governance; +mod governance; mod nonces; mod ownable; mod pausable; @@ -38,8 +36,6 @@ mod timelock_controller; mod upgradeable; pub use access_control::AccessControlError; -pub use checkpoints::CheckpointsError; -pub use crypto::CryptoError; pub use diamond::DiamondError; pub use flashloan::{ FlashBorrowerError, diff --git a/contracts/src/traits/errors/nonces.rs b/contracts/src/traits/errors/nonces.rs index 3255b660e..d97afbbcb 100644 --- a/contracts/src/traits/errors/nonces.rs +++ b/contracts/src/traits/errors/nonces.rs @@ -25,7 +25,7 @@ use openbrush::traits::AccountId; #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum NoncesError { - /// - InvalidAccountNonce(AccountId, u128), + /// Invalid nonce for account + InvalidAccountNonce(AccountId, u64), NonceOverflow, } diff --git a/contracts/src/traits/errors/psp22.rs b/contracts/src/traits/errors/psp22.rs index 4967af0e8..2d4cd6421 100644 --- a/contracts/src/traits/errors/psp22.rs +++ b/contracts/src/traits/errors/psp22.rs @@ -21,6 +21,7 @@ use super::{ AccessControlError, + NoncesError, OwnableError, PausableError, ReentrancyGuardError, @@ -47,6 +48,8 @@ pub enum PSP22Error { PermitInvalidSignature, /// Returned if permit deadline is expired PermitExpired, + /// Returned if permit nonce is invalid + NoncesError(NoncesError), } impl From for PSP22Error { @@ -144,3 +147,9 @@ impl From for PSP22TokenTimelockError { PSP22TokenTimelockError::PSP22Error(guard.into()) } } + +impl From for PSP22Error { + fn from(error: NoncesError) -> Self { + PSP22Error::NoncesError(error) + } +} diff --git a/contracts/src/traits/governance/governor/mod.rs b/contracts/src/traits/governance/governor/mod.rs index a6c04bfb2..85958abce 100644 --- a/contracts/src/traits/governance/governor/mod.rs +++ b/contracts/src/traits/governance/governor/mod.rs @@ -29,7 +29,7 @@ pub use crate::traits::{ Transaction, VoteType, }, - types::SignatureType, + types::Signature, }; use ink::prelude::vec::Vec; use openbrush::traits::{ @@ -137,7 +137,7 @@ pub trait Governor { proposal_id: ProposalId, support: VoteType, reason: String, - signature: SignatureType, + signature: Signature, ) -> Result; /// Casts a vote with signature and parameters for a proposal from a message sender. Returns the number of votes already casted for the proposal by the sender @@ -147,7 +147,7 @@ pub trait Governor { proposal_id: ProposalId, support: VoteType, reason: String, - signature: SignatureType, + signature: Signature, params: Vec, ) -> Result; diff --git a/contracts/src/traits/governance/mod.rs b/contracts/src/traits/governance/mod.rs index d07bdd0a1..4f639a536 100644 --- a/contracts/src/traits/governance/mod.rs +++ b/contracts/src/traits/governance/mod.rs @@ -43,6 +43,8 @@ pub mod utils { pub mod votes; } +pub use openbrush::utils::crypto::Signature; + pub type ProposalId = [u8; 32]; pub type HashType = [u8; 32]; pub type Selector = [u8; 4]; diff --git a/contracts/src/traits/governance/utils/votes.rs b/contracts/src/traits/governance/utils/votes.rs index 78e2c5de9..ed3ba6ebf 100644 --- a/contracts/src/traits/governance/utils/votes.rs +++ b/contracts/src/traits/governance/utils/votes.rs @@ -22,7 +22,7 @@ pub use crate::traits::{ errors::GovernanceError, - types::SignatureType, + types::Signature, }; use openbrush::traits::{ AccountId, @@ -59,9 +59,9 @@ pub trait Votes { &mut self, signer: AccountId, delegatee: AccountId, - nonce: u128, + nonce: u64, expiry: Timestamp, - signature: SignatureType, + signature: Signature, ) -> Result<(), GovernanceError>; } diff --git a/contracts/src/traits/mod.rs b/contracts/src/traits/mod.rs index d6716b00b..81884a985 100644 --- a/contracts/src/traits/mod.rs +++ b/contracts/src/traits/mod.rs @@ -25,6 +25,7 @@ pub mod diamond; pub mod errors; pub mod flashloan; pub mod governance; +pub mod nonces; pub mod ownable; pub mod pausable; pub mod payment_splitter; @@ -33,6 +34,5 @@ pub mod psp22; pub mod psp34; pub mod psp37; pub mod upgradeable; -pub mod utils; pub mod types; diff --git a/contracts/src/traits/errors/crypto.rs b/contracts/src/traits/nonces/mod.rs similarity index 78% rename from contracts/src/traits/errors/crypto.rs rename to contracts/src/traits/nonces/mod.rs index e75370064..53c180e9d 100644 --- a/contracts/src/traits/errors/crypto.rs +++ b/contracts/src/traits/nonces/mod.rs @@ -20,12 +20,13 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use openbrush::traits::String; +pub use crate::traits::errors::NoncesError; +use openbrush::traits::AccountId; -#[derive(scale::Decode, scale::Encode, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] -pub enum CryptoError { - EcdsaRecoverFailed, - EcdsaToEthAddressFailed, - Other(String), +/// Provides tracking nonces for addresses. Nonces will only increment. +#[openbrush::trait_definition] +pub trait Nonces { + /// Returns the nonce of `account`. + #[ink(message)] + fn nonces(&self, account: AccountId) -> u64; } diff --git a/contracts/src/traits/psp22/extensions/permit.rs b/contracts/src/traits/psp22/extensions/permit.rs index e167931f6..d3c603d3f 100644 --- a/contracts/src/traits/psp22/extensions/permit.rs +++ b/contracts/src/traits/psp22/extensions/permit.rs @@ -22,11 +22,11 @@ /// Extension of [`PSP22`] that allows create `amount` tokens /// and assigns them to `account`, increasing the total supply pub use crate::traits::errors::PSP22Error; -pub use openbrush::traits::Signature; use openbrush::traits::{ AccountId, Balance, }; +pub use openbrush::utils::crypto::Signature; #[openbrush::wrapper] pub type PSP22PermitRef = dyn PSP22Permit; @@ -46,12 +46,6 @@ pub trait PSP22Permit { signature: Signature, ) -> Result<(), PSP22Error>; - /// Returns the current permit nonce for `owner`. This value must be - /// - /// This value must be included whenever a signature is generated for - #[ink(message)] - fn nonces(&self, owner: AccountId) -> u64; - #[ink(message)] fn domain_separator(&mut self) -> [u8; 32]; } diff --git a/contracts/src/traits/psp22/extensions/votes.rs b/contracts/src/traits/psp22/extensions/votes.rs index 4115cd111..acaedfd25 100644 --- a/contracts/src/traits/psp22/extensions/votes.rs +++ b/contracts/src/traits/psp22/extensions/votes.rs @@ -20,14 +20,12 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use crate::{ - traits::{ - errors::GovernanceError, - governance::utils::votes::*, - }, - utils::checkpoint::Checkpoint, +use crate::traits::{ + errors::GovernanceError, + governance::utils::votes::*, }; use openbrush::traits::AccountId; +pub use openbrush::utils::checkpoints::Checkpoint; /// Extension of ERC20 to support Compound-like voting and delegation. /// diff --git a/contracts/src/traits/types.rs b/contracts/src/traits/types.rs index 1ded80e2e..f33e58206 100644 --- a/contracts/src/traits/types.rs +++ b/contracts/src/traits/types.rs @@ -42,4 +42,4 @@ impl Default for Id { } } -pub type SignatureType = [u8; 65]; +pub use openbrush::utils::crypto::Signature; diff --git a/contracts/src/traits/utils/mod.rs b/contracts/src/traits/utils/mod.rs deleted file mode 100644 index 6da806eaa..000000000 --- a/contracts/src/traits/utils/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod nonces; diff --git a/contracts/src/traits/utils/nonces.rs b/contracts/src/traits/utils/nonces.rs deleted file mode 100644 index 1d234fd75..000000000 --- a/contracts/src/traits/utils/nonces.rs +++ /dev/null @@ -1,9 +0,0 @@ -use openbrush::traits::AccountId; - -/// Provides tracking nonces for addresses. Nonces will only increment. -#[openbrush::trait_definition] -pub trait Nonces { - /// Returns the nonce of `account`. - #[ink(message)] - fn nonces(&self, account: AccountId) -> u128; -} diff --git a/contracts/src/utils/crypto/mod.rs b/contracts/src/utils/crypto/mod.rs deleted file mode 100644 index 010bf37c0..000000000 --- a/contracts/src/utils/crypto/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2023 Brushfam -// Copyright (c) 2012-2022 Supercolony -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the"Software"), -// to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -use crate::traits::{ - errors::CryptoError, - types::SignatureType, -}; -use ink::primitives::AccountId; - -/// Verifies the signature of a message hash with the account -pub fn verify_signature( - message_hash: &[u8; 32], - account: &AccountId, - signature: &SignatureType, -) -> Result { - let mut output = [0u8; 33]; - - ink::env::ecdsa_recover(&signature, message_hash, &mut output).map_err(|_| CryptoError::EcdsaRecoverFailed)?; - - let recovered_account = pub_key_to_ss58(&output)?; - - if recovered_account != account.clone() { - return Ok(false) - } - - Ok(true) -} - -/// Hashes a message -pub fn hash_message(message: &[u8]) -> Result<[u8; 32], CryptoError> { - let mut output = [0u8; 32]; - - ink::env::hash_bytes::(message, &mut output); - - Ok(output) -} - -/// Converts a public key to SS58 -pub fn pub_key_to_ss58(pub_key: &[u8; 33]) -> Result { - hash_message(pub_key).map(|hash| AccountId::from(hash)) -} - -/// Converts a public key to an Ethereum address -pub fn pub_key_to_eth_address(pub_key: &[u8; 33]) -> Result<[u8; 20], CryptoError> { - let mut output = [0u8; 20]; - - ink::env::ecdsa_to_eth_address(pub_key, &mut output).map_err(|_| CryptoError::EcdsaToEthAddressFailed)?; - - Ok(output) -} diff --git a/contracts/src/utils/mod.rs b/contracts/src/utils/mod.rs index 71eb1ff64..2fe214417 100644 --- a/contracts/src/utils/mod.rs +++ b/contracts/src/utils/mod.rs @@ -20,9 +20,5 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#[cfg(feature = "utils")] -pub mod checkpoint; -#[cfg(feature = "utils")] -pub mod crypto; -#[cfg(feature = "utils")] +#[cfg(feature = "nonces")] pub mod nonces; diff --git a/contracts/src/utils/nonces/mod.rs b/contracts/src/utils/nonces/mod.rs index b28400857..0524e3301 100644 --- a/contracts/src/utils/nonces/mod.rs +++ b/contracts/src/utils/nonces/mod.rs @@ -20,52 +20,6 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -pub use crate::{ - traits::{ - errors::NoncesError, - utils::nonces::*, - }, - utils::nonces, -}; -use openbrush::{ - storage::Mapping, - traits::{ - AccountId, - Storage, - }, -}; +pub mod nonces; -#[derive(Default, Debug)] -#[openbrush::storage_item] -pub struct Data { - pub nonces: Mapping, -} - -/// Provides tracking nonces for addresses. Nonces will only increment. -pub trait NoncesImpl: Storage { - /// Returns the nonce of `account`. - fn nonces(&self, account: &AccountId) -> u128 { - self.data().nonces.get(account).unwrap_or_default() - } - - /// Returns the next nonce of `account`, and increments the nonce. - fn _use_nonce(&mut self, account: &AccountId) -> Result { - let nonce = self.nonces(account); - self.data() - .nonces - .insert(account, &(nonce.checked_add(1).ok_or(NoncesError::NonceOverflow)?)); - Ok(nonce) - } - - /// Returns the next nonce of `account`, and increments the nonce if `nonce` matches the current nonce. - fn _use_checked_nonce(&mut self, account: &AccountId, nonce: u128) -> Result { - let current_nonce = self.nonces(&account); - if nonce != current_nonce { - return Err(NoncesError::InvalidAccountNonce(account.clone(), current_nonce)) - } - self.data() - .nonces - .insert(account, &(nonce.checked_add(1).ok_or(NoncesError::NonceOverflow)?)); - Ok(nonce) - } -} +pub use nonces::*; diff --git a/contracts/src/utils/nonces/nonces.rs b/contracts/src/utils/nonces/nonces.rs new file mode 100644 index 000000000..67a03a350 --- /dev/null +++ b/contracts/src/utils/nonces/nonces.rs @@ -0,0 +1,68 @@ +// Copyright (c) 2023 Brushfam +// Copyright (c) 2012-2022 Supercolony +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the"Software"), +// to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +pub use crate::{ + nonces, + traits::nonces::*, +}; +use openbrush::{ + storage::Mapping, + traits::{ + AccountId, + Storage, + }, +}; + +#[derive(Default, Debug)] +#[openbrush::storage_item] +pub struct Data { + pub nonces: Mapping, +} + +/// Provides tracking nonces for addresses. Nonces will only increment. +pub trait NoncesImpl: Storage { + /// Returns the nonce of `account`. + fn nonces(&self, account: &AccountId) -> u64 { + self.data().nonces.get(account).unwrap_or_default() + } + + /// Returns the next nonce of `account`, and increments the nonce. + fn _use_nonce(&mut self, account: &AccountId) -> Result { + let nonce = self.nonces(account); + self.data() + .nonces + .insert(account, &(nonce.checked_add(1).ok_or(NoncesError::NonceOverflow)?)); + Ok(nonce) + } + + /// Returns the next nonce of `account`, and increments the nonce if `nonce` matches the current nonce. + fn _use_checked_nonce(&mut self, account: &AccountId, nonce: u64) -> Result { + let current_nonce = self.nonces(&account); + if nonce != current_nonce { + return Err(NoncesError::InvalidAccountNonce(account.clone(), current_nonce)) + } + self.data() + .nonces + .insert(account, &(nonce.checked_add(1).ok_or(NoncesError::NonceOverflow)?)); + Ok(nonce) + } +} diff --git a/examples/governance/governor/Cargo.toml b/examples/governance/governor/Cargo.toml index 192b5b136..732345226 100755 --- a/examples/governance/governor/Cargo.toml +++ b/examples/governance/governor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "my_governor" -version= "4.0.0-beta" +version= "4.0.0-beta.1" authors = ["Brushfam "] edition = "2021" @@ -11,7 +11,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } # These dependencies -openbrush = { path = "../../..", default-features = false, features = ["governance", "utils"] } +openbrush = { path = "../../..", default-features = false, features = ["governance"] } [dev-dependencies] ink_e2e = "4.2.1" diff --git a/examples/psp22_extensions/permit/lib.rs b/examples/psp22_extensions/permit/lib.rs index bfa2ae994..bd036af70 100644 --- a/examples/psp22_extensions/permit/lib.rs +++ b/examples/psp22_extensions/permit/lib.rs @@ -2,7 +2,7 @@ pub use crate::my_psp22_permit::*; -#[openbrush::implementation(PSP22, PSP22Permit)] +#[openbrush::implementation(PSP22, PSP22Permit, Nonces)] #[openbrush::contract] pub mod my_psp22_permit { use openbrush::traits::Storage; @@ -13,6 +13,8 @@ pub mod my_psp22_permit { #[storage_field] psp22: psp22::Data, #[storage_field] + nonces: nonces::Data, + #[storage_field] psp22_permit: psp22::extensions::permit::Data, } diff --git a/examples/psp22_extensions/votes/Cargo.toml b/examples/psp22_extensions/votes/Cargo.toml index 4789b3c3a..3e4d2649c 100755 --- a/examples/psp22_extensions/votes/Cargo.toml +++ b/examples/psp22_extensions/votes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "my_psp22_votes" -version= "4.0.0-beta" +version= "4.0.0-beta.1" authors = ["Brushfam "] edition = "2021" diff --git a/examples/utils/nonces/Cargo.toml b/examples/utils/nonces/Cargo.toml index 776402a38..c6c160ab7 100755 --- a/examples/utils/nonces/Cargo.toml +++ b/examples/utils/nonces/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nonces" -version= "4.0.0-beta" +version= "4.0.0-beta.1" authors = ["Brushfam "] edition = "2021" diff --git a/lang/Cargo.toml b/lang/Cargo.toml index a144542e4..0c54cbd33 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -37,4 +37,6 @@ std = [ "ink/std", "scale/std", "scale-info/std", -] \ No newline at end of file +] +checkpoints = [] +crypto = [] \ No newline at end of file diff --git a/lang/codegen/src/implementations.rs b/lang/codegen/src/implementations.rs index 9d5c5ed87..05aa3b9d5 100644 --- a/lang/codegen/src/implementations.rs +++ b/lang/codegen/src/implementations.rs @@ -47,7 +47,7 @@ impl<'a> ImplArgs<'a> { fn signature_import(&mut self) { let sig_import = syn::parse2::(quote!( - use openbrush::traits::Signature; + use openbrush::utils::crypto::Signature; )) .expect("Should parse"); self.imports.insert("Signature", sig_import); @@ -282,18 +282,9 @@ pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { ) -> Result<(), PSP22Error> { permit::InternalImpl::_permit(self, owner, spender, amount, deadline, signature) } - - fn _nonces(&self, owner: AccountId) -> u64 { - permit::InternalImpl::_nonces(self, owner) - } - fn _domain_separator(&mut self) -> [u8; 32] { permit::InternalImpl::_domain_separator(self) } - - fn _use_nonce(&mut self, owner: AccountId) -> u64 { - permit::InternalImpl::_use_nonce(self, owner) - } } )) .expect("Should parse"); @@ -317,11 +308,6 @@ pub(crate) fn impl_psp22_permit(impl_args: &mut ImplArgs) { permit::PSP22PermitImpl::permit(self, owner, spender, value, deadline, signature) } - #[ink(message)] - fn nonces(&self, owner: AccountId) -> u64 { - permit::PSP22PermitImpl::nonces(self, owner) - } - #[ink(message)] fn domain_separator(&mut self) -> [u8; 32] { permit::PSP22PermitImpl::domain_separator(self) @@ -725,9 +711,9 @@ pub(crate) fn impl_psp22_votes(impl_args: &mut ImplArgs) { &mut self, signer: AccountId, delegatee: AccountId, - nonce: u128, + nonce: u64, expiry: Timestamp, - signature: SignatureType, + signature: Signature, ) -> Result<(), GovernanceError> { VotesImpl::delegate_by_signature(self, signer, delegatee, nonce, expiry, signature) } @@ -3117,7 +3103,7 @@ pub(crate) fn impl_governor(impl_args: &mut ImplArgs) { proposal_id: ProposalId, support: VoteType, reason: String, - signature: SignatureType, + signature: Signature, ) -> Result { GovernorImpl::cast_vote_with_signature(self, proposal_id, support, reason, signature) } @@ -3128,7 +3114,7 @@ pub(crate) fn impl_governor(impl_args: &mut ImplArgs) { proposal_id: ProposalId, support: VoteType, reason: String, - signature: SignatureType, + signature: Signature, params: Vec, ) -> Result { GovernorImpl::cast_vote_with_signature_and_params(self, proposal_id, support, reason, signature, params) @@ -3166,7 +3152,7 @@ pub(crate) fn impl_nonces(impl_args: &mut ImplArgs) { let nonces = syn::parse2::(quote!( impl Nonces for #storage_struct_name { #[ink(message)] - fn nonces(&self, account: AccountId) -> u128 { + fn nonces(&self, account: AccountId) -> u64 { NoncesImpl::nonces(self, &account) } } @@ -3174,7 +3160,7 @@ pub(crate) fn impl_nonces(impl_args: &mut ImplArgs) { .expect("Should parse"); let import = syn::parse2::(quote!( - use openbrush::contracts::utils::nonces::*; + use openbrush::contracts::nonces::*; )) .expect("Should parse"); impl_args.imports.insert("Nonces", import); diff --git a/lang/src/macros.rs b/lang/src/macros.rs index 5d3a86bb2..ccdef93ce 100644 --- a/lang/src/macros.rs +++ b/lang/src/macros.rs @@ -25,14 +25,14 @@ #[macro_export] macro_rules! storage_unique_key { ($struct:ident) => {{ - $crate::utils::ConstHasher::hash($crate::utils::const_format::concatcp!( + $crate::traits::ConstHasher::hash($crate::traits::const_format::concatcp!( ::core::module_path!(), "::", ::core::stringify!($struct) )) }}; ($struct:literal, $field:literal) => {{ - $crate::utils::ConstHasher::hash($crate::utils::const_format::concatcp!( + $crate::traits::ConstHasher::hash($crate::traits::const_format::concatcp!( ::core::module_path!(), "::", $struct, @@ -44,7 +44,7 @@ macro_rules! storage_unique_key { #[test] fn correct_storage_key() { - use crate::utils::ConstHasher; + use crate::traits::ConstHasher; use ink::storage::traits::StorageKey; mod contracts { diff --git a/lang/src/traits.rs b/lang/src/traits.rs index f03f5d50c..f182fada7 100644 --- a/lang/src/traits.rs +++ b/lang/src/traits.rs @@ -19,18 +19,18 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// use crate::utils::blake2b_256; -use crate::utils::hash_blake2b256; use ::ink::env::{ DefaultEnvironment, Environment, }; +pub use const_format; use core::mem::ManuallyDrop; use ink::storage::traits::{ Storable, StorageKey, }; pub use openbrush_lang_macro::Storage; +use xxhash_rust::const_xxh32::xxh32; /// Aliases for types of the default environment pub type AccountId = ::AccountId; @@ -126,27 +126,13 @@ pub trait Flush: Storable + Sized + StorageKey { impl Flush for T {} -#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum Signature { - ECDSA([u8; 65]), -} - -impl Signature { - #[allow(unreachable_patterns)] - pub fn verify(&self, message: &[u8], pub_key: &AccountId) -> bool { - match self { - Signature::ECDSA(sig) => { - let mut output: [u8; 33] = [0; 33]; - let message_hash = hash_blake2b256(message); - - let result = ink::env::ecdsa_recover(sig, &message_hash, &mut output); +/// The value 0 is a valid seed. +const XXH32_SEED: u32 = 0; - let address = hash_blake2b256(&output); +pub struct ConstHasher; - return result.is_ok() && address == pub_key.as_ref() - } - _ => false, - } +impl ConstHasher { + pub const fn hash(str: &str) -> u32 { + xxh32(str.as_bytes(), XXH32_SEED) } } diff --git a/lang/src/utils.rs b/lang/src/utils.rs deleted file mode 100644 index 005fb858f..000000000 --- a/lang/src/utils.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2012-2022 Supercolony -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the"Software"), -// to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pub use const_format; -use ink::env::hash; -pub use xxhash_rust; - -use xxhash_rust::const_xxh32::xxh32; - -/// The value 0 is a valid seed. -const XXH32_SEED: u32 = 0; - -pub struct ConstHasher; - -impl ConstHasher { - pub const fn hash(str: &str) -> u32 { - xxh32(str.as_bytes(), XXH32_SEED) - } -} - -pub fn hash_blake2b256(input: &[u8]) -> [u8; 32] { - let mut output = ::Type::default(); - ink::env::hash_bytes::(input, &mut output); - output -} diff --git a/contracts/src/utils/checkpoint/mod.rs b/lang/src/utils/checkpoints.rs similarity index 97% rename from contracts/src/utils/checkpoint/mod.rs rename to lang/src/utils/checkpoints.rs index 6951e1883..389197fe5 100644 --- a/contracts/src/utils/checkpoint/mod.rs +++ b/lang/src/utils/checkpoints.rs @@ -20,7 +20,6 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -pub use crate::traits::errors::CheckpointsError; use ink::prelude::vec::Vec; /// Struct, for checkpointing values as they change at different points in @@ -41,6 +40,13 @@ pub struct Checkpoint { pub value: u128, } +#[derive(scale::Decode, scale::Encode, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] +pub enum CheckpointsError { + UnorderedInsertion, +} + +/// Counting sqrt using Newton's method. fn sqrt(x: u64) -> u64 { let mut z = (x + 1) / 2; let mut y = x; diff --git a/lang/src/utils/crypto.rs b/lang/src/utils/crypto.rs new file mode 100644 index 000000000..9cd8c2b62 --- /dev/null +++ b/lang/src/utils/crypto.rs @@ -0,0 +1,98 @@ +// Copyright (c) 2012-2022 Supercolony +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the"Software"), +// to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +use crate::traits::String; +use ink::env::hash; + +use crate::traits::AccountId; + +/// Hashing function for bytes +pub fn hash_blake2b256(input: &[u8]) -> [u8; 32] { + let mut output = ::Type::default(); + ink::env::hash_bytes::(input, &mut output); + output +} + +/// Converts a compressed public key to SS58 format +pub fn pub_key_to_ss58(pub_key: &[u8; 33]) -> AccountId { + AccountId::from(hash_blake2b256(pub_key)) +} + +/// Converts a public key to an Ethereum address +pub fn pub_key_to_eth_address(pub_key: &[u8; 33]) -> Result<[u8; 20], CryptoError> { + let mut output = [0u8; 20]; + + ink::env::ecdsa_to_eth_address(pub_key, &mut output).map_err(|_| CryptoError::EcdsaToEthAddressFailed)?; + + Ok(output) +} + +/// Enum to represent different signature types +/// +/// # Support of signatures +/// +/// - `ECDSA` - ECDSA signature with 65 bytes +#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum Signature { + ECDSA([u8; 65]), +} + +impl Signature { + /// Verifies different type of signatures + /// + /// # Arguments + /// + /// - `message` - The message to verify + /// - `pub_key` - The public key to verify the message with + /// + /// # Returns + /// + /// - `true` if the signature is valid + /// - `false` if the signature is invalid + /// + /// # Supported signatures + /// + /// - `ECDSA` + #[allow(unreachable_patterns)] + pub fn verify(&self, message: &[u8], address: &AccountId) -> bool { + match self { + // Verifies ECDSA signature + Signature::ECDSA(sig) => { + let mut output: [u8; 33] = [0; 33]; + let message_hash = hash_blake2b256(message); + + let result = ink::env::ecdsa_recover(sig, &message_hash, &mut output); + + return result.is_ok() && pub_key_to_ss58(&output) == address.clone() + } + _ => false, + } + } +} + +#[derive(scale::Decode, scale::Encode, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] +pub enum CryptoError { + EcdsaRecoverFailed, + EcdsaToEthAddressFailed, + Other(String), +} diff --git a/contracts/src/traits/errors/checkpoints.rs b/lang/src/utils/mod.rs similarity index 86% rename from contracts/src/traits/errors/checkpoints.rs rename to lang/src/utils/mod.rs index a1d904b2e..46af1b0a6 100644 --- a/contracts/src/traits/errors/checkpoints.rs +++ b/lang/src/utils/mod.rs @@ -20,8 +20,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum CheckpointsError { - UnorderedInsertion, -} +#[cfg(feature = "checkpoints")] +pub mod checkpoints; +#[cfg(feature = "crypto")] +pub mod crypto; diff --git a/package.json b/package.json index 33e086f69..f2cd64897 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbrush-contracts", - "version": "4.0.0-beta", + "version": "4.0.0-beta.1", "private": true, "dependencies": { "@727-ventures/typechain-compiler": "^1.1.4", From 4e3db4f83664a16b3bde63d010f405e9f5385bf2 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 29 Aug 2023 15:49:19 +0300 Subject: [PATCH 15/17] fix e2e tests --- examples/psp22_extensions/permit/tests/e2e.rs | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/examples/psp22_extensions/permit/tests/e2e.rs b/examples/psp22_extensions/permit/tests/e2e.rs index a732baf5a..03b2673a6 100644 --- a/examples/psp22_extensions/permit/tests/e2e.rs +++ b/examples/psp22_extensions/permit/tests/e2e.rs @@ -2,21 +2,47 @@ extern crate my_psp22_permit; -use ink::env::hash::{Blake2x256, HashOutput}; +use ink::env::hash::{ + Blake2x256, + HashOutput, +}; #[rustfmt::skip] use ink_e2e::build_message; -use openbrush::contracts::psp22::{extensions::permit::psp22permit_external::PSP22Permit, psp22_external::PSP22}; +use openbrush::contracts::{ + nonces::nonces_external::Nonces, + psp22::{ + extensions::permit::psp22permit_external::PSP22Permit, + psp22_external::PSP22, + }, +}; #[rustfmt::skip] use crate::my_psp22_permit::*; // use openbrush::contracts::psp22::extensions::permit::PERMIT_TYPE_HASH; use openbrush::{ contracts::psp22::extensions::permit::PermitMessage, - traits::{AccountId, Balance, Signature}, - utils::hash_blake2b256, + traits::{ + AccountId, + Balance, + }, + utils::crypto::{ + hash_blake2b256, + Signature, + }, }; use scale::Encode; -use secp256k1::{ecdsa::RecoverableSignature, Message, PublicKey, SecretKey, SECP256K1}; -use test_helpers::{address_of, balance_of, method_call, method_call_dry_run}; +use secp256k1::{ + ecdsa::RecoverableSignature, + Message, + PublicKey, + SecretKey, + SECP256K1, +}; +use test_helpers::{ + address_of, + balance_of, + method_call, + method_call_dry_run, +}; type E2EResult = Result>; From eabd13209f06136f43b865d943faac7e484eb8f4 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 29 Aug 2023 16:21:38 +0300 Subject: [PATCH 16/17] fix lending build --- example_project_structure/contracts/lending/lib.rs | 12 +++++------- lang/src/traits.rs | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/example_project_structure/contracts/lending/lib.rs b/example_project_structure/contracts/lending/lib.rs index c5f6c1505..6040fd8ab 100644 --- a/example_project_structure/contracts/lending/lib.rs +++ b/example_project_structure/contracts/lending/lib.rs @@ -40,13 +40,11 @@ pub mod my_lending { use ink::ToAccountId; use lending_project::impls::lending::*; use loan_contract::loan::LoanContractRef; - use openbrush::{ - traits::{ - DefaultEnv, - Storage, - String, - }, - utils::xxhash_rust::const_xxh32::xxh32, + use openbrush::traits::{ + xxh32, + DefaultEnv, + Storage, + String, }; use scale::Encode; use shares_contract::shares::SharesContractRef; diff --git a/lang/src/traits.rs b/lang/src/traits.rs index f182fada7..ab38b9aa2 100644 --- a/lang/src/traits.rs +++ b/lang/src/traits.rs @@ -30,7 +30,7 @@ use ink::storage::traits::{ StorageKey, }; pub use openbrush_lang_macro::Storage; -use xxhash_rust::const_xxh32::xxh32; +pub use xxhash_rust::const_xxh32::xxh32; /// Aliases for types of the default environment pub type AccountId = ::AccountId; From ec8408238e279819c3f2db7ded7dca7a078aeef2 Mon Sep 17 00:00:00 2001 From: Artemka374 Date: Tue, 29 Aug 2023 17:35:09 +0300 Subject: [PATCH 17/17] fix nonces build --- examples/utils/nonces/Cargo.toml | 2 +- examples/utils/nonces/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/utils/nonces/Cargo.toml b/examples/utils/nonces/Cargo.toml index c6c160ab7..127d1b3f1 100755 --- a/examples/utils/nonces/Cargo.toml +++ b/examples/utils/nonces/Cargo.toml @@ -11,7 +11,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } # These dependencies -openbrush = { path = "../../..", default-features = false, features = ["utils"] } +openbrush = { path = "../../..", default-features = false, features = ["nonces"] } [dev-dependencies] ink_e2e = "4.2.1" diff --git a/examples/utils/nonces/lib.rs b/examples/utils/nonces/lib.rs index ebb0398e9..dffea725b 100755 --- a/examples/utils/nonces/lib.rs +++ b/examples/utils/nonces/lib.rs @@ -2,7 +2,7 @@ #[openbrush::implementation(Nonces)] #[openbrush::contract] -pub mod my_nonces { +pub mod nonces { use openbrush::traits::Storage; #[ink(storage)] @@ -19,12 +19,12 @@ pub mod my_nonces { } #[ink(message)] - pub fn use_nonce(&mut self, account: AccountId) -> Result { + pub fn use_nonce(&mut self, account: AccountId) -> Result { NoncesImpl::_use_nonce(self, &account) } #[ink(message)] - pub fn use_checked_nonce(&mut self, account: AccountId, nonce: u128) -> Result { + pub fn use_checked_nonce(&mut self, account: AccountId, nonce: u64) -> Result { NoncesImpl::_use_checked_nonce(self, &account, nonce) } }