diff --git a/Cargo.lock b/Cargo.lock index 3f326f356..37f932150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7556,6 +7556,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", + "hex-literal 0.3.4", "log", "mangata-support", "mangata-types", diff --git a/pallets/market/src/lib.rs b/pallets/market/src/lib.rs index 5a8db6db8..847c7477a 100644 --- a/pallets/market/src/lib.rs +++ b/pallets/market/src/lib.rs @@ -165,6 +165,9 @@ pub mod pallet { /// A list of Foundation members with elevated rights type FoundationAccountsProvider: Get>; + /// A special account used for nontransferable tokens to allow 'selling' to balance pools + type ArbitrageBot: Contains; + #[cfg(feature = "runtime-benchmarks")] type ComputeIssuance: ComputeIssuance; } @@ -1000,7 +1003,7 @@ pub mod pallet { // check input asset id, or the foundation has a veto ensure!( !T::NontransferableTokens::contains(&swap.0) || - T::FoundationAccountsProvider::get().contains(&sender), + T::ArbitrageBot::contains(sender), Error::::NontransferableToken ); diff --git a/rollup/runtime/Cargo.toml b/rollup/runtime/Cargo.toml index bd448a3cb..596ec17a1 100644 --- a/rollup/runtime/Cargo.toml +++ b/rollup/runtime/Cargo.toml @@ -15,10 +15,11 @@ targets = ["x86_64-unknown-linux-gnu"] substrate-wasm-builder = { workspace = true } [dependencies] +array-bytes = { workspace = true } codec = { workspace = true, default-features = false, features = ["derive"] } -scale-info = { workspace = true, default-features = false, features = ["derive"] } +hex-literal = { workspace = true } log = { workspace = true, default-features = false } -array-bytes = { workspace = true } +scale-info = { workspace = true, default-features = false, features = ["derive"] } smallvec = "1.6.1" static_assertions = "1.1.0" diff --git a/rollup/runtime/integration-test/src/lib.rs b/rollup/runtime/integration-test/src/lib.rs index 37e3f65c5..52419ae4c 100644 --- a/rollup/runtime/integration-test/src/lib.rs +++ b/rollup/runtime/integration-test/src/lib.rs @@ -6,6 +6,7 @@ mod identity; mod maintenance; mod market; mod membership; +mod nontransfer; mod proof_of_stake; mod proxy; mod setup; diff --git a/rollup/runtime/integration-test/src/nontransfer.rs b/rollup/runtime/integration-test/src/nontransfer.rs new file mode 100644 index 000000000..201a94967 --- /dev/null +++ b/rollup/runtime/integration-test/src/nontransfer.rs @@ -0,0 +1,230 @@ +use sp_runtime::AccountId20; + +use crate::setup::*; + +const ASSET_ID_1: TokenId = NATIVE_ASSET_ID + 1; +const POOL_ID: TokenId = ASSET_ID_1 + 1; +const ARB: [u8; 20] = hex_literal::hex!["fc741134c82b81b7ab7efbf334b0c90ff8dbf22c"]; + +fn test_env() -> TestExternalities { + ExtBuilder { + balances: vec![ + (AccountId::from(ALICE), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ARB), NATIVE_ASSET_ID, 100 * UNIT), + (AccountId::from(ALICE), ASSET_ID_1, 100 * UNIT), + ], + ..ExtBuilder::default() + } + .build() +} + +fn root() -> RuntimeOrigin { + RuntimeOrigin::root().into() +} + +fn origin() -> RuntimeOrigin { + RuntimeOrigin::signed(AccountId::from(ALICE)) +} + +#[test] +fn test_tokens_calls_should_block() { + let mut ext = test_env(); + ext.execute_with(|| { + let who = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let amount = UNIT; + assert_err!( + orml_tokens::Pallet::::mint(root(), NATIVE_ASSET_ID, who, amount), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::set_balance( + root(), + who, + NATIVE_ASSET_ID, + amount, + amount + ), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::transfer(origin(), bob, NATIVE_ASSET_ID, amount), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::force_transfer( + root(), + who, + bob, + NATIVE_ASSET_ID, + amount + ), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::transfer_all(origin(), bob, NATIVE_ASSET_ID, false), + orml_tokens::Error::::NontransferableToken + ); + assert_err!( + orml_tokens::Pallet::::transfer_keep_alive( + origin(), + bob, + NATIVE_ASSET_ID, + amount + ), + orml_tokens::Error::::NontransferableToken + ); + }); +} + +#[test] +fn test_market_should_block() { + let mut ext = test_env(); + ext.execute_with(|| { + let foundation = AccountId::from(ALICE); + let bob = AccountId::from(BOB); + let amount = 100 * UNIT; + + // user cannot create pool, foundation can + assert_err!( + pallet_market::Pallet::::create_pool( + RuntimeOrigin::signed(bob), + pallet_market::PoolKind::Xyk, + NATIVE_ASSET_ID, + amount, + ASSET_ID_1, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_ok!(pallet_market::Pallet::::create_pool( + RuntimeOrigin::signed(foundation), + pallet_market::PoolKind::Xyk, + NATIVE_ASSET_ID, + amount, + ASSET_ID_1, + amount, + )); + + // none can mint + assert_err!( + pallet_market::Pallet::::mint_liquidity( + RuntimeOrigin::signed(foundation), + POOL_ID, + NATIVE_ASSET_ID, + amount, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::mint_liquidity_fixed_amounts( + RuntimeOrigin::signed(foundation), + POOL_ID, + (amount, amount), + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::mint_liquidity_using_vesting_native_tokens( + RuntimeOrigin::signed(foundation), + POOL_ID, + amount, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::mint_liquidity_using_vesting_native_tokens_by_vesting_index( + RuntimeOrigin::signed(foundation), + POOL_ID, + 0, + None, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + + // user cannot burn, foundation can + assert_err!( + pallet_market::Pallet::::burn_liquidity( + RuntimeOrigin::signed(bob), + POOL_ID, + amount, + amount, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_ok!(pallet_market::Pallet::::burn_liquidity( + RuntimeOrigin::signed(foundation), + POOL_ID, + UNIT, + 0, + 0, + )); + + // user & foundation cannot sell, ARB bot can + assert_err!( + pallet_market::Pallet::::multiswap_asset( + RuntimeOrigin::signed(bob), + vec![POOL_ID], + NATIVE_ASSET_ID, + UNIT, + ASSET_ID_1, + 0, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::multiswap_asset( + RuntimeOrigin::signed(foundation), + vec![POOL_ID], + NATIVE_ASSET_ID, + UNIT, + ASSET_ID_1, + 0, + ), + pallet_market::Error::::NontransferableToken + ); + assert_ok!(pallet_market::Pallet::::multiswap_asset( + RuntimeOrigin::signed(AccountId::from(ARB)), + vec![POOL_ID], + NATIVE_ASSET_ID, + UNIT, + ASSET_ID_1, + 0, + )); + assert_err!( + pallet_market::Pallet::::multiswap_asset_buy( + RuntimeOrigin::signed(bob), + vec![POOL_ID], + ASSET_ID_1, + UNIT, + NATIVE_ASSET_ID, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_err!( + pallet_market::Pallet::::multiswap_asset_buy( + RuntimeOrigin::signed(foundation), + vec![POOL_ID], + ASSET_ID_1, + UNIT, + NATIVE_ASSET_ID, + amount, + ), + pallet_market::Error::::NontransferableToken + ); + assert_ok!(pallet_market::Pallet::::multiswap_asset_buy( + RuntimeOrigin::signed(AccountId::from(ARB)), + vec![POOL_ID], + ASSET_ID_1, + UNIT, + NATIVE_ASSET_ID, + amount, + )); + }); +} diff --git a/rollup/runtime/src/lib.rs b/rollup/runtime/src/lib.rs index d6f5014d9..002366db1 100644 --- a/rollup/runtime/src/lib.rs +++ b/rollup/runtime/src/lib.rs @@ -828,6 +828,7 @@ impl pallet_market::Config for Runtime { type ComputeIssuance = Issuance; type NontransferableTokens = tokens::NontransferableTokens; type FoundationAccountsProvider = cfg::pallet_membership::FoundationAccountsProvider; + type ArbitrageBot = tokens::ArbitrageBot; } // Create the runtime by composing the FRAME pallets that were previously configured. diff --git a/rollup/runtime/src/runtime_config.rs b/rollup/runtime/src/runtime_config.rs index c38899dc4..1da529f07 100644 --- a/rollup/runtime/src/runtime_config.rs +++ b/rollup/runtime/src/runtime_config.rs @@ -53,9 +53,17 @@ pub mod tokens { #[cfg(not(feature = "unlocked"))] pub type NontransferableTokens = Equals>; + #[cfg(any(feature = "unlocked", feature = "runtime-benchmarks"))] + pub type ArbitrageBot = Nothing; + #[cfg(not(feature = "unlocked"))] + pub type ArbitrageBot = Equals; + parameter_types! { pub const RxTokenId: TokenId = RX_TOKEN_ID; pub const EthTokenId: TokenId = ETH_TOKEN_ID; + pub ArbitrageBotAddr: AccountId = sp_runtime::AccountId20::from( + hex_literal::hex!["fc741134c82b81b7ab7efbf334b0c90ff8dbf22c"] + ); } }