difftreelog
feat add tests
in: master
12 files changed
node/cli/src/chain_spec.rsdiffbeforeafterboth--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -267,7 +267,7 @@
pub fn development_config() -> DefaultChainSpec {
let mut properties = Map::new();
properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());
- properties.insert("tokenDecimals".into(), 18.into());
+ properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());
properties.insert(
"ss58Format".into(),
default_runtime::SS58Prefix::get().into(),
@@ -341,7 +341,7 @@
pub fn local_testnet_config() -> DefaultChainSpec {
let mut properties = Map::new();
properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());
- properties.insert("tokenDecimals".into(), 18.into());
+ properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());
properties.insert(
"ss58Format".into(),
default_runtime::SS58Prefix::get().into(),
pallets/balances-adapter/src/erc.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/erc.rs
+++ b/pallets/balances-adapter/src/erc.rs
@@ -38,14 +38,14 @@
#[solidity_interface(name = ERC20, events(ERC20Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x942e8b22)]
impl<T: Config> NativeFungibleHandle<T> {
- fn allowance(&self, owner: Address, spender: Address) -> Result<U256> {
+ fn allowance(&self, _owner: Address, _spender: Address) -> Result<U256> {
Ok(U256::zero())
}
// #[weight(<SelfWeightOf<T>>::approve())]
- fn approve(&mut self, caller: Caller, spender: Address, amount: U256) -> Result<bool> {
+ fn approve(&mut self, _caller: Caller, _spender: Address, _amount: U256) -> Result<bool> {
// self.consume_store_reads(1)?;
- Err("Approve not supported now".into())
+ Err("Approve not supported".into())
}
fn balance_of(&self, owner: Address) -> Result<U256> {
@@ -106,7 +106,7 @@
let to = T::CrossAccountId::from_eth(to);
let amount = amount.try_into().map_err(|_| "amount overflow")?;
- if from != to {
+ if from != caller {
return Err("no permission".into());
}
// let budget = self
@@ -171,7 +171,7 @@
let to = to.into_sub_cross_account::<T>()?;
let amount = amount.try_into().map_err(|_| "amount overflow")?;
- if from != to {
+ if from != caller {
return Err("no permission".into());
}
pallets/common/src/dispatch.rsdiffbeforeafterboth1//! Module with interfaces for dispatching collections.23use frame_support::{4 dispatch::{5 DispatchResultWithPostInfo, PostDispatchInfo, Weight, DispatchErrorWithPostInfo,6 DispatchResult,7 },8 dispatch::Pays,9 traits::Get,10};11use sp_runtime::DispatchError;12use up_data_structs::{CollectionId, CreateCollectionData, CollectionFlags};1314use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle};1516// TODO: move to benchmarking17/// Price of [`dispatch_tx`] call with noop `call` argument18pub fn dispatch_weight<T: Config>() -> Weight {19 // Read collection20 <T as frame_system::Config>::DbWeight::get().reads(1)21 // Dynamic dispatch?22 + Weight::from_parts(6_000_000, 0)23 // submit_logs is measured as part of collection pallets24}2526/// Helper function to implement substrate calls for common collection methods.27///28/// * `collection` - The collection on which to call the method.29/// * `call` - The function in which to call the corresponding method from [`CommonCollectionOperations`].30pub fn dispatch_tx<31 T: Config,32 C: FnOnce(&dyn CommonCollectionOperations<T>) -> DispatchResultWithPostInfo,33>(34 collection: CollectionId,35 call: C,36) -> DispatchResultWithPostInfo {37 let dispatched = T::CollectionDispatch::dispatch(collection)38 .and_then(|dispatched| {39 dispatched.check_is_internal()?;40 Ok(dispatched)41 })42 .map_err(|error| DispatchErrorWithPostInfo {43 post_info: PostDispatchInfo {44 actual_weight: Some(dispatch_weight::<T>()),45 pays_fee: Pays::Yes,46 },47 error,48 })?;49 let mut result = call(dispatched.as_dyn());50 match &mut result {51 Ok(PostDispatchInfo {52 actual_weight: Some(weight),53 ..54 })55 | Err(DispatchErrorWithPostInfo {56 post_info: PostDispatchInfo {57 actual_weight: Some(weight),58 ..59 },60 ..61 }) => *weight += dispatch_weight::<T>(),62 _ => {}63 }64 result65}6667/// Interface for working with different collections through the dispatcher.68pub trait CollectionDispatch<T: Config> {69 fn check_is_internal(&self) -> DispatchResult;7071 /// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).72 ///73 /// * `sender` - The user who will become the owner of the collection.74 /// * `data` - Description of the created collection.75 fn create(76 sender: T::CrossAccountId,77 payer: T::CrossAccountId,78 data: CreateCollectionData<T::AccountId>,79 flags: CollectionFlags,80 ) -> Result<CollectionId, DispatchError>;8182 /// Delete the collection.83 ///84 /// * `sender` - The owner of the collection.85 /// * `handle` - Collection handle.86 fn destroy(sender: T::CrossAccountId, handle: CollectionHandle<T>) -> DispatchResult;8788 /// Get a specialized collection from the handle.89 ///90 /// * `handle` - Collection handle.91 fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError>92 where93 Self: Sized;9495 /// Get the implementation of [`CommonCollectionOperations`].96 fn as_dyn(&self) -> &dyn CommonCollectionOperations<T>;97}1//! Module with interfaces for dispatching collections.23use frame_support::{4 dispatch::{5 DispatchResultWithPostInfo, PostDispatchInfo, Weight, DispatchErrorWithPostInfo,6 DispatchResult,7 },8 dispatch::Pays,9 traits::Get,10};11use sp_runtime::DispatchError;12use up_data_structs::{CollectionId, CreateCollectionData, CollectionFlags};1314use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle};1516// TODO: move to benchmarking17/// Price of [`dispatch_tx`] call with noop `call` argument18pub fn dispatch_weight<T: Config>() -> Weight {19 // Read collection20 <T as frame_system::Config>::DbWeight::get().reads(1)21 // Dynamic dispatch?22 + Weight::from_parts(6_000_000, 0)23 // submit_logs is measured as part of collection pallets24}2526/// Helper function to implement substrate calls for common collection methods.27///28/// * `collection` - The collection on which to call the method.29/// * `call` - The function in which to call the corresponding method from [`CommonCollectionOperations`].30pub fn dispatch_tx<31 T: Config,32 C: FnOnce(&dyn CommonCollectionOperations<T>) -> DispatchResultWithPostInfo,33>(34 collection: CollectionId,35 call: C,36) -> DispatchResultWithPostInfo {37 let dispatched = T::CollectionDispatch::dispatch(collection)38 .and_then(|dispatched| {39 dispatched.check_is_internal()?;40 Ok(dispatched)41 })42 .map_err(|error| DispatchErrorWithPostInfo {43 post_info: PostDispatchInfo {44 actual_weight: Some(dispatch_weight::<T>()),45 pays_fee: Pays::Yes,46 },47 error,48 })?;49 let mut result = call(dispatched.as_dyn());50 match &mut result {51 Ok(PostDispatchInfo {52 actual_weight: Some(weight),53 ..54 })55 | Err(DispatchErrorWithPostInfo {56 post_info: PostDispatchInfo {57 actual_weight: Some(weight),58 ..59 },60 ..61 }) => *weight += dispatch_weight::<T>(),62 _ => {}63 }64 result65}6667/// Interface for working with different collections through the dispatcher.68pub trait CollectionDispatch<T: Config> {69 fn check_is_internal(&self) -> DispatchResult;7071 /// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).72 ///73 /// * `sender` - The user who will become the owner of the collection.74 /// * `data` - Description of the created collection.75 fn create(76 sender: T::CrossAccountId,77 payer: T::CrossAccountId,78 data: CreateCollectionData<T::AccountId>,79 flags: CollectionFlags,80 ) -> Result<CollectionId, DispatchError>;8182 /// Delete the collection.83 ///84 /// * `sender` - The owner of the collection.85 /// * `handle` - Collection handle.86 fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult;8788 /// Get a specialized collection from the handle.89 ///90 /// * `handle` - Collection handle.91 fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError>92 where93 Self: Sized;9495 /// Get the implementation of [`CommonCollectionOperations`].96 fn as_dyn(&self) -> &dyn CommonCollectionOperations<T>;97}pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -1296,10 +1296,7 @@
sender: T::CrossAccountId,
collection_id: CollectionId,
) -> DispatchResult {
- let collection = <CollectionHandle<T>>::try_get(collection_id)?;
- collection.check_is_internal()?;
-
- T::CollectionDispatch::destroy(sender, collection)?;
+ T::CollectionDispatch::destroy(sender, collection_id)?;
// TODO: basket cleanup should be moved elsewhere
// Maybe runtime dispatch.rs should perform it?
runtime/common/config/pallets/mod.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -24,7 +24,8 @@
weights::CommonWeights,
RelayChainBlockNumberProvider,
},
- Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, RUNTIME_NAME, TOKEN_SYMBOL, Balances,
+ Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, RUNTIME_NAME, TOKEN_SYMBOL, DECIMALS,
+ Balances,
};
use frame_support::traits::{ConstU32, ConstU64, Currency};
use up_common::{
@@ -53,7 +54,7 @@
parameter_types! {
pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
- pub const Decimals: u8 = 32;
+ pub const Decimals: u8 = DECIMALS;
pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
pub Name: String = RUNTIME_NAME.to_string();
pub Symbol: String = TOKEN_SYMBOL.to_string();
runtime/common/dispatch.rsdiffbeforeafterboth--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -99,7 +99,10 @@
Ok(id)
}
- fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
+ fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
+ let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ collection.check_is_internal()?;
+
match collection.mode {
CollectionMode::ReFungible => {
PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -45,6 +45,7 @@
pub const RUNTIME_NAME: &str = "opal";
pub const TOKEN_SYMBOL: &str = "OPL";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -48,6 +48,7 @@
#[cfg(not(feature = "become-sapphire"))]
pub const RUNTIME_NAME: &str = "quartz";
pub const TOKEN_SYMBOL: &str = "QTZ";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -45,6 +45,7 @@
pub const RUNTIME_NAME: &str = "unique";
pub const TOKEN_SYMBOL: &str = "UNQ";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
tests/src/eth/nativeFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nativeFungible.test.ts
+++ b/tests/src/eth/nativeFungible.test.ts
@@ -15,29 +15,157 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import {itEth, usingEthPlaygrounds} from './util';
+import {expect, itEth, usingEthPlaygrounds} from './util';
-describe('NativeFungible: Plain calls', () => {
+describe('NativeFungible: ERC20 calls', () => {
let donor: IKeyringPair;
- let alice: IKeyringPair;
- let owner: IKeyringPair;
before(async function() {
await usingEthPlaygrounds(async (helper, privateKey) => {
donor = await privateKey({url: import.meta.url});
- [alice, owner] = await helper.arrange.createAccounts([30n, 20n], donor);
+ // [alice] = await helper.arrange.createAccounts([30n], donor);
});
});
- itEth.skip('Can perform approve()', async ({helper}) => {
+ itEth('approve()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const spender = helper.eth.createAccount();
- const collection = await helper.ft.mintCollection(alice);
- await collection.mint(alice, 200n, {Ethereum: owner});
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ await expect(contract.methods.approve(spender, 100).call({from: owner})).to.be.rejectedWith('Approve not supported');
+ });
+
+ itEth('balanceOf()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor, 123n);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balance = await contract.methods.balanceOf(owner).call({from: owner});
+ expect(balance).to.be.eq('123000000000000000000');
+ });
+
+ itEth('decimals()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const decimals = await contract.methods.decimals().call({from: owner});
+ expect(decimals).to.be.eq('18');
+ });
+
+ itEth('name()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const name = await contract.methods.name().call({from: owner});
+ expect(name).to.be.eq('opal');
+ });
+
+ itEth('symbol()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const name = await contract.methods.symbol().call({from: owner});
+ expect(name).to.be.eq('OPL');
+ });
+
+ itEth('totalSupply()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const totalSupplyEth = BigInt(await contract.methods.totalSupply().call({from: owner}));
+ const totalSupplySub = await helper.balance.getTotalIssuance();
+ expect(totalSupplyEth).to.be.eq(totalSupplySub);
+ });
+
+ itEth('transfer()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+ await contract.methods.transfer(receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+ });
+ itEth('transferFrom()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.eth.createAccountWithBalance(donor);
const collectionAddress = helper.ethAddress.fromCollectionId(0);
const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
- await contract.methods.approve(spender, 100).send({from: owner});
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+ await contract.methods.transferFrom(owner, receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+ await expect(contract.methods.transferFrom(receiver, receiver, 50).call({from: owner})).to.be.rejectedWith('no permission');
+ });
+});
+
+describe('NativeFungible: ERC20UniqueExtensions calls', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({url: import.meta.url});
+ // [alice] = await helper.arrange.createAccounts([30n], donor);
+ });
+ });
+
+ itEth('transferCross()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+ await contract.methods.transferCross(receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+ });
+
+ itEth('transferFromCross()', async ({helper}) => {
+ const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner.eth);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner.eth);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+ await contract.methods.transferFromCross(owner, receiver, 50).send({from: owner.eth});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner.eth);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+ await expect(contract.methods.transferFromCross(receiver, receiver, 50).call({from: owner.eth})).to.be.rejectedWith('no permission');
});
});
\ No newline at end of file
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -142,7 +142,7 @@
async collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
let abi;
- if (address === '0x17C4e6453cC49aaAAEaCA894E6d9683e00000000' && mode === 'ft') {
+ if (address === '0x17C4e6453cC49aaAAEaCA894E6d9683e00000000') {
abi = nativeFungibleAbi;
} else {
abi ={
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -2400,7 +2400,16 @@
return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
}
- async getLocked(address: TSubstrateAccount): Promise<[{ id: string, amount: bigint, reason: string }]> {
+ /**
+ * Get total issuance
+ * @returns
+ */
+ async getTotalIssuance(): Promise<bigint> {
+ const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));
+ return total.toBigInt();
+ }
+
+ async getLocked(address: TSubstrateAccount): Promise<[{id: string, amount: bigint, reason: string}]> {
const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();
return locks.map((lock: any) => { return {id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}; });
}
@@ -2488,6 +2497,14 @@
}
/**
+ * Get total issuance
+ * @returns
+ */
+ getTotalIssuance(): Promise<bigint> {
+ return this.subBalanceGroup.getTotalIssuance();
+ }
+
+ /**
* Get locked balances
* @param address substrate address
* @returns locked balances with reason via api.query.balances.locks