From c97cdc22e58ee201948bd11929d0fba638aeef5d Mon Sep 17 00:00:00 2001 From: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:20:17 +0800 Subject: [PATCH] Feat/vtoken voting vbnc (#1442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bifrost v0.12.0 * remove old migrations * Fix vtokenVoting.removeDelegatorVote (#1288) * add PollClass type * add the calss parameter to the remove_delegator_vote method * Add metadata hash (#1299) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * Fix compile * feat: 🎸 add metadata-hash-extension (#1265) * feat: 🎸 add metadata-hash-extension * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Feat/add vtoken exchange rate rpc (#1277) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * supplement omissions: adjust the RPC exchange rate precision to three decimal places * Remove dmp queue (#1279) * fix: 🐛 CheckMetadataHash to false * fix: 🐛 update sp-api * refactor: 💡 fmt * fix: 🐛 add metadata-hash * fix: 🐛 add feature metadata-hash * fix: 🐛 rm metadata-hash feature --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: Edwin Wang * refactor: 💡 update MaxTurnout and whitelisted_caller track (#1304) * Allow to receive and send Ethereum assets (#1305) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * feat: 🎸 add metadata-hash-extension (#1265) * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Allow to receive and send Ethereum assets * Add constants --------- Co-authored-by: yooml Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: Edwin Wang * Disable supplement_fee_reserve (#1306) * Remove call_switchgear and add tx_pause (#1308) * Remove call_switchgear and add tx_pause * Fix clippy * Optimize flexible fee (#1307) * Optimize flexible fee * Fix bug * Fix bug * Add PalletId * Add error handle * feat: 🎸 add market-bond * fix: 🐛 test-all * fix: 🐛 try-runtime * Add the channel_id field to the Minted event * Upgrade to polkadot-v1.13.0 (#1312) * Upgrade to polkadot-v1.13.0 * remove the warning * Fix dep issues * Upgrade to polkadot-v1.13.0 * supplement and upgrade the code * Upgrade the new parts from v0.12.0 * Fix dep issues * format Cargo.toml * Modify the AddOrigin type of the fellowship in bifrost-polkadot * Cancel the cross-in-out migration. * fix clippy --------- Co-authored-by: hqwangningbo <2536935847@qq.com> * Use github dependencies (#1314) * Fix flexible_fee TransferTo (#1315) * Refactor code structure: extract the vote operation from the relaychain. * Refactor code structure: extract the remove_vote operation from the relaychain. * Move the XCM-related methods into the relaychain_agent. * Move the call.rs into the relaychain_agent. * vtokenvoting functionality supports vBNC * Bifrost v0.13.0 * Remove getter in buy-back. (#1354) * Bifrost v0.12.0 * remove old migrations * Fix vtokenVoting.removeDelegatorVote (#1288) * add PollClass type * add the calss parameter to the remove_delegator_vote method * Add metadata hash (#1299) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * Fix compile * feat: 🎸 add metadata-hash-extension (#1265) * feat: 🎸 add metadata-hash-extension * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Feat/add vtoken exchange rate rpc (#1277) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * supplement omissions: adjust the RPC exchange rate precision to three decimal places * Remove dmp queue (#1279) * fix: 🐛 CheckMetadataHash to false * fix: 🐛 update sp-api * refactor: 💡 fmt * fix: 🐛 add metadata-hash * fix: 🐛 add feature metadata-hash * fix: 🐛 rm metadata-hash feature --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: Edwin Wang * refactor: 💡 update MaxTurnout and whitelisted_caller track (#1304) * Allow to receive and send Ethereum assets (#1305) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * feat: 🎸 add metadata-hash-extension (#1265) * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Allow to receive and send Ethereum assets * Add constants --------- Co-authored-by: yooml Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: Edwin Wang * Disable supplement_fee_reserve (#1306) * Remove call_switchgear and add tx_pause (#1308) * Remove call_switchgear and add tx_pause * Fix clippy * Optimize flexible fee (#1307) * Optimize flexible fee * Fix bug * Fix bug * Add PalletId * Add error handle * feat: 🎸 add market-bond * fix: 🐛 test-all * fix: 🐛 try-runtime * Add the channel_id field to the Minted event * Upgrade to polkadot-v1.13.0 (#1312) * Upgrade to polkadot-v1.13.0 * remove the warning * Fix dep issues * Upgrade to polkadot-v1.13.0 * supplement and upgrade the code * Upgrade the new parts from v0.12.0 * Fix dep issues * format Cargo.toml * Modify the AddOrigin type of the fellowship in bifrost-polkadot * Cancel the cross-in-out migration. * fix clippy --------- Co-authored-by: hqwangningbo <2536935847@qq.com> * Use github dependencies (#1314) * Fix flexible_fee TransferTo (#1315) * Fix generate_genesis_state (#1317) * Optimize oracle (#1318) * refactor: 💡 optimize orml-oracle * fix: 🐛 MinimumTimestampInterval * style: 💄 rename to MaximumValueInterval * Add some tests for ve-minting * Add some tests for ve-minting * Integrate evm (#1319) * Update dep * Ingrate EVM * Fix deps * Add precompiles.rs * Remove pallet-hotfix-sufficients * Add pallet-evm-accounts * Evm precompiles * EVM integration * Fix compile * Fix rpc * Fix `encode_evm_address` and `decode_evm_address` * Fix evm precompile multicurrency * Implementing Erc20Mapping using xc-20 standard * Compatible with BNC Decimal * Increase min_gas_price * feat: enable dev mode with manual seal & fix pending block problem * Add copyright and fix some bugs * Change native token to WETH * Support evm flexible fee * Add inner_swap_exact_assets_for_assets to evm withdraw fee * Remove swap * EVM migration from bifrost kusama to bifrost polkadot * Fix erc20 precompile * Use OraclePrice * Fix some errors * feat: add prices genesis config & optimize dev mode code * Fix conflicts * Fix some errors * Fix clippy * Format Cargo.toml * Remove unused code * Fix clippy * Fix some errors * Fix erc20 precompile --------- Co-authored-by: Edwin Wang Co-authored-by: Damian.lu * Fix try-runtime (#1323) * Fix manual seal * Fix manual seal (#1324) * fix: 🐛 fellowship collective data (#1325) * fix: add parachain mock inherent data provider * Fix ethereum transfer fee (#1328) * Add evm genesis migration (#1338) * Bifrost v0.12.0 (#1286) * Bifrost v0.12.0 * remove old migrations * Fix vtokenVoting.removeDelegatorVote (#1288) * add PollClass type * add the calss parameter to the remove_delegator_vote method * Add metadata hash (#1299) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * Fix compile * feat: 🎸 add metadata-hash-extension (#1265) * feat: 🎸 add metadata-hash-extension * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Feat/add vtoken exchange rate rpc (#1277) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * supplement omissions: adjust the RPC exchange rate precision to three decimal places * Remove dmp queue (#1279) * fix: 🐛 CheckMetadataHash to false * fix: 🐛 update sp-api * refactor: 💡 fmt * fix: 🐛 add metadata-hash * fix: 🐛 add feature metadata-hash * fix: 🐛 rm metadata-hash feature --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: Edwin Wang * refactor: 💡 update MaxTurnout and whitelisted_caller track (#1304) * Allow to receive and send Ethereum assets (#1305) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * feat: 🎸 add metadata-hash-extension (#1265) * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Allow to receive and send Ethereum assets * Add constants --------- Co-authored-by: yooml Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: Edwin Wang * Disable supplement_fee_reserve (#1306) * Remove call_switchgear and add tx_pause (#1308) * Remove call_switchgear and add tx_pause * Fix clippy * Optimize flexible fee (#1307) * Optimize flexible fee * Fix bug * Fix bug * Add PalletId * Add error handle * feat: 🎸 add market-bond * fix: 🐛 test-all * fix: 🐛 try-runtime * Add the channel_id field to the Minted event * Upgrade to polkadot-v1.13.0 (#1312) * Upgrade to polkadot-v1.13.0 * remove the warning * Fix dep issues * Upgrade to polkadot-v1.13.0 * supplement and upgrade the code * Upgrade the new parts from v0.12.0 * Fix dep issues * format Cargo.toml * Modify the AddOrigin type of the fellowship in bifrost-polkadot * Cancel the cross-in-out migration. * fix clippy --------- Co-authored-by: hqwangningbo <2536935847@qq.com> * Use github dependencies (#1314) * Fix flexible_fee TransferTo (#1315) * Fix generate_genesis_state (#1317) * Optimize oracle (#1318) * refactor: 💡 optimize orml-oracle * fix: 🐛 MinimumTimestampInterval * style: 💄 rename to MaximumValueInterval * Add some tests for ve-minting * Add some tests for ve-minting * Integrate evm (#1319) * Update dep * Ingrate EVM * Fix deps * Add precompiles.rs * Remove pallet-hotfix-sufficients * Add pallet-evm-accounts * Evm precompiles * EVM integration * Fix compile * Fix rpc * Fix `encode_evm_address` and `decode_evm_address` * Fix evm precompile multicurrency * Implementing Erc20Mapping using xc-20 standard * Compatible with BNC Decimal * Increase min_gas_price * feat: enable dev mode with manual seal & fix pending block problem * Add copyright and fix some bugs * Change native token to WETH * Support evm flexible fee * Add inner_swap_exact_assets_for_assets to evm withdraw fee * Remove swap * EVM migration from bifrost kusama to bifrost polkadot * Fix erc20 precompile * Use OraclePrice * Fix some errors * feat: add prices genesis config & optimize dev mode code * Fix conflicts * Fix some errors * Fix clippy * Format Cargo.toml * Remove unused code * Fix clippy * Fix some errors * Fix erc20 precompile --------- Co-authored-by: Edwin Wang Co-authored-by: Damian.lu * Fix try-runtime (#1323) * Fix manual seal * Fix manual seal (#1324) * fix: 🐛 fellowship collective data (#1325) * fix: add parachain mock inherent data provider * Fix ethereum transfer fee (#1328) --------- Co-authored-by: Edwin Wang Co-authored-by: yooml Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: Gemma Co-authored-by: Damian.lu * Add evm genesis storage * [skip ci] Add evmSince --------- Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: Edwin Wang Co-authored-by: yooml Co-authored-by: Gemma Co-authored-by: Damian.lu * Bump version to 0.12.1 * Update Cargo.lock * Fix migration (#1341) * Bifrost v0.12.0 (#1286) * Bifrost v0.12.0 * remove old migrations * Fix vtokenVoting.removeDelegatorVote (#1288) * add PollClass type * add the calss parameter to the remove_delegator_vote method * Add metadata hash (#1299) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * Fix compile * feat: 🎸 add metadata-hash-extension (#1265) * feat: 🎸 add metadata-hash-extension * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Feat/add vtoken exchange rate rpc (#1277) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * supplement omissions: adjust the RPC exchange rate precision to three decimal places * Remove dmp queue (#1279) * fix: 🐛 CheckMetadataHash to false * fix: 🐛 update sp-api * refactor: 💡 fmt * fix: 🐛 add metadata-hash * fix: 🐛 add feature metadata-hash * fix: 🐛 rm metadata-hash feature --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: Edwin Wang * refactor: 💡 update MaxTurnout and whitelisted_caller track (#1304) * Allow to receive and send Ethereum assets (#1305) * feat: 🎸 add buy-back * Bifrost v0.10.001 * feat: 🎸 benchmark * SLPx mint support commission id (#1261) * Add vtoken exchange rate rpc (#1260) * fix: 🐛 add event (#1263) * add update_currency_metadata function (#1259) * add update_currency_metadata function * fix: 🐛 correct update_currency_metadata weight * add the use of Vec (#1264) * Add vtoken exchange rate rpc * add the use of Vec * Upgrade to Polkadot-SDK v1.7.0 (#1262) * Bifrost v0.10.0 * fix: 🐛 clippy * Fix chain spec (#1243) * Upgrade to polkadot-v1.7.0 --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: Edwin Wang * feat: 🎸 add metadata-hash-extension (#1265) * Fix compile (#1266) * Fix/fix vtoken voting unlock (#1267) * update the exchange rate of vtokenminting of vtokenvoting test * Resolve the frozen anomaly when unlocking * Revert "feat: 🎸 add metadata-hash-extension (#1265)" (#1268) This reverts commit 489fc9c6adeae44d23edf449e74fde3d6aa40bc3. * rename the version number to 0.11.0 * Slp supports XCMv4 and reconstruct integration tests (#1272) * Fix compile * Reconstruct integration tests and slp supports XCMv4 * Fix slp construct_xcm_message * Feat/slpx mint add commission (#1273) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * Feat/add vtoken exchange rate rpc (#1274) * Add vtoken exchange rate rpc * add the use of Vec * supplement the RPC logic && fix bugs * adjust the RPC exchange rate precision to three decimal places * fix clippy * fix bug: storage migration anomaly (#1276) * SLPx mint support commission id * remove the channel_id parameter from the mint method * add mint_with_channel_id method * perform data migration for OrderQueue storage * slpx: set the default value of channel_id to 0 * resolve conflicts * change the location of the old data structure during storage migration * fix bug: storage migration anomaly * Slp removes refund (#1275) * Slp removes refund * Fix clippy * Allow to receive and send Ethereum assets * Add constants --------- Co-authored-by: yooml Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: Edwin Wang * Disable supplement_fee_reserve (#1306) * Remove call_switchgear and add tx_pause (#1308) * Remove call_switchgear and add tx_pause * Fix clippy * Optimize flexible fee (#1307) * Optimize flexible fee * Fix bug * Fix bug * Add PalletId * Add error handle * feat: 🎸 add market-bond * fix: 🐛 test-all * fix: 🐛 try-runtime * Add the channel_id field to the Minted event * Upgrade to polkadot-v1.13.0 (#1312) * Upgrade to polkadot-v1.13.0 * remove the warning * Fix dep issues * Upgrade to polkadot-v1.13.0 * supplement and upgrade the code * Upgrade the new parts from v0.12.0 * Fix dep issues * format Cargo.toml * Modify the AddOrigin type of the fellowship in bifrost-polkadot * Cancel the cross-in-out migration. * fix clippy --------- Co-authored-by: hqwangningbo <2536935847@qq.com> * Use github dependencies (#1314) * Fix flexible_fee TransferTo (#1315) * Fix generate_genesis_state (#1317) * Optimize oracle (#1318) * refactor: 💡 optimize orml-oracle * fix: 🐛 MinimumTimestampInterval * style: 💄 rename to MaximumValueInterval * Add some tests for ve-minting * Add some tests for ve-minting * Integrate evm (#1319) * Update dep * Ingrate EVM * Fix deps * Add precompiles.rs * Remove pallet-hotfix-sufficients * Add pallet-evm-accounts * Evm precompiles * EVM integration * Fix compile * Fix rpc * Fix `encode_evm_address` and `decode_evm_address` * Fix evm precompile multicurrency * Implementing Erc20Mapping using xc-20 standard * Compatible with BNC Decimal * Increase min_gas_price * feat: enable dev mode with manual seal & fix pending block problem * Add copyright and fix some bugs * Change native token to WETH * Support evm flexible fee * Add inner_swap_exact_assets_for_assets to evm withdraw fee * Remove swap * EVM migration from bifrost kusama to bifrost polkadot * Fix erc20 precompile * Use OraclePrice * Fix some errors * feat: add prices genesis config & optimize dev mode code * Fix conflicts * Fix some errors * Fix clippy * Format Cargo.toml * Remove unused code * Fix clippy * Fix some errors * Fix erc20 precompile --------- Co-authored-by: Edwin Wang Co-authored-by: Damian.lu * Fix try-runtime (#1323) * Fix manual seal * Fix manual seal (#1324) * fix: 🐛 fellowship collective data (#1325) * fix: add parachain mock inherent data provider * Fix ethereum transfer fee (#1328) --------- Co-authored-by: Edwin Wang Co-authored-by: yooml Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: Gemma Co-authored-by: Damian.lu * Add evm genesis storage * [skip ci] Add evmSince * Fix migration * Fix migration * Remove unused code --------- Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: Edwin Wang Co-authored-by: yooml Co-authored-by: Gemma Co-authored-by: Damian.lu * Remove getter in buy-back --------- Co-authored-by: SunTiebing <1045060705@qq.com> Co-authored-by: SunTiebing <87381708+SunTiebing@users.noreply.github.com> Co-authored-by: Edwin Wang Co-authored-by: yooml Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: Damian.lu * Remove getters in xcm-interface (#1352) * Remove getters in salp. (#1351) * Remove getter in evm-accounts (#1350) * Remove getters in slp (#1353) * Remove getters in slpx (#1360) * Remove getters in vstoken-conversion. (#1362) * Remove getters in vtoken-minting. (#1363) * Remove getter in system-maker. (#1364) * Remove getter in fee-share (#1365) * Remove getters in prices (#1367) * Remove getters in lend-market (#1366) * Remove getters in ve-minting (#1368) * Optimize clippy (#1369) * Deprecated unused pallets (#1383) * Review format (#1386) * style: 💄 format * style: 💄 check-all * fix: 🐛 bench * Update buy back (#1387) * style: 💄 format * style: 💄 check-all * fix: 🐛 bench * feat: 🎸 add destruction_ratio * fix: 🐛 benchmark * EVM flexible fee logic optimization (#1370) * EVM flexible fee logic optimization * Add comments * Add support for handling different currency precisions & included error handling. * Adjust the gas_fee parameter in the get_balance_in_currency method to an exact value * fix clippy * add test code of flexiable-fee pallet * Update fee share (#1395) * feat: 🎸 add usd_cumulation * feat: 🎸 bench * fix: 🐛 fmt * docs: ✏️ add note * Feat/change evm address convert rule (#1396) * Change EVM Address convert rule * Change EVM Address convert rule * Remove the unused function:is_evm_account & rename truncated_account_id function to convert_account_id * fix clippy * refactor: 💡 move to TechAdmin (#1398) * refactor: 💡 transfer to BuyBackAccount (#1399) * Fix mint_with_channel_id weights (#1400) * Recoverd imgs of banner and logo (#1401) * feat: 🎸 add fn set_swap_out_min (#1389) * feat: 🎸 add fn set_swap_out_min * fix: 🐛 fmt * fix: 🐛 fmt * fix: 🐛 error * feat: 🎸 add bais * fix: 🐛 add test * fix: 🐛 rename field bias * Optimize the `channel-commission` code. (#1390) * Optimize the `channel-commission` code. * Replace the insert method with the mutate method * Added a result check for the clear_prefix method * Supplement the test code of `channel-commission`. * Handling Error cases in hooks. * Optimize the handling of the `check_removed_all` method. * Remove getters in cross-in-out (#1402) * Removed getters in channel-commission. (#1404) * Removed getters in flexible-fee. (#1405) * Removed getters in channel-commission. * Removed getters in flexible-fee. * Removed getters in parachain-staking. (#1406) * Removed getters in channel-commission. * Removed getters in flexible-fee. * Removed getterd in parachain-staking. * Update buy back (#1407) * fix: 🐛 add field last_buyback_cycle for accurate judgment * style: 💄 fmt * Update ve minting (#1408) * refactor: 💡 transfer to BuyBackAccount * feat: 🎸 add auto notify_reward * Optimize vtokenvoting pallet. * Use the call.dispatch method to support vBNC in vTokenVoting. * Use the call.dispatch method to support vBNC in vTokenVoting. * adjust mock file * Change the way the vtoken-voting pallet interacts with other runtime pallets to a decoding-based approach. * Plump vtokenvoting supports vbnc * optimize test * optimize reviewed code * Add vtoken-voting Kusama tests to the test-all command. --------- Co-authored-by: Edwin Wang Co-authored-by: yooml Co-authored-by: NingBo Wang <2536935847@qq.com> Co-authored-by: MJLNSN <96321798+MJLNSN@users.noreply.github.com> Co-authored-by: Damian.lu --- Makefile | 5 +- pallets/slp/src/lib.rs | 15 +- pallets/vtoken-minting/src/lib.rs | 2 +- .../src/agents/bifrost_agent/agent.rs | 150 ++ .../src/agents/bifrost_agent/call.rs | 73 + .../src/agents/bifrost_agent/mod.rs | 22 + pallets/vtoken-voting/src/agents/mod.rs | 23 + .../src/agents/relaychain_agent/agent.rs | 120 ++ .../src/{ => agents/relaychain_agent}/call.rs | 35 +- .../src/agents/relaychain_agent/mod.rs | 22 + pallets/vtoken-voting/src/lib.rs | 584 +++--- pallets/vtoken-voting/src/mock.rs | 149 +- pallets/vtoken-voting/src/tests.rs | 1379 --------------- .../vtoken-voting/src/tests/common_test.rs | 1564 +++++++++++++++++ pallets/vtoken-voting/src/tests/mod.rs | 23 + pallets/vtoken-voting/src/tests/vbnc_test.rs | 841 +++++++++ pallets/vtoken-voting/src/traits.rs | 142 ++ pallets/vtoken-voting/src/vote.rs | 4 +- primitives/src/traits.rs | 4 +- runtime/bifrost-kusama/src/lib.rs | 1 + runtime/bifrost-polkadot/src/lib.rs | 1 + 21 files changed, 3526 insertions(+), 1633 deletions(-) create mode 100644 pallets/vtoken-voting/src/agents/bifrost_agent/agent.rs create mode 100644 pallets/vtoken-voting/src/agents/bifrost_agent/call.rs create mode 100644 pallets/vtoken-voting/src/agents/bifrost_agent/mod.rs create mode 100644 pallets/vtoken-voting/src/agents/mod.rs create mode 100644 pallets/vtoken-voting/src/agents/relaychain_agent/agent.rs rename pallets/vtoken-voting/src/{ => agents/relaychain_agent}/call.rs (78%) create mode 100644 pallets/vtoken-voting/src/agents/relaychain_agent/mod.rs delete mode 100644 pallets/vtoken-voting/src/tests.rs create mode 100644 pallets/vtoken-voting/src/tests/common_test.rs create mode 100644 pallets/vtoken-voting/src/tests/mod.rs create mode 100644 pallets/vtoken-voting/src/tests/vbnc_test.rs create mode 100644 pallets/vtoken-voting/src/traits.rs diff --git a/Makefile b/Makefile index 121749f9c..e72e6af14 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ check-all: format SKIP_WASM_BUILD= cargo check -p bifrost-cli --locked --features "with-all-runtime,runtime-benchmarks,try-runtime" .PHONY: test-all # cargo test all -test-all: test-runtimes test-benchmarks +test-all: test-runtimes test-benchmarks test-vtoken-voting-kusama .PHONY: test-runtimes test-runtimes: @@ -40,6 +40,9 @@ test-runtimes: test-benchmarks: SKIP_WASM_BUILD= cargo test benchmarking --features="with-bifrost-runtime, runtime-benchmarks, polkadot" +test-vtoken-voting-kusama: + SKIP_WASM_BUILD= cargo test -p bifrost-vtoken-voting --features="kusama, runtime-benchmarks" + .PHONY: clean # cargo clean clean: cargo clean diff --git a/pallets/slp/src/lib.rs b/pallets/slp/src/lib.rs index d4a5fd67a..4ca0f422a 100644 --- a/pallets/slp/src/lib.rs +++ b/pallets/slp/src/lib.rs @@ -2254,7 +2254,8 @@ pub mod pallet { pub struct DerivativeAccountProvider(PhantomData<(T, F)>); impl>> - DerivativeAccountHandler, BalanceOf> for DerivativeAccountProvider + DerivativeAccountHandler, BalanceOf, AccountIdOf> + for DerivativeAccountProvider { fn check_derivative_index_exists( token: CurrencyIdOf, @@ -2270,6 +2271,18 @@ impl>> DelegatorsIndex2Multilocation::::get(token, derivative_index) } + fn get_account_id( + token: CurrencyIdOf, + derivative_index: DerivativeIndex, + ) -> Option> { + Self::get_multilocation(token, derivative_index).and_then(|location| { + location.interior.last().and_then(|interior| match interior { + AccountId32 { id, .. } => T::AccountId::decode(&mut &id[..]).ok(), + _ => None, + }) + }) + } + fn get_stake_info( token: CurrencyIdOf, derivative_index: DerivativeIndex, diff --git a/pallets/vtoken-minting/src/lib.rs b/pallets/vtoken-minting/src/lib.rs index 687ee5475..b145c2de7 100644 --- a/pallets/vtoken-minting/src/lib.rs +++ b/pallets/vtoken-minting/src/lib.rs @@ -2182,7 +2182,7 @@ impl VTokenSupplyProvider, BalanceOf> for Pallet) -> Option> { - if CurrencyId::is_token(&token) { + if CurrencyId::is_token(&token) | CurrencyId::is_native(&token) { Some(TokenPool::::get(token)) } else { None diff --git a/pallets/vtoken-voting/src/agents/bifrost_agent/agent.rs b/pallets/vtoken-voting/src/agents/bifrost_agent/agent.rs new file mode 100644 index 000000000..24c250a24 --- /dev/null +++ b/pallets/vtoken-voting/src/agents/bifrost_agent/agent.rs @@ -0,0 +1,150 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::*; +use bifrost_primitives::{CurrencyId, DerivativeIndex}; +use frame_support::pallet_prelude::*; +use xcm::v4::Location; + +use crate::{agents::bifrost_agent::BifrostCall, pallet::Error, traits::*}; + +/// VotingAgent implementation for Bifrost +pub struct BifrostAgent { + vtoken: CurrencyIdOf, + location: Location, +} + +impl BifrostAgent { + // Only polkadot networks are supported. + pub fn new(vtoken: CurrencyId) -> Result> { + if cfg!(feature = "polkadot") { + let location = Pallet::::convert_vtoken_to_dest_location(vtoken)?; + Ok(Self { vtoken, location }) + } else { + Err(Error::::VTokenNotSupport) + } + } +} + +impl VotingAgent for BifrostAgent { + fn vtoken(&self) -> CurrencyIdOf { + self.vtoken + } + + fn location(&self) -> Location { + self.location.clone() + } + + fn delegate_vote( + &self, + who: AccountIdOf, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + _submitted: bool, + new_delegator_votes: Vec<(DerivativeIndex, AccountVote>)>, + maybe_old_vote: Option<(AccountVote>, BalanceOf)>, + ) -> DispatchResult { + // Get the derivative index from the first delegator vote. + let derivative_index = new_delegator_votes[0].0; + let call_encode = + self.vote_call_encode(new_delegator_votes, poll_index, derivative_index)?; + let vote_call: ::RuntimeCall = + ::RuntimeCall::decode(&mut &*call_encode) + .map_err(|_| Error::::CallDecodeFailed)?; + + let token = CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?; + let delegator: AccountIdOf = + T::DerivativeAccount::get_account_id(token, derivative_index) + .ok_or(Error::::NoData)?; + let origin = RawOrigin::Signed(delegator).into(); + let success = vote_call.dispatch(origin).is_ok(); + Pallet::::handle_vote_result( + success, + who, + vtoken, + poll_index, + maybe_old_vote, + derivative_index, + )?; + + if success { + Ok(()) + } else { + Err(Error::::InvalidCallDispatch.into()) + } + } + + fn vote_call_encode( + &self, + new_delegator_votes: Vec<(DerivativeIndex, AccountVote>)>, + poll_index: PollIndex, + _derivative_index: DerivativeIndex, + ) -> Result, Error> { + let vote_calls = new_delegator_votes + .iter() + .map(|(_derivative_index, vote)| { + as ConvictionVotingCall>::vote(poll_index, *vote) + }) + .collect::>(); + let vote_call = if vote_calls.len() == 1 { + vote_calls.into_iter().nth(0).ok_or(Error::::NoData)? + } else { + ensure!(false, Error::::NoPermissionYet); + as UtilityCall>>::batch_all(vote_calls) + }; + + Ok(vote_call.encode()) + } + + fn delegate_remove_delegator_vote( + &self, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + class: PollClass, + derivative_index: DerivativeIndex, + ) -> DispatchResult { + let call_encode = + self.remove_delegator_vote_call_encode(class, poll_index, derivative_index)?; + let call = ::RuntimeCall::decode(&mut &*call_encode) + .map_err(|_| Error::::CallDecodeFailed)?; + + let token = CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?; + let delegator: AccountIdOf = + T::DerivativeAccount::get_account_id(token, derivative_index) + .ok_or(Error::::NoData)?; + let origin = RawOrigin::Signed(delegator).into(); + let success = call.dispatch(origin).is_ok(); + + if success { + Pallet::::handle_remove_delegator_vote_success(vtoken, poll_index); + Ok(()) + } else { + Err(Error::::InvalidCallDispatch.into()) + } + } + + fn remove_delegator_vote_call_encode( + &self, + class: PollClass, + poll_index: PollIndex, + _derivative_index: DerivativeIndex, + ) -> Result, Error> { + Ok( as ConvictionVotingCall>::remove_vote(Some(class), poll_index) + .encode()) + } +} diff --git a/pallets/vtoken-voting/src/agents/bifrost_agent/call.rs b/pallets/vtoken-voting/src/agents/bifrost_agent/call.rs new file mode 100644 index 000000000..ee89348e3 --- /dev/null +++ b/pallets/vtoken-voting/src/agents/bifrost_agent/call.rs @@ -0,0 +1,73 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{traits::*, *}; +use parity_scale_codec::{Decode, Encode}; +use sp_runtime::{traits::StaticLookup, RuntimeDebug}; + +pub(in crate::agents::bifrost_agent) use bifrost::*; + +pub(in crate::agents::bifrost_agent) mod bifrost { + use crate::agents::bifrost_agent::call::*; + + #[derive(Encode, Decode, RuntimeDebug)] + pub(in crate::agents::bifrost_agent) enum BifrostCall { + #[codec(index = 36)] + ConvictionVoting(ConvictionVoting), + #[codec(index = 50)] + Utility(Utility), + } +} + +#[derive(Encode, Decode, RuntimeDebug, Clone)] +pub(in crate::agents::bifrost_agent) enum ConvictionVoting { + #[codec(index = 0)] + Vote(#[codec(compact)] PollIndex, AccountVote>), + #[codec(index = 3)] + Unlock(PollClass, ::Source), + #[codec(index = 4)] + RemoveVote(Option, PollIndex), +} + +impl ConvictionVotingCall for BifrostCall { + fn vote(poll_index: PollIndex, vote: AccountVote>) -> Self { + Self::ConvictionVoting(ConvictionVoting::Vote(poll_index, vote)) + } + + fn remove_vote(class: Option, poll_index: PollIndex) -> Self { + Self::ConvictionVoting(ConvictionVoting::RemoveVote(class, poll_index)) + } +} + +#[derive(Encode, Decode, RuntimeDebug, Clone)] +pub(in crate::agents::bifrost_agent) enum Utility { + #[codec(index = 1)] + AsDerivative(DerivativeIndex, Box), + #[codec(index = 2)] + BatchAll(Vec), +} + +impl UtilityCall> for BifrostCall { + fn as_derivative(derivative_index: DerivativeIndex, call: Self) -> Self { + Self::Utility(Utility::AsDerivative(derivative_index, Box::new(call))) + } + + fn batch_all(calls: Vec) -> Self { + Self::Utility(Utility::BatchAll(calls)) + } +} diff --git a/pallets/vtoken-voting/src/agents/bifrost_agent/mod.rs b/pallets/vtoken-voting/src/agents/bifrost_agent/mod.rs new file mode 100644 index 000000000..533ae6ef3 --- /dev/null +++ b/pallets/vtoken-voting/src/agents/bifrost_agent/mod.rs @@ -0,0 +1,22 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +pub mod agent; +mod call; + +pub use agent::*; +pub(in crate::agents::bifrost_agent) use call::*; diff --git a/pallets/vtoken-voting/src/agents/mod.rs b/pallets/vtoken-voting/src/agents/mod.rs new file mode 100644 index 000000000..b89becf7d --- /dev/null +++ b/pallets/vtoken-voting/src/agents/mod.rs @@ -0,0 +1,23 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +mod bifrost_agent; +mod relaychain_agent; + +pub use bifrost_agent::*; +pub use relaychain_agent::*; diff --git a/pallets/vtoken-voting/src/agents/relaychain_agent/agent.rs b/pallets/vtoken-voting/src/agents/relaychain_agent/agent.rs new file mode 100644 index 000000000..73e2a9b2e --- /dev/null +++ b/pallets/vtoken-voting/src/agents/relaychain_agent/agent.rs @@ -0,0 +1,120 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::*; +use bifrost_primitives::{CurrencyId, DerivativeIndex}; +use frame_support::{ensure, pallet_prelude::*}; +use xcm::v4::Location; + +use crate::{agents::relaychain_agent::call::*, pallet::Error, traits::*}; + +/// VotingAgent implementation for relay chain +pub struct RelaychainAgent { + vtoken: CurrencyIdOf, + location: Location, +} +impl RelaychainAgent { + pub fn new(vtoken: CurrencyId) -> Result> { + let location = Pallet::::convert_vtoken_to_dest_location(vtoken)?; + Ok(Self { vtoken, location }) + } +} + +impl VotingAgent for RelaychainAgent { + fn vtoken(&self) -> CurrencyIdOf { + self.vtoken + } + + fn location(&self) -> Location { + self.location.clone() + } + fn delegate_vote( + &self, + who: AccountIdOf, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + submitted: bool, + new_delegator_votes: Vec<(DerivativeIndex, AccountVote>)>, + maybe_old_vote: Option<(AccountVote>, BalanceOf)>, + ) -> DispatchResult { + Pallet::::send_xcm_vote_message( + who, + vtoken, + poll_index, + submitted, + new_delegator_votes, + maybe_old_vote, + ) + } + + fn vote_call_encode( + &self, + new_delegator_votes: Vec<(DerivativeIndex, AccountVote>)>, + poll_index: PollIndex, + derivative_index: DerivativeIndex, + ) -> Result, Error> { + let vote_calls = new_delegator_votes + .iter() + .map(|(_derivative_index, vote)| { + as ConvictionVotingCall>::vote(poll_index, *vote) + }) + .collect::>(); + let vote_call = if vote_calls.len() == 1 { + vote_calls.into_iter().nth(0).ok_or(Error::::NoData)? + } else { + ensure!(false, Error::::NoPermissionYet); + as UtilityCall>>::batch_all(vote_calls) + }; + + let encode_call = + as UtilityCall>>::as_derivative(derivative_index, vote_call) + .encode(); + + Ok(encode_call) + } + + fn delegate_remove_delegator_vote( + &self, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + class: PollClass, + derivative_index: DerivativeIndex, + ) -> DispatchResult { + Pallet::::send_xcm_remove_delegator_vote_message( + vtoken, + poll_index, + class, + derivative_index, + ) + } + + fn remove_delegator_vote_call_encode( + &self, + class: PollClass, + poll_index: PollIndex, + derivative_index: DerivativeIndex, + ) -> Result, Error> { + let remove_vote_call = + as ConvictionVotingCall>::remove_vote(Some(class), poll_index); + Ok( as UtilityCall>>::as_derivative( + derivative_index, + remove_vote_call, + ) + .encode()) + } +} diff --git a/pallets/vtoken-voting/src/call.rs b/pallets/vtoken-voting/src/agents/relaychain_agent/call.rs similarity index 78% rename from pallets/vtoken-voting/src/call.rs rename to pallets/vtoken-voting/src/agents/relaychain_agent/call.rs index 956d2a15a..8fb6ce1f1 100644 --- a/pallets/vtoken-voting/src/call.rs +++ b/pallets/vtoken-voting/src/agents/relaychain_agent/call.rs @@ -19,23 +19,22 @@ #![allow(ambiguous_glob_reexports)] #![allow(unused_imports)] -use crate::{AccountVote, BalanceOf, Config, DerivativeIndex, PollClass, PollIndex}; +use crate::{traits::*, *}; use parity_scale_codec::{Decode, Encode}; use sp_runtime::{traits::StaticLookup, RuntimeDebug}; -use sp_std::prelude::*; #[cfg(feature = "kusama")] -pub use kusama::*; +pub(in crate::agents::relaychain_agent) use kusama::*; #[cfg(feature = "polkadot")] -pub use polkadot::*; +pub(in crate::agents::relaychain_agent) use polkadot::*; #[cfg(feature = "kusama")] -mod kusama { - use crate::*; +pub(in crate::agents::relaychain_agent) mod kusama { + use crate::agents::relaychain_agent::call::*; #[derive(Encode, Decode, RuntimeDebug)] - pub enum RelayCall { + pub(in crate::agents::relaychain_agent) enum RelayCall { #[codec(index = 20)] ConvictionVoting(ConvictionVoting), #[codec(index = 24)] @@ -44,11 +43,11 @@ mod kusama { } #[cfg(feature = "polkadot")] -mod polkadot { - use crate::*; +pub(in crate::agents::relaychain_agent) mod polkadot { + use crate::agents::relaychain_agent::call::*; #[derive(Encode, Decode, RuntimeDebug)] - pub enum RelayCall { + pub(in crate::agents::relaychain_agent) enum RelayCall { #[codec(index = 20)] ConvictionVoting(ConvictionVoting), #[codec(index = 26)] @@ -57,7 +56,7 @@ mod polkadot { } #[derive(Encode, Decode, RuntimeDebug, Clone)] -pub enum ConvictionVoting { +pub(in crate::agents::relaychain_agent) enum ConvictionVoting { #[codec(index = 0)] Vote(#[codec(compact)] PollIndex, AccountVote>), #[codec(index = 3)] @@ -66,12 +65,6 @@ pub enum ConvictionVoting { RemoveVote(Option, PollIndex), } -pub trait ConvictionVotingCall { - fn vote(poll_index: PollIndex, vote: AccountVote>) -> Self; - - fn remove_vote(class: Option, poll_index: PollIndex) -> Self; -} - impl ConvictionVotingCall for RelayCall { fn vote(poll_index: PollIndex, vote: AccountVote>) -> Self { Self::ConvictionVoting(ConvictionVoting::Vote(poll_index, vote)) @@ -83,19 +76,13 @@ impl ConvictionVotingCall for RelayCall { } #[derive(Encode, Decode, RuntimeDebug, Clone)] -pub enum Utility { +pub(in crate::agents::relaychain_agent) enum Utility { #[codec(index = 1)] AsDerivative(DerivativeIndex, Box), #[codec(index = 2)] BatchAll(Vec), } -pub trait UtilityCall { - fn as_derivative(derivative_index: DerivativeIndex, call: Call) -> Call; - - fn batch_all(calls: Vec) -> Call; -} - impl UtilityCall> for RelayCall { fn as_derivative(derivative_index: DerivativeIndex, call: Self) -> Self { Self::Utility(Utility::AsDerivative(derivative_index, Box::new(call))) diff --git a/pallets/vtoken-voting/src/agents/relaychain_agent/mod.rs b/pallets/vtoken-voting/src/agents/relaychain_agent/mod.rs new file mode 100644 index 000000000..321c37045 --- /dev/null +++ b/pallets/vtoken-voting/src/agents/relaychain_agent/mod.rs @@ -0,0 +1,22 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub mod agent; +mod call; + +pub use agent::*; diff --git a/pallets/vtoken-voting/src/lib.rs b/pallets/vtoken-voting/src/lib.rs index 5d99397b8..95d4bd80b 100644 --- a/pallets/vtoken-voting/src/lib.rs +++ b/pallets/vtoken-voting/src/lib.rs @@ -27,41 +27,48 @@ mod mock; #[cfg(test)] mod tests; -mod call; +mod agents; mod vote; // pub mod migration; +pub mod traits; pub mod weights; -use crate::vote::{Casting, Tally, Voting}; -pub use crate::{ - call::*, - vote::{AccountVote, PollStatus, ReferendumInfo, ReferendumStatus, VoteRole}, +pub use crate::vote::{AccountVote, PollStatus, ReferendumInfo, ReferendumStatus, VoteRole}; +use crate::{ + agents::{BifrostAgent, RelaychainAgent}, + traits::VotingAgent, + vote::{Casting, Tally, Voting}, }; use bifrost_primitives::{ - currency::{VDOT, VKSM}, + currency::{BNC, DOT, KSM, VBNC, VDOT, VKSM}, traits::{DerivativeAccountHandler, VTokenSupplyProvider, XcmDestWeightAndFeeHandler}, CurrencyId, DerivativeIndex, XcmOperationType, }; use cumulus_primitives_core::{ParaId, QueryId, Response}; use frame_support::{ - dispatch::GetDispatchInfo, + dispatch::{GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*, traits::{Get, LockIdentifier}, }; -use frame_system::pallet_prelude::{BlockNumberFor, *}; +use frame_system::{ + pallet_prelude::{BlockNumberFor, *}, + RawOrigin, +}; use orml_traits::{MultiCurrency, MultiLockableCurrency}; pub use pallet::*; +pub use pallet_conviction_voting::AccountVote as ConvictionVotingAccountVote; use pallet_conviction_voting::{Conviction, UnvoteScope, Vote}; use sp_runtime::{ traits::{ - BlockNumberProvider, Bounded, CheckedDiv, CheckedMul, Saturating, UniqueSaturatedInto, Zero, + BlockNumberProvider, Bounded, CheckedDiv, CheckedMul, Dispatchable, Saturating, + UniqueSaturatedInto, Zero, }, ArithmeticError, Perbill, }; -use sp_std::prelude::*; +use sp_std::{boxed::Box, vec::Vec}; pub use weights::WeightInfo; -use xcm::v4::{prelude::*, Weight as XcmWeight}; +use xcm::v4::{prelude::*, Location, Weight as XcmWeight}; const CONVICTION_VOTING_ID: LockIdentifier = *b"vtvoting"; @@ -82,9 +89,12 @@ type VotingOf = pub type ReferendumInfoOf = ReferendumInfo, TallyOf>; +type VotingAgentBoxType = Box>; + #[frame_support::pallet] pub mod pallet { use super::*; + use frame_support::traits::CallerTrait; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); @@ -102,7 +112,11 @@ pub mod pallet { type RuntimeCall: IsType<::RuntimeCall> + From> - + GetDispatchInfo; + + GetDispatchInfo + + Dispatchable< + RuntimeOrigin = ::RuntimeOrigin, + PostInfo = PostDispatchInfo, + > + Parameter; type MultiCurrency: MultiLockableCurrency, CurrencyId = CurrencyId>; @@ -110,12 +124,16 @@ pub mod pallet { type ResponseOrigin: EnsureOrigin< ::RuntimeOrigin, - Success = xcm::v4::Location, + Success = Location, >; type XcmDestWeightAndFee: XcmDestWeightAndFeeHandler, BalanceOf>; - type DerivativeAccount: DerivativeAccountHandler, BalanceOf>; + type DerivativeAccount: DerivativeAccountHandler< + CurrencyIdOf, + BalanceOf, + AccountIdOf, + >; type RelaychainBlockNumberProvider: BlockNumberProvider>; @@ -136,11 +154,20 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + type PalletsOrigin: CallerTrait; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// A vote has been cast. + /// + /// - `who`: The account that cast the vote. + /// - `vtoken`: The token used for voting. + /// - `poll_index`: The index of the poll being voted on. + /// - `token_vote`: The vote cast using the token. + /// - `delegator_vote`: The vote cast by a delegator. Voted { who: AccountIdOf, vtoken: CurrencyIdOf, @@ -148,61 +175,101 @@ pub mod pallet { token_vote: AccountVote>, delegator_vote: AccountVote>, }, - Unlocked { - who: AccountIdOf, - vtoken: CurrencyIdOf, - poll_index: PollIndex, - }, + + /// A user's vote has been unlocked, allowing them to retrieve their tokens. + /// + /// - `who`: The account whose tokens are unlocked. + /// - `vtoken`: The token that was locked during voting. + /// - `poll_index`: The index of the poll associated with the unlocking. + Unlocked { who: AccountIdOf, vtoken: CurrencyIdOf, poll_index: PollIndex }, + + /// A delegator's vote has been removed. + /// + /// - `who`: The account that dispatched remove_delegator_vote. + /// - `vtoken`: The token associated with the delegator's vote. + /// - `derivative_index`: The index of the derivative. DelegatorVoteRemoved { who: AccountIdOf, vtoken: CurrencyIdOf, derivative_index: DerivativeIndex, }, - DelegatorAdded { - vtoken: CurrencyIdOf, - derivative_index: DerivativeIndex, - }, + + /// A delegator has been added. + /// + /// - `vtoken`: The token associated with the delegator. + /// - `derivative_index`: The index of the derivative being added for the delegator. + DelegatorAdded { vtoken: CurrencyIdOf, derivative_index: DerivativeIndex }, + + /// A new referendum information has been created. + /// + /// - `vtoken`: The token associated with the referendum. + /// - `poll_index`: The index of the poll. + /// - `info`: The referendum information (details about the poll). ReferendumInfoCreated { vtoken: CurrencyIdOf, poll_index: PollIndex, info: ReferendumInfoOf, }, + + /// Referendum information has been updated. + /// + /// - `vtoken`: The token associated with the referendum. + /// - `poll_index`: The index of the poll. + /// - `info`: The updated referendum information. ReferendumInfoSet { vtoken: CurrencyIdOf, poll_index: PollIndex, info: ReferendumInfoOf, }, - VoteLockingPeriodSet { - vtoken: CurrencyIdOf, - locking_period: BlockNumberFor, - }, - UndecidingTimeoutSet { - vtoken: CurrencyIdOf, - undeciding_timeout: BlockNumberFor, - }, - ReferendumKilled { - vtoken: CurrencyIdOf, - poll_index: PollIndex, - }, - VoteNotified { - vtoken: CurrencyIdOf, - poll_index: PollIndex, - success: bool, - }, + + /// The vote locking period has been set. + /// + /// - `vtoken`: The token for which the locking period is being set. + /// - `locking_period`: The period for which votes will be locked (in block numbers). + VoteLockingPeriodSet { vtoken: CurrencyIdOf, locking_period: BlockNumberFor }, + + /// The undeciding timeout period has been set. + /// + /// - `vtoken`: The token associated with the timeout. + /// - `undeciding_timeout`: The period of time before a poll is considered undecided. + UndecidingTimeoutSet { vtoken: CurrencyIdOf, undeciding_timeout: BlockNumberFor }, + + /// A referendum has been killed (cancelled or ended). + /// + /// - `vtoken`: The token associated with the referendum. + /// - `poll_index`: The index of the poll being killed. + ReferendumKilled { vtoken: CurrencyIdOf, poll_index: PollIndex }, + + /// A notification about the result of a vote has been sent. + /// + /// - `vtoken`: The token associated with the poll. + /// - `poll_index`: The index of the poll. + /// - `success`: Whether the notification was successful or not. + VoteNotified { vtoken: CurrencyIdOf, poll_index: PollIndex, success: bool }, + + /// A notification about the removal of a delegator's vote has been sent. + /// + /// - `vtoken`: The token associated with the poll. + /// - `poll_index`: The index of the poll. + /// - `success`: Whether the notification was successful or not. DelegatorVoteRemovedNotified { vtoken: CurrencyIdOf, poll_index: PollIndex, success: bool, }, - ResponseReceived { - responder: xcm::v4::Location, - query_id: QueryId, - response: Response, - }, - VoteCapRatioSet { - vtoken: CurrencyIdOf, - vote_cap_ratio: Perbill, - }, + + /// A response has been received from a specific location. + /// + /// - `responder`: The location that sent the response. + /// - `query_id`: The ID of the query that was responded to. + /// - `response`: The content of the response. + ResponseReceived { responder: Location, query_id: QueryId, response: Response }, + + /// The vote cap ratio has been set. + /// + /// - `vtoken`: The token associated with the cap. + /// - `vote_cap_ratio`: The maximum allowed ratio for the vote. + VoteCapRatioSet { vtoken: CurrencyIdOf, vote_cap_ratio: Perbill }, } #[pallet::error] @@ -247,6 +314,8 @@ pub mod pallet { InvalidConviction, /// The given value is out of range. OutOfRange, + InvalidCallDispatch, + CallDecodeFailed, } /// Information concerning any given referendum. @@ -366,20 +435,25 @@ pub mod pallet { #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { - pub delegators: (CurrencyIdOf, Vec), + pub delegators: Vec<(CurrencyIdOf, Vec)>, pub undeciding_timeouts: Vec<(CurrencyIdOf, BlockNumberFor)>, - pub vote_cap_ratio: (CurrencyIdOf, Perbill), + pub vote_cap_ratio: Vec<(CurrencyIdOf, Perbill)>, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - let (vtoken, delegators) = &self.delegators; - Delegators::::insert(vtoken, BoundedVec::truncate_from(delegators.clone())); + self.delegators.iter().for_each(|(vtoken, delegators)| { + Delegators::::insert(vtoken, BoundedVec::truncate_from(delegators.clone())); + }); + self.undeciding_timeouts.iter().for_each(|(vtoken, undeciding_timeout)| { UndecidingTimeout::::insert(vtoken, undeciding_timeout); }); - VoteCapRatio::::insert(self.vote_cap_ratio.0, self.vote_cap_ratio.1); + + self.vote_cap_ratio.iter().for_each(|(vtoken, cap_ratio)| { + VoteCapRatio::::insert(vtoken, cap_ratio); + }); } } @@ -435,7 +509,7 @@ pub mod pallet { pub fn vote( origin: OriginFor, vtoken: CurrencyIdOf, - #[pallet::compact] poll_index: PollIndex, + poll_index: PollIndex, vtoken_vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -479,42 +553,14 @@ pub mod pallet { Ok(()) })?; - // send XCM message - let vote_calls = new_delegator_votes - .iter() - .map(|(_derivative_index, vote)| { - as ConvictionVotingCall>::vote(poll_index, *vote) - }) - .collect::>(); - let vote_call = if vote_calls.len() == 1 { - vote_calls.into_iter().nth(0).ok_or(Error::::NoData)? - } else { - ensure!(false, Error::::NoPermissionYet); - as UtilityCall>>::batch_all(vote_calls) - }; - let notify_call = Call::::notify_vote { query_id: 0, response: Default::default() }; - let (weight, extra_fee) = T::XcmDestWeightAndFee::get_operation_weight_and_fee( - CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?, - XcmOperationType::Vote, - ) - .ok_or(Error::::NoData)?; - - let derivative_index = new_delegator_votes[0].0; - Self::send_xcm_with_notify( - derivative_index, - vote_call, - notify_call, - weight, - extra_fee, - |query_id| { - if !submitted { - PendingReferendumInfo::::insert(query_id, (vtoken, poll_index)); - } - PendingVotingInfo::::insert( - query_id, - (vtoken, poll_index, derivative_index, who.clone(), maybe_old_vote), - ) - }, + let voting_agent = Self::get_voting_agent(&vtoken)?; + voting_agent.delegate_vote( + who.clone(), + vtoken, + poll_index, + submitted, + new_delegator_votes.clone(), + maybe_old_vote, )?; Self::deposit_event(Event::::Voted { @@ -567,29 +613,12 @@ pub mod pallet { ensure!(DelegatorVotes::::get(vtoken, poll_index).len() > 0, Error::::NoData); Self::ensure_referendum_expired(vtoken, poll_index)?; - let notify_call = Call::::notify_remove_delegator_vote { - query_id: 0, - response: Default::default(), - }; - let remove_vote_call = - as ConvictionVotingCall>::remove_vote(Some(class), poll_index); - let (weight, extra_fee) = T::XcmDestWeightAndFee::get_operation_weight_and_fee( - CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?, - XcmOperationType::RemoveVote, - ) - .ok_or(Error::::NoData)?; - Self::send_xcm_with_notify( + let voting_agent = Self::get_voting_agent(&vtoken)?; + voting_agent.delegate_remove_delegator_vote( + vtoken, + poll_index, + class, derivative_index, - remove_vote_call, - notify_call, - weight, - extra_fee, - |query_id| { - PendingRemoveDelegatorVote::::insert( - query_id, - (vtoken, poll_index, derivative_index), - ); - }, )?; Self::deposit_event(Event::::DelegatorVoteRemoved { who, vtoken, derivative_index }); @@ -710,33 +739,15 @@ pub mod pallet { if let Some((vtoken, poll_index, derivative_index, who, maybe_old_vote)) = PendingVotingInfo::::get(query_id) { - if !success { - // rollback vote - let _ = PendingDelegatorVotes::::clear(u32::MAX, None); - Self::try_remove_vote(&who, vtoken, poll_index, UnvoteScope::Any)?; - Self::update_lock(&who, vtoken)?; - if let Some((old_vote, vtoken_balance)) = maybe_old_vote { - Self::try_vote(&who, vtoken, poll_index, old_vote, vtoken_balance)?; - } - } else { - if !VoteDelegatorFor::::contains_key((&who, vtoken, poll_index)) { - VoteDelegatorFor::::insert((&who, vtoken, poll_index), derivative_index); - } - DelegatorVotes::::remove(vtoken, poll_index); - DelegatorVotes::::try_mutate( - vtoken, - poll_index, - |item| -> DispatchResult { - for (derivative_index, vote) in - PendingDelegatorVotes::::take(vtoken, poll_index).iter() - { - item.try_push((*derivative_index, *vote)) - .map_err(|_| Error::::TooMany)?; - } - Ok(()) - }, - )?; - } + Self::handle_vote_result( + success, + who, + vtoken, + poll_index, + maybe_old_vote, + derivative_index, + )?; + PendingVotingInfo::::remove(query_id); Self::deposit_event(Event::::VoteNotified { vtoken, poll_index, success }); } @@ -797,7 +808,7 @@ pub mod pallet { { let success = Response::DispatchResult(MaybeErrorCode::Success) == response; if success { - DelegatorVotes::::remove(vtoken, poll_index); + Self::handle_remove_delegator_vote_success(vtoken, poll_index); } PendingRemoveDelegatorVote::::remove(query_id); Self::deposit_event(Event::::DelegatorVoteRemovedNotified { @@ -828,6 +839,201 @@ pub mod pallet { } impl Pallet { + pub(crate) fn handle_remove_delegator_vote_success( + vtoken: CurrencyIdOf, + poll_index: PollIndex, + ) { + DelegatorVotes::::remove(vtoken, poll_index); + } + + pub(crate) fn handle_vote_result( + success: bool, + who: AccountIdOf, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + maybe_old_vote: Option<(AccountVote>, BalanceOf)>, + derivative_index: DerivativeIndex, + ) -> DispatchResult { + if !success { + // rollback vote + let _ = PendingDelegatorVotes::::clear(u32::MAX, None); + Self::try_remove_vote(&who, vtoken, poll_index, UnvoteScope::Any)?; + Self::update_lock(&who, vtoken)?; + if let Some((old_vote, vtoken_balance)) = maybe_old_vote { + Self::try_vote(&who, vtoken, poll_index, old_vote, vtoken_balance)?; + } + } else { + if !VoteDelegatorFor::::contains_key((&who, vtoken, poll_index)) { + VoteDelegatorFor::::insert((&who, vtoken, poll_index), derivative_index); + } + DelegatorVotes::::remove(vtoken, poll_index); + DelegatorVotes::::try_mutate(vtoken, poll_index, |item| -> DispatchResult { + for (derivative_index, vote) in + PendingDelegatorVotes::::take(vtoken, poll_index).iter() + { + item.try_push((*derivative_index, *vote)) + .map_err(|_| Error::::TooMany)?; + } + Ok(()) + })?; + } + + Ok(()) + } + + pub(crate) fn send_xcm_vote_message( + who: AccountIdOf, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + submitted: bool, + new_delegator_votes: Vec<(DerivativeIndex, AccountVote>)>, + maybe_old_vote: Option<(AccountVote>, BalanceOf)>, + ) -> DispatchResult { + let notify_call = Call::::notify_vote { query_id: 0, response: Default::default() }; + let (weight, extra_fee) = T::XcmDestWeightAndFee::get_operation_weight_and_fee( + CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?, + XcmOperationType::Vote, + ) + .ok_or(Error::::NoData)?; + + let derivative_index = new_delegator_votes[0].0; + + let voting_agent = Self::get_voting_agent(&vtoken)?; + let encode_call = voting_agent.vote_call_encode( + new_delegator_votes.clone(), + poll_index, + derivative_index, + )?; + + Self::send_xcm_with_notify( + voting_agent.location(), + encode_call, + notify_call, + weight, + extra_fee, + |query_id| { + if !submitted { + PendingReferendumInfo::::insert(query_id, (vtoken, poll_index)); + } + PendingVotingInfo::::insert( + query_id, + (vtoken, poll_index, derivative_index, who.clone(), maybe_old_vote), + ) + }, + )?; + + Ok(()) + } + + pub(crate) fn send_xcm_remove_delegator_vote_message( + vtoken: CurrencyIdOf, + poll_index: PollIndex, + class: PollClass, + derivative_index: DerivativeIndex, + ) -> DispatchResult { + let voting_agent = Self::get_voting_agent(&vtoken)?; + let encode_call = voting_agent.remove_delegator_vote_call_encode( + class, + poll_index, + derivative_index, + )?; + let notify_call = Call::::notify_remove_delegator_vote { + query_id: 0, + response: Default::default(), + }; + + let (weight, extra_fee) = T::XcmDestWeightAndFee::get_operation_weight_and_fee( + CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?, + XcmOperationType::RemoveVote, + ) + .ok_or(Error::::NoData)?; + + Self::send_xcm_with_notify( + voting_agent.location(), + encode_call, + notify_call, + weight, + extra_fee, + |query_id| { + PendingRemoveDelegatorVote::::insert( + query_id, + (vtoken, poll_index, derivative_index), + ); + }, + )?; + + Ok(()) + } + + pub(crate) fn send_xcm_with_notify( + responder_location: Location, + encode_call: Vec, + notify_call: Call, + transact_weight: XcmWeight, + extra_fee: BalanceOf, + f: impl FnOnce(QueryId) -> (), + ) -> DispatchResult { + let now = frame_system::Pallet::::block_number(); + let timeout = now.saturating_add(T::QueryTimeout::get()); + let notify_runtime_call = ::RuntimeCall::from(notify_call); + let notify_call_weight = notify_runtime_call.get_dispatch_info().weight; + let query_id = pallet_xcm::Pallet::::new_notify_query( + responder_location.clone(), + notify_runtime_call, + timeout, + xcm::v4::Junctions::Here, + ); + f(query_id); + + let xcm_message = Self::construct_xcm_message( + encode_call, + extra_fee, + transact_weight, + notify_call_weight, + query_id, + )?; + + xcm::v4::send_xcm::(responder_location, xcm_message) + .map_err(|_| Error::::XcmFailure)?; + + Ok(()) + } + + pub(crate) fn construct_xcm_message( + call: Vec, + extra_fee: BalanceOf, + transact_weight: XcmWeight, + notify_call_weight: XcmWeight, + query_id: QueryId, + ) -> Result, Error> { + let para_id = T::ParachainId::get().into(); + let asset = Asset { + id: AssetId(Location::here()), + fun: Fungible(UniqueSaturatedInto::::unique_saturated_into(extra_fee)), + }; + let xcm_message = sp_std::vec![ + WithdrawAsset(asset.clone().into()), + BuyExecution { fees: asset, weight_limit: Unlimited }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: transact_weight, + call: call.into(), + }, + ReportTransactStatus(QueryResponseInfo { + destination: Location::from(Parachain(para_id)), + query_id, + max_weight: notify_call_weight, + }), + RefundSurplus, + DepositAsset { + assets: All.into(), + beneficiary: Location::new(0, [Parachain(para_id)]), + }, + ]; + + Ok(Xcm(xcm_message)) + } + fn try_vote( who: &AccountIdOf, vtoken: CurrencyIdOf, @@ -1004,79 +1210,8 @@ pub mod pallet { } } - fn send_xcm_with_notify( - derivative_index: DerivativeIndex, - call: RelayCall, - notify_call: Call, - transact_weight: XcmWeight, - extra_fee: BalanceOf, - f: impl FnOnce(QueryId) -> (), - ) -> DispatchResult { - let responder = xcm::v4::Location::parent(); - let now = frame_system::Pallet::::block_number(); - let timeout = now.saturating_add(T::QueryTimeout::get()); - let notify_runtime_call = ::RuntimeCall::from(notify_call); - let notify_call_weight = notify_runtime_call.get_dispatch_info().weight; - let query_id = pallet_xcm::Pallet::::new_notify_query( - responder, - notify_runtime_call, - timeout, - xcm::v4::Junctions::Here, - ); - f(query_id); - - let xcm_message = Self::construct_xcm_message( - as UtilityCall>>::as_derivative(derivative_index, call) - .encode(), - extra_fee, - transact_weight, - notify_call_weight, - query_id, - )?; - - xcm::v4::send_xcm::(Parent.into(), xcm_message) - .map_err(|_e| Error::::XcmFailure)?; - - Ok(()) - } - - fn construct_xcm_message( - call: Vec, - extra_fee: BalanceOf, - transact_weight: XcmWeight, - notify_call_weight: XcmWeight, - query_id: QueryId, - ) -> Result, Error> { - let para_id = T::ParachainId::get().into(); - let asset = Asset { - id: AssetId(Location::here()), - fun: Fungible(UniqueSaturatedInto::::unique_saturated_into(extra_fee)), - }; - let xcm_message = sp_std::vec![ - WithdrawAsset(asset.clone().into()), - BuyExecution { fees: asset, weight_limit: Unlimited }, - Transact { - origin_kind: OriginKind::SovereignAccount, - require_weight_at_most: transact_weight, - call: call.into(), - }, - ReportTransactStatus(QueryResponseInfo { - destination: Location::from(Parachain(para_id)), - query_id, - max_weight: notify_call_weight, - }), - RefundSurplus, - DepositAsset { - assets: All.into(), - beneficiary: Location::new(0, [Parachain(para_id)]), - }, - ]; - - Ok(Xcm(xcm_message)) - } - fn ensure_vtoken(vtoken: &CurrencyIdOf) -> Result<(), DispatchError> { - ensure!([VKSM, VDOT].contains(vtoken), Error::::VTokenNotSupport); + ensure!([VKSM, VDOT, VBNC].contains(vtoken), Error::::VTokenNotSupport); Ok(()) } @@ -1156,7 +1291,7 @@ pub mod pallet { fn ensure_xcm_response_or_governance( origin: OriginFor, - ) -> Result { + ) -> Result { let responder = T::ResponseOrigin::ensure_origin(origin.clone()).or_else(|_| { T::ControlOrigin::ensure_origin(origin).map(|_| xcm::v4::Junctions::Here.into()) })?; @@ -1293,5 +1428,26 @@ pub mod pallet { Ok(delegator_votes) } + + pub(crate) fn get_voting_agent( + currency_id: &CurrencyIdOf, + ) -> Result, Error> { + match *currency_id { + VKSM | VDOT => Ok(Box::new(RelaychainAgent::::new(*currency_id)?)), + VBNC => Ok(Box::new(BifrostAgent::::new(*currency_id)?)), + _ => Err(Error::::VTokenNotSupport), + } + } + + pub(crate) fn convert_vtoken_to_dest_location( + vtoken: CurrencyId, + ) -> Result> { + let token = CurrencyId::to_token(&vtoken).map_err(|_| Error::::NoData)?; + match token { + KSM | DOT => Ok(Location::parent()), + BNC => Ok(Location::new(1, [Parachain(T::ParachainId::get().into())])), + _ => Err(Error::::VTokenNotSupport), + } + } } } diff --git a/pallets/vtoken-voting/src/mock.rs b/pallets/vtoken-voting/src/mock.rs index 16a6adfc6..397c7f9a9 100644 --- a/pallets/vtoken-voting/src/mock.rs +++ b/pallets/vtoken-voting/src/mock.rs @@ -21,24 +21,26 @@ use crate as vtoken_voting; use crate::{BalanceOf, DerivativeAccountHandler, DerivativeIndex, DispatchResult}; use bifrost_primitives::{ - currency::{KSM, VBNC, VKSM}, + currency::{DOT, KSM, VBNC, VDOT, VKSM}, traits::XcmDestWeightAndFeeHandler, CurrencyId, MockXcmRouter, VTokenSupplyProvider, XcmOperationType, BNC, }; use cumulus_primitives_core::ParaId; use frame_support::{ derive_impl, ord_parameter_types, - pallet_prelude::Weight, + pallet_prelude::{DispatchError, Weight}, parameter_types, - traits::{Everything, Get, Nothing}, + traits::{ConstU64, Everything, Get, Nothing, PollStatus, Polling, VoteTally}, weights::RuntimeDbWeight, }; use frame_system::EnsureRoot; +use pallet_conviction_voting::{Tally, TallyOf}; use pallet_xcm::EnsureResponse; use sp_runtime::{ traits::{BlockNumberProvider, ConstU32, IdentityLookup}, BuildStorage, Perbill, }; +use std::collections::BTreeMap; use xcm::{prelude::*, v3::MultiLocation}; use xcm_builder::{FixedWeightBounds, FrameTransactionalProcessor}; use xcm_executor::XcmExecutor; @@ -63,6 +65,7 @@ frame_support::construct_runtime!( Currencies: bifrost_currencies, PolkadotXcm: pallet_xcm, VtokenVoting: vtoken_voting, + ConvictionVoting: pallet_conviction_voting = 36, } ); @@ -103,7 +106,7 @@ impl pallet_balances::Config for Runtime { type DustRemoval = (); type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; - type MaxLocks = (); + type MaxLocks = ConstU32<100>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); @@ -113,10 +116,107 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = ConstU32<0>; } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TestPollState { + Ongoing(TallyOf, u8), + Completed(u64, bool), +} +use TestPollState::*; + +parameter_types! { + pub static Polls: BTreeMap = (0u8..=255) + .map(|i| (i, Ongoing(Tally::from_parts(0, 0, 0), 0))) + .collect(); +} + +pub struct TestPolls; +impl Polling> for TestPolls { + type Index = u8; + type Votes = u128; + type Moment = u64; + type Class = u8; + fn classes() -> Vec { + vec![0, 1, 2] + } + fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { + Polls::get().remove(&index).and_then(|x| { + if let TestPollState::Ongoing(t, c) = x { + Some((t, c)) + } else { + None + } + }) + } + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, u64, u8>) -> R, + ) -> R { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }; + Polls::set(polls); + r + } + fn try_access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, u64, u8>) -> Result, + ) -> Result { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }?; + Polls::set(polls); + Ok(r) + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(class: Self::Class) -> Result { + let mut polls = Polls::get(); + let i = polls.keys().rev().next().map_or(0, |x| x + 1); + polls.insert(i, Ongoing(Tally::new(0), class)); + Polls::set(polls); + Ok(i) + } + + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { + let mut polls = Polls::get(); + match polls.get(&index) { + Some(Ongoing(..)) => {}, + _ => return Err(()), + } + let now = frame_system::Pallet::::block_number(); + polls.insert(index, Completed(now, approved)); + Polls::set(polls); + Ok(()) + } +} + +impl pallet_conviction_voting::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type VoteLockingPeriod = ConstU64<3>; + type MaxVotes = ConstU32<512>; + type MaxTurnout = frame_support::traits::TotalIssuanceOf; + type Polls = TestPolls; +} + orml_traits::parameter_type_with_key! { pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { match currency_id { + &DOT => 0, &KSM => 0, + &VDOT => 0, &VBNC => 0, &VKSM => 0, _ => 0, @@ -239,7 +339,7 @@ impl XcmDestWeightAndFeeHandler> for XcmDestWeigh } pub struct DerivativeAccount; -impl DerivativeAccountHandler for DerivativeAccount { +impl DerivativeAccountHandler for DerivativeAccount { fn check_derivative_index_exists( _token: CurrencyId, _derivative_index: DerivativeIndex, @@ -254,6 +354,10 @@ impl DerivativeAccountHandler for DerivativeAccount { Some(xcm::v3::Parent.into()) } + fn get_account_id(_token: CurrencyId, _derivative_index: DerivativeIndex) -> Option { + Some(CHARLIE) + } + fn get_stake_info( token: CurrencyId, derivative_index: DerivativeIndex, @@ -337,26 +441,51 @@ impl vtoken_voting::Config for Runtime { type QueryTimeout = QueryTimeout; type ReferendumCheckInterval = ReferendumCheckInterval; type WeightInfo = (); + type PalletsOrigin = OriginCaller; } pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(ALICE, 10), (BOB, 20), (CHARLIE, 30)], + balances: vec![(ALICE, 10), (BOB, 20), (CHARLIE, 3000)], } .assimilate_storage(&mut t) .unwrap(); orml_tokens::GenesisConfig:: { - balances: vec![(1, VKSM, 10), (2, VKSM, 20), (3, VKSM, 30), (4, VKSM, 40), (5, VKSM, 50)], + balances: vec![ + (1, VKSM, 10), + (2, VKSM, 20), + (3, VKSM, 30), + (4, VKSM, 40), + (5, VKSM, 50), + (1, VDOT, 10), + (2, VDOT, 20), + (3, VDOT, 30), + (4, VDOT, 40), + (5, VDOT, 50), + (1, VBNC, 10), + (2, VBNC, 20), + (3, VBNC, 30), + (4, VBNC, 40), + (5, VBNC, 50), + ], } .assimilate_storage(&mut t) .unwrap(); vtoken_voting::GenesisConfig:: { - delegators: (VKSM, vec![0, 1, 2, 3, 4, 5, 10, 11, 15, 20, 21]), - undeciding_timeouts: vec![(VKSM, 100)], - vote_cap_ratio: (VKSM, Perbill::from_percent(10)), + delegators: vec![ + (VKSM, vec![0, 1, 2, 3, 4, 5, 10, 11, 15, 20, 21]), + (VDOT, vec![0, 1, 2, 3, 4, 5, 10, 11, 15, 20, 21]), + (VBNC, vec![0, 1, 2, 3, 4, 5, 10, 11, 15, 20, 21]), + ], + undeciding_timeouts: vec![(VDOT, 100), (VKSM, 100), (VBNC, 100)], + vote_cap_ratio: vec![ + (VDOT, Perbill::from_percent(10)), + (VKSM, Perbill::from_percent(10)), + (VBNC, Perbill::from_percent(10)), + ], } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/vtoken-voting/src/tests.rs b/pallets/vtoken-voting/src/tests.rs deleted file mode 100644 index de56e74e0..000000000 --- a/pallets/vtoken-voting/src/tests.rs +++ /dev/null @@ -1,1379 +0,0 @@ -// This file is part of Bifrost. - -// Copyright (C) Liebi Technologies PTE. LTD. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// Ensure we're `no_std` when compiling for Wasm. - -use super::*; -use crate::mock::*; -use bifrost_primitives::currency::{VBNC, VKSM}; -use frame_support::{ - assert_noop, assert_ok, - traits::{ - fungibles::Inspect, - tokens::{Fortitude::Polite, Preservation::Expendable}, - }, - weights::RuntimeDbWeight, -}; -use pallet_conviction_voting::Vote; -use pallet_xcm::Origin as XcmOrigin; - -fn aye(amount: Balance, conviction: u8) -> AccountVote { - let vote = Vote { aye: true, conviction: conviction.try_into().unwrap() }; - AccountVote::Standard { vote, balance: amount } -} - -fn nay(amount: Balance, conviction: u8) -> AccountVote { - let vote = Vote { aye: false, conviction: conviction.try_into().unwrap() }; - AccountVote::Standard { vote, balance: amount } -} - -fn tally(vtoken: CurrencyId, poll_index: u32) -> TallyOf { - VtokenVoting::ensure_referendum_ongoing(vtoken, poll_index) - .expect("No poll") - .tally -} - -fn usable_balance(vtoken: CurrencyId, who: &AccountId) -> Balance { - Tokens::reducible_balance(vtoken, who, Expendable, Polite) -} - -fn origin_response() -> RuntimeOrigin { - XcmOrigin::Response(Parent.into()).into() -} - -fn response_success() -> Response { - Response::DispatchResult(MaybeErrorCode::Success) -} - -fn response_fail() -> Response { - Response::DispatchResult(MaybeErrorCode::Error(BoundedVec::try_from(vec![0u8, 1u8]).unwrap())) -} - -#[test] -fn basic_voting_works() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: ALICE, - vtoken, - poll_index, - token_vote: aye(4, 5), - delegator_vote: aye(200, 0), - })); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - - assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); - - assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - }); -} - -#[test] -fn voting_balance_gets_locked() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - nay(10, 0) - )); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 2, 0)); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - - assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); - - assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - }); -} - -#[test] -fn successful_but_zero_conviction_vote_balance_can_be_unlocked() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(1, 1))); - assert_eq!(usable_balance(vtoken, &ALICE), 9); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(3, 1))); - assert_eq!(usable_balance(vtoken, &ALICE), 7); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(BOB), vtoken, poll_index, nay(20, 0))); - assert_eq!(usable_balance(vtoken, &BOB), 0); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response_success())); - - assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(3), - )); - - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(BOB), vtoken, poll_index)); - assert_eq!(usable_balance(vtoken, &BOB), 20); - - RelaychainDataProvider::set_block_number(13); - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - }); -} - -#[test] -fn unsuccessful_conviction_vote_balance_can_be_unlocked() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - let locking_period = 10; - assert_ok!(VtokenVoting::set_vote_locking_period( - RuntimeOrigin::root(), - vtoken, - locking_period, - )); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(1, 1))); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(BOB), vtoken, poll_index, nay(20, 0))); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); - - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(3), - )); - RelaychainDataProvider::set_block_number(13); - assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); - assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - }); -} - -#[test] -fn ensure_balance_after_unlock() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let poll_index_2 = 4; - let vtoken = VKSM; - let locking_period = 10; - assert_ok!(VtokenVoting::set_vote_locking_period( - RuntimeOrigin::root(), - vtoken, - locking_period, - )); - - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - aye(10, 1) - )); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index_2, - aye(10, 5) - )); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); - - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(3), - )); - RelaychainDataProvider::set_block_number(13); - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 10); - assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); - }); -} - -#[test] -fn ensure_comprehensive_balance_after_unlock() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let poll_index_2 = 4; - let poll_index_3 = 5; - let vtoken = VKSM; - let locking_period = 10; - assert_ok!(VtokenVoting::set_vote_locking_period( - RuntimeOrigin::root(), - vtoken, - locking_period, - )); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 1))); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index_2, - aye(1, 5) - )); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index_3, - aye(2, 5) - )); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response_success())); - - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(3), - )); - RelaychainDataProvider::set_block_number(13); - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); - assert_eq!(usable_balance(vtoken, &ALICE), 8); - assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 2); - assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 2); - - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index_2, - aye(10, 5) - )); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 3, response_success())); - - assert_eq!(usable_balance(vtoken, &ALICE), 0); - assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 10); - assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); - }); -} - -#[test] -fn successful_conviction_vote_balance_stays_locked_for_correct_time() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - let locking_period = 10; - assert_ok!(VtokenVoting::set_vote_locking_period( - RuntimeOrigin::root(), - vtoken, - locking_period, - )); - for i in 1..=5 { - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(i), - vtoken, - poll_index, - aye(10, i as u8) - )); - assert_ok!(VtokenVoting::notify_vote(origin_response(), i - 1, response_success())); - } - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(3), - )); - RelaychainDataProvider::set_block_number(163); - for i in 1..=5 { - assert_ok!(VtokenVoting::try_remove_vote(&i, vtoken, poll_index, UnvoteScope::Any)); - } - for i in 1..=5 { - assert_ok!(VtokenVoting::update_lock(&i, vtoken)); - assert_eq!(usable_balance(vtoken, &i), 10 * i as u128); - } - }); -} - -#[test] -fn lock_amalgamation_valid_with_multiple_removed_votes() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let response = response_success(); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 0, aye(5, 1))); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response.clone())); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5),]).unwrap() - ); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(10, 1))); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response.clone())); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]).unwrap() - ); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(5, 1))); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response.clone())); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5),]).unwrap() - ); - assert_eq!(usable_balance(vtoken, &ALICE), 5); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 2, aye(10, 2))); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 3, response.clone())); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]).unwrap() - ); - - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - 0, - ReferendumInfoOf::::Completed(1), - )); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - 1, - ReferendumInfoOf::::Completed(1), - )); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - 2, - ReferendumInfoOf::::Completed(1), - )); - - let locking_period = 10; - assert_ok!(VtokenVoting::set_vote_locking_period( - RuntimeOrigin::root(), - vtoken, - locking_period, - )); - assert_eq!(VoteLockingPeriod::::get(vtoken), Some(10)); - - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]).unwrap() - ); - - RelaychainDataProvider::set_block_number(10); - assert_noop!( - VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0), - Error::::NoPermissionYet - ); - assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - - RelaychainDataProvider::set_block_number(11); - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0)); - assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]).unwrap() - ); - - RelaychainDataProvider::set_block_number(11); - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 1)); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10)]).unwrap() - ); - - RelaychainDataProvider::set_block_number(21); - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 2)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![]).unwrap() - ); - }); -} - -#[test] -fn removed_votes_when_referendum_killed() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let response = response_success(); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 0, aye(5, 1))); - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(10, 1))); - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 2, aye(5, 2))); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - - assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response.clone())); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response.clone())); - assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response.clone())); - - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - 0, - ReferendumInfoOf::::Completed(1), - )); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - 1, - ReferendumInfoOf::::Completed(1), - )); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - 2, - ReferendumInfoOf::::Completed(1), - )); - - assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 0)); - assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 1)); - assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 2)); - - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]).unwrap() - ); - - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0)); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]).unwrap() - ); - - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 1)); - assert_eq!(usable_balance(vtoken, &ALICE), 5); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5)]).unwrap() - ); - - assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 2)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - assert_eq!( - ClassLocksFor::::get(&ALICE), - BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![]).unwrap() - ); - }); -} - -#[test] -fn errors_with_vote_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - - assert_noop!( - VtokenVoting::vote(RuntimeOrigin::signed(1), VBNC, 0, aye(10, 0)), - Error::::VTokenNotSupport - ); - assert_noop!( - VtokenVoting::vote(RuntimeOrigin::signed(1), vtoken, 3, aye(11, 0)), - Error::::InsufficientFunds - ); - - for poll_index in 0..256 { - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(1), - vtoken, - poll_index, - aye(10, 0) - )); - } - assert_noop!( - VtokenVoting::vote(RuntimeOrigin::signed(1), vtoken, 256, aye(10, 0)), - Error::::MaxVotesReached - ); - }); -} - -#[test] -fn kill_referendum_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let poll_index = 3; - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(5, 1))); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(1), - )); - assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, poll_index)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ReferendumKilled { - vtoken, - poll_index, - })); - }); -} - -#[test] -fn kill_referendum_with_origin_signed_fails() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let poll_index = 3; - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(5, 1))); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(1), - )); - assert_noop!( - VtokenVoting::kill_referendum(RuntimeOrigin::signed(ALICE), vtoken, poll_index), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn add_delegator_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let derivative_index: DerivativeIndex = 100; - - assert_ok!(VtokenVoting::add_delegator(RuntimeOrigin::root(), vtoken, derivative_index,)); - - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::DelegatorAdded { - vtoken, - derivative_index, - })); - }); -} - -#[test] -fn set_referendum_status_works() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - let info = ReferendumInfo::Completed(3); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - info.clone(), - )); - - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ReferendumInfoSet { - vtoken, - poll_index, - info, - })); - }); -} - -#[test] -fn set_referendum_status_without_vote_should_fail() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - let info = ReferendumInfo::Completed(3); - - assert_noop!( - VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - info.clone(), - ), - Error::::NoData - ); - }); -} - -#[test] -fn set_referendum_status_with_origin_signed_should_fail() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - let info = ReferendumInfo::Completed(3); - - assert_noop!( - VtokenVoting::set_referendum_status( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - info.clone(), - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn set_vote_locking_period_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let locking_period = 100; - - assert_ok!(VtokenVoting::set_vote_locking_period( - RuntimeOrigin::root(), - vtoken, - locking_period, - )); - - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::VoteLockingPeriodSet { - vtoken, - locking_period, - })); - }); -} - -#[test] -fn set_vote_locking_period_with_origin_signed_should_fail() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let locking_period = 100; - - assert_noop!( - VtokenVoting::set_vote_locking_period( - RuntimeOrigin::signed(ALICE), - vtoken, - locking_period, - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn set_undeciding_timeout_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let undeciding_timeout = 100; - - assert_ok!(VtokenVoting::set_undeciding_timeout( - RuntimeOrigin::root(), - vtoken, - undeciding_timeout, - )); - - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::UndecidingTimeoutSet { - vtoken, - undeciding_timeout, - })); - }); -} - -#[test] -fn set_undeciding_timeout_with_origin_signed_should_fail() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let undeciding_timeout = 100; - - assert_noop!( - VtokenVoting::set_undeciding_timeout( - RuntimeOrigin::signed(ALICE), - vtoken, - undeciding_timeout, - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn notify_vote_success_works() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - let query_id = 0; - let response = response_success(); - let derivative_index = 0; - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); - assert_eq!( - ReferendumInfoFor::::get(vtoken, poll_index), - Some(ReferendumInfo::Ongoing(ReferendumStatus { - submitted: None, - tally: TallyOf::::from_parts(20, 0, 4), - })) - ); - assert_eq!( - PendingDelegatorVotes::::get(vtoken, poll_index), - BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( - derivative_index, - aye(200, 0) - )]) - .unwrap() - ); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: ALICE, - vtoken, - poll_index, - token_vote: aye(4, 5), - delegator_vote: aye(200, 0), - })); - - assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); - assert_eq!( - ReferendumInfoFor::::get(vtoken, poll_index), - Some(ReferendumInfo::Ongoing(ReferendumStatus { - submitted: Some(1), - tally: TallyOf::::from_parts(20, 0, 4), - })) - ); - assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); - assert_eq!( - DelegatorVotes::::get(vtoken, poll_index), - BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( - derivative_index, - aye(200, 0) - )]) - .unwrap() - ); - System::assert_has_event(RuntimeEvent::VtokenVoting(Event::VoteNotified { - vtoken, - poll_index, - success: true, - })); - System::assert_has_event(RuntimeEvent::VtokenVoting(Event::ReferendumInfoCreated { - vtoken, - poll_index, - info: ReferendumInfo::Ongoing(ReferendumStatus { - submitted: Some(1), - tally: TallyOf::::from_parts(20, 0, 4), - }), - })); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { - responder: Parent.into(), - query_id, - response, - })); - }); -} - -#[test] -fn notify_vote_success_max_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - - for poll_index in 0..256 { - RelaychainDataProvider::set_block_number(1); - - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - aye(2, 5) - )); - assert_ok!(VtokenVoting::notify_vote( - origin_response(), - poll_index as QueryId, - response_success() - )); - - RelaychainDataProvider::set_block_number( - 1 + UndecidingTimeout::::get(vtoken).unwrap(), - ); - VtokenVoting::on_idle(Zero::zero(), Weight::MAX); - } - }); -} - -#[test] -fn notify_vote_success_exceed_max_fail() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - - for poll_index in 0..50 { - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - poll_index, - aye(2, 5) - )); - assert_ok!(VtokenVoting::notify_vote( - origin_response(), - poll_index as QueryId, - response_success() - )); - } - let poll_index = 50; - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); - assert_noop!( - VtokenVoting::notify_vote(origin_response(), poll_index as QueryId, response_success()), - Error::::TooMany - ); - }); -} - -#[test] -fn notify_vote_fail_works() { - new_test_ext().execute_with(|| { - let poll_index = 3; - let vtoken = VKSM; - let query_id = 0; - let response = response_fail(); - let derivative_index = 0; - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); - assert_eq!( - ReferendumInfoFor::::get(vtoken, poll_index), - Some(ReferendumInfo::Ongoing(ReferendumStatus { - submitted: None, - tally: TallyOf::::from_parts(20, 0, 4), - })) - ); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); - assert_eq!( - PendingDelegatorVotes::::get(vtoken, poll_index), - BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( - derivative_index, - aye(200, 0) - )]) - .unwrap() - ); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: ALICE, - vtoken, - poll_index, - token_vote: aye(4, 5), - delegator_vote: aye(200, 0), - })); - - assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); - assert_eq!(ReferendumInfoFor::::get(vtoken, poll_index), None); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); - assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { - responder: Parent.into(), - query_id, - response, - })); - }); -} - -#[test] -fn notify_vote_with_no_data_works() { - new_test_ext().execute_with(|| { - let query_id = 0; - let response = response_success(); - - assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { - responder: Parent.into(), - query_id, - response, - })); - }); -} - -#[test] -fn notify_remove_delegator_vote_success_works() { - new_test_ext().execute_with(|| { - let class = 0; - let poll_index = 3; - let vtoken = VKSM; - let mut query_id = 0; - let derivative_index = 0; - let response = response_success(); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); - assert_eq!( - PendingDelegatorVotes::::get(vtoken, poll_index), - BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( - derivative_index, - aye(200, 0) - )]) - .unwrap() - ); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: ALICE, - vtoken, - poll_index, - token_vote: aye(4, 5), - delegator_vote: aye(200, 0), - })); - assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); - assert_eq!( - DelegatorVotes::::get(vtoken, poll_index), - BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( - derivative_index, - aye(200, 0) - )]) - .unwrap() - ); - assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); - - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(3), - )); - assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); - - RelaychainDataProvider::set_block_number(3); - assert_ok!(VtokenVoting::remove_delegator_vote( - RuntimeOrigin::signed(ALICE), - vtoken, - class, - poll_index, - derivative_index, - )); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); - - query_id = 1; - assert_ok!(VtokenVoting::notify_remove_delegator_vote( - origin_response(), - query_id, - response.clone() - )); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); - System::assert_has_event(RuntimeEvent::VtokenVoting(Event::DelegatorVoteRemovedNotified { - vtoken, - poll_index, - success: true, - })); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { - responder: Parent.into(), - query_id, - response, - })); - }); -} - -#[test] -fn notify_remove_delegator_vote_fail_works() { - new_test_ext().execute_with(|| { - let class = 0; - let poll_index = 3; - let vtoken = VKSM; - let mut query_id = 0; - let derivative_index = 0; - let response = response_fail(); - - assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, poll_index, aye(2, 5))); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); - assert_eq!( - PendingDelegatorVotes::::get(vtoken, poll_index), - BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( - derivative_index, - aye(200, 0) - )]) - .unwrap() - ); - assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { - who: ALICE, - vtoken, - poll_index, - token_vote: aye(4, 5), - delegator_vote: aye(200, 0), - })); - assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response_success())); - assert_eq!( - DelegatorVotes::::get(vtoken, poll_index), - BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from(vec![( - derivative_index, - aye(200, 0) - )]) - .unwrap() - ); - assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); - - assert_ok!(VtokenVoting::set_referendum_status( - RuntimeOrigin::root(), - vtoken, - poll_index, - ReferendumInfoOf::::Completed(3), - )); - assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); - - RelaychainDataProvider::set_block_number(3); - assert_ok!(VtokenVoting::remove_delegator_vote( - RuntimeOrigin::signed(ALICE), - vtoken, - class, - poll_index, - derivative_index, - )); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); - - query_id = 1; - assert_ok!(VtokenVoting::notify_remove_delegator_vote( - origin_response(), - query_id, - response.clone() - )); - assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { - responder: Parent.into(), - query_id, - response, - })); - }); -} - -#[test] -fn notify_remove_delegator_vote_with_no_data_works() { - new_test_ext().execute_with(|| { - let query_id = 0; - let response = response_success(); - - assert_ok!(VtokenVoting::notify_remove_delegator_vote( - origin_response(), - query_id, - response.clone(), - )); - System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { - responder: Parent.into(), - query_id, - response, - })); - }); -} - -#[test] -fn on_idle_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - for (index, poll_index) in (0..50).collect::>().iter().enumerate() { - let relay_block_number = index as BlockNumber; - let query_id = index as QueryId; - RelaychainDataProvider::set_block_number(relay_block_number); - assert_ok!(VtokenVoting::vote( - RuntimeOrigin::signed(ALICE), - vtoken, - *poll_index, - aye(2, 5) - )); - assert_ok!(VtokenVoting::notify_vote( - origin_response(), - query_id as QueryId, - response_success() - )); - } - - let count = 30; - RelaychainDataProvider::set_block_number( - count + UndecidingTimeout::::get(vtoken).unwrap(), - ); - let db_weight = RuntimeDbWeight { read: 1, write: 1 }; - let weight = - db_weight.reads(3) + db_weight.reads_writes(1, 2) * count + db_weight.writes(2) * count; - let used_weight = VtokenVoting::on_idle(Zero::zero(), weight); - assert_eq!(used_weight, Weight::from_parts(0, 0)); - - let mut actual_count = 0; - for poll_index in 0..50 { - let relay_block_number = poll_index as BlockNumber; - if ReferendumTimeout::::get( - relay_block_number + UndecidingTimeout::::get(vtoken).unwrap(), - ) - .is_empty() - { - actual_count += 1; - } - } - assert_eq!(actual_count, 31); - }); -} - -#[test] -fn set_vote_cap_ratio_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - - assert_ok!(VtokenVoting::set_vote_cap_ratio( - RuntimeOrigin::root(), - vtoken, - Perbill::from_percent(0) - )); - assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(0)); - - assert_ok!(VtokenVoting::set_vote_cap_ratio( - RuntimeOrigin::root(), - vtoken, - Perbill::from_percent(10) - )); - assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(10)); - - assert_ok!(VtokenVoting::set_vote_cap_ratio( - RuntimeOrigin::root(), - vtoken, - Perbill::from_percent(100) - )); - assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(100)); - }); -} - -#[test] -fn vote_cap_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - assert_eq!(VtokenVoting::vote_cap(vtoken), Ok((u64::MAX / 10) as Balance)); - }); -} - -#[test] -fn vote_to_capital_works() { - new_test_ext().execute_with(|| { - assert_eq!(VtokenVoting::vote_to_capital(Conviction::None, 300), 3000); - assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked1x, 300), 300); - assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked2x, 300), 150); - assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked3x, 300), 100); - assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked4x, 300), 75); - assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked5x, 300), 60); - assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked6x, 300), 50); - }); -} - -#[test] -fn compute_delegator_total_vote_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(10, 0)), Ok(aye(10, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 1)), Ok(aye(20, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 2)), Ok(aye(40, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 3)), Ok(aye(60, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 4)), Ok(aye(80, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 5)), Ok(aye(100, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 6)), Ok(aye(120, 0))); - - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(10, 0)), Ok(nay(10, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 1)), Ok(nay(20, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 2)), Ok(nay(40, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 3)), Ok(nay(60, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 4)), Ok(nay(80, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 5)), Ok(nay(100, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 6)), Ok(nay(120, 0))); - - SimpleVTokenSupplyProvider::set_token_supply(10_000_000); - assert_eq!(VtokenVoting::vote_cap(vtoken), Ok(1_000_000)); - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_000_000, 0)), - Ok(aye(1_000_000, 0)) - ); - for i in 1..=6_u8 { - assert_eq!( - VtokenVoting::compute_delegator_total_vote( - vtoken, - aye(10_000_000 * i as Balance, 0) - ), - Ok(aye(1_000_000, i)) - ); - } - - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(100_000, 1)), - Ok(aye(1_000_000, 0)) - ); - for i in 1..=6_u8 { - assert_eq!( - VtokenVoting::compute_delegator_total_vote( - vtoken, - aye(1_000_000 * i as Balance, 1) - ), - Ok(aye(1_000_000, i)) - ); - } - assert_noop!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(6_000_006, 1)), - Error::::InsufficientFunds - ); - - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(50_000, 2)), - Ok(aye(1_000_000, 0)) - ); - for i in 1..=6_u8 { - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(500_000 * i as Balance, 2)), - Ok(aye(1_000_000, i)) - ); - } - assert_noop!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(3_000_003, 2)), - Error::::InsufficientFunds - ); - - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(33_333, 3)), - Ok(aye(999_990, 0)) - ); - for i in 1..=6_u8 { - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(333_333 * i as Balance, 3)), - Ok(aye(999_999, i)) - ); - } - assert_noop!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(2_000_002, 3)), - Error::::InsufficientFunds - ); - - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(25_000, 4)), - Ok(aye(1_000_000, 0)) - ); - for i in 1..=6_u8 { - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(250_000 * i as Balance, 4)), - Ok(aye(1_000_000, i)) - ); - } - assert_noop!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_500_002, 4)), - Error::::InsufficientFunds - ); - - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(20_000, 5)), - Ok(aye(1_000_000, 0)) - ); - for i in 1..=6_u8 { - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(200_000 * i as Balance, 5)), - Ok(aye(1_000_000, i)) - ); - } - assert_noop!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_200_002, 5)), - Error::::InsufficientFunds - ); - - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(16_666, 6)), - Ok(aye(999_960, 0)) - ); - for i in 1..=6_u8 { - assert_eq!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(166_666 * i as Balance, 6)), - Ok(aye(999_996, i)) - ); - } - assert_noop!( - VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_000_001, 6)), - Error::::InsufficientFunds - ); - }); -} - -#[test] -fn compute_delegator_total_vote_with_low_value_will_loss() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, aye(9, 0)), Ok(aye(0, 0))); - assert_eq!(VtokenVoting::compute_delegator_total_vote(vtoken, nay(9, 0)), Ok(nay(0, 0))); - }); -} - -#[test] -fn allocate_delegator_votes_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - let poll_index = 3; - - for conviction in 0..=6 { - let vote = aye(5e9 as Balance, conviction); - let delegator_votes = VtokenVoting::allocate_delegator_votes(vtoken, poll_index, vote); - assert_eq!( - delegator_votes, - Ok(vec![(0, aye(4294967295, conviction)), (1, aye(705032705, conviction))]) - ); - assert_eq!( - delegator_votes.unwrap().into_iter().map(|(_derivative_index, vote)| vote).fold( - aye(0, conviction), - |mut acc, vote| { - let _ = acc.checked_add(vote); - acc - }, - ), - vote - ); - } - - for conviction in 0..=6 { - let vote = aye(3e10 as Balance, conviction); - let delegator_votes = VtokenVoting::allocate_delegator_votes(vtoken, poll_index, vote); - assert_eq!( - delegator_votes, - Ok(vec![ - (0, aye(4294967295, conviction)), - (1, aye(4294967295, conviction)), - (2, aye(4294967295, conviction)), - (3, aye(4294967295, conviction)), - (4, aye(4294967295, conviction)), - (5, aye(4294967295, conviction)), - (10, aye(4230196230, conviction)) - ]) - ); - assert_eq!( - delegator_votes.unwrap().into_iter().map(|(_derivative_index, vote)| vote).fold( - aye(0, conviction), - |mut acc, vote| { - let _ = acc.checked_add(vote); - acc - }, - ), - vote - ); - } - }); -} - -#[test] -fn tally_convert_works() { - assert_eq!( - TallyOf::::from_parts(10, 9, 0).account_vote(Conviction::Locked1x), - aye(1, 1) - ); - assert_eq!( - TallyOf::::from_parts(10, 11, 0).account_vote(Conviction::Locked1x), - nay(1, 1) - ); - assert_eq!( - TallyOf::::from_parts(10, 10, 0).account_vote(Conviction::Locked1x), - aye(0, 1) - ); -} - -#[test] -fn set_lock_works() { - new_test_ext().execute_with(|| { - let vtoken = VKSM; - - assert_ok!(VtokenVoting::set_lock(&ALICE, vtoken, 10)); - assert_eq!(usable_balance(vtoken, &ALICE), 0); - - assert_ok!(VtokenVoting::set_lock(&ALICE, vtoken, 1)); - assert_eq!(usable_balance(vtoken, &ALICE), 9); - - assert_ok!(VtokenVoting::set_lock(&ALICE, vtoken, 0)); - assert_eq!(usable_balance(vtoken, &ALICE), 10); - }); -} diff --git a/pallets/vtoken-voting/src/tests/common_test.rs b/pallets/vtoken-voting/src/tests/common_test.rs new file mode 100644 index 000000000..721d4aebd --- /dev/null +++ b/pallets/vtoken-voting/src/tests/common_test.rs @@ -0,0 +1,1564 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Ensure we're `no_std` when compiling for Wasm. +use crate::{mock::*, *}; +use bifrost_primitives::currency::VPHA; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + fungibles::Inspect, + tokens::{Fortitude::Polite, Preservation::Expendable}, + }, + weights::RuntimeDbWeight, +}; +use pallet_xcm::Origin as XcmOrigin; + +const TOKENS: &[CurrencyId] = if cfg!(feature = "polkadot") { + &[VDOT] +} else if cfg!(feature = "kusama") { + &[VKSM] +} else { + &[] +}; + +fn aye(amount: Balance, conviction: u8) -> AccountVote { + let vote = Vote { aye: true, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } +} + +fn nay(amount: Balance, conviction: u8) -> AccountVote { + let vote = Vote { aye: false, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } +} + +fn tally(vtoken: CurrencyId, poll_index: u32) -> TallyOf { + VtokenVoting::ensure_referendum_ongoing(vtoken, poll_index) + .expect("No poll") + .tally +} + +fn usable_balance(vtoken: CurrencyId, who: &AccountId) -> Balance { + Tokens::reducible_balance(vtoken, who, Expendable, Polite) +} + +fn origin_response() -> RuntimeOrigin { + XcmOrigin::Response(Parent.into()).into() +} + +fn response_success() -> Response { + Response::DispatchResult(MaybeErrorCode::Success) +} + +fn response_fail() -> Response { + Response::DispatchResult(MaybeErrorCode::Error(BoundedVec::try_from(vec![0u8, 1u8]).unwrap())) +} + +#[test] +fn basic_voting_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { + who: ALICE, + vtoken, + poll_index, + token_vote: aye(4, 5), + delegator_vote: aye(200, 0), + })); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); + + assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); + + assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn voting_balance_gets_locked() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + nay(10, 0) + )); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 2, 0)); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + + assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); + + assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn successful_but_zero_conviction_vote_balance_can_be_unlocked() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(1, 1) + )); + assert_eq!(usable_balance(vtoken, &ALICE), 9); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(3, 1) + )); + assert_eq!(usable_balance(vtoken, &ALICE), 7); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(BOB), + vtoken, + poll_index, + nay(20, 0) + )); + assert_eq!(usable_balance(vtoken, &BOB), 0); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response_success())); + + assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(BOB), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &BOB), 20); + + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn unsuccessful_conviction_vote_balance_can_be_unlocked() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(1, 1) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(BOB), + vtoken, + poll_index, + nay(20, 0) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); + assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn ensure_balance_after_unlock() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let poll_index_2 = 4; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(10, 1) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_2, + aye(10, 5) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 10); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + }); + } +} + +#[test] +fn ensure_comprehensive_balance_after_unlock() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let poll_index_2 = 4; + let poll_index_3 = 5; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 1) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response_success())); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_2, + aye(1, 5) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response_success())); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_3, + aye(2, 5) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response_success())); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &ALICE), 8); + assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 2); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 2); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_2, + aye(10, 5) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 3, response_success())); + + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 10); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + }); + } +} + +#[test] +fn successful_conviction_vote_balance_stays_locked_for_correct_time() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + for i in 1..=5 { + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(i), + vtoken, + poll_index, + aye(10, i as u8) + )); + assert_ok!(VtokenVoting::notify_vote(origin_response(), i - 1, response_success())); + } + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(163); + for i in 1..=5 { + assert_ok!(VtokenVoting::try_remove_vote(&i, vtoken, poll_index, UnvoteScope::Any)); + } + for i in 1..=5 { + assert_ok!(VtokenVoting::update_lock(&i, vtoken)); + assert_eq!(usable_balance(vtoken, &i), 10 * i as u128); + } + }); + } +} + +#[test] +fn lock_amalgamation_valid_with_multiple_removed_votes() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let response = response_success(); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 0, aye(5, 1))); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response.clone())); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(10, 1))); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response.clone())); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(5, 1))); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response.clone())); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5),]) + .unwrap() + ); + assert_eq!(usable_balance(vtoken, &ALICE), 5); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 2, aye(10, 2))); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 3, response.clone())); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 0, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 1, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 2, + ReferendumInfoOf::::Completed(1), + )); + + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + assert_eq!(VoteLockingPeriod::::get(vtoken), Some(10)); + + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + RelaychainDataProvider::set_block_number(10); + assert_noop!( + VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0), + Error::::NoPermissionYet + ); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + + RelaychainDataProvider::set_block_number(11); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0)); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + RelaychainDataProvider::set_block_number(11); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 1)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10)]) + .unwrap() + ); + + RelaychainDataProvider::set_block_number(21); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 2)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![]).unwrap() + ); + }); + } +} + +#[test] +fn removed_votes_when_referendum_killed() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let response = response_success(); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 0, aye(5, 1))); + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(10, 1))); + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 2, aye(5, 2))); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + + assert_ok!(VtokenVoting::notify_vote(origin_response(), 0, response.clone())); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 1, response.clone())); + assert_ok!(VtokenVoting::notify_vote(origin_response(), 2, response.clone())); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 0, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 1, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 2, + ReferendumInfoOf::::Completed(1), + )); + + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 0)); + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 1)); + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 2)); + + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 1)); + assert_eq!(usable_balance(vtoken, &ALICE), 5); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5)]) + .unwrap() + ); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 2)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![]).unwrap() + ); + }); + } +} + +#[test] +fn errors_with_vote_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_noop!( + VtokenVoting::vote(RuntimeOrigin::signed(1), VPHA, 0, aye(10, 0)), + Error::::VTokenNotSupport + ); + assert_noop!( + VtokenVoting::vote(RuntimeOrigin::signed(1), vtoken, 3, aye(11, 0)), + Error::::InsufficientFunds + ); + + for poll_index in 0..256 { + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(1), + vtoken, + poll_index, + aye(10, 0) + )); + } + assert_noop!( + VtokenVoting::vote(RuntimeOrigin::signed(1), vtoken, 256, aye(10, 0)), + Error::::MaxVotesReached + ); + }); + } +} + +#[test] +fn kill_referendum_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(5, 1) + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, poll_index)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ReferendumKilled { + vtoken, + poll_index, + })); + }); + } +} + +#[test] +fn kill_referendum_with_origin_signed_fails() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(5, 1) + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(1), + )); + assert_noop!( + VtokenVoting::kill_referendum(RuntimeOrigin::signed(ALICE), vtoken, poll_index), + DispatchError::BadOrigin + ); + }); + } +} + +#[test] +fn add_delegator_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let derivative_index: DerivativeIndex = 100; + + assert_ok!(VtokenVoting::add_delegator( + RuntimeOrigin::root(), + vtoken, + derivative_index, + )); + + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::DelegatorAdded { + vtoken, + derivative_index, + })); + }); + } +} + +#[test] +fn set_referendum_status_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let info = ReferendumInfo::Completed(3); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + info.clone(), + )); + + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ReferendumInfoSet { + vtoken, + poll_index, + info, + })); + }); + } +} + +#[test] +fn set_referendum_status_without_vote_should_fail() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let info = ReferendumInfo::Completed(3); + + assert_noop!( + VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + info.clone(), + ), + Error::::NoData + ); + }); + } +} + +#[test] +fn set_referendum_status_with_origin_signed_should_fail() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let info = ReferendumInfo::Completed(3); + + assert_noop!( + VtokenVoting::set_referendum_status( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + info.clone(), + ), + DispatchError::BadOrigin + ); + }); + } +} + +#[test] +fn set_vote_locking_period_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let locking_period = 100; + + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::VoteLockingPeriodSet { + vtoken, + locking_period, + })); + }); + } +} + +#[test] +fn set_vote_locking_period_with_origin_signed_should_fail() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let locking_period = 100; + + assert_noop!( + VtokenVoting::set_vote_locking_period( + RuntimeOrigin::signed(ALICE), + vtoken, + locking_period, + ), + DispatchError::BadOrigin + ); + }); + } +} + +#[test] +fn set_undeciding_timeout_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let undeciding_timeout = 100; + + assert_ok!(VtokenVoting::set_undeciding_timeout( + RuntimeOrigin::root(), + vtoken, + undeciding_timeout, + )); + + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::UndecidingTimeoutSet { + vtoken, + undeciding_timeout, + })); + }); + } +} + +#[test] +fn set_undeciding_timeout_with_origin_signed_should_fail() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let undeciding_timeout = 100; + + assert_noop!( + VtokenVoting::set_undeciding_timeout( + RuntimeOrigin::signed(ALICE), + vtoken, + undeciding_timeout, + ), + DispatchError::BadOrigin + ); + }); + } +} + +#[test] +fn notify_vote_success_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let query_id = 0; + let response = response_success(); + let derivative_index = 0; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_eq!( + ReferendumInfoFor::::get(vtoken, poll_index), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + submitted: None, + tally: TallyOf::::from_parts(20, 0, 4), + })) + ); + assert_eq!( + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from( + vec![(derivative_index, aye(200, 0))] + ) + .unwrap() + ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { + who: ALICE, + vtoken, + poll_index, + token_vote: aye(4, 5), + delegator_vote: aye(200, 0), + })); + + assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); + assert_eq!( + ReferendumInfoFor::::get(vtoken, poll_index), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + submitted: Some(1), + tally: TallyOf::::from_parts(20, 0, 4), + })) + ); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!( + DelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from( + vec![(derivative_index, aye(200, 0))] + ) + .unwrap() + ); + System::assert_has_event(RuntimeEvent::VtokenVoting(Event::VoteNotified { + vtoken, + poll_index, + success: true, + })); + System::assert_has_event(RuntimeEvent::VtokenVoting(Event::ReferendumInfoCreated { + vtoken, + poll_index, + info: ReferendumInfo::Ongoing(ReferendumStatus { + submitted: Some(1), + tally: TallyOf::::from_parts(20, 0, 4), + }), + })); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { + responder: Parent.into(), + query_id, + response, + })); + }); + } +} + +#[test] +fn notify_vote_success_max_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + for poll_index in 0..256 { + RelaychainDataProvider::set_block_number(1); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_ok!(VtokenVoting::notify_vote( + origin_response(), + poll_index as QueryId, + response_success() + )); + + RelaychainDataProvider::set_block_number( + 1 + UndecidingTimeout::::get(vtoken).unwrap(), + ); + VtokenVoting::on_idle(Zero::zero(), Weight::MAX); + } + }); + } +} + +#[test] +fn notify_vote_success_exceed_max_fail() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + for poll_index in 0..50 { + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_ok!(VtokenVoting::notify_vote( + origin_response(), + poll_index as QueryId, + response_success() + )); + } + let poll_index = 50; + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_noop!( + VtokenVoting::notify_vote( + origin_response(), + poll_index as QueryId, + response_success() + ), + Error::::TooMany + ); + }); + } +} + +#[test] +fn notify_vote_fail_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let query_id = 0; + let response = response_fail(); + let derivative_index = 0; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_eq!( + ReferendumInfoFor::::get(vtoken, poll_index), + Some(ReferendumInfo::Ongoing(ReferendumStatus { + submitted: None, + tally: TallyOf::::from_parts(20, 0, 4), + })) + ); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!( + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from( + vec![(derivative_index, aye(200, 0))] + ) + .unwrap() + ); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { + who: ALICE, + vtoken, + poll_index, + token_vote: aye(4, 5), + delegator_vote: aye(200, 0), + })); + + assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); + assert_eq!(ReferendumInfoFor::::get(vtoken, poll_index), None); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { + responder: Parent.into(), + query_id, + response, + })); + }); + } +} + +#[test] +fn notify_vote_with_no_data_works() { + new_test_ext().execute_with(|| { + let query_id = 0; + let response = response_success(); + + assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { + responder: Parent.into(), + query_id, + response, + })); + }); +} + +#[test] +fn notify_remove_delegator_vote_success_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let class = 0; + let poll_index = 3; + let mut query_id = 0; + let derivative_index = 0; + let response = response_success(); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!( + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from( + vec![(derivative_index, aye(200, 0))] + ) + .unwrap() + ); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { + who: ALICE, + vtoken, + poll_index, + token_vote: aye(4, 5), + delegator_vote: aye(200, 0), + })); + assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response.clone())); + assert_eq!( + DelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from( + vec![(derivative_index, aye(200, 0))] + ) + .unwrap() + ); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); + + RelaychainDataProvider::set_block_number(3); + assert_ok!(VtokenVoting::remove_delegator_vote( + RuntimeOrigin::signed(ALICE), + vtoken, + class, + poll_index, + derivative_index, + )); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); + + query_id = 1; + assert_ok!(VtokenVoting::notify_remove_delegator_vote( + origin_response(), + query_id, + response.clone() + )); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + System::assert_has_event(RuntimeEvent::VtokenVoting( + Event::DelegatorVoteRemovedNotified { vtoken, poll_index, success: true }, + )); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { + responder: Parent.into(), + query_id, + response, + })); + }); + } +} + +#[test] +fn notify_remove_delegator_vote_fail_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let class = 0; + let poll_index = 3; + let mut query_id = 0; + let derivative_index = 0; + let response = response_fail(); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 0); + assert_eq!( + PendingDelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from( + vec![(derivative_index, aye(200, 0))] + ) + .unwrap() + ); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { + who: ALICE, + vtoken, + poll_index, + token_vote: aye(4, 5), + delegator_vote: aye(200, 0), + })); + assert_ok!(VtokenVoting::notify_vote(origin_response(), query_id, response_success())); + assert_eq!( + DelegatorVotes::::get(vtoken, poll_index), + BoundedVec::<(DerivativeIndex, AccountVote), ConstU32<100>>::try_from( + vec![(derivative_index, aye(200, 0))] + ) + .unwrap() + ); + assert_eq!(PendingDelegatorVotes::::get(vtoken, poll_index).len(), 0); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); + + RelaychainDataProvider::set_block_number(3); + assert_ok!(VtokenVoting::remove_delegator_vote( + RuntimeOrigin::signed(ALICE), + vtoken, + class, + poll_index, + derivative_index, + )); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); + + query_id = 1; + assert_ok!(VtokenVoting::notify_remove_delegator_vote( + origin_response(), + query_id, + response.clone() + )); + assert_eq!(DelegatorVotes::::get(vtoken, poll_index).len(), 1); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { + responder: Parent.into(), + query_id, + response, + })); + }); + } +} + +#[test] +fn notify_remove_delegator_vote_with_no_data_works() { + new_test_ext().execute_with(|| { + let query_id = 0; + let response = response_success(); + + assert_ok!(VtokenVoting::notify_remove_delegator_vote( + origin_response(), + query_id, + response.clone(), + )); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ResponseReceived { + responder: Parent.into(), + query_id, + response, + })); + }); +} + +#[test] +fn on_idle_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + for (index, poll_index) in (0..50).collect::>().iter().enumerate() { + let relay_block_number = index as BlockNumber; + let query_id = index as QueryId; + RelaychainDataProvider::set_block_number(relay_block_number); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + *poll_index, + aye(2, 5) + )); + assert_ok!(VtokenVoting::notify_vote( + origin_response(), + query_id as QueryId, + response_success() + )); + } + + let count = 30; + RelaychainDataProvider::set_block_number( + count + UndecidingTimeout::::get(vtoken).unwrap(), + ); + let db_weight = RuntimeDbWeight { read: 1, write: 1 }; + let weight = db_weight.reads(3) + + db_weight.reads_writes(1, 2) * count + + db_weight.writes(2) * count; + let used_weight = VtokenVoting::on_idle(Zero::zero(), weight); + assert_eq!(used_weight, Weight::from_parts(0, 0)); + + let mut actual_count = 0; + for poll_index in 0..50 { + let relay_block_number = poll_index as BlockNumber; + if ReferendumTimeout::::get( + relay_block_number + UndecidingTimeout::::get(vtoken).unwrap(), + ) + .is_empty() + { + actual_count += 1; + } + } + assert_eq!(actual_count, 31); + }); + } +} + +#[test] +fn set_vote_cap_ratio_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_ok!(VtokenVoting::set_vote_cap_ratio( + RuntimeOrigin::root(), + vtoken, + Perbill::from_percent(0) + )); + assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(0)); + + assert_ok!(VtokenVoting::set_vote_cap_ratio( + RuntimeOrigin::root(), + vtoken, + Perbill::from_percent(10) + )); + assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(10)); + + assert_ok!(VtokenVoting::set_vote_cap_ratio( + RuntimeOrigin::root(), + vtoken, + Perbill::from_percent(100) + )); + assert_eq!(VoteCapRatio::::get(vtoken), Perbill::from_percent(100)); + }); + } +} + +#[test] +fn vote_cap_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_eq!(VtokenVoting::vote_cap(vtoken), Ok((u64::MAX / 10) as Balance)); + }); + } +} + +#[test] +fn vote_to_capital_works() { + new_test_ext().execute_with(|| { + assert_eq!(VtokenVoting::vote_to_capital(Conviction::None, 300), 3000); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked1x, 300), 300); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked2x, 300), 150); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked3x, 300), 100); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked4x, 300), 75); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked5x, 300), 60); + assert_eq!(VtokenVoting::vote_to_capital(Conviction::Locked6x, 300), 50); + }); +} + +#[test] +fn compute_delegator_total_vote_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(10, 0)), + Ok(aye(10, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 1)), + Ok(aye(20, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 2)), + Ok(aye(40, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 3)), + Ok(aye(60, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 4)), + Ok(aye(80, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 5)), + Ok(aye(100, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 6)), + Ok(aye(120, 0)) + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(10, 0)), + Ok(nay(10, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 1)), + Ok(nay(20, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 2)), + Ok(nay(40, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 3)), + Ok(nay(60, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 4)), + Ok(nay(80, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 5)), + Ok(nay(100, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 6)), + Ok(nay(120, 0)) + ); + + SimpleVTokenSupplyProvider::set_token_supply(10_000_000); + assert_eq!(VtokenVoting::vote_cap(vtoken), Ok(1_000_000)); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_000_000, 0)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(10_000_000 * i as Balance, 0) + ), + Ok(aye(1_000_000, i)) + ); + } + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(100_000, 1)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(1_000_000 * i as Balance, 1) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(6_000_006, 1)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(50_000, 2)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(500_000 * i as Balance, 2) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(3_000_003, 2)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(33_333, 3)), + Ok(aye(999_990, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(333_333 * i as Balance, 3) + ), + Ok(aye(999_999, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2_000_002, 3)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(25_000, 4)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(250_000 * i as Balance, 4) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_500_002, 4)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(20_000, 5)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(200_000 * i as Balance, 5) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_200_002, 5)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(16_666, 6)), + Ok(aye(999_960, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(166_666 * i as Balance, 6) + ), + Ok(aye(999_996, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_000_001, 6)), + Error::::InsufficientFunds + ); + }); + } +} + +#[test] +fn compute_delegator_total_vote_with_low_value_will_loss() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(9, 0)), + Ok(aye(0, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(9, 0)), + Ok(nay(0, 0)) + ); + }); + } +} + +#[test] +fn allocate_delegator_votes_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + for conviction in 0..=6 { + let vote = aye(5e9 as Balance, conviction); + let delegator_votes = + VtokenVoting::allocate_delegator_votes(vtoken, poll_index, vote); + assert_eq!( + delegator_votes, + Ok(vec![(0, aye(4294967295, conviction)), (1, aye(705032705, conviction))]) + ); + assert_eq!( + delegator_votes + .unwrap() + .into_iter() + .map(|(_derivative_index, vote)| vote) + .fold(aye(0, conviction), |mut acc, vote| { + let _ = acc.checked_add(vote); + acc + },), + vote + ); + } + + for conviction in 0..=6 { + let vote = aye(3e10 as Balance, conviction); + let delegator_votes = + VtokenVoting::allocate_delegator_votes(vtoken, poll_index, vote); + assert_eq!( + delegator_votes, + Ok(vec![ + (0, aye(4294967295, conviction)), + (1, aye(4294967295, conviction)), + (2, aye(4294967295, conviction)), + (3, aye(4294967295, conviction)), + (4, aye(4294967295, conviction)), + (5, aye(4294967295, conviction)), + (10, aye(4230196230, conviction)) + ]) + ); + assert_eq!( + delegator_votes + .unwrap() + .into_iter() + .map(|(_derivative_index, vote)| vote) + .fold(aye(0, conviction), |mut acc, vote| { + let _ = acc.checked_add(vote); + acc + },), + vote + ); + } + }); + } +} + +#[test] +fn tally_convert_works() { + assert_eq!( + TallyOf::::from_parts(10, 9, 0).account_vote(Conviction::Locked1x), + aye(1, 1) + ); + assert_eq!( + TallyOf::::from_parts(10, 11, 0).account_vote(Conviction::Locked1x), + nay(1, 1) + ); + assert_eq!( + TallyOf::::from_parts(10, 10, 0).account_vote(Conviction::Locked1x), + aye(0, 1) + ); +} + +#[test] +fn set_lock_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_ok!(VtokenVoting::set_lock(&ALICE, vtoken, 10)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + + assert_ok!(VtokenVoting::set_lock(&ALICE, vtoken, 1)); + assert_eq!(usable_balance(vtoken, &ALICE), 9); + + assert_ok!(VtokenVoting::set_lock(&ALICE, vtoken, 0)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} diff --git a/pallets/vtoken-voting/src/tests/mod.rs b/pallets/vtoken-voting/src/tests/mod.rs new file mode 100644 index 000000000..955f282ad --- /dev/null +++ b/pallets/vtoken-voting/src/tests/mod.rs @@ -0,0 +1,23 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#[cfg(test)] +mod common_test; + +#[cfg(test)] +mod vbnc_test; diff --git a/pallets/vtoken-voting/src/tests/vbnc_test.rs b/pallets/vtoken-voting/src/tests/vbnc_test.rs new file mode 100644 index 000000000..ebf45665e --- /dev/null +++ b/pallets/vtoken-voting/src/tests/vbnc_test.rs @@ -0,0 +1,841 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Ensure we're `no_std` when compiling for Wasm. +use crate::{mock::*, *}; +use bifrost_primitives::currency::VPHA; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + fungibles::Inspect, + tokens::{Fortitude::Polite, Preservation::Expendable}, + }, +}; + +const TOKENS: &[CurrencyId] = if cfg!(feature = "polkadot") { &[VBNC] } else { &[] }; + +fn aye(amount: Balance, conviction: u8) -> AccountVote { + let vote = Vote { aye: true, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } +} + +fn nay(amount: Balance, conviction: u8) -> AccountVote { + let vote = Vote { aye: false, conviction: conviction.try_into().unwrap() }; + AccountVote::Standard { vote, balance: amount } +} + +fn tally(vtoken: CurrencyId, poll_index: u32) -> TallyOf { + VtokenVoting::ensure_referendum_ongoing(vtoken, poll_index) + .expect("No poll") + .tally +} + +fn usable_balance(vtoken: CurrencyId, who: &AccountId) -> Balance { + Tokens::reducible_balance(vtoken, who, Expendable, Polite) +} + +#[test] +fn basic_voting_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 5) + )); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(20, 0, 4)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::Voted { + who: ALICE, + vtoken, + poll_index, + token_vote: aye(4, 5), + delegator_vote: aye(200, 0), + })); + + assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); + + assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn voting_balance_gets_locked() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + nay(10, 0) + )); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 2, 0)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + + assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); + assert_eq!(tally(vtoken, poll_index), Tally::from_parts(0, 0, 0)); + + assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn successful_but_zero_conviction_vote_balance_can_be_unlocked() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(1, 1) + )); + assert_eq!(usable_balance(vtoken, &ALICE), 9); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(3, 1) + )); + assert_eq!(usable_balance(vtoken, &ALICE), 7); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(BOB), + vtoken, + poll_index, + nay(20, 0) + )); + assert_eq!(usable_balance(vtoken, &BOB), 0); + + assert_ok!(VtokenVoting::set_vote_locking_period(RuntimeOrigin::root(), vtoken, 10)); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(BOB), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &BOB), 20); + + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn unsuccessful_conviction_vote_balance_can_be_unlocked() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(1, 1) + )); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(BOB), + vtoken, + poll_index, + nay(20, 0) + )); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::try_remove_vote(&ALICE, vtoken, poll_index, UnvoteScope::Any)); + assert_ok!(VtokenVoting::update_lock(&ALICE, vtoken)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + }); + } +} + +#[test] +fn ensure_balance_after_unlock() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let poll_index_2 = 4; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(10, 1) + )); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_2, + aye(10, 5) + )); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 10); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + }); + } +} + +#[test] +fn ensure_comprehensive_balance_after_unlock() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let poll_index_2 = 4; + let poll_index_3 = 5; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(2, 1) + )); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_2, + aye(1, 5) + )); + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_3, + aye(2, 5) + )); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(13); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, poll_index)); + assert_eq!(usable_balance(vtoken, &ALICE), 8); + assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 2); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 2); + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index_2, + aye(10, 5) + )); + + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!(Tokens::accounts(&ALICE, vtoken).frozen, 10); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + }); + } +} + +#[test] +fn successful_conviction_vote_balance_stays_locked_for_correct_time() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + for i in 1..=5 { + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(i), + vtoken, + poll_index, + aye(10, i as u8) + )); + } + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(3), + )); + RelaychainDataProvider::set_block_number(163); + for i in 1..=5 { + assert_ok!(VtokenVoting::try_remove_vote(&i, vtoken, poll_index, UnvoteScope::Any)); + } + for i in 1..=5 { + assert_ok!(VtokenVoting::update_lock(&i, vtoken)); + assert_eq!(usable_balance(vtoken, &i), 10 * i as u128); + } + }); + } +} + +#[test] +fn lock_amalgamation_valid_with_multiple_removed_votes() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 0, aye(5, 1))); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(10, 1))); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(5, 1))); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5),]) + .unwrap() + ); + assert_eq!(usable_balance(vtoken, &ALICE), 5); + + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 2, aye(10, 2))); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 0, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 1, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 2, + ReferendumInfoOf::::Completed(1), + )); + + let locking_period = 10; + assert_ok!(VtokenVoting::set_vote_locking_period( + RuntimeOrigin::root(), + vtoken, + locking_period, + )); + assert_eq!(VoteLockingPeriod::::get(vtoken), Some(10)); + + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + RelaychainDataProvider::set_block_number(10); + assert_noop!( + VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0), + Error::::NoPermissionYet + ); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + + RelaychainDataProvider::set_block_number(11); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0)); + assert_eq!(VotingFor::::get(&ALICE).locked_balance(), 10); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + RelaychainDataProvider::set_block_number(11); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 1)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10)]) + .unwrap() + ); + + RelaychainDataProvider::set_block_number(21); + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 2)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![]).unwrap() + ); + }); + } +} + +#[test] +fn removed_votes_when_referendum_killed() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 0, aye(5, 1))); + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 1, aye(10, 1))); + assert_ok!(VtokenVoting::vote(RuntimeOrigin::signed(ALICE), vtoken, 2, aye(5, 2))); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 0, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 1, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + 2, + ReferendumInfoOf::::Completed(1), + )); + + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 0)); + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 1)); + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, 2)); + + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 0)); + assert_eq!(usable_balance(vtoken, &ALICE), 0); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 10),]) + .unwrap() + ); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 1)); + assert_eq!(usable_balance(vtoken, &ALICE), 5); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![(vtoken, 5)]) + .unwrap() + ); + + assert_ok!(VtokenVoting::unlock(RuntimeOrigin::signed(ALICE), vtoken, 2)); + assert_eq!(usable_balance(vtoken, &ALICE), 10); + assert_eq!( + ClassLocksFor::::get(&ALICE), + BoundedVec::<(CurrencyId, u128), ConstU32<256>>::try_from(vec![]).unwrap() + ); + }); + } +} + +#[test] +fn errors_with_vote_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_noop!( + VtokenVoting::vote(RuntimeOrigin::signed(1), VPHA, 0, aye(10, 0)), + Error::::VTokenNotSupport + ); + assert_noop!( + VtokenVoting::vote(RuntimeOrigin::signed(1), vtoken, 3, aye(11, 0)), + Error::::InsufficientFunds + ); + + for poll_index in 0..256 { + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(1), + vtoken, + poll_index, + aye(10, 0) + )); + } + assert_noop!( + VtokenVoting::vote(RuntimeOrigin::signed(1), vtoken, 256, aye(10, 0)), + Error::::MaxVotesReached + ); + }); + } +} + +#[test] +fn kill_referendum_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(5, 1) + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(1), + )); + assert_ok!(VtokenVoting::kill_referendum(RuntimeOrigin::root(), vtoken, poll_index)); + System::assert_last_event(RuntimeEvent::VtokenVoting(Event::ReferendumKilled { + vtoken, + poll_index, + })); + }); + } +} + +#[test] +fn kill_referendum_with_origin_signed_fails() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + assert_ok!(VtokenVoting::vote( + RuntimeOrigin::signed(ALICE), + vtoken, + poll_index, + aye(5, 1) + )); + assert_ok!(VtokenVoting::set_referendum_status( + RuntimeOrigin::root(), + vtoken, + poll_index, + ReferendumInfoOf::::Completed(1), + )); + assert_noop!( + VtokenVoting::kill_referendum(RuntimeOrigin::signed(ALICE), vtoken, poll_index), + DispatchError::BadOrigin + ); + }); + } +} + +#[test] +fn compute_delegator_total_vote_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(10, 0)), + Ok(aye(10, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 1)), + Ok(aye(20, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 2)), + Ok(aye(40, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 3)), + Ok(aye(60, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 4)), + Ok(aye(80, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 5)), + Ok(aye(100, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2, 6)), + Ok(aye(120, 0)) + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(10, 0)), + Ok(nay(10, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 1)), + Ok(nay(20, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 2)), + Ok(nay(40, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 3)), + Ok(nay(60, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 4)), + Ok(nay(80, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 5)), + Ok(nay(100, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(2, 6)), + Ok(nay(120, 0)) + ); + + SimpleVTokenSupplyProvider::set_token_supply(10_000_000); + assert_eq!(VtokenVoting::vote_cap(vtoken), Ok(1_000_000)); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_000_000, 0)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(10_000_000 * i as Balance, 0) + ), + Ok(aye(1_000_000, i)) + ); + } + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(100_000, 1)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(1_000_000 * i as Balance, 1) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(6_000_006, 1)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(50_000, 2)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(500_000 * i as Balance, 2) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(3_000_003, 2)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(33_333, 3)), + Ok(aye(999_990, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(333_333 * i as Balance, 3) + ), + Ok(aye(999_999, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(2_000_002, 3)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(25_000, 4)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(250_000 * i as Balance, 4) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_500_002, 4)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(20_000, 5)), + Ok(aye(1_000_000, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(200_000 * i as Balance, 5) + ), + Ok(aye(1_000_000, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_200_002, 5)), + Error::::InsufficientFunds + ); + + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(16_666, 6)), + Ok(aye(999_960, 0)) + ); + for i in 1..=6_u8 { + assert_eq!( + VtokenVoting::compute_delegator_total_vote( + vtoken, + aye(166_666 * i as Balance, 6) + ), + Ok(aye(999_996, i)) + ); + } + assert_noop!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(1_000_001, 6)), + Error::::InsufficientFunds + ); + }); + } +} + +#[test] +fn compute_delegator_total_vote_with_low_value_will_loss() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, aye(9, 0)), + Ok(aye(0, 0)) + ); + assert_eq!( + VtokenVoting::compute_delegator_total_vote(vtoken, nay(9, 0)), + Ok(nay(0, 0)) + ); + }); + } +} + +#[test] +fn allocate_delegator_votes_works() { + for &vtoken in TOKENS { + new_test_ext().execute_with(|| { + let poll_index = 3; + + for conviction in 0..=6 { + let vote = aye(5e9 as Balance, conviction); + let delegator_votes = + VtokenVoting::allocate_delegator_votes(vtoken, poll_index, vote); + assert_eq!( + delegator_votes, + Ok(vec![(0, aye(4294967295, conviction)), (1, aye(705032705, conviction))]) + ); + assert_eq!( + delegator_votes + .unwrap() + .into_iter() + .map(|(_derivative_index, vote)| vote) + .fold(aye(0, conviction), |mut acc, vote| { + let _ = acc.checked_add(vote); + acc + },), + vote + ); + } + + for conviction in 0..=6 { + let vote = aye(3e10 as Balance, conviction); + let delegator_votes = + VtokenVoting::allocate_delegator_votes(vtoken, poll_index, vote); + assert_eq!( + delegator_votes, + Ok(vec![ + (0, aye(4294967295, conviction)), + (1, aye(4294967295, conviction)), + (2, aye(4294967295, conviction)), + (3, aye(4294967295, conviction)), + (4, aye(4294967295, conviction)), + (5, aye(4294967295, conviction)), + (10, aye(4230196230, conviction)) + ]) + ); + assert_eq!( + delegator_votes + .unwrap() + .into_iter() + .map(|(_derivative_index, vote)| vote) + .fold(aye(0, conviction), |mut acc, vote| { + let _ = acc.checked_add(vote); + acc + },), + vote + ); + } + }); + } +} diff --git a/pallets/vtoken-voting/src/traits.rs b/pallets/vtoken-voting/src/traits.rs new file mode 100644 index 000000000..30fe18202 --- /dev/null +++ b/pallets/vtoken-voting/src/traits.rs @@ -0,0 +1,142 @@ +// This file is part of Bifrost. + +// Copyright (C) Liebi Technologies PTE. LTD. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{AccountVote, PollClass, PollIndex, *}; +use bifrost_primitives::DerivativeIndex; +use sp_std::vec::Vec; + +/// Abstraction over a voting agent for a certain parachain. +/// +/// This trait defines the operations a voting agent should implement for handling votes and +/// delegator-related actions within a certain parachain's voting system. +pub trait VotingAgent { + /// Retrieves the voting token (`vtoken`) associated with the agent. + /// + /// This function should return the currency ID representing the token used for voting. + fn vtoken(&self) -> CurrencyIdOf; + + /// Retrieves the location of the agent. + /// + /// This function returns the location associated with the agent, which could be used to + /// identify the origin or context within the parachain system. + fn location(&self) -> Location; + + /// Delegate a vote on behalf of a user. + /// + /// - `who`: The account for which the vote is being delegated. + /// - `vtoken`: The token used for voting. + /// - `poll_index`: The index of the poll on which the vote is being cast. + /// - `submitted`: A flag indicating whether the vote was already submitted. + /// - `new_delegator_votes`: A vector of delegator votes, represented by the index of the + /// derivative and the account's vote. + /// - `maybe_old_vote`: An optional tuple representing the old vote and its associated balance, + /// in case an old vote exists. + /// + /// This function handles the delegation of votes for the specified account and updates the + /// voting state accordingly. It returns a `DispatchResult` to indicate success or failure. + fn delegate_vote( + &self, + who: AccountIdOf, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + submitted: bool, + new_delegator_votes: Vec<(DerivativeIndex, AccountVote>)>, + maybe_old_vote: Option<(AccountVote>, BalanceOf)>, + ) -> DispatchResult; + + /// Encode the call data for voting. + /// + /// - `new_delegator_votes`: A vector of new delegator votes to be encoded. + /// - `poll_index`: The index of the poll. + /// - `derivative_index`: The index of the derivative (delegator) involved in the voting + /// process. + /// + /// This function encodes the call for a vote delegation action, returning the byte-encoded + /// representation of the call data. In case of errors during encoding, an `Error` is + /// returned. + fn vote_call_encode( + &self, + new_delegator_votes: Vec<(DerivativeIndex, AccountVote>)>, + poll_index: PollIndex, + derivative_index: DerivativeIndex, + ) -> Result, Error>; + + /// Remove a delegator's vote. + /// + /// - `vtoken`: The token associated with the vote. + /// - `poll_index`: The index of the poll from which the vote is being removed. + /// - `class`: The class/type of the poll. + /// - `derivative_index`: The index of the delegator's derivative whose vote is being removed. + /// + /// This function handles the removal of a delegator's vote for the specified poll and class, + /// returning a `DispatchResult` to indicate success or failure. + fn delegate_remove_delegator_vote( + &self, + vtoken: CurrencyIdOf, + poll_index: PollIndex, + class: PollClass, + derivative_index: DerivativeIndex, + ) -> DispatchResult; + + /// Encode the call data for removing a delegator's vote. + /// + /// - `class`: The class/type of the poll. + /// - `poll_index`: The index of the poll from which the vote is being removed. + /// - `derivative_index`: The index of the delegator's derivative involved in the vote removal. + /// + /// This function encodes the call for removing a delegator's vote, returning the byte-encoded + /// representation of the call data. In case of errors during encoding, an `Error` is + /// returned. + fn remove_delegator_vote_call_encode( + &self, + class: PollClass, + poll_index: PollIndex, + derivative_index: DerivativeIndex, + ) -> Result, Error>; +} + +/// Trait defining calls related to conviction voting mechanisms. +pub trait ConvictionVotingCall { + /// Casts a vote in a poll. + /// + /// - `poll_index`: The index of the poll where the vote is being cast. + /// - `vote`: The vote being cast, which includes the voter's balance and conviction. + fn vote(poll_index: PollIndex, vote: AccountVote>) -> Self; + + /// Removes a vote from a poll. + /// + /// - `class`: Optionally specify the class of the poll (if applicable). + /// - `poll_index`: The index of the poll from which the vote is being removed. + fn remove_vote(class: Option, poll_index: PollIndex) -> Self; +} + +/// Trait defining utility calls for handling batch or derivative actions. +pub trait UtilityCall { + /// Executes a call as a derivative of another account. + /// + /// - `derivative_index`: The index representing a specific derivative account (usually for + /// staking or delegation purposes). + /// - `call`: The call that will be executed by the derivative account. + fn as_derivative(derivative_index: DerivativeIndex, call: Call) -> Call; + + /// Executes a batch of calls where all must succeed or none will be executed. + /// + /// - `calls`: A vector of calls that will be executed in batch. If any call fails, none of the + /// calls will be applied. + fn batch_all(calls: Vec) -> Call; +} diff --git a/pallets/vtoken-voting/src/vote.rs b/pallets/vtoken-voting/src/vote.rs index a9a9b1b86..f34984d7c 100644 --- a/pallets/vtoken-voting/src/vote.rs +++ b/pallets/vtoken-voting/src/vote.rs @@ -357,8 +357,8 @@ where #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxVotes))] #[codec(mel_bound( -Balance: MaxEncodedLen, AccountId: MaxEncodedLen, BlockNumber: MaxEncodedLen, -PollIndex: MaxEncodedLen, + Balance: MaxEncodedLen, AccountId: MaxEncodedLen, BlockNumber: MaxEncodedLen, + PollIndex: MaxEncodedLen, ))] pub enum Voting where diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index c6cf28de0..1d9f630bc 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -387,7 +387,7 @@ where } } -pub trait DerivativeAccountHandler { +pub trait DerivativeAccountHandler { fn check_derivative_index_exists(token: CurrencyId, derivative_index: DerivativeIndex) -> bool; fn get_multilocation( @@ -395,6 +395,8 @@ pub trait DerivativeAccountHandler { derivative_index: DerivativeIndex, ) -> Option; + fn get_account_id(token: CurrencyId, derivative_index: DerivativeIndex) -> Option; + fn get_stake_info( token: CurrencyId, derivative_index: DerivativeIndex, diff --git a/runtime/bifrost-kusama/src/lib.rs b/runtime/bifrost-kusama/src/lib.rs index 2a71076ee..19d700739 100644 --- a/runtime/bifrost-kusama/src/lib.rs +++ b/runtime/bifrost-kusama/src/lib.rs @@ -1349,6 +1349,7 @@ impl bifrost_vtoken_voting::Config for Runtime { type QueryTimeout = QueryTimeout; type ReferendumCheckInterval = ReferendumCheckInterval; type WeightInfo = weights::bifrost_vtoken_voting::BifrostWeight; + type PalletsOrigin = OriginCaller; } // Bifrost modules end diff --git a/runtime/bifrost-polkadot/src/lib.rs b/runtime/bifrost-polkadot/src/lib.rs index f63de4618..ee97874bf 100644 --- a/runtime/bifrost-polkadot/src/lib.rs +++ b/runtime/bifrost-polkadot/src/lib.rs @@ -1261,6 +1261,7 @@ impl bifrost_vtoken_voting::Config for Runtime { type QueryTimeout = QueryTimeout; type ReferendumCheckInterval = ReferendumCheckInterval; type WeightInfo = weights::bifrost_vtoken_voting::BifrostWeight; + type PalletsOrigin = OriginCaller; } // Bifrost modules end