Skip to content

Commit

Permalink
config
Browse files Browse the repository at this point in the history
  • Loading branch information
Szegoo committed Feb 5, 2024
1 parent c8b7982 commit 66443e9
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 51 deletions.
93 changes: 55 additions & 38 deletions contracts/coretime_market/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ mod types;

#[openbrush::contract(env = environment::ExtendedEnvironment)]
pub mod coretime_market {
use crate::types::{Listing, MarketError};
use crate::types::{Config, Listing, MarketError};
use block_number_extension::BlockNumberProviderExtension;
use environment::ExtendedEnvironment;
use ink::{
Expand All @@ -49,7 +49,7 @@ pub mod coretime_market {
};
use openbrush::{contracts::traits::psp34::Id, storage::Mapping, traits::Storage};
use primitives::{
coretime::{RawRegionId, Region, Timeslice, CORE_MASK_BIT_LEN, TIMESLICE_PERIOD},
coretime::{RawRegionId, Region, Timeslice, CORE_MASK_BIT_LEN},
ensure, Version,
};
use sp_arithmetic::{traits::SaturatedConversion, FixedPointNumber, FixedU128};
Expand All @@ -58,20 +58,13 @@ pub mod coretime_market {
#[ink(storage)]
#[derive(Storage)]
pub struct CoretimeMarket {
/// A mapping that holds information about each region listed for sale.
/// A mapping that holds information about each region listed on sale.
pub listings: Mapping<RawRegionId, Listing>,
/// A vector containing all the region ids of regions listed on sale.
///
/// TODO: incentivize the removal of expired regions.
/// A vector containing all the regions listed on sale.
pub listed_regions: Vec<RawRegionId>,
/// The `AccountId` of the xc-regions contract.
///
/// Set on contract initialization. Can't be changed afterwards.
pub xc_regions_contract: AccountId,
/// The deposit required to list a region on sale.
///
/// Set on contract initialization. Can't be changed afterwards.
pub listing_deposit: Balance,
/// The configuration of the market. Set on contract initialization. Can't be changed
/// afterwards.
pub config: Config,
}

#[ink(event)]
Expand Down Expand Up @@ -111,18 +104,21 @@ pub mod coretime_market {

impl CoretimeMarket {
#[ink(constructor)]
pub fn new(xc_regions_contract: AccountId, listing_deposit: Balance) -> Self {
pub fn new(
xc_regions_contract: AccountId,
listing_deposit: Balance,
timeslice_period: BlockNumber,
) -> Self {
Self {
listings: Default::default(),
listed_regions: Default::default(),
xc_regions_contract,
listing_deposit,
config: Config { xc_regions_contract, listing_deposit, timeslice_period },
}
}

#[ink(message)]
pub fn xc_regions_contract(&self) -> AccountId {
self.xc_regions_contract
self.config.xc_regions_contract
}

#[ink(message)]
Expand All @@ -140,8 +136,9 @@ pub mod coretime_market {
pub fn region_price(&self, id: Id) -> Result<Balance, MarketError> {
let Id::U128(region_id) = id else { return Err(MarketError::InvalidRegionId) };

let metadata = RegionMetadataRef::get_metadata(&self.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;
let metadata =
RegionMetadataRef::get_metadata(&self.config.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;
let listing = self.listings.get(&region_id).ok_or(MarketError::RegionNotListed)?;

self.calculate_region_price(metadata.region, listing)
Expand Down Expand Up @@ -176,22 +173,28 @@ pub mod coretime_market {
let Id::U128(region_id) = id else { return Err(MarketError::InvalidRegionId) };

// Ensure that the region exists and its metadata is set.
let metadata = RegionMetadataRef::get_metadata(&self.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;
let metadata =
RegionMetadataRef::get_metadata(&self.config.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;

let current_timeslice = self.current_timeslice();

// It doesn't make sense to list a region that expired.
ensure!(metadata.region.end > current_timeslice, MarketError::RegionExpired);

ensure!(
self.env().transferred_value() == self.listing_deposit,
self.env().transferred_value() == self.config.listing_deposit,
MarketError::MissingDeposit
);

// Transfer the region to the market.
PSP34Ref::transfer(&self.xc_regions_contract, market, id.clone(), Default::default())
.map_err(MarketError::XcRegionsPsp34Error)?;
PSP34Ref::transfer(
&self.config.xc_regions_contract,
market,
id.clone(),
Default::default(),
)
.map_err(MarketError::XcRegionsPsp34Error)?;

let sale_recepient = sale_recepient.unwrap_or(caller);

Expand Down Expand Up @@ -223,7 +226,7 @@ pub mod coretime_market {
/// - `region_id`: The `u128` encoded identifier of the region that the caller intends to
/// unlist from sale.
///
/// In case the regino is expired, this is callable by anyone and the caller will receive
/// In case the region is expired, this is callable by anyone and the caller will receive
/// the listing deposit as a reward.
#[ink(message)]
pub fn unlist_region(&mut self, id: Id) -> Result<(), MarketError> {
Expand All @@ -232,8 +235,9 @@ pub mod coretime_market {
let Id::U128(region_id) = id else { return Err(MarketError::InvalidRegionId) };

let listing = self.listings.get(&region_id).ok_or(MarketError::RegionNotListed)?;
let metadata = RegionMetadataRef::get_metadata(&self.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;
let metadata =
RegionMetadataRef::get_metadata(&self.config.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;

let current_timeslice = self.current_timeslice();

Expand All @@ -245,15 +249,20 @@ pub mod coretime_market {
);

// Transfer the region to the seller.
PSP34Ref::transfer(&self.xc_regions_contract, listing.seller, id.clone(), Default::default())
.map_err(MarketError::XcRegionsPsp34Error)?;
PSP34Ref::transfer(
&self.config.xc_regions_contract,
listing.seller,
id.clone(),
Default::default(),
)
.map_err(MarketError::XcRegionsPsp34Error)?;

// Remove the region from sale:
self.remove_from_sale(region_id)?;

// Reward the caller with listing deposit.
self.env()
.transfer(caller, self.listing_deposit)
.transfer(caller, self.config.listing_deposit)
.map_err(|_| MarketError::TransferFailed)?;

self.emit_event(RegionUnlisted { region_id, caller });
Expand Down Expand Up @@ -297,17 +306,23 @@ pub mod coretime_market {
let Id::U128(region_id) = id else { return Err(MarketError::InvalidRegionId) };
let listing = self.listings.get(&region_id).ok_or(MarketError::RegionNotListed)?;

let metadata = RegionMetadataRef::get_metadata(&self.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;
let metadata =
RegionMetadataRef::get_metadata(&self.config.xc_regions_contract, region_id)
.map_err(MarketError::XcRegionsMetadataError)?;

let price = self.calculate_region_price(metadata.region, listing.clone())?;
ensure!(transferred_value >= price, MarketError::InsufficientFunds);

ensure!(listing.metadata_version == metadata_version, MarketError::MetadataNotMatching);

// Transfer the region to the buyer.
PSP34Ref::transfer(&self.xc_regions_contract, caller, id.clone(), Default::default())
.map_err(MarketError::XcRegionsPsp34Error)?;
PSP34Ref::transfer(
&self.config.xc_regions_contract,
caller,
id.clone(),
Default::default(),
)
.map_err(MarketError::XcRegionsPsp34Error)?;

// Remove the region from sale:
self.remove_from_sale(region_id)?;
Expand Down Expand Up @@ -373,13 +388,13 @@ pub mod coretime_market {
pub(crate) fn current_timeslice(&self) -> Timeslice {
let latest_rc_block =
self.env().extension().relay_chain_block_number().unwrap_or_default();
(latest_rc_block / TIMESLICE_PERIOD).saturated_into()
(latest_rc_block / self.config.timeslice_period).saturated_into()
}

#[cfg(test)]
pub(crate) fn current_timeslice(&self) -> Timeslice {
let latest_block = self.env().block_number();
(latest_block / TIMESLICE_PERIOD).saturated_into()
(latest_block / self.config.timeslice_period).saturated_into()
}

fn emit_event<Event: Into<<CoretimeMarket as ContractEventBase>::Type>>(&self, e: Event) {
Expand All @@ -395,6 +410,7 @@ pub mod coretime_market {
use super::*;
use environment::ExtendedEnvironment;
use ink_e2e::MessageBuilder;
use primitives::coretime::TIMESLICE_PERIOD;
use xc_regions::xc_regions::XcRegionsRef;

type E2EResult<T> = Result<T, Box<dyn std::error::Error>>;
Expand All @@ -410,7 +426,8 @@ pub mod coretime_market {
.expect("instantiate failed")
.account_id;

let constructor = CoretimeMarketRef::new(xc_regions_acc_id, REQUIRED_DEPOSIT);
let constructor =
CoretimeMarketRef::new(xc_regions_acc_id, REQUIRED_DEPOSIT, TIMESLICE_PERIOD);
let market_acc_id = client
.instantiate("coretime-market", &ink_e2e::alice(), constructor, 0, None)
.await
Expand Down
2 changes: 1 addition & 1 deletion contracts/coretime_market/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use primitives::coretime::{CoreMask, Region, Timeslice, TIMESLICE_PERIOD};
fn calculate_region_price_works() {
let DefaultAccounts::<DefaultEnvironment> { charlie, .. } = get_default_accounts();

let market = CoretimeMarket::new(charlie, 0);
let market = CoretimeMarket::new(charlie, 0, TIMESLICE_PERIOD);
// Works for regions which haven't yet started.

// complete coremask, so 80 active bits.
Expand Down
17 changes: 16 additions & 1 deletion contracts/coretime_market/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,25 @@
// You should have received a copy of the GNU General Public License
// along with RegionX. If not, see <https://www.gnu.org/licenses/>.

use openbrush::{contracts::traits::psp34::PSP34Error, traits::AccountId};
use openbrush::{
contracts::traits::psp34::PSP34Error,
traits::{AccountId, BlockNumber},
};
use primitives::{Balance, Version};
use xc_regions::types::XcRegionsError;

/// The configuration of the coretime market
#[derive(scale::Decode, scale::Encode, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))]
pub struct Config {
/// The `AccountId` of the xc-regions contract.
pub xc_regions_contract: AccountId,
/// The deposit required to list a region on sale.
pub listing_deposit: Balance,
/// The duration of a timeslice in block numbers.
pub timeslice_period: BlockNumber,
}

#[derive(scale::Decode, scale::Encode, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum MarketError {
Expand Down
2 changes: 2 additions & 0 deletions tests/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,5 @@ export async function balanceOf(api: ApiPromise, acc: string): Promise<number> {
export function parseHNString(str: string): number {
return parseInt(str.replace(/,/g, ''));
}

export const wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
17 changes: 10 additions & 7 deletions tests/market/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ import {
expectOnSale,
initRegion,
mintRegion,
wait,
} from '../common';

use(chaiAsPromised);

const REGION_COLLECTION_ID = 42;
const LISTING_DEPOIST = 100;
// Set to only one relay
const TIMIESLICE_PERIOD = 80;

const wsProvider = new WsProvider('ws://127.0.0.1:9944');
// Create a keyring instance
Expand Down Expand Up @@ -175,12 +178,12 @@ describe('Coretime market listing', () => {
const id: any = api.createType('Id', { U128: region.getEncodedRegionId(api) });
await xcRegions.withSigner(alice).tx.approve(market.address, id, true);

setTimeout(async () => {
const timeslicePrice = 50;
const result = await market
.withSigner(alice)
.query.listRegion(id, timeslicePrice, alice.address, { value: LISTING_DEPOIST });
expect(result.value.unwrap().err).to.deep.equal(MarketErrorBuilder.RegionExpired());
}, 6000);
await wait(6000);

const timeslicePrice = 50;
const result = await market
.withSigner(alice)
.query.listRegion(id, timeslicePrice, alice.address, { value: LISTING_DEPOIST });
expect(result.value.unwrap().err).to.deep.equal(MarketErrorBuilder.RegionExpired());
});
});
61 changes: 57 additions & 4 deletions tests/market/unlist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
expectOnSale,
initRegion,
mintRegion,
wait,
} from '../common';
import { MarketErrorBuilder } from '../../types/types-returns/coretime_market';

Expand Down Expand Up @@ -99,9 +100,8 @@ describe('Coretime market unlisting', () => {
expect(market.query.listedRegions()).to.eventually.be.equal([]);
expect((await market.query.listedRegion(id)).value.unwrap().ok).to.be.equal(null);

// The caller receives the deposit back.
// TODO fix:
//expect(await balanceOf(api, alice.address)).to.be.greaterThan(aliceBalance - LISTING_DEPOIST);
// Alice receives the region back:
expect((await xcRegions.query.ownerOf(id)).value.unwrap()).to.be.equal(alice.address);
});

it('Unlisting not listed region fails', async () => {
Expand Down Expand Up @@ -129,7 +129,7 @@ describe('Coretime market unlisting', () => {
expect(result.value.unwrap().err).to.deep.equal(MarketErrorBuilder.RegionNotListed());
});

it('Only owner can unlisting unexpired region', async () => {
it('Only owner can unlist unexpired region', async () => {
const regionId: RegionId = {
begin: 30,
core: 22,
Expand Down Expand Up @@ -170,5 +170,58 @@ describe('Coretime market unlisting', () => {
// Ensure the region is removed from sale:
expect(market.query.listedRegions()).to.eventually.be.equal([]);
expect((await market.query.listedRegion(id)).value.unwrap().ok).to.be.equal(null);

// Alice receives the region back:
expect((await xcRegions.query.ownerOf(id)).value.unwrap()).to.be.equal(alice.address);
});

it('Anyone can unlist an expired region', async () => {
const regionId: RegionId = {
begin: 0,
core: 23,
mask: CoreMask.completeMask(),
};
const regionRecord: RegionRecord = {
end: 1,
owner: alice.address,
paid: null,
};
const region = new Region(regionId, regionRecord);

await mintRegion(api, alice, region);
await approveTransfer(api, alice, region, xcRegions.address);

await initRegion(api, xcRegions, alice, region);

const id: any = api.createType('Id', { U128: region.getEncodedRegionId(api) });
await xcRegions.withSigner(alice).tx.approve(market.address, id, true);

const timeslicePrice = 5 * Math.pow(10, 12);
await market
.withSigner(alice)
.tx.listRegion(id, timeslicePrice, alice.address, { value: LISTING_DEPOIST });

await expectOnSale(market, id, alice, timeslicePrice);
expect((await xcRegions.query.ownerOf(id)).value.unwrap()).to.deep.equal(market.address);
/*
const bobBalance = await balanceOf(api, bob.address);
const result = await market.withSigner(bob).tx.unlistRegion(id);
expectEvent(result, 'RegionUnlisted', {
regionId: id.toPrimitive().u128,
caller: bob.address,
});
// Ensure the region is removed from sale:
expect(market.query.listedRegions()).to.eventually.be.equal([]);
expect((await market.query.listedRegion(id)).value.unwrap().ok).to.be.equal(null);
// Alice receives the region back:
expect((await xcRegions.query.ownerOf(id)).value.unwrap()).to.be.equal(alice.address);
// Bob receives the listing deposit:
expect(await balanceOf(api, bob.address)).to.be.eq(bobBalance + LISTING_DEPOIST);
*/
});
});

0 comments on commit 66443e9

Please sign in to comment.