difftreelog
test fix for new ouroboros behavior
in: master
4 files changed
crates/struct-versioning/src/lib.rsdiffbeforeafterboth1use proc_macro::TokenStream;2use quote::format_ident;3use syn::{4 parse::{Parse, ParseStream},5 Token, LitInt, parse_macro_input, ItemStruct, Error, Fields, Result, Field, Expr,6 parenthesized,7};8use quote::quote;910mod kw {11 syn::custom_keyword!(version);12 syn::custom_keyword!(first_version);13 syn::custom_keyword!(versions);14 syn::custom_keyword!(upper);15}1617struct VersionedAttrs {18 current_version: u32,19 first_version: u32,20 upper: bool,21}2223/// #[versioned(version = 2)]24impl Parse for VersionedAttrs {25 fn parse(input: ParseStream) -> Result<Self> {26 let mut current_version = None::<u32>;27 let mut first_version = None::<u32>;28 let mut upper = false;2930 loop {31 if input.is_empty() {32 break;33 }34 let lookahead = input.lookahead1();35 if lookahead.peek(kw::version) {36 input.parse::<kw::version>()?;37 input.parse::<Token![=]>()?;38 let t = input.parse::<LitInt>()?;39 if current_version.is_some() {40 return Err(Error::new_spanned(t, "version is already set"));41 }42 current_version = Some(t.base10_parse()?)43 } else if lookahead.peek(kw::first_version) {44 input.parse::<kw::first_version>()?;45 input.parse::<Token![=]>()?;46 let t = input.parse::<LitInt>()?;47 if first_version.is_some() {48 return Err(Error::new_spanned(t, "first version is already set"));49 }50 first_version = Some(t.base10_parse()?)51 } else if lookahead.peek(kw::upper) {52 input.parse::<kw::upper>()?;53 upper = true;54 } else {55 return Err(lookahead.error());56 }5758 if input.is_empty() {59 break;60 } else if input.peek(Token![,]) {61 input.parse::<Token![,]>()?;62 continue;63 } else {64 return Err(input.error("unexpected token"));65 }66 }67 let first_version = first_version.unwrap_or(1);68 let current_version = current_version.unwrap_or(first_version);6970 if current_version == 0 || first_version == 0 || first_version > current_version {71 return Err(Error::new(input.span(), "1 <= first_version <= version"));72 }7374 Ok(Self {75 current_version,76 first_version,77 upper,78 })79 }80}8182/// #[version(..3)] - field vas removed in version 3 (i.e it was exist on version 2, but doesn't on version 3)83/// #[version(3..)] - field has appeared in version 384/// #[version(2..4)] - field was on versions 2, 385/// #[version(1..2, upper(old_field + 1))] - when updating struct from old version to new - calculate new field value from passed expression86struct VersionAttr {87 since: u32,88 before: Option<u32>,8990 upper: Option<Expr>,91}92impl VersionAttr {93 fn exists_on(&self, version: u32) -> bool {94 version >= self.since && self.before.map_or(true, |before| version < before)95 }96}97impl Parse for VersionAttr {98 fn parse(input: ParseStream) -> Result<Self> {99 let mut since = None::<u32>;100 let mut before = None::<u32>;101 let lookahead = input.lookahead1();102103 if lookahead.peek(LitInt) {104 let t: LitInt = input.parse()?;105 since = Some(t.base10_parse()?);106 } else if !lookahead.peek(Token![..]) {107 return Err(lookahead.error());108 }109 let range = input.parse::<Token![..]>()?;110 let lookahead = input.lookahead1();111 if lookahead.peek(LitInt) {112 let t: LitInt = input.parse()?;113 before = Some(t.base10_parse()?);114 } else if !input.is_empty() && !lookahead.peek(Token![,]) {115 return Err(lookahead.error());116 }117118 let upper = if input.peek(Token![,]) {119 input.parse::<Token![,]>()?;120 input.parse::<kw::upper>()?;121 let expr;122 parenthesized!(expr in input);123124 Some(Expr::parse(&expr)?)125 } else {126 None127 };128129 if since.is_none() && before.is_none() {130 return Err(Error::new_spanned(131 range,132 "noop range, remove this version attribute",133 ));134 }135 Ok(Self {136 since: since.unwrap_or(1),137 before,138 upper,139 })140 }141}142impl Default for VersionAttr {143 fn default() -> Self {144 Self {145 since: 1,146 before: None,147 upper: None,148 }149 }150}151152/// Generate versioned variants of a struct153///154/// `#[versioned(version = 1[, first_version = 1][, upper][, versions])]`155/// - *version* - current version of a struct156/// - *first_version* - allows to skip generation of structs, which predates first supported version157/// - *upper* - generate From impls, which converts old version of structs to new158/// - *versions* - generate enum, which contains all possible versions of struct159///160/// Each field may have version attribute161/// `#[version([1]..[2][, upper(old)])]`162/// - *1* - version, on which this field is appeared163/// - *2* - version, in which this field was removed164/// (i.e if set to 2, this field was exist on version 1, and no longer exist on version 2)165/// - *upper* - code, which should be executed to transform old value to new/create new value166#[proc_macro_attribute]167pub fn versioned(attr: TokenStream, input: TokenStream) -> TokenStream {168 let attr = parse_macro_input!(attr as VersionedAttrs);169 let input = parse_macro_input!(input as ItemStruct);170171 let fields = match input.fields {172 Fields::Named(named) => named.named,173 _ => {174 return Error::new_spanned(input, "expected named fields")175 .into_compile_error()176 .into()177 }178 };179 let fields = fields180 .iter()181 .map(|field| {182 let version_attr = match field.attrs.iter().find(|a| a.path.is_ident("version")) {183 Some(v) => v.parse_args::<VersionAttr>()?,184 None => return Ok((VersionAttr::default(), field.clone())),185 };186 let mut field = field.clone();187 field.attrs.retain(|a| !a.path.is_ident("version"));188 Ok((version_attr, field))189 })190 .collect::<Result<Vec<(VersionAttr, Field)>>>();191 let fields = match fields {192 Ok(f) => f,193 Err(e) => return e.into_compile_error().into(),194 };195196 let attrs = input.attrs;197 let vis = input.vis;198 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();199 let mut out = Vec::new();200 for version in attr.first_version..=attr.current_version {201 let name = if version == attr.current_version {202 input.ident.clone() 203 } else {204 format_ident!("{}Version{}", &input.ident, version)205 };206 let current_fields = fields207 .iter()208 .filter_map(|(ver, field)| ver.exists_on(version).then(|| field));209210 let mut doc = Vec::new();211 if version > attr.first_version {212 doc.push(" # Versioning".into());213 doc.push(format!(" Changes between {} and {}:", version - 1, version));214 for (ver, field) in fields.iter() {215 match (ver.exists_on(version - 1), ver.exists_on(version)) {216 (true, false) => {217 let ty = &field.ty;218 doc.push(format!(219 " - {}: {} was removed",220 field.ident.as_ref().unwrap(),221 quote! {#ty}222 ))223 }224 (false, true) => {225 let ty = &field.ty;226 doc.push(format!(227 " - [`{}`]: {} was added",228 field.ident.as_ref().unwrap(),229 quote! {#ty}230 ))231 }232 _ => {}233 }234 }235 }236237 let upper = if attr.upper && version > attr.first_version {238 let prev_version = format_ident!("{}Version{}", &input.ident, version - 1);239 let removed_fields = fields240 .iter()241 .filter(|(v, _)| v.exists_on(version - 1) && !v.exists_on(version))242 .map(|(_, f)| f.ident.as_ref().unwrap())243 .collect::<Vec<_>>();244 let added_fields = fields245 .iter()246 .filter(|(v, _)| !v.exists_on(version - 1) && v.exists_on(version))247 .map(|(v, f)| {248 let name = f.ident.as_ref().unwrap();249 let value = v.upper.clone().unwrap_or_else(|| {250 Expr::Verbatim(251 Error::new_spanned(f, "missing upper declaration").to_compile_error(),252 )253 });254 quote! { #name: #value }255 });256 let passed_fields = fields257 .iter()258 .filter(|(v, _)| v.exists_on(version - 1) && v.exists_on(version))259 .map(|(_, f)| f.ident.as_ref().unwrap())260 .collect::<Vec<_>>();261 // let added_fields = fields;262 quote! {263 impl #impl_generics From<#prev_version #ty_generics> for #name #ty_generics #where_clause {264 fn from(old: #prev_version #ty_generics) -> Self {265 let #prev_version {266 #(#removed_fields,)*267 #(#passed_fields,)*268 } = old;269 #(let _ = &#removed_fields;)*270 Self {271 #(#added_fields,)*272 #(#passed_fields,)*273 }274 }275 }276 }277 } else {278 quote! {}279 };280281 out.push(quote! {282 #(#attrs)*283 #(#[doc = #doc])*284 #vis struct #name #impl_generics #where_clause {285 #(#current_fields,)*286 }287288 #upper289 });290 }291292 let ident = &input.ident;293 let last_version = format_ident!("{}Version{}", input.ident, attr.current_version);294295 quote! {296 #(#out)*297298 #vis type #last_version #ty_generics = #ident #ty_generics;299 }300 .into()301}1use proc_macro::TokenStream;2use quote::format_ident;3use syn::{4 parse::{Parse, ParseStream},5 Token, LitInt, parse_macro_input, ItemStruct, Error, Fields, Result, Field, Expr,6 parenthesized,7};8use quote::quote;910mod kw {11 syn::custom_keyword!(version);12 syn::custom_keyword!(first_version);13 syn::custom_keyword!(versions);14 syn::custom_keyword!(upper);15}1617struct VersionedAttrs {18 current_version: u32,19 first_version: u32,20 upper: bool,21}2223/// #[versioned(version = 2)]24impl Parse for VersionedAttrs {25 fn parse(input: ParseStream) -> Result<Self> {26 let mut current_version = None::<u32>;27 let mut first_version = None::<u32>;28 let mut upper = false;2930 loop {31 if input.is_empty() {32 break;33 }34 let lookahead = input.lookahead1();35 if lookahead.peek(kw::version) {36 input.parse::<kw::version>()?;37 input.parse::<Token![=]>()?;38 let t = input.parse::<LitInt>()?;39 if current_version.is_some() {40 return Err(Error::new_spanned(t, "version is already set"));41 }42 current_version = Some(t.base10_parse()?)43 } else if lookahead.peek(kw::first_version) {44 input.parse::<kw::first_version>()?;45 input.parse::<Token![=]>()?;46 let t = input.parse::<LitInt>()?;47 if first_version.is_some() {48 return Err(Error::new_spanned(t, "first version is already set"));49 }50 first_version = Some(t.base10_parse()?)51 } else if lookahead.peek(kw::upper) {52 input.parse::<kw::upper>()?;53 upper = true;54 } else {55 return Err(lookahead.error());56 }5758 if input.is_empty() {59 break;60 } else if input.peek(Token![,]) {61 input.parse::<Token![,]>()?;62 continue;63 } else {64 return Err(input.error("unexpected token"));65 }66 }67 let first_version = first_version.unwrap_or(1);68 let current_version = current_version.unwrap_or(first_version);6970 if current_version == 0 || first_version == 0 || first_version > current_version {71 return Err(Error::new(input.span(), "1 <= first_version <= version"));72 }7374 Ok(Self {75 current_version,76 first_version,77 upper,78 })79 }80}8182/// #[version(..3)] - field vas removed in version 3 (i.e it was exist on version 2, but doesn't on version 3)83/// #[version(3..)] - field has appeared in version 384/// #[version(2..4)] - field was on versions 2, 385/// #[version(1..2, upper(old_field + 1))] - when updating struct from old version to new - calculate new field value from passed expression86struct VersionAttr {87 since: u32,88 before: Option<u32>,8990 upper: Option<Expr>,91}92impl VersionAttr {93 fn exists_on(&self, version: u32) -> bool {94 version >= self.since && self.before.map_or(true, |before| version < before)95 }96}97impl Parse for VersionAttr {98 fn parse(input: ParseStream) -> Result<Self> {99 let mut since = None::<u32>;100 let mut before = None::<u32>;101 let lookahead = input.lookahead1();102103 if lookahead.peek(LitInt) {104 let t: LitInt = input.parse()?;105 since = Some(t.base10_parse()?);106 } else if !lookahead.peek(Token![..]) {107 return Err(lookahead.error());108 }109 let range = input.parse::<Token![..]>()?;110 let lookahead = input.lookahead1();111 if lookahead.peek(LitInt) {112 let t: LitInt = input.parse()?;113 before = Some(t.base10_parse()?);114 } else if !input.is_empty() && !lookahead.peek(Token![,]) {115 return Err(lookahead.error());116 }117118 let upper = if input.peek(Token![,]) {119 input.parse::<Token![,]>()?;120 input.parse::<kw::upper>()?;121 let expr;122 parenthesized!(expr in input);123124 Some(Expr::parse(&expr)?)125 } else {126 None127 };128129 if since.is_none() && before.is_none() {130 return Err(Error::new_spanned(131 range,132 "noop range, remove this version attribute",133 ));134 }135 Ok(Self {136 since: since.unwrap_or(1),137 before,138 upper,139 })140 }141}142impl Default for VersionAttr {143 fn default() -> Self {144 Self {145 since: 1,146 before: None,147 upper: None,148 }149 }150}151152/// Generate versioned variants of a struct153///154/// `#[versioned(version = 1[, first_version = 1][, upper][, versions])]`155/// - *version* - current version of a struct156/// - *first_version* - allows to skip generation of structs, which predates first supported version157/// - *upper* - generate From impls, which converts old version of structs to new158/// - *versions* - generate enum, which contains all possible versions of struct159///160/// Each field may have version attribute161/// `#[version([1]..[2][, upper(old)])]`162/// - *1* - version, on which this field is appeared163/// - *2* - version, in which this field was removed164/// (i.e if set to 2, this field was exist on version 1, and no longer exist on version 2)165/// - *upper* - code, which should be executed to transform old value to new/create new value166#[proc_macro_attribute]167pub fn versioned(attr: TokenStream, input: TokenStream) -> TokenStream {168 let attr = parse_macro_input!(attr as VersionedAttrs);169 let input = parse_macro_input!(input as ItemStruct);170171 let fields = match input.fields {172 Fields::Named(named) => named.named,173 _ => {174 return Error::new_spanned(input, "expected named fields")175 .into_compile_error()176 .into()177 }178 };179 let fields = fields180 .iter()181 .map(|field| {182 let version_attr = match field.attrs.iter().find(|a| a.path.is_ident("version")) {183 Some(v) => v.parse_args::<VersionAttr>()?,184 None => return Ok((VersionAttr::default(), field.clone())),185 };186 let mut field = field.clone();187 field.attrs.retain(|a| !a.path.is_ident("version"));188 Ok((version_attr, field))189 })190 .collect::<Result<Vec<(VersionAttr, Field)>>>();191 let fields = match fields {192 Ok(f) => f,193 Err(e) => return e.into_compile_error().into(),194 };195196 let attrs = input.attrs;197 let vis = input.vis;198 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();199 let mut out = Vec::new();200 for version in attr.first_version..=attr.current_version {201 let name = if version == attr.current_version {202 input.ident.clone()203 } else {204 format_ident!("{}Version{}", &input.ident, version)205 };206 let current_fields = fields207 .iter()208 .filter_map(|(ver, field)| ver.exists_on(version).then(|| field));209210 let mut doc = Vec::new();211 if version > attr.first_version {212 doc.push(" # Versioning".into());213 doc.push(format!(" Changes between {} and {}:", version - 1, version));214 for (ver, field) in fields.iter() {215 match (ver.exists_on(version - 1), ver.exists_on(version)) {216 (true, false) => {217 let ty = &field.ty;218 doc.push(format!(219 " - {}: {} was removed",220 field.ident.as_ref().unwrap(),221 quote! {#ty}222 ))223 }224 (false, true) => {225 let ty = &field.ty;226 doc.push(format!(227 " - [`{}`]: {} was added",228 field.ident.as_ref().unwrap(),229 quote! {#ty}230 ))231 }232 _ => {}233 }234 }235 }236237 let upper = if attr.upper && version > attr.first_version {238 let prev_version = format_ident!("{}Version{}", &input.ident, version - 1);239 let removed_fields = fields240 .iter()241 .filter(|(v, _)| v.exists_on(version - 1) && !v.exists_on(version))242 .map(|(_, f)| f.ident.as_ref().unwrap())243 .collect::<Vec<_>>();244 let added_fields = fields245 .iter()246 .filter(|(v, _)| !v.exists_on(version - 1) && v.exists_on(version))247 .map(|(v, f)| {248 let name = f.ident.as_ref().unwrap();249 let value = v.upper.clone().unwrap_or_else(|| {250 Expr::Verbatim(251 Error::new_spanned(f, "missing upper declaration").to_compile_error(),252 )253 });254 quote! { #name: #value }255 });256 let passed_fields = fields257 .iter()258 .filter(|(v, _)| v.exists_on(version - 1) && v.exists_on(version))259 .map(|(_, f)| f.ident.as_ref().unwrap())260 .collect::<Vec<_>>();261 // let added_fields = fields;262 quote! {263 impl #impl_generics From<#prev_version #ty_generics> for #name #ty_generics #where_clause {264 fn from(old: #prev_version #ty_generics) -> Self {265 let #prev_version {266 #(#removed_fields,)*267 #(#passed_fields,)*268 } = old;269 #(let _ = &#removed_fields;)*270 Self {271 #(#added_fields,)*272 #(#passed_fields,)*273 }274 }275 }276 }277 } else {278 quote! {}279 };280281 out.push(quote! {282 #(#attrs)*283 #(#[doc = #doc])*284 #vis struct #name #impl_generics #where_clause {285 #(#current_fields,)*286 }287288 #upper289 });290 }291292 let ident = &input.ident;293 let last_version = format_ident!("{}Version{}", input.ident, attr.current_version);294295 quote! {296 #(#out)*297298 #vis type #last_version #ty_generics = #ident #ty_generics;299 }300 .into()301}tests/src/nesting/unnest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -4,14 +4,13 @@
import usingApi, {executeTransaction} from '../substrate/substrate-api';
import {
createCollectionExpectSuccess,
- createItemExpectFailure,
+ createItemExpectFailure,
createItemExpectSuccess,
- getTokenOwner,
- getTopmostTokenOwner,
- normalizeAccountId,
- setCollectionLimitsExpectSuccess,
- transferExpectFailure,
- transferExpectSuccess,
+ getTokenOwner,
+ getTopmostTokenOwner,
+ normalizeAccountId,
+ setCollectionLimitsExpectSuccess,
+ transferExpectSuccess,
} from '../util/helpers';
import {IKeyringPair} from '@polkadot/types/types';
@@ -36,8 +35,8 @@
// Unnest
await expect(executeTransaction(
- api,
- alice,
+ api,
+ alice,
api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1),
)).to.not.be.rejected;
expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
@@ -45,8 +44,8 @@
// Nest and burn
await transferExpectSuccess(collection, nestedToken, alice, targetAddress);
await expect(executeTransaction(
- api,
- alice,
+ api,
+ alice,
api.tx.unique.burnFrom(collection, normalizeAccountId(alice.address), nestedToken, 1),
)).to.not.be.rejected;
await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected; // 'owner == null'
@@ -61,7 +60,7 @@
alice = privateKey('//Alice');
bob = privateKey('//Bob');
});
-
+
it('Disallows a non-owner to unnest/burn a token', async () => {
await usingApi(async api => {
const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
@@ -74,8 +73,8 @@
// Try to unnest
await expect(executeTransaction(
- api,
- bob,
+ api,
+ bob,
api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1),
)).to.be.rejectedWith(/^common\.ApprovedValueTooLow$/);
//await transferFromExpectSuccess(collection, nestedToken, bob, targetAddress, {Substrate: bob.address});
@@ -83,14 +82,14 @@
// Try to burn
await expect(executeTransaction(
- api,
- bob,
+ api,
+ bob,
api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1),
)).to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/);
expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
});
});
-
+
it('Disallows excessive token nesting', async () => {
await usingApi(async api => {
const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
@@ -108,23 +107,15 @@
});
// todo another test for creating excessive depth matryoshka with Ethereum, move this one to nest ^
-
- // Recursive nesting
- it('Prevents Ouroboros-nested operations', async () => {
- await usingApi(async api => {
- const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
- await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
- const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
- // Create a nested token ouroboros
- const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
- await transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)});
-
- expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
+ // Recursive nesting
+ it('Prevents ouroboros creation', async () => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
+ const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
- // Make sure the ouroboros is detected
- await expect(getTopmostTokenOwner(api, collection, nestedToken)).to.be.rejected; // With(/^common\.DepthLimit$/);
- // todo transferFrom, must exit with Ouroboros error
- });
+ // Create a nested token ouroboros
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
+ expect(transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)})).to.be.rejectedWith(/^structure\.OuroborosDetected$/);
});
-});
\ No newline at end of file
+});
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -30,6 +30,7 @@
'timestamp',
'transactionpayment',
'treasury',
+ 'structure',
'system',
'vesting',
'parachainsystem',
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -17,15 +17,15 @@
import '../interfaces/augment-api-rpc';
import '../interfaces/augment-api-query';
import {ApiPromise, Keyring} from '@polkadot/api';
-import type {AccountId, EventRecord} from '@polkadot/types/interfaces';
-import {IKeyringPair} from '@polkadot/types/types';
+import type {AccountId, EventRecord, Event} from '@polkadot/types/interfaces';
+import {AnyTuple, IEvent, IKeyringPair} from '@polkadot/types/types';
import {evmToAddress} from '@polkadot/util-crypto';
import BN from 'bn.js';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {alicesPublicKey} from '../accounts';
import privateKey from '../substrate/privateKey';
-import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api';
+import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api';
import {hexToStr, strToUTF16, utf16ToStr} from './util';
import {UpDataStructsRpcCollection} from '@polkadot/types/lookup';
@@ -105,7 +105,6 @@
}
interface TransferResult {
- success: boolean;
collectionId: number;
itemId: number;
sender?: CrossAccountId;
@@ -168,6 +167,12 @@
return result;
}
+export function getEvent<T extends Event>(events: EventRecord[], check: (event: IEvent<AnyTuple>) => event is T): T | undefined {
+ const event = events.find(r => check(r.event));
+ if (!event) return;
+ return event.event as T;
+}
+
export function getGenericResult(events: EventRecord[]): GenericResult {
const result: GenericResult = {
success: false,
@@ -225,27 +230,20 @@
return result;
}
-export function getTransferResult(events: EventRecord[]): TransferResult {
- const result: TransferResult = {
- success: false,
- collectionId: 0,
- itemId: 0,
- value: 0n,
- };
-
- events.forEach(({event: {data, method, section}}) => {
- if (method === 'ExtrinsicSuccess') {
- result.success = true;
- } else if (section === 'common' && method === 'Transfer') {
- result.collectionId = +data[0].toString();
- result.itemId = +data[1].toString();
- result.sender = normalizeAccountId(data[2].toJSON() as any);
- result.recipient = normalizeAccountId(data[3].toJSON() as any);
- result.value = BigInt(data[4].toString());
+export function getTransferResult(api: ApiPromise, events: EventRecord[]): TransferResult {
+ for (const {event} of events) {
+ if (api.events.common.Transfer.is(event)) {
+ const [collection, token, sender, recipient, value] = event.data;
+ return {
+ collectionId: collection.toNumber(),
+ itemId: token.toNumber(),
+ sender: normalizeAccountId(sender.toJSON() as any),
+ recipient: normalizeAccountId(recipient.toJSON() as any),
+ value: value.toBigInt(),
+ };
}
- });
-
- return result;
+ }
+ throw new Error('no transfer event');
}
interface Nft {
@@ -300,9 +298,9 @@
}
const tx = api.tx.unique.createCollectionEx({
- name: strToUTF16(name),
- description: strToUTF16(description),
- tokenPrefix: strToUTF16(tokenPrefix),
+ name: strToUTF16(name),
+ description: strToUTF16(description),
+ tokenPrefix: strToUTF16(tokenPrefix),
mode: modeprm as any,
schemaVersion: schemaVersion,
});
@@ -912,15 +910,15 @@
balanceBefore = await getBalance(api, collectionId, to, tokenId);
}
const transferTx = api.tx.unique.transfer(to, collectionId, tokenId, value);
- const events = await submitTransactionAsync(sender, transferTx);
- const result = getTransferResult(events);
- // tslint:disable-next-line:no-unused-expression
- expect(result.success).to.be.true;
+ const events = await executeTransaction(api, sender, transferTx);
+
+ const result = getTransferResult(api, events);
expect(result.collectionId).to.be.equal(collectionId);
expect(result.itemId).to.be.equal(tokenId);
expect(result.sender).to.be.deep.equal(normalizeAccountId(sender.address));
expect(result.recipient).to.be.deep.equal(to);
expect(result.value).to.be.equal(BigInt(value));
+
if (type === 'NFT') {
expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to);
}