From c92e83f6dc8a4af7f664d6a1a5832878b2e98128 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Fri, 1 Mar 2024 16:23:24 +0800 Subject: [PATCH 01/21] Add TODO.md - Todo Guidelines --- README.md | 8 ++++++++ TODO.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 TODO.md diff --git a/README.md b/README.md index a7069243..65f2f479 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Setheum's Blockchain Network node Implementation in Rust, ready for hacking :roc - [6.4.2. Generate Runtime Weights](#642-generate-runtime-weights) - [7.0. Fork Setheum Chain](#70-fork-setheum-chain) - [8.0. Contributing \& Code of Conduct](#80-contributing--code-of-conduct) + - [8.1. ToDo List](#81-todo-list) - [9.0. License](#90-license) @@ -360,6 +361,13 @@ If you would like to contribute, please fork the repository, introduce your chan In every interaction and contribution, this project adheres to the [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md). +### 8.1. ToDo List + +Note> Before adding/removing a TODO, please carefully read the [TODO.md file](./TODO.md) + +Whenever you write a TODO in any file, please add a reference to it [here](./TODO.md). +Whenever you remove a TODO in any file, please remove its reference from [here](./TODO.md). + ## 9.0. License The code in this repository is licensed under the [GNU GPL Version 3 License](./LICENSE.md) diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..6d5c2e7e --- /dev/null +++ b/TODO.md @@ -0,0 +1,55 @@ +# ToDo List - The Monofile for Setheum Repo ToDos + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#setheum---the-monofile-for-setheum-repo-todos) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to open an [Issue](https://github.com/Setheum-Labs/Setheum/issues) which references the commit that adds/writes the task or vice-versa. The `Issue Title` should be the `task_prefix` andn the `task_details` altogether just as it is written in the TODO. + +Whenever you write a TODO in any file, please add a reference to it here. Please make sure to title the task reference here exactly as the `task_prefix` (excluding the `task_details`). + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and add a reference to the `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), add this `-C` as a suffix to its `file_name`, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +Note > The suffix need not be added to the reference Issue too, but you may if you want. + + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. +To search for a todo, you can search for TODOs based on the `task_prefix`. You can also contribute by updating old tasks to the new standard syntax using `task_prefix`, you can also open issues for those Tasks if not opened before, if opened before you can link to those Issues as the reference. + +## 3. Tasks + +- [x] [Add TODO.md File](TODO.md) +- [x] [Categorize the Tasks with a `task_prefix`](/TODO.md/#tasks) From 4daf8d987ff71e87fe19f46b5baa1ca4616fcdc8 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Fri, 1 Mar 2024 16:28:33 +0800 Subject: [PATCH 02/21] Update Weight Templates --- .maintain/module-weight-template.hbs | 242 ++++++++++++++------------ .maintain/orml-weight-template.hbs | 151 ++++++++-------- .maintain/runtime-weight-template.hbs | 148 +++++++++------- 3 files changed, 288 insertions(+), 253 deletions(-) diff --git a/.maintain/module-weight-template.hbs b/.maintain/module-weight-template.hbs index 0a212554..c1aa11b9 100644 --- a/.maintain/module-weight-template.hbs +++ b/.maintain/module-weight-template.hbs @@ -1,108 +1,134 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! Autogenerated weights for {{pallet}} -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} -//! DATE: {{date}}, STEPS: {{cmd.steps}}, REPEAT: {{cmd.repeat}}, LOW RANGE: {{cmd.lowest_range_values}}, HIGH RANGE: {{cmd.highest_range_values}} -//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} - -// Executed Command: -{{#each args as |arg|~}} -// {{arg}} -{{/each}} - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for {{pallet}}. -pub trait WeightInfo { - {{~#each benchmarks as |benchmark|}} - fn {{benchmark.name~}} - ( - {{~#each benchmark.components as |c| ~}} - {{c.name}}: u32, {{/each~}} - ) -> Weight; - {{~/each}} -} - -/// Weights for {{pallet}} using the Setheum node and recommended hardware. -pub struct SetheumWeight(PhantomData); -impl WeightInfo for SetheumWeight { - {{~#each benchmarks as |benchmark|}} - fn {{benchmark.name~}} - ( - {{~#each benchmark.components as |c| ~}} - {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} - ) -> Weight { - ({{underscore benchmark.base_weight}} as Weight) - {{~#each benchmark.component_weight as |cw|}} - // Standard Error: {{underscore cw.error}} - .saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)) - {{~/each}} - {{~#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as Weight)) - {{~/if}} - {{~#each benchmark.component_reads as |cr|}} - .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight))) - {{~/each}} - {{~#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as Weight)) - {{~/if}} - {{~#each benchmark.component_writes as |cw|}} - .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))) - {{~/each}} - } - {{~/each}} -} - -// For backwards compatibility and tests -impl WeightInfo for () { - {{~#each benchmarks as |benchmark|}} - fn {{benchmark.name~}} - ( - {{~#each benchmark.components as |c| ~}} - {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} - ) -> Weight { - ({{underscore benchmark.base_weight}} as Weight) - {{~#each benchmark.component_weight as |cw|}} - // Standard Error: {{underscore cw.error}} - .saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)) - {{~/each}} - {{~#if (ne benchmark.base_reads "0")}} - .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as Weight)) - {{~/if}} - {{~#each benchmark.component_reads as |cr|}} - .saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight))) - {{~/each}} - {{~#if (ne benchmark.base_writes "0")}} - .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}} as Weight)) - {{~/if}} - {{~#each benchmark.component_writes as |cw|}} - .saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))) - {{~/each}} - } - {{~/each}} -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for {{pallet}} +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` +//! WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for {{pallet}}. +pub trait WeightInfo { + {{#each benchmarks as |benchmark|}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{c.name}}: u32, {{/each~}} + ) -> Weight; + {{/each}} +} + +/// Weights for {{pallet}} using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + // Proof Size summary in bytes: + // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} + {{#each benchmark.component_calculated_proof_size as |cp|}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} + } + {{/each}} +} + +// For backwards compatibility and tests +impl WeightInfo for () { + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + // Proof Size summary in bytes: + // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(RocksDbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(RocksDbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} + {{#each benchmark.component_calculated_proof_size as |cp|}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} + } + {{/each}} +} diff --git a/.maintain/orml-weight-template.hbs b/.maintain/orml-weight-template.hbs index 43ba0442..1b5c861e 100644 --- a/.maintain/orml-weight-template.hbs +++ b/.maintain/orml-weight-template.hbs @@ -1,78 +1,73 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! Autogenerated weights for {{pallet}} -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} -//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` -//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} - -// Executed Command: -{{#each args as |arg|~}} -// {{arg}} -{{/each}} - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for {{pallet}}. -pub trait WeightInfo { - {{~#each benchmarks as |benchmark|}} - fn {{benchmark.name~}} - ( - {{~#each benchmark.components as |c| ~}} - {{c.name}}: u32, {{/each~}} - ) -> Weight; - {{~/each}} -} - -/// Default weights. -impl WeightInfo for () { - {{~#each benchmarks as |benchmark|}} - fn {{benchmark.name~}} - ( - {{~#each benchmark.components as |c| ~}} - {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} - ) -> Weight { - ({{underscore benchmark.base_weight}} as Weight) - {{~#each benchmark.component_weight as |cw|}} - // Standard Error: {{underscore cw.error}} - .saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)) - {{~/each}} - {{~#if (ne benchmark.base_reads "0")}} - .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as Weight)) - {{~/if}} - {{~#each benchmark.component_reads as |cr|}} - .saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight))) - {{~/each}} - {{~#if (ne benchmark.base_writes "0")}} - .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}} as Weight)) - {{~/if}} - {{~#each benchmark.component_writes as |cw|}} - .saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))) - {{~/each}} - } - {{~/each}} -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +//! Autogenerated weights for {{pallet}} +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` +//! WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for {{pallet}}. +pub trait WeightInfo { + {{#each benchmarks as |benchmark|}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{c.name}}: u32, {{/each~}} + ) -> Weight; + {{/each}} +} + +/// Default weights. +impl WeightInfo for () { + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + // Proof Size summary in bytes: + // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(RocksDbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(RocksDbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} + {{#each benchmark.component_calculated_proof_size as |cp|}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} + } + {{/each}} +} diff --git a/.maintain/runtime-weight-template.hbs b/.maintain/runtime-weight-template.hbs index a10f83e8..126bd7c3 100644 --- a/.maintain/runtime-weight-template.hbs +++ b/.maintain/runtime-weight-template.hbs @@ -1,67 +1,81 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! Autogenerated weights for {{pallet}} -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} -//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` -//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} - -// Executed Command: -{{#each args as |arg|~}} -// {{arg}} -{{/each}} - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; - -/// Weight functions for {{pallet}}. -pub struct WeightInfo(PhantomData); -impl {{pallet}}::WeightInfo for WeightInfo { - {{~#each benchmarks as |benchmark|}} - fn {{benchmark.name~}} - ( - {{~#each benchmark.components as |c| ~}} - {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} - ) -> Weight { - ({{underscore benchmark.base_weight}} as Weight) - {{~#each benchmark.component_weight as |cw|}} - // Standard Error: {{underscore cw.error}} - .saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)) - {{~/each}} - {{~#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as Weight)) - {{~/if}} - {{~#each benchmark.component_reads as |cr|}} - .saturating_add(T::DbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight))) - {{~/each}} - {{~#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as Weight)) - {{~/if}} - {{~#each benchmark.component_writes as |cw|}} - .saturating_add(T::DbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))) - {{~/each}} - } - {{~/each}} -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for {{pallet}} +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` +//! WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for {{pallet}}. +pub struct WeightInfo(PhantomData); +impl {{pallet}}::WeightInfo for WeightInfo { + {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} + fn {{benchmark.name~}} + ( + {{~#each benchmark.components as |c| ~}} + {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} + ) -> Weight { + // Proof Size summary in bytes: + // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. + Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) + {{#each benchmark.component_weight as |cw|}} + // Standard Error: {{underscore cw.error}} + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) + {{/each}} + {{#if (ne benchmark.base_reads "0")}} + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) + {{/if}} + {{#each benchmark.component_reads as |cr|}} + .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) + {{/each}} + {{#if (ne benchmark.base_writes "0")}} + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) + {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} + {{#each benchmark.component_calculated_proof_size as |cp|}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} + } + {{/each}} +} From a4b308d14b52ac0df67a5c25966069537952d8e2 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sat, 2 Mar 2024 03:13:14 +0800 Subject: [PATCH 03/21] Update EVM Module --- blockchain/modules/asset-registry/src/mock.rs | 2 - blockchain/modules/currencies/src/mock.rs | 754 ++- blockchain/modules/edfis-swap/src/mock.rs | 14 +- blockchain/modules/edfis-swap/src/tests.rs | 614 +-- blockchain/modules/edfis-swap/src/weights.rs | 8 +- blockchain/modules/evm/Cargo.toml | 189 +- .../modules/evm/benches/orml_benches.rs | 21 + blockchain/modules/evm/rpc/Cargo.toml | 33 - .../modules/evm/rpc/runtime_api/Cargo.toml | 47 +- .../modules/evm/rpc/runtime_api/src/lib.rs | 141 +- blockchain/modules/evm/rpc/src/lib.rs | 453 -- blockchain/modules/evm/src/bench/mock.rs | 273 + blockchain/modules/evm/src/bench/mod.rs | 274 + blockchain/modules/evm/src/lib.rs | 3661 +++++++------ blockchain/modules/evm/src/mock.rs | 594 ++- blockchain/modules/evm/src/precompiles.rs | 424 -- .../evm/src/precompiles/blake2/eip_152.rs | 95 + .../modules/evm/src/precompiles/blake2/mod.rs | 263 + .../modules/evm/src/precompiles/bn128.rs | 449 ++ .../modules/evm/src/precompiles/ecrecover.rs | 105 + .../src/precompiles/ecrecover_publickey.rs | 67 + .../precompiles/identity.rs} | 83 +- blockchain/modules/evm/src/precompiles/mod.rs | 213 + .../modules/evm/src/precompiles/modexp.rs | 751 +++ .../modules/evm/src/precompiles/ripemd.rs | 39 + .../modules/evm/src/precompiles/sha256.rs | 37 + .../modules/evm/src/precompiles/sha3fips.rs | 149 + blockchain/modules/evm/src/runner/mod.rs | 200 +- blockchain/modules/evm/src/runner/stack.rs | 1615 +++--- blockchain/modules/evm/src/runner/state.rs | 2616 ++++++---- .../modules/evm/src/runner/storage_meter.rs | 593 +-- .../runner/tagged_runtime.rs} | 90 +- blockchain/modules/evm/src/tests.rs | 4612 +++++++++++------ blockchain/modules/evm/src/weights.rs | 610 ++- blockchain/modules/prices/src/lib.rs | 9 +- blockchain/modules/prices/src/mock.rs | 8 +- blockchain/modules/support/src/lib.rs | 16 - .../modules/transaction-payment/src/mock.rs | 10 +- .../modules/transaction-payment/src/tests.rs | 130 +- .../runtime/common/src/precompile/mock.rs | 8 +- .../runtime/common/src/precompile/tests.rs | 1810 +++---- blockchain/runtime/src/benchmarking/dex.rs | 670 +-- .../runtime/src/benchmarking/serp_setmint.rs | 634 +-- blockchain/runtime/src/lib.rs | 14 +- blockchain/runtime/src/weights/mod.rs | 2 +- blockchain/runtime/src/weights/module_dex.rs | 6 +- 46 files changed, 14257 insertions(+), 9149 deletions(-) create mode 100644 blockchain/modules/evm/benches/orml_benches.rs delete mode 100644 blockchain/modules/evm/rpc/Cargo.toml delete mode 100644 blockchain/modules/evm/rpc/src/lib.rs create mode 100644 blockchain/modules/evm/src/bench/mock.rs create mode 100644 blockchain/modules/evm/src/bench/mod.rs delete mode 100644 blockchain/modules/evm/src/precompiles.rs create mode 100644 blockchain/modules/evm/src/precompiles/blake2/eip_152.rs create mode 100644 blockchain/modules/evm/src/precompiles/blake2/mod.rs create mode 100644 blockchain/modules/evm/src/precompiles/bn128.rs create mode 100644 blockchain/modules/evm/src/precompiles/ecrecover.rs create mode 100644 blockchain/modules/evm/src/precompiles/ecrecover_publickey.rs rename blockchain/modules/evm/{rpc/src/evm_api.rs => src/precompiles/identity.rs} (55%) create mode 100644 blockchain/modules/evm/src/precompiles/mod.rs create mode 100644 blockchain/modules/evm/src/precompiles/modexp.rs create mode 100644 blockchain/modules/evm/src/precompiles/ripemd.rs create mode 100644 blockchain/modules/evm/src/precompiles/sha256.rs create mode 100644 blockchain/modules/evm/src/precompiles/sha3fips.rs rename blockchain/modules/evm/{rpc/src/call_request.rs => src/runner/tagged_runtime.rs} (51%) diff --git a/blockchain/modules/asset-registry/src/mock.rs b/blockchain/modules/asset-registry/src/mock.rs index 050cb7a3..d8866e05 100644 --- a/blockchain/modules/asset-registry/src/mock.rs +++ b/blockchain/modules/asset-registry/src/mock.rs @@ -101,8 +101,6 @@ impl module_evm::Config for Runtime { type Runner = module_evm::runner::stack::Runner; type FindAuthor = (); - type Task = (); - type IdleScheduler = (); type WeightInfo = (); } diff --git a/blockchain/modules/currencies/src/mock.rs b/blockchain/modules/currencies/src/mock.rs index c76da11a..fc89e88f 100644 --- a/blockchain/modules/currencies/src/mock.rs +++ b/blockchain/modules/currencies/src/mock.rs @@ -1,378 +1,376 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! Mocks for the currencies module. - -#![cfg(test)] - -use super::*; -pub use crate as currencies; - -use frame_support::{ - assert_ok, ord_parameter_types, parameter_types, - traits::{ConstU128, ConstU32, ConstU64, Everything, Nothing}, - PalletId, -}; -use frame_system::EnsureSignedBy; -use module_support::{mocks::MockAddressMapping, AddressMapping}; -use orml_traits::{currency::MutationHooks, parameter_type_with_key}; -use primitives::{evm::convert_decimals_to_evm, CurrencyId, ReserveIdentifier, TokenSymbol}; -use sp_core::H256; -use sp_core::{H160, U256}; -use sp_runtime::{ - testing::Header, - traits::{AccountIdConversion, IdentityLookup}, - AccountId32, BuildStorage, -}; -use sp_std::str::FromStr; - -pub const CHARLIE: AccountId = AccountId32::new([6u8; 32]); -pub const DAVE: AccountId = AccountId32::new([7u8; 32]); -pub const EVE: AccountId = AccountId32::new([8u8; 32]); -pub const FERDIE: AccountId = AccountId32::new([9u8; 32]); - -pub type AccountId = AccountId32; -impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} - -type Balance = u128; - -parameter_type_with_key! { - pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { - if *currency_id == EDF { return 2; } - Default::default() - }; -} - -parameter_types! { - pub DustAccount: AccountId = PalletId(*b"orml/dst").into_account_truncating(); -} - -pub struct CurrencyHooks(marker::PhantomData); -impl MutationHooks for CurrencyHooks -where - T::AccountId: From, -{ - type OnDust = orml_tokens::TransferDust; - type OnSlash = (); - type PreDeposit = (); - type PostDeposit = (); - type PreTransfer = (); - type PostTransfer = (); - type OnNewTokenAccount = (); - type OnKilledTokenAccount = (); -} - -impl orml_tokens::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Balance = Balance; - type Amount = i64; - type CurrencyId = CurrencyId; - type ExistentialDeposits = ExistentialDeposits; - type CurrencyHooks = CurrencyHooks; - type WeightInfo = (); - type MaxLocks = ConstU32<100>; - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type DustRemovalWhitelist = Nothing; -} - -pub const NATIVE_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); -pub const X_TOKEN_ID: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); -pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); - -parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; -} - -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ConstU128<2>; - type AccountStore = module_support::SystemAccountStore; - type MaxLocks = (); - type MaxReserves = ConstU32<50>; - type ReserveIdentifier = ReserveIdentifier; - type WeightInfo = (); - type RuntimeHoldReason = (); - type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; - type MaxFreezes = (); -} - -pub type PalletBalances = pallet_balances::Pallet; - -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<1000>; - type WeightInfo = (); -} - -parameter_types! { - pub NetworkContractSource: H160 = alice_evm_addr(); -} - -ord_parameter_types! { - pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); - pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); - pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); - pub const StorageDepositPerByte: u128 = convert_decimals_to_evm(10); - pub const TxFeePerGas: u128 = 10; - pub const DeveloperDeposit: u64 = 1000; - pub const PublicationFee: u64 = 200; -} - -pub struct GasToWeight; -impl Convert for GasToWeight { - fn convert(a: u64) -> Weight { - Weight::from_parts(a, 0) - } -} - -impl module_evm::Config for Runtime { - type AddressMapping = MockAddressMapping; - type Currency = PalletBalances; - type TransferAll = (); - type NewContractExtraBytes = ConstU32<1>; - type StorageDepositPerByte = StorageDepositPerByte; - type TxFeePerGas = TxFeePerGas; - type RuntimeEvent = RuntimeEvent; - type PrecompilesType = (); - type PrecompilesValue = (); - type GasToWeight = GasToWeight; - type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; - type NetworkContractOrigin = EnsureSignedBy; - type NetworkContractSource = NetworkContractSource; - - type DeveloperDeposit = DeveloperDeposit; - type PublicationFee = PublicationFee; - type TreasuryAccount = TreasuryAccount; - type FreePublicationOrigin = EnsureSignedBy; - - type Runner = module_evm::runner::stack::Runner; - type FindAuthor = (); - type Task = (); - type IdleScheduler = (); - type WeightInfo = (); -} - -impl module_evm_bridge::Config for Runtime { - type EVM = EVM; -} - -parameter_types! { - pub Erc20HoldingAccount: H160 = primitives::evm::ERC20_HOLDING_ACCOUNT; -} - -impl Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type MultiCurrency = Tokens; - type NativeCurrency = AdaptedBasicCurrency; - type GetNativeCurrencyId = GetNativeCurrencyId; - type Erc20HoldingAccount = Erc20HoldingAccount; - type WeightInfo = (); - type AddressMapping = MockAddressMapping; - type EVMBridge = module_evm_bridge::EVMBridge; - type GasToWeight = GasToWeight; - type SweepOrigin = EnsureSignedBy; - type OnDust = crate::TransferDust; -} - -pub type NativeCurrency = Currency; -pub type AdaptedBasicCurrency = BasicCurrencyAdapter; - -pub type SignedExtra = module_evm::SetEvmOrigin; - -pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; - -frame_support::construct_runtime!( - pub enum Runtime { - System: frame_system, - Balances: pallet_balances, - Tokens: orml_tokens, - Currencies: currencies, - EVM: module_evm, - EVMBridge: module_evm_bridge, - } -); - -pub fn alice() -> AccountId { - ::AddressMapping::get_account_id(&alice_evm_addr()) -} - -pub fn alice_evm_addr() -> EvmAddress { - EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap() -} - -pub fn bob() -> AccountId { - ::AddressMapping::get_account_id(&bob_evm_addr()) -} - -pub fn bob_evm_addr() -> EvmAddress { - EvmAddress::from_str("1000000000000000000000000000000000000002").unwrap() -} - -pub fn eva() -> AccountId { - ::AddressMapping::get_account_id(&eva_evm_addr()) -} - -pub fn eva_evm_addr() -> EvmAddress { - EvmAddress::from_str("1000000000000000000000000000000000000005").unwrap() -} - -pub const ID_1: LockIdentifier = *b"1 "; - -pub fn erc20_address() -> EvmAddress { - EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() -} - -pub fn erc20_address_not_exist() -> EvmAddress { - EvmAddress::from_str("0x00ddfce53ee040d9eb21afbc0ae1bb4dbb0ba600").unwrap() -} - -pub const ALICE_BALANCE: u128 = 100_000_000_000_000_000_000_000u128; - -pub fn deploy_contracts() { - let json: serde_json::Value = - serde_json::from_str(include_str!("../../../ts-tests/build/Erc20DemoContract2.json")).unwrap(); - let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); - assert_ok!(EVM::create( - RuntimeOrigin::signed(alice()), - code, - 0, - 2_100_000, - 10_000, - vec![] - )); - - System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Created { - from: alice_evm_addr(), - contract: erc20_address(), - logs: vec![module_evm::Log { - address: H160::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap(), - topics: vec![ - H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), - H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), - ], - data: { - let mut buf = [0u8; 32]; - U256::from(ALICE_BALANCE).to_big_endian(&mut buf); - H256::from_slice(&buf).as_bytes().to_vec() - }, - }], - used_gas: 1235455, - used_storage: 5131, - })); - - assert_ok!(EVM::publish_free( - RuntimeOrigin::signed(CouncilAccount::get()), - erc20_address() - )); -} - -pub struct ExtBuilder { - balances: Vec<(AccountId, CurrencyId, Balance)>, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { balances: vec![] } - } -} - -impl ExtBuilder { - pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { - self.balances = balances; - self - } - - pub fn one_hundred_for_alice_n_bob(self) -> Self { - self.balances(vec![ - (alice(), NATIVE_CURRENCY_ID, 100), - (bob(), NATIVE_CURRENCY_ID, 100), - (alice(), X_TOKEN_ID, 100), - (bob(), X_TOKEN_ID, 100), - ]) - } - - pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: self - .balances - .clone() - .into_iter() - .filter(|(_, currency_id, _)| *currency_id == NATIVE_CURRENCY_ID) - .map(|(account_id, _, initial_balance)| (account_id, initial_balance)) - .collect::>(), - } - .assimilate_storage(&mut t) - .unwrap(); - - orml_tokens::GenesisConfig:: { - balances: self - .balances - .into_iter() - .filter(|(_, currency_id, _)| *currency_id != NATIVE_CURRENCY_ID) - .collect::>(), - } - .assimilate_storage(&mut t) - .unwrap(); - - module_evm::GenesisConfig::::default() - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the currencies module. + +#![cfg(test)] + +use super::*; +pub use crate as currencies; + +use frame_support::{ + assert_ok, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Everything, Nothing}, + PalletId, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockAddressMapping, AddressMapping}; +use orml_traits::{currency::MutationHooks, parameter_type_with_key}; +use primitives::{evm::convert_decimals_to_evm, CurrencyId, ReserveIdentifier, TokenSymbol}; +use sp_core::H256; +use sp_core::{H160, U256}; +use sp_runtime::{ + testing::Header, + traits::{AccountIdConversion, IdentityLookup}, + AccountId32, BuildStorage, +}; +use sp_std::str::FromStr; + +pub const CHARLIE: AccountId = AccountId32::new([6u8; 32]); +pub const DAVE: AccountId = AccountId32::new([7u8; 32]); +pub const EVE: AccountId = AccountId32::new([8u8; 32]); +pub const FERDIE: AccountId = AccountId32::new([9u8; 32]); + +pub type AccountId = AccountId32; +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +type Balance = u128; + +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { + if *currency_id == EDF { return 2; } + Default::default() + }; +} + +parameter_types! { + pub DustAccount: AccountId = PalletId(*b"orml/dst").into_account_truncating(); +} + +pub struct CurrencyHooks(marker::PhantomData); +impl MutationHooks for CurrencyHooks +where + T::AccountId: From, +{ + type OnDust = orml_tokens::TransferDust; + type OnSlash = (); + type PreDeposit = (); + type PostDeposit = (); + type PreTransfer = (); + type PostTransfer = (); + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = i64; + type CurrencyId = CurrencyId; + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = CurrencyHooks; + type WeightInfo = (); + type MaxLocks = ConstU32<100>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +pub const NATIVE_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const X_TOKEN_ID: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<2>; + type AccountStore = module_support::SystemAccountStore; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = ConstU32<1>; + type MaxFreezes = (); +} + +pub type PalletBalances = pallet_balances::Pallet; + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1000>; + type WeightInfo = (); +} + +parameter_types! { + pub NetworkContractSource: H160 = alice_evm_addr(); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); + pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); + pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); + pub const StorageDepositPerByte: u128 = convert_decimals_to_evm(10); + pub const TxFeePerGas: u128 = 10; + pub const DeveloperDeposit: u64 = 1000; + pub const PublicationFee: u64 = 200; +} + +pub struct GasToWeight; +impl Convert for GasToWeight { + fn convert(a: u64) -> Weight { + Weight::from_parts(a, 0) + } +} + +impl module_evm::Config for Runtime { + type AddressMapping = MockAddressMapping; + type Currency = PalletBalances; + type TransferAll = (); + type NewContractExtraBytes = ConstU32<1>; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = GasToWeight; + type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + + type DeveloperDeposit = DeveloperDeposit; + type PublicationFee = PublicationFee; + type TreasuryAccount = TreasuryAccount; + type FreePublicationOrigin = EnsureSignedBy; + + type Runner = module_evm::runner::stack::Runner; + type FindAuthor = (); + type WeightInfo = (); +} + +impl module_evm_bridge::Config for Runtime { + type EVM = EVM; +} + +parameter_types! { + pub Erc20HoldingAccount: H160 = primitives::evm::ERC20_HOLDING_ACCOUNT; +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type Erc20HoldingAccount = Erc20HoldingAccount; + type WeightInfo = (); + type AddressMapping = MockAddressMapping; + type EVMBridge = module_evm_bridge::EVMBridge; + type GasToWeight = GasToWeight; + type SweepOrigin = EnsureSignedBy; + type OnDust = crate::TransferDust; +} + +pub type NativeCurrency = Currency; +pub type AdaptedBasicCurrency = BasicCurrencyAdapter; + +pub type SignedExtra = module_evm::SetEvmOrigin; + +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + Tokens: orml_tokens, + Currencies: currencies, + EVM: module_evm, + EVMBridge: module_evm_bridge, + } +); + +pub fn alice() -> AccountId { + ::AddressMapping::get_account_id(&alice_evm_addr()) +} + +pub fn alice_evm_addr() -> EvmAddress { + EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap() +} + +pub fn bob() -> AccountId { + ::AddressMapping::get_account_id(&bob_evm_addr()) +} + +pub fn bob_evm_addr() -> EvmAddress { + EvmAddress::from_str("1000000000000000000000000000000000000002").unwrap() +} + +pub fn eva() -> AccountId { + ::AddressMapping::get_account_id(&eva_evm_addr()) +} + +pub fn eva_evm_addr() -> EvmAddress { + EvmAddress::from_str("1000000000000000000000000000000000000005").unwrap() +} + +pub const ID_1: LockIdentifier = *b"1 "; + +pub fn erc20_address() -> EvmAddress { + EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() +} + +pub fn erc20_address_not_exist() -> EvmAddress { + EvmAddress::from_str("0x00ddfce53ee040d9eb21afbc0ae1bb4dbb0ba600").unwrap() +} + +pub const ALICE_BALANCE: u128 = 100_000_000_000_000_000_000_000u128; + +pub fn deploy_contracts() { + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/Erc20DemoContract2.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + assert_ok!(EVM::create( + RuntimeOrigin::signed(alice()), + code, + 0, + 2_100_000, + 10_000, + vec![] + )); + + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![module_evm::Log { + address: H160::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap(), + topics: vec![ + H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), + ], + data: { + let mut buf = [0u8; 32]; + U256::from(ALICE_BALANCE).to_big_endian(&mut buf); + H256::from_slice(&buf).as_bytes().to_vec() + }, + }], + used_gas: 1235455, + used_storage: 5131, + })); + + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address() + )); +} + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![] } + } +} + +impl ExtBuilder { + pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn one_hundred_for_alice_n_bob(self) -> Self { + self.balances(vec![ + (alice(), NATIVE_CURRENCY_ID, 100), + (bob(), NATIVE_CURRENCY_ID, 100), + (alice(), X_TOKEN_ID, 100), + (bob(), X_TOKEN_ID, 100), + ]) + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self + .balances + .clone() + .into_iter() + .filter(|(_, currency_id, _)| *currency_id == NATIVE_CURRENCY_ID) + .map(|(account_id, _, initial_balance)| (account_id, initial_balance)) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self + .balances + .into_iter() + .filter(|(_, currency_id, _)| *currency_id != NATIVE_CURRENCY_ID) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + module_evm::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/blockchain/modules/edfis-swap/src/mock.rs b/blockchain/modules/edfis-swap/src/mock.rs index 375facd0..1be114ba 100644 --- a/blockchain/modules/edfis-swap/src/mock.rs +++ b/blockchain/modules/edfis-swap/src/mock.rs @@ -18,7 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Mocks for the dex module. +//! Mocks for the edfis_swap module. #![cfg(test)] @@ -51,7 +51,7 @@ parameter_types! { pub static EDFWBTCPair: TradingPair = TradingPair::from_currency_ids(EDF, WBTC).unwrap(); } -mod dex { +mod edfis_swap { pub use super::super::*; } @@ -101,7 +101,7 @@ ord_parameter_types! { parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); - pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); pub AlternativeSwapPathJointList: Vec> = vec![ vec![EDF], ]; @@ -140,15 +140,15 @@ parameter_types! { pub SEEJoint: Vec> = vec![vec![SEE]]; } -pub type USSDJointSwap = SpecificJointsSwap; -pub type SEEJointSwap = SpecificJointsSwap; +pub type USSDJointSwap = SpecificJointsSwap; +pub type SEEJointSwap = SpecificJointsSwap; type Block = frame_system::mocking::MockBlock; construct_runtime!( pub enum Runtime { System: frame_system, - DexModule: dex, + EdfisSwapModule: edfis_swap, Tokens: orml_tokens, } ); @@ -209,7 +209,7 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - dex::GenesisConfig:: { + edfis_swap::GenesisConfig:: { initial_listing_trading_pairs: self.initial_listing_trading_pairs, initial_enabled_trading_pairs: self.initial_enabled_trading_pairs, initial_added_liquidity_pools: self.initial_added_liquidity_pools, diff --git a/blockchain/modules/edfis-swap/src/tests.rs b/blockchain/modules/edfis-swap/src/tests.rs index 975645b8..880198e5 100644 --- a/blockchain/modules/edfis-swap/src/tests.rs +++ b/blockchain/modules/edfis-swap/src/tests.rs @@ -25,7 +25,7 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{ - SEEJointSwap, USSDWBTCPair, USSDEDFPair, USSDJointSwap, EDFWBTCPair, DexModule, ExtBuilder, ListingOrigin, Runtime, + SEEJointSwap, USSDWBTCPair, USSDEDFPair, USSDJointSwap, EDFWBTCPair, EdfisSwapModule, ExtBuilder, ListingOrigin, Runtime, RuntimeEvent, RuntimeOrigin, System, Tokens, SEE, ALICE, USSD, USSD_EDF_POOL_RECORD, BOB, WBTC, CAROL, EDF, }; use module_support::{Swap, SwapError}; @@ -40,7 +40,7 @@ fn list_provisioning_work() { System::set_block_number(1); assert_noop!( - DexModule::list_provisioning( + EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -54,10 +54,10 @@ fn list_provisioning_work() { ); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Disabled ); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -68,7 +68,7 @@ fn list_provisioning_work() { 10, )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -76,12 +76,12 @@ fn list_provisioning_work() { not_before: 10, }) ); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::ListProvisioning { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::ListProvisioning { trading_pair: USSDEDFPair::get(), })); assert_noop!( - DexModule::list_provisioning( + EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, USSD, @@ -95,7 +95,7 @@ fn list_provisioning_work() { ); assert_noop!( - DexModule::list_provisioning( + EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -109,7 +109,7 @@ fn list_provisioning_work() { ); assert_noop!( - DexModule::list_provisioning( + EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), CurrencyId::ForeignAsset(0), USSD, @@ -122,7 +122,7 @@ fn list_provisioning_work() { Error::::AssetUnregistered ); assert_noop!( - DexModule::list_provisioning( + EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, CurrencyId::ForeignAsset(0), @@ -143,7 +143,7 @@ fn update_provisioning_parameters_work() { System::set_block_number(1); assert_noop!( - DexModule::update_provisioning_parameters( + EdfisSwapModule::update_provisioning_parameters( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -157,7 +157,7 @@ fn update_provisioning_parameters_work() { ); assert_noop!( - DexModule::update_provisioning_parameters( + EdfisSwapModule::update_provisioning_parameters( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -170,7 +170,7 @@ fn update_provisioning_parameters_work() { Error::::MustBeProvisioning ); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -181,7 +181,7 @@ fn update_provisioning_parameters_work() { 10, )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -190,7 +190,7 @@ fn update_provisioning_parameters_work() { }) ); - assert_ok!(DexModule::update_provisioning_parameters( + assert_ok!(EdfisSwapModule::update_provisioning_parameters( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -201,7 +201,7 @@ fn update_provisioning_parameters_work() { 50, )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (2_000_000_000_000u128, 0), target_provision: (3_000_000_000_000u128, 2_000_000_000_000u128), @@ -218,29 +218,29 @@ fn enable_diabled_trading_pair_work() { System::set_block_number(1); assert_noop!( - DexModule::enable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), + EdfisSwapModule::enable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), BadOrigin ); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Disabled ); - assert_ok!(DexModule::enable_trading_pair( + assert_ok!(EdfisSwapModule::enable_trading_pair( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Enabled ); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::EnableTradingPair { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::EnableTradingPair { trading_pair: USSDEDFPair::get(), })); assert_noop!( - DexModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), EDF, USSD), + EdfisSwapModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), EDF, USSD), Error::::AlreadyEnabled ); }); @@ -251,7 +251,7 @@ fn enable_provisioning_without_provision_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -261,7 +261,7 @@ fn enable_provisioning_without_provision_work() { 2_000_000_000_000u128, 10, )); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC, @@ -271,7 +271,7 @@ fn enable_provisioning_without_provision_work() { 2_000_000_000_000u128, 10, )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, WBTC, @@ -280,7 +280,7 @@ fn enable_provisioning_without_provision_work() { )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -288,21 +288,21 @@ fn enable_provisioning_without_provision_work() { not_before: 10, }) ); - assert_ok!(DexModule::enable_trading_pair( + assert_ok!(EdfisSwapModule::enable_trading_pair( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Enabled ); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::EnableTradingPair { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::EnableTradingPair { trading_pair: USSDEDFPair::get(), })); assert_noop!( - DexModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + EdfisSwapModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), Error::::StillProvisioning ); }); @@ -313,7 +313,7 @@ fn end_provisioning_trading_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -324,7 +324,7 @@ fn end_provisioning_trading_work() { 10, )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -333,7 +333,7 @@ fn end_provisioning_trading_work() { }) ); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC, @@ -343,7 +343,7 @@ fn end_provisioning_trading_work() { 2_000_000_000_000u128, 10, )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, WBTC, @@ -352,13 +352,13 @@ fn end_provisioning_trading_work() { )); assert_noop!( - DexModule::end_provisioning(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + EdfisSwapModule::end_provisioning(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), Error::::UnqualifiedProvision ); System::set_block_number(10); assert_eq!( - DexModule::trading_pair_statuses(USSDWBTCPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDWBTCPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -367,37 +367,37 @@ fn end_provisioning_trading_work() { }) ); assert_eq!( - DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDWBTCPair::get()), Default::default() ); - assert_eq!(DexModule::liquidity_pool(USSDWBTCPair::get()), (0, 0)); + assert_eq!(EdfisSwapModule::liquidity_pool(USSDWBTCPair::get()), (0, 0)); assert_eq!(Tokens::total_issuance(USSDWBTCPair::get().dex_share_currency_id()), 0); assert_eq!( - Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &DexModule::account_id()), + Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &EdfisSwapModule::account_id()), 0 ); - assert_ok!(DexModule::end_provisioning( + assert_ok!(EdfisSwapModule::end_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::ProvisioningToEnabled { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::ProvisioningToEnabled { trading_pair: USSDWBTCPair::get(), pool_0: 1_000_000_000_000u128, pool_1: 2_000_000_000_000u128, share_amount: 2_000_000_000_000u128, })); assert_eq!( - DexModule::trading_pair_statuses(USSDWBTCPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDWBTCPair::get()), TradingPairStatus::<_, _>::Enabled ); assert_eq!( - DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDWBTCPair::get()), (ExchangeRate::one(), ExchangeRate::checked_from_rational(1, 2).unwrap()) ); assert_eq!( - DexModule::liquidity_pool(USSDWBTCPair::get()), + EdfisSwapModule::liquidity_pool(USSDWBTCPair::get()), (1_000_000_000_000u128, 2_000_000_000_000u128) ); assert_eq!( @@ -405,7 +405,7 @@ fn end_provisioning_trading_work() { 2_000_000_000_000u128 ); assert_eq!( - Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &DexModule::account_id()), + Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &EdfisSwapModule::account_id()), 2_000_000_000_000u128 ); }); @@ -417,11 +417,11 @@ fn abort_provisioning_work() { System::set_block_number(1); assert_noop!( - DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF), + EdfisSwapModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF), Error::::MustBeProvisioning ); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -431,7 +431,7 @@ fn abort_provisioning_work() { 2_000_000_000_000u128, 1000, )); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC, @@ -442,14 +442,14 @@ fn abort_provisioning_work() { 1000, )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, EDF, 1_000_000_000_000u128, 1_000_000_000_000u128 )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(BOB), USSD, WBTC, @@ -459,10 +459,10 @@ fn abort_provisioning_work() { // not expired, nothing happened. System::set_block_number(2000); - assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); - assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_ok!(EdfisSwapModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + assert_ok!(EdfisSwapModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -471,11 +471,11 @@ fn abort_provisioning_work() { }) ); assert_eq!( - DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDEDFPair::get()), Default::default() ); assert_eq!( - DexModule::trading_pair_statuses(USSDWBTCPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDWBTCPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -484,31 +484,31 @@ fn abort_provisioning_work() { }) ); assert_eq!( - DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDWBTCPair::get()), Default::default() ); // both expired, the provision for USSD-EDF could be aborted, the provision for USSD-WBTC // couldn't be aborted because it's already met the target. System::set_block_number(3001); - assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::ProvisioningAborted { + assert_ok!(EdfisSwapModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::ProvisioningAborted { trading_pair: USSDEDFPair::get(), accumulated_provision_0: 1_000_000_000_000u128, accumulated_provision_1: 1_000_000_000_000u128, })); - assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_ok!(EdfisSwapModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Disabled ); assert_eq!( - DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDEDFPair::get()), Default::default() ); assert_eq!( - DexModule::trading_pair_statuses(USSDWBTCPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDWBTCPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -517,7 +517,7 @@ fn abort_provisioning_work() { }) ); assert_eq!( - DexModule::initial_share_exchange_rates(USSDWBTCPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDWBTCPair::get()), Default::default() ); }); @@ -528,7 +528,7 @@ fn refund_provision_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -538,7 +538,7 @@ fn refund_provision_work() { 4_000_000_000_000_000_000u128, 1000, )); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC, @@ -549,21 +549,21 @@ fn refund_provision_work() { 1000, )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, EDF, 1_000_000_000_000_000_000u128, 1_000_000_000_000_000_000u128 )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(BOB), USSD, EDF, 0, 600_000_000_000_000_000u128, )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(BOB), USSD, WBTC, @@ -572,36 +572,36 @@ fn refund_provision_work() { )); assert_noop!( - DexModule::refund_provision(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), + EdfisSwapModule::refund_provision(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), Error::::MustBeDisabled ); // abort provisioning of USSD-EDF System::set_block_number(3001); - assert_ok!(DexModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + assert_ok!(EdfisSwapModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Disabled ); assert_eq!( - DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDEDFPair::get()), Default::default() ); assert_eq!( - DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), + EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), ALICE), (1_000_000_000_000_000_000u128, 1_000_000_000_000_000_000u128) ); assert_eq!( - DexModule::provisioning_pool(USSDEDFPair::get(), BOB), + EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 600_000_000_000_000_000u128) ); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 1_100_000_000_000_000_000u128 ); assert_eq!( - Tokens::free_balance(EDF, &DexModule::account_id()), + Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 1_600_000_000_000_000_000u128 ); assert_eq!(Tokens::free_balance(USSD, &ALICE), 0); @@ -612,13 +612,13 @@ fn refund_provision_work() { let alice_ref_count_0 = System::consumers(&ALICE); let bob_ref_count_0 = System::consumers(&BOB); - assert_ok!(DexModule::refund_provision( + assert_ok!(EdfisSwapModule::refund_provision( RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RefundProvision { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::RefundProvision { who: ALICE, currency_0: USSD, contribution_0: 1_000_000_000_000_000_000u128, @@ -626,26 +626,26 @@ fn refund_provision_work() { contribution_1: 1_000_000_000_000_000_000u128, })); - assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!(EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 100_000_000_000_000_000u128 ); assert_eq!( - Tokens::free_balance(EDF, &DexModule::account_id()), + Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 600_000_000_000_000_000u128 ); assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000u128); assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); assert_eq!(System::consumers(&ALICE), alice_ref_count_0 - 1); - assert_ok!(DexModule::refund_provision( + assert_ok!(EdfisSwapModule::refund_provision( RuntimeOrigin::signed(ALICE), BOB, USSD, EDF )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RefundProvision { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::RefundProvision { who: BOB, currency_0: USSD, contribution_0: 0, @@ -653,33 +653,33 @@ fn refund_provision_work() { contribution_1: 600_000_000_000_000_000u128, })); - assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); + assert_eq!(EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 100_000_000_000_000_000u128 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 0); assert_eq!(Tokens::free_balance(USSD, &BOB), 900_000_000_000_000_000u128); assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000u128); assert_eq!(System::consumers(&BOB), bob_ref_count_0 - 1); // not allow refund if the provisioning has been ended before. - assert_ok!(DexModule::end_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); - assert_ok!(DexModule::disable_trading_pair( + assert_ok!(EdfisSwapModule::end_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_ok!(EdfisSwapModule::disable_trading_pair( RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC )); assert_eq!( - DexModule::trading_pair_statuses(USSDWBTCPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDWBTCPair::get()), TradingPairStatus::<_, _>::Disabled ); assert_eq!( - DexModule::provisioning_pool(USSDWBTCPair::get(), BOB), + EdfisSwapModule::provisioning_pool(USSDWBTCPair::get(), BOB), (100_000_000_000_000_000u128, 100_000_000_000_000_000u128) ); assert_noop!( - DexModule::refund_provision(RuntimeOrigin::signed(BOB), BOB, USSD, WBTC), + EdfisSwapModule::refund_provision(RuntimeOrigin::signed(BOB), BOB, USSD, WBTC), Error::::NotAllowedRefund ); }); @@ -690,40 +690,40 @@ fn disable_trading_pair_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::enable_trading_pair( + assert_ok!(EdfisSwapModule::enable_trading_pair( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Enabled ); assert_noop!( - DexModule::disable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), + EdfisSwapModule::disable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), BadOrigin ); - assert_ok!(DexModule::disable_trading_pair( + assert_ok!(EdfisSwapModule::disable_trading_pair( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Disabled ); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::DisableTradingPair { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::DisableTradingPair { trading_pair: USSDEDFPair::get(), })); assert_noop!( - DexModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF), + EdfisSwapModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF), Error::::MustBeEnabled ); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC, @@ -734,7 +734,7 @@ fn disable_trading_pair_work() { 10, )); assert_noop!( - DexModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + EdfisSwapModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), Error::::MustBeEnabled ); }); @@ -746,7 +746,7 @@ fn on_liquidity_pool_updated_work() { .initialize_enabled_trading_pairs() .build() .execute_with(|| { - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, WBTC, @@ -757,7 +757,7 @@ fn on_liquidity_pool_updated_work() { )); assert_eq!(USSD_EDF_POOL_RECORD.with(|v| *v.borrow()), (0, 0)); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -779,7 +779,7 @@ fn add_provision_work() { System::set_block_number(1); assert_noop!( - DexModule::add_provision( + EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -789,7 +789,7 @@ fn add_provision_work() { Error::::MustBeProvisioning ); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -801,7 +801,7 @@ fn add_provision_work() { )); assert_noop!( - DexModule::add_provision( + EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -812,7 +812,7 @@ fn add_provision_work() { ); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (5_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000_000u128, 1_000_000_000_000_000u128), @@ -820,14 +820,14 @@ fn add_provision_work() { not_before: 10, }) ); - assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!(EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000u128); assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 0); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 0); let alice_ref_count_0 = System::consumers(&ALICE); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -835,7 +835,7 @@ fn add_provision_work() { 0, )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (5_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000_000u128, 1_000_000_000_000_000u128), @@ -844,19 +844,19 @@ fn add_provision_work() { }) ); assert_eq!( - DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), + EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), ALICE), (5_000_000_000_000u128, 0) ); assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_995_000_000_000_000u128); assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 5_000_000_000_000u128 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 0); let alice_ref_count_1 = System::consumers(&ALICE); assert_eq!(alice_ref_count_1, alice_ref_count_0 + 1); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::AddProvision { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::AddProvision { who: ALICE, currency_0: USSD, contribution_0: 5_000_000_000_000u128, @@ -871,7 +871,7 @@ fn claim_dex_share_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -882,14 +882,14 @@ fn claim_dex_share_work() { 0, )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(ALICE), USSD, EDF, 1_000_000_000_000_000u128, 200_000_000_000_000u128, )); - assert_ok!(DexModule::add_provision( + assert_ok!(EdfisSwapModule::add_provision( RuntimeOrigin::signed(BOB), USSD, EDF, @@ -898,11 +898,11 @@ fn claim_dex_share_work() { )); assert_noop!( - DexModule::claim_dex_share(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), + EdfisSwapModule::claim_dex_share(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), Error::::StillProvisioning ); - assert_ok!(DexModule::end_provisioning( + assert_ok!(EdfisSwapModule::end_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF @@ -912,19 +912,19 @@ fn claim_dex_share_work() { assert!(InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); assert_eq!( - DexModule::initial_share_exchange_rates(USSDEDFPair::get()), + EdfisSwapModule::initial_share_exchange_rates(USSDEDFPair::get()), (ExchangeRate::one(), ExchangeRate::saturating_from_rational(5, 1)) ); assert_eq!( - Tokens::free_balance(lp_currency_id, &DexModule::account_id()), + Tokens::free_balance(lp_currency_id, &EdfisSwapModule::account_id()), 10_000_000_000_000_000u128 ); assert_eq!( - DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), + EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), ALICE), (1_000_000_000_000_000u128, 200_000_000_000_000u128) ); assert_eq!( - DexModule::provisioning_pool(USSDEDFPair::get(), BOB), + EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), BOB), (4_000_000_000_000_000u128, 800_000_000_000_000u128) ); assert_eq!(Tokens::free_balance(lp_currency_id, &ALICE), 0); @@ -933,29 +933,29 @@ fn claim_dex_share_work() { let alice_ref_count_0 = System::consumers(&ALICE); let bob_ref_count_0 = System::consumers(&BOB); - assert_ok!(DexModule::claim_dex_share( + assert_ok!(EdfisSwapModule::claim_dex_share( RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF )); assert_eq!( - Tokens::free_balance(lp_currency_id, &DexModule::account_id()), + Tokens::free_balance(lp_currency_id, &EdfisSwapModule::account_id()), 8_000_000_000_000_000u128 ); - assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!(EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); assert_eq!(Tokens::free_balance(lp_currency_id, &ALICE), 2_000_000_000_000_000u128); assert_eq!(System::consumers(&ALICE), alice_ref_count_0 - 1); assert!(InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); - assert_ok!(DexModule::disable_trading_pair( + assert_ok!(EdfisSwapModule::disable_trading_pair( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF )); - assert_ok!(DexModule::claim_dex_share(RuntimeOrigin::signed(BOB), BOB, USSD, EDF)); - assert_eq!(Tokens::free_balance(lp_currency_id, &DexModule::account_id()), 0); - assert_eq!(DexModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); + assert_ok!(EdfisSwapModule::claim_dex_share(RuntimeOrigin::signed(BOB), BOB, USSD, EDF)); + assert_eq!(Tokens::free_balance(lp_currency_id, &EdfisSwapModule::account_id()), 0); + assert_eq!(EdfisSwapModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); assert_eq!(Tokens::free_balance(lp_currency_id, &BOB), 8_000_000_000_000_000u128); assert_eq!(System::consumers(&BOB), bob_ref_count_0 - 1); assert!(!InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); @@ -966,35 +966,35 @@ fn claim_dex_share_work() { fn get_liquidity_work() { ExtBuilder::default().build().execute_with(|| { LiquidityPool::::insert(USSDEDFPair::get(), (1000, 20)); - assert_eq!(DexModule::liquidity_pool(USSDEDFPair::get()), (1000, 20)); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (1000, 20)); - assert_eq!(DexModule::get_liquidity(EDF, USSD), (20, 1000)); + assert_eq!(EdfisSwapModule::liquidity_pool(USSDEDFPair::get()), (1000, 20)); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (1000, 20)); + assert_eq!(EdfisSwapModule::get_liquidity(EDF, USSD), (20, 1000)); }); } #[test] fn get_target_amount_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(DexModule::get_target_amount(10000, 0, 1000), 0); - assert_eq!(DexModule::get_target_amount(0, 20000, 1000), 0); - assert_eq!(DexModule::get_target_amount(10000, 20000, 0), 0); - assert_eq!(DexModule::get_target_amount(10000, 1, 1000000), 0); - assert_eq!(DexModule::get_target_amount(10000, 20000, 10000), 9949); - assert_eq!(DexModule::get_target_amount(10000, 20000, 1000), 1801); + assert_eq!(EdfisSwapModule::get_target_amount(10000, 0, 1000), 0); + assert_eq!(EdfisSwapModule::get_target_amount(0, 20000, 1000), 0); + assert_eq!(EdfisSwapModule::get_target_amount(10000, 20000, 0), 0); + assert_eq!(EdfisSwapModule::get_target_amount(10000, 1, 1000000), 0); + assert_eq!(EdfisSwapModule::get_target_amount(10000, 20000, 10000), 9949); + assert_eq!(EdfisSwapModule::get_target_amount(10000, 20000, 1000), 1801); }); } #[test] fn get_supply_amount_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(DexModule::get_supply_amount(10000, 0, 1000), 0); - assert_eq!(DexModule::get_supply_amount(0, 20000, 1000), 0); - assert_eq!(DexModule::get_supply_amount(10000, 20000, 0), 0); - assert_eq!(DexModule::get_supply_amount(10000, 1, 1), 0); - assert_eq!(DexModule::get_supply_amount(10000, 20000, 9949), 9999); - assert_eq!(DexModule::get_target_amount(10000, 20000, 9999), 9949); - assert_eq!(DexModule::get_supply_amount(10000, 20000, 1801), 1000); - assert_eq!(DexModule::get_target_amount(10000, 20000, 1000), 1801); + assert_eq!(EdfisSwapModule::get_supply_amount(10000, 0, 1000), 0); + assert_eq!(EdfisSwapModule::get_supply_amount(0, 20000, 1000), 0); + assert_eq!(EdfisSwapModule::get_supply_amount(10000, 20000, 0), 0); + assert_eq!(EdfisSwapModule::get_supply_amount(10000, 1, 1), 0); + assert_eq!(EdfisSwapModule::get_supply_amount(10000, 20000, 9949), 9999); + assert_eq!(EdfisSwapModule::get_target_amount(10000, 20000, 9999), 9949); + assert_eq!(EdfisSwapModule::get_supply_amount(10000, 20000, 1801), 1000); + assert_eq!(EdfisSwapModule::get_target_amount(10000, 20000, 1000), 1801); }); } @@ -1007,39 +1007,39 @@ fn get_target_amounts_work() { LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); assert_noop!( - DexModule::get_target_amounts(&[EDF], 10000), + EdfisSwapModule::get_target_amounts(&[EDF], 10000), Error::::InvalidTradingPathLength, ); assert_noop!( - DexModule::get_target_amounts(&[EDF, USSD, WBTC, EDF], 10000), + EdfisSwapModule::get_target_amounts(&[EDF, USSD, WBTC, EDF], 10000), Error::::InvalidTradingPathLength, ); assert_noop!( - DexModule::get_target_amounts(&[EDF, EDF], 10000), + EdfisSwapModule::get_target_amounts(&[EDF, EDF], 10000), Error::::InvalidTradingPath, ); assert_noop!( - DexModule::get_target_amounts(&[EDF, USSD, EDF], 10000), + EdfisSwapModule::get_target_amounts(&[EDF, USSD, EDF], 10000), Error::::InvalidTradingPath, ); assert_noop!( - DexModule::get_target_amounts(&[EDF, USSD, SEE], 10000), + EdfisSwapModule::get_target_amounts(&[EDF, USSD, SEE], 10000), Error::::MustBeEnabled, ); assert_eq!( - DexModule::get_target_amounts(&[EDF, USSD], 10000), + EdfisSwapModule::get_target_amounts(&[EDF, USSD], 10000), Ok(vec![10000, 24874]) ); assert_eq!( - DexModule::get_target_amounts(&[EDF, USSD, WBTC], 10000), + EdfisSwapModule::get_target_amounts(&[EDF, USSD, WBTC], 10000), Ok(vec![10000, 24874, 1]) ); assert_noop!( - DexModule::get_target_amounts(&[EDF, USSD, WBTC], 100), + EdfisSwapModule::get_target_amounts(&[EDF, USSD, WBTC], 100), Error::::ZeroTargetAmount, ); assert_noop!( - DexModule::get_target_amounts(&[EDF, WBTC], 100), + EdfisSwapModule::get_target_amounts(&[EDF, WBTC], 100), Error::::InsufficientLiquidity, ); }); @@ -1053,7 +1053,7 @@ fn calculate_amount_for_big_number_work() { (171_000_000_000_000_000_000_000, 56_000_000_000_000_000_000_000), ); assert_eq!( - DexModule::get_supply_amount( + EdfisSwapModule::get_supply_amount( 171_000_000_000_000_000_000_000, 56_000_000_000_000_000_000_000, 1_000_000_000_000_000_000_000 @@ -1061,7 +1061,7 @@ fn calculate_amount_for_big_number_work() { 3_140_495_867_768_595_041_323 ); assert_eq!( - DexModule::get_target_amount( + EdfisSwapModule::get_target_amount( 171_000_000_000_000_000_000_000, 56_000_000_000_000_000_000_000, 3_140_495_867_768_595_041_323 @@ -1080,39 +1080,39 @@ fn get_supply_amounts_work() { LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); assert_noop!( - DexModule::get_supply_amounts(&[EDF], 10000), + EdfisSwapModule::get_supply_amounts(&[EDF], 10000), Error::::InvalidTradingPathLength, ); assert_noop!( - DexModule::get_supply_amounts(&[EDF, USSD, WBTC, EDF], 10000), + EdfisSwapModule::get_supply_amounts(&[EDF, USSD, WBTC, EDF], 10000), Error::::InvalidTradingPathLength, ); assert_noop!( - DexModule::get_supply_amounts(&[EDF, EDF], 10000), + EdfisSwapModule::get_supply_amounts(&[EDF, EDF], 10000), Error::::InvalidTradingPath, ); assert_noop!( - DexModule::get_supply_amounts(&[EDF, USSD, EDF], 10000), + EdfisSwapModule::get_supply_amounts(&[EDF, USSD, EDF], 10000), Error::::InvalidTradingPath, ); assert_noop!( - DexModule::get_supply_amounts(&[EDF, USSD, SEE], 10000), + EdfisSwapModule::get_supply_amounts(&[EDF, USSD, SEE], 10000), Error::::MustBeEnabled, ); assert_eq!( - DexModule::get_supply_amounts(&[EDF, USSD], 24874), + EdfisSwapModule::get_supply_amounts(&[EDF, USSD], 24874), Ok(vec![10000, 24874]) ); assert_eq!( - DexModule::get_supply_amounts(&[EDF, USSD], 25000), + EdfisSwapModule::get_supply_amounts(&[EDF, USSD], 25000), Ok(vec![10102, 25000]) ); assert_noop!( - DexModule::get_supply_amounts(&[EDF, USSD, WBTC], 10000), + EdfisSwapModule::get_supply_amounts(&[EDF, USSD, WBTC], 10000), Error::::ZeroSupplyAmount, ); assert_noop!( - DexModule::get_supply_amounts(&[EDF, WBTC], 10000), + EdfisSwapModule::get_supply_amounts(&[EDF, WBTC], 10000), Error::::InsufficientLiquidity, ); }); @@ -1126,15 +1126,15 @@ fn _swap_work() { .execute_with(|| { LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (50000, 10000)); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (50000, 10000)); assert_noop!( - DexModule::_swap(USSD, EDF, 50000, 5001), + EdfisSwapModule::_swap(USSD, EDF, 50000, 5001), Error::::InvariantCheckFailed ); - assert_ok!(DexModule::_swap(USSD, EDF, 50000, 5000)); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (100000, 5000)); - assert_ok!(DexModule::_swap(EDF, USSD, 100, 800)); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (99200, 5100)); + assert_ok!(EdfisSwapModule::_swap(USSD, EDF, 50000, 5000)); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (100000, 5000)); + assert_ok!(EdfisSwapModule::_swap(EDF, USSD, 100, 800)); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (99200, 5100)); }); } @@ -1147,13 +1147,13 @@ fn _swap_by_path_work() { LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (50000, 10000)); - assert_eq!(DexModule::get_liquidity(USSD, WBTC), (100000, 10)); - assert_ok!(DexModule::_swap_by_path(&[EDF, USSD], &[10000, 25000])); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (25000, 20000)); - assert_ok!(DexModule::_swap_by_path(&[EDF, USSD, WBTC], &[100000, 20000, 1])); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (5000, 120000)); - assert_eq!(DexModule::get_liquidity(USSD, WBTC), (120000, 9)); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (50000, 10000)); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, WBTC), (100000, 10)); + assert_ok!(EdfisSwapModule::_swap_by_path(&[EDF, USSD], &[10000, 25000])); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (25000, 20000)); + assert_ok!(EdfisSwapModule::_swap_by_path(&[EDF, USSD, WBTC], &[100000, 20000, 1])); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (5000, 120000)); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, WBTC), (120000, 9)); }); } @@ -1166,7 +1166,7 @@ fn add_liquidity_work() { System::set_block_number(1); assert_noop!( - DexModule::add_liquidity( + EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), SEE, USSD, @@ -1178,13 +1178,13 @@ fn add_liquidity_work() { Error::::MustBeEnabled ); assert_noop!( - DexModule::add_liquidity(RuntimeOrigin::signed(ALICE), USSD, EDF, 0, 100_000_000, 0, false), + EdfisSwapModule::add_liquidity(RuntimeOrigin::signed(ALICE), USSD, EDF, 0, 100_000_000, 0, false), Error::::InvalidLiquidityIncrement ); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (0, 0)); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 0); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 0); assert_eq!( Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), 0 @@ -1196,7 +1196,7 @@ fn add_liquidity_work() { assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000); assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1205,7 +1205,7 @@ fn add_liquidity_work() { 0, false, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::AddLiquidity { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::AddLiquidity { who: ALICE, currency_0: USSD, pool_0: 5_000_000_000_000, @@ -1214,11 +1214,11 @@ fn add_liquidity_work() { share_increment: 10_000_000_000_000, })); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (5_000_000_000_000, 1_000_000_000_000) ); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 5_000_000_000_000); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 1_000_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 5_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 1_000_000_000_000); assert_eq!( Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), 10_000_000_000_000 @@ -1241,12 +1241,12 @@ fn add_liquidity_work() { assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); assert_noop!( - DexModule::add_liquidity(RuntimeOrigin::signed(BOB), USSD, EDF, 4, 1, 0, true,), + EdfisSwapModule::add_liquidity(RuntimeOrigin::signed(BOB), USSD, EDF, 4, 1, 0, true,), Error::::InvalidLiquidityIncrement, ); assert_noop!( - DexModule::add_liquidity( + EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(BOB), USSD, EDF, @@ -1258,7 +1258,7 @@ fn add_liquidity_work() { Error::::UnacceptableShareIncrement ); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(BOB), USSD, EDF, @@ -1267,7 +1267,7 @@ fn add_liquidity_work() { 80_000_000_000_000, true, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::AddLiquidity { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::AddLiquidity { who: BOB, currency_0: USSD, pool_0: 40_000_000_000_000, @@ -1276,11 +1276,11 @@ fn add_liquidity_work() { share_increment: 80_000_000_000_000, })); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (45_000_000_000_000, 9_000_000_000_000) ); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 45_000_000_000_000); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 9_000_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 45_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 9_000_000_000_000); assert_eq!( Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), 0 @@ -1302,7 +1302,7 @@ fn remove_liquidity_work() { .execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1312,7 +1312,7 @@ fn remove_liquidity_work() { false )); assert_noop!( - DexModule::remove_liquidity( + EdfisSwapModule::remove_liquidity( RuntimeOrigin::signed(ALICE), USSDEDFPair::get().dex_share_currency_id(), EDF, @@ -1325,11 +1325,11 @@ fn remove_liquidity_work() { ); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (5_000_000_000_000, 1_000_000_000_000) ); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 5_000_000_000_000); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 1_000_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 5_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 1_000_000_000_000); assert_eq!( Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), 10_000_000_000_000 @@ -1338,7 +1338,7 @@ fn remove_liquidity_work() { assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_000_000_000_000); assert_noop!( - DexModule::remove_liquidity( + EdfisSwapModule::remove_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1350,7 +1350,7 @@ fn remove_liquidity_work() { Error::::UnacceptableLiquidityWithdrawn ); assert_noop!( - DexModule::remove_liquidity( + EdfisSwapModule::remove_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1361,7 +1361,7 @@ fn remove_liquidity_work() { ), Error::::UnacceptableLiquidityWithdrawn ); - assert_ok!(DexModule::remove_liquidity( + assert_ok!(EdfisSwapModule::remove_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1370,7 +1370,7 @@ fn remove_liquidity_work() { 800_000_000_000, false, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RemoveLiquidity { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::RemoveLiquidity { who: ALICE, currency_0: USSD, pool_0: 4_000_000_000_000, @@ -1379,11 +1379,11 @@ fn remove_liquidity_work() { share_decrement: 8_000_000_000_000, })); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (1_000_000_000_000, 200_000_000_000) ); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 1_000_000_000_000); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 200_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 1_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 200_000_000_000); assert_eq!( Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), 2_000_000_000_000 @@ -1391,7 +1391,7 @@ fn remove_liquidity_work() { assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_999_000_000_000_000); assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_800_000_000_000); - assert_ok!(DexModule::remove_liquidity( + assert_ok!(EdfisSwapModule::remove_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1400,7 +1400,7 @@ fn remove_liquidity_work() { 0, false, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::RemoveLiquidity { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::RemoveLiquidity { who: ALICE, currency_0: USSD, pool_0: 1_000_000_000_000, @@ -1408,9 +1408,9 @@ fn remove_liquidity_work() { pool_1: 200_000_000_000, share_decrement: 2_000_000_000_000, })); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (0, 0)); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 0); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 0); assert_eq!( Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), 0 @@ -1418,7 +1418,7 @@ fn remove_liquidity_work() { assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000); assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(BOB), USSD, EDF, @@ -1435,7 +1435,7 @@ fn remove_liquidity_work() { Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), 10_000_000_000_000 ); - assert_ok!(DexModule::remove_liquidity( + assert_ok!(EdfisSwapModule::remove_liquidity( RuntimeOrigin::signed(BOB), USSD, EDF, @@ -1463,7 +1463,7 @@ fn do_swap_with_exact_supply_work() { .execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1472,7 +1472,7 @@ fn do_swap_with_exact_supply_work() { 0, false, )); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, WBTC, @@ -1483,94 +1483,94 @@ fn do_swap_with_exact_supply_work() { )); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (500_000_000_000_000, 100_000_000_000_000) ); assert_eq!( - DexModule::get_liquidity(USSD, WBTC), + EdfisSwapModule::get_liquidity(USSD, WBTC), (100_000_000_000_000, 10_000_000_000) ); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 600_000_000_000_000 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 100_000_000_000_000); - assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 100_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapModule::account_id()), 10_000_000_000); assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); assert_noop!( - DexModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD], 100_000_000_000_000, 250_000_000_000_000,), + EdfisSwapModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD], 100_000_000_000_000, 250_000_000_000_000,), Error::::InsufficientTargetAmount ); assert_noop!( - DexModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, WBTC, EDF], 100_000_000_000_000, 0), + EdfisSwapModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, WBTC, EDF], 100_000_000_000_000, 0), Error::::InvalidTradingPathLength, ); assert_noop!( - DexModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, EDF], 100_000_000_000_000, 0), + EdfisSwapModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, EDF], 100_000_000_000_000, 0), Error::::InvalidTradingPath, ); assert_noop!( - DexModule::do_swap_with_exact_supply(&BOB, &[EDF, SEE], 100_000_000_000_000, 0), + EdfisSwapModule::do_swap_with_exact_supply(&BOB, &[EDF, SEE], 100_000_000_000_000, 0), Error::::MustBeEnabled, ); - assert_ok!(DexModule::do_swap_with_exact_supply( + assert_ok!(EdfisSwapModule::do_swap_with_exact_supply( &BOB, &[EDF, USSD], 100_000_000_000_000, 200_000_000_000_000, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::Swap { trader: BOB, path: vec![EDF, USSD], liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], })); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (251_256_281_407_036, 200_000_000_000_000) ); assert_eq!( - DexModule::get_liquidity(USSD, WBTC), + EdfisSwapModule::get_liquidity(USSD, WBTC), (100_000_000_000_000, 10_000_000_000) ); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 351_256_281_407_036 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 200_000_000_000_000); - assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 200_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapModule::account_id()), 10_000_000_000); assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_248_743_718_592_964); assert_eq!(Tokens::free_balance(EDF, &BOB), 999_900_000_000_000_000); assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); - assert_ok!(DexModule::do_swap_with_exact_supply( + assert_ok!(EdfisSwapModule::do_swap_with_exact_supply( &BOB, &[EDF, USSD, WBTC], 200_000_000_000_000, 1, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::Swap { trader: BOB, path: vec![EDF, USSD, WBTC], liquidity_changes: vec![200_000_000_000_000, 124_996_843_514_053, 5_530_663_837], })); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (126_259_437_892_983, 400_000_000_000_000) ); assert_eq!( - DexModule::get_liquidity(USSD, WBTC), + EdfisSwapModule::get_liquidity(USSD, WBTC), (224_996_843_514_053, 4_469_336_163) ); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 351_256_281_407_036 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 400_000_000_000_000); - assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 4_469_336_163); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 400_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapModule::account_id()), 4_469_336_163); assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_248_743_718_592_964); assert_eq!(Tokens::free_balance(EDF, &BOB), 999_700_000_000_000_000); assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_005_530_663_837); @@ -1585,7 +1585,7 @@ fn do_swap_with_exact_target_work() { .execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1594,7 +1594,7 @@ fn do_swap_with_exact_target_work() { 0, false, )); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, WBTC, @@ -1605,29 +1605,29 @@ fn do_swap_with_exact_target_work() { )); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (500_000_000_000_000, 100_000_000_000_000) ); assert_eq!( - DexModule::get_liquidity(USSD, WBTC), + EdfisSwapModule::get_liquidity(USSD, WBTC), (100_000_000_000_000, 10_000_000_000) ); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 600_000_000_000_000 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 100_000_000_000_000); - assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 100_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapModule::account_id()), 10_000_000_000); assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); assert_noop!( - DexModule::do_swap_with_exact_target(&BOB, &[EDF, USSD], 250_000_000_000_000, 100_000_000_000_000,), + EdfisSwapModule::do_swap_with_exact_target(&BOB, &[EDF, USSD], 250_000_000_000_000, 100_000_000_000_000,), Error::::ExcessiveSupplyAmount ); assert_noop!( - DexModule::do_swap_with_exact_target( + EdfisSwapModule::do_swap_with_exact_target( &BOB, &[EDF, USSD, WBTC, EDF], 250_000_000_000_000, @@ -1636,68 +1636,68 @@ fn do_swap_with_exact_target_work() { Error::::InvalidTradingPathLength, ); assert_noop!( - DexModule::do_swap_with_exact_target(&BOB, &[EDF, USSD, EDF], 250_000_000_000_000, 200_000_000_000_000,), + EdfisSwapModule::do_swap_with_exact_target(&BOB, &[EDF, USSD, EDF], 250_000_000_000_000, 200_000_000_000_000,), Error::::InvalidTradingPath, ); assert_noop!( - DexModule::do_swap_with_exact_target(&BOB, &[EDF, SEE], 250_000_000_000_000, 200_000_000_000_000), + EdfisSwapModule::do_swap_with_exact_target(&BOB, &[EDF, SEE], 250_000_000_000_000, 200_000_000_000_000), Error::::MustBeEnabled, ); - assert_ok!(DexModule::do_swap_with_exact_target( + assert_ok!(EdfisSwapModule::do_swap_with_exact_target( &BOB, &[EDF, USSD], 250_000_000_000_000, 200_000_000_000_000, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::Swap { trader: BOB, path: vec![EDF, USSD], liquidity_changes: vec![101_010_101_010_102, 250_000_000_000_000], })); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (250_000_000_000_000, 201_010_101_010_102) ); assert_eq!( - DexModule::get_liquidity(USSD, WBTC), + EdfisSwapModule::get_liquidity(USSD, WBTC), (100_000_000_000_000, 10_000_000_000) ); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 350_000_000_000_000 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 201_010_101_010_102); - assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 201_010_101_010_102); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapModule::account_id()), 10_000_000_000); assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_250_000_000_000_000); assert_eq!(Tokens::free_balance(EDF, &BOB), 999_898_989_898_989_898); assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); - assert_ok!(DexModule::do_swap_with_exact_target( + assert_ok!(EdfisSwapModule::do_swap_with_exact_target( &BOB, &[EDF, USSD, WBTC], 5_000_000_000, 2_000_000_000_000_000, )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::Swap { trader: BOB, path: vec![EDF, USSD, WBTC], liquidity_changes: vec![137_654_580_386_993, 101_010_101_010_102, 5_000_000_000], })); assert_eq!( - DexModule::get_liquidity(USSD, EDF), + EdfisSwapModule::get_liquidity(USSD, EDF), (148_989_898_989_898, 338_664_681_397_095) ); assert_eq!( - DexModule::get_liquidity(USSD, WBTC), + EdfisSwapModule::get_liquidity(USSD, WBTC), (201_010_101_010_102, 5_000_000_000) ); assert_eq!( - Tokens::free_balance(USSD, &DexModule::account_id()), + Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 350_000_000_000_000 ); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 338_664_681_397_095); - assert_eq!(Tokens::free_balance(WBTC, &DexModule::account_id()), 5_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 338_664_681_397_095); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapModule::account_id()), 5_000_000_000); assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_250_000_000_000_000); assert_eq!(Tokens::free_balance(EDF, &BOB), 999_761_335_318_602_905); assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_005_000_000_000); @@ -1713,9 +1713,9 @@ fn initialize_added_liquidity_pools_genesis_work() { .execute_with(|| { System::set_block_number(1); - assert_eq!(DexModule::get_liquidity(USSD, EDF), (1000000, 2000000)); - assert_eq!(Tokens::free_balance(USSD, &DexModule::account_id()), 2000000); - assert_eq!(Tokens::free_balance(EDF, &DexModule::account_id()), 3000000); + assert_eq!(EdfisSwapModule::get_liquidity(USSD, EDF), (1000000, 2000000)); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapModule::account_id()), 2000000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapModule::account_id()), 3000000); assert_eq!( Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), 2000000 @@ -1731,19 +1731,19 @@ fn get_swap_amount_work() { .execute_with(|| { LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); assert_eq!( - DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 0)), + EdfisSwapModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 0)), Some((10000, 24874)) ); assert_eq!( - DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 24875)), + EdfisSwapModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 24875)), None ); assert_eq!( - DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(Balance::max_value(), 24874)), + EdfisSwapModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(Balance::max_value(), 24874)), Some((10000, 24874)) ); assert_eq!( - DexModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(9999, 24874)), + EdfisSwapModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(9999, 24874)), None ); }); @@ -1760,68 +1760,68 @@ fn get_best_price_swap_path_work() { LiquidityPool::::insert(EDFWBTCPair::get(), (10000, 10000)); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![]), Some((vec![EDF, USSD], 10, 29)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 30), vec![]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 30), vec![]), None ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(0, 0), vec![]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(0, 0), vec![]), None ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![SEE]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![SEE]]), Some((vec![EDF, USSD], 10, 29)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![EDF]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![EDF]]), Some((vec![EDF, USSD], 10, 29)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![USSD]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![USSD]]), Some((vec![EDF, USSD], 10, 29)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![WBTC]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![WBTC]]), Some((vec![EDF, WBTC, USSD], 10, 44)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10000, 0), vec![vec![WBTC]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10000, 0), vec![vec![WBTC]]), Some((vec![EDF, USSD], 10000, 27024)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![]), Some((vec![EDF, USSD], 11, 30)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(10, 30), vec![]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(10, 30), vec![]), None ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(0, 0), vec![]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(0, 0), vec![]), None ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![SEE]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![SEE]]), Some((vec![EDF, USSD], 11, 30)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![EDF]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![EDF]]), Some((vec![EDF, USSD], 11, 30)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![USSD]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![USSD]]), Some((vec![EDF, USSD], 11, 30)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![WBTC]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![WBTC]]), Some((vec![EDF, WBTC, USSD], 8, 30)) ); assert_eq!( - DexModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(100000, 20000), vec![vec![WBTC]]), + EdfisSwapModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(100000, 20000), vec![vec![WBTC]]), Some((vec![EDF, USSD], 7216, 20000)) ); }); @@ -1834,7 +1834,7 @@ fn swap_with_specific_path_work() { .build() .execute_with(|| { System::set_block_number(1); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1845,7 +1845,7 @@ fn swap_with_specific_path_work() { )); assert_noop!( - DexModule::swap_with_specific_path( + EdfisSwapModule::swap_with_specific_path( &BOB, &[EDF, USSD], SwapLimit::ExactSupply(100_000_000_000_000, 248_743_718_592_965) @@ -1853,19 +1853,19 @@ fn swap_with_specific_path_work() { Error::::InsufficientTargetAmount ); - assert_ok!(DexModule::swap_with_specific_path( + assert_ok!(EdfisSwapModule::swap_with_specific_path( &BOB, &[EDF, USSD], SwapLimit::ExactSupply(100_000_000_000_000, 200_000_000_000_000) )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::Swap { trader: BOB, path: vec![EDF, USSD], liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], })); assert_noop!( - DexModule::swap_with_specific_path( + EdfisSwapModule::swap_with_specific_path( &BOB, &[USSD, EDF], SwapLimit::ExactTarget(253_794_223_643_470, 100_000_000_000_000) @@ -1873,12 +1873,12 @@ fn swap_with_specific_path_work() { Error::::ExcessiveSupplyAmount ); - assert_ok!(DexModule::swap_with_specific_path( + assert_ok!(EdfisSwapModule::swap_with_specific_path( &BOB, &[USSD, EDF], SwapLimit::ExactTarget(300_000_000_000_000, 100_000_000_000_000) )); - System::assert_last_event(RuntimeEvent::DexModule(crate::Event::Swap { + System::assert_last_event(RuntimeEvent::EdfisSwapModule(crate::Event::Swap { trader: BOB, path: vec![USSD, EDF], liquidity_changes: vec![253_794_223_643_471, 100_000_000_000_000], @@ -1892,12 +1892,12 @@ fn get_liquidity_token_address_work() { System::set_block_number(1); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Disabled ); - assert_eq!(DexModule::get_liquidity_token_address(USSD, EDF), None); + assert_eq!(EdfisSwapModule::get_liquidity_token_address(USSD, EDF), None); - assert_ok!(DexModule::list_provisioning( + assert_ok!(EdfisSwapModule::list_provisioning( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF, @@ -1908,7 +1908,7 @@ fn get_liquidity_token_address_work() { 10, )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), @@ -1917,21 +1917,21 @@ fn get_liquidity_token_address_work() { }) ); assert_eq!( - DexModule::get_liquidity_token_address(USSD, EDF), + EdfisSwapModule::get_liquidity_token_address(USSD, EDF), Some(H160::from_str("0x0000000000000000000200000000010000000002").unwrap()) ); - assert_ok!(DexModule::enable_trading_pair( + assert_ok!(EdfisSwapModule::enable_trading_pair( RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF )); assert_eq!( - DexModule::trading_pair_statuses(USSDEDFPair::get()), + EdfisSwapModule::trading_pair_statuses(USSDEDFPair::get()), TradingPairStatus::<_, _>::Enabled ); assert_eq!( - DexModule::get_liquidity_token_address(USSD, EDF), + EdfisSwapModule::get_liquidity_token_address(USSD, EDF), Some(H160::from_str("0x0000000000000000000200000000010000000002").unwrap()) ); }); @@ -1943,7 +1943,7 @@ fn specific_joint_swap_work() { .initialize_enabled_trading_pairs() .build() .execute_with(|| { - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, EDF, @@ -1952,7 +1952,7 @@ fn specific_joint_swap_work() { 0, false, )); - assert_ok!(DexModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), USSD, WBTC, diff --git a/blockchain/modules/edfis-swap/src/weights.rs b/blockchain/modules/edfis-swap/src/weights.rs index 959719de..efc167cd 100644 --- a/blockchain/modules/edfis-swap/src/weights.rs +++ b/blockchain/modules/edfis-swap/src/weights.rs @@ -18,7 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Autogenerated weights for module_dex +//! Autogenerated weights for edfis_swap_module //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: 2021-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` @@ -30,7 +30,7 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=module_dex +// --pallet=edfis_swap_module // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -46,7 +46,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for module_dex. +/// Weight functions needed for edfis_swap_module. pub trait WeightInfo { fn enable_trading_pair() -> Weight; fn disable_trading_pair() -> Weight; @@ -65,7 +65,7 @@ pub trait WeightInfo { fn abort_provisioning() -> Weight; } -/// Weights for module_dex using the Setheum node and recommended hardware. +/// Weights for edfis_swap_module using the Setheum node and recommended hardware. pub struct SetheumWeight(PhantomData); impl WeightInfo for SetheumWeight { fn enable_trading_pair() -> Weight { diff --git a/blockchain/modules/evm/Cargo.toml b/blockchain/modules/evm/Cargo.toml index 550b246e..1d1ed9cd 100644 --- a/blockchain/modules/evm/Cargo.toml +++ b/blockchain/modules/evm/Cargo.toml @@ -1,71 +1,118 @@ -[package] -name = "module-evm" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -hex-literal = { version = "0.3.1" } -impl-trait-for-tuples = "0.1" -primitive-types = { version = "0.10.0", default-features = false, features = ["rlp", "byteorder"] } -ripemd160 = { version = "0.9", default-features = false } -rlp = { version = "0.5", default-features = false } -serde = { version = "1.0.124", optional = true, features = ["derive"] } -sha3 = { version = "0.9.1", default-features = false } -tiny-keccak = { version = "2.0", features = ["fips202"] } - -evm = { version = "0.30.0", default-features = false, features = ["with-codec" ] } -evm-gasometer = { version = "0.30.0", default-features = false } -evm-runtime = { version = "0.30.0", default-features = false } - -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } - -orml-traits = { path = "../submodules/orml/traits", default-features = false } - -module-support = { path = "../support", default-features = false } -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } - -[dev-dependencies] -env_logger = "0.7" -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -orml-currencies = { path = "../submodules/orml/currencies" } -orml-tokens = { path = "../submodules/orml/tokens" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-core/std", - "sp-runtime/std", - "frame-support/std", - "frame-system/std", - "sp-io/std", - "sp-std/std", - "sha3/std", - "rlp/std", - "primitive-types/std", - "evm/std", - "evm/with-serde", - "evm-runtime/std", - "evm-gasometer/std", - "pallet-timestamp/std", - "ripemd160/std", - "primitives/std", - "orml-traits/std", - "module-support/std", -] -with-ethereum-compatibility = [] -tracing = [ - "evm/tracing", - "evm-gasometer/tracing", - "evm-runtime/tracing", -] +[package] +name = "module-evm" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[[bench]] +name = "orml_benches" +harness = false +required-features = ["wasm-bench"] + +[dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true } +hex-literal = { workspace = true } +ripemd = { workspace = true } +rlp = { workspace = true } +serde = { workspace = true, features = ["alloc", "derive"] } +sha3 = { workspace = true } +tiny-keccak = { workspace = true, features = ["fips202"] } +scale-info = { workspace = true } +serde_json = { workspace = true, features = ["alloc"], optional = true } +hex = { workspace = true, features = ["alloc"], optional = true } +num = { workspace = true, features = ["alloc"] } +bn = { workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-balances = { workspace = true, optional = true } + +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +orml-traits = { workspace = true } +wasm-bencher = { workspace = true, optional = true } +orml-currencies = { workspace = true, optional = true } +orml-tokens = { workspace = true, optional = true } + +module-support = { workspace = true } +module-evm-utility = { workspace = true } +primitives = { workspace = true } +module-idle-scheduler = { workspace = true, optional = true } +module-transaction-payment = { workspace = true } +module-dex = { workspace = true, optional = true } + +xcm-builder = { workspace = true } + +# we don't directly depends on this but need frame-benchmarking/std to fix build +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +hex = { workspace = true, features = ["std"] } +env_logger = { workspace = true } +serde_json = { workspace = true, features = ["std"] } +pallet-utility = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "serde/std", + + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "module-dex/std", + "module-evm-utility/std", + "module-idle-scheduler/std", + "module-support/std", + "module-transaction-payment/std", + "num/std", + "wasm-bencher/std", + "orml-currencies/std", + "orml-tokens/std", + "orml-traits/std", + "pallet-balances/std", + "pallet-timestamp/std", + "primitives/std", + "ripemd/std", + "rlp/std", + "scale-info/std", + "sha3/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "frame-benchmarking/std", +] +with-ethereum-compatibility = [] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "module-dex/try-runtime", + "module-idle-scheduler/try-runtime", + "module-transaction-payment/try-runtime", + "orml-currencies/try-runtime", + "orml-tokens/try-runtime", + "pallet-balances/try-runtime", + "pallet-timestamp/try-runtime", +] +tracing = ["module-evm-utility/tracing"] +wasm-bench = [ + "wasm-bencher/wasm-bench", + "hex", + "module-dex", + "module-idle-scheduler", + "orml-currencies", + "orml-tokens", + "pallet-balances", + "serde_json", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +evm-tests = [ + "primitives/evm-tests" +] diff --git a/blockchain/modules/evm/benches/orml_benches.rs b/blockchain/modules/evm/benches/orml_benches.rs new file mode 100644 index 00000000..4d6e3661 --- /dev/null +++ b/blockchain/modules/evm/benches/orml_benches.rs @@ -0,0 +1,21 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +wasm_bencher::main!({ module_evm::bench::mock::AllPalletsWithSystem::storage_info() }); diff --git a/blockchain/modules/evm/rpc/Cargo.toml b/blockchain/modules/evm/rpc/Cargo.toml deleted file mode 100644 index c02cf0ed..00000000 --- a/blockchain/modules/evm/rpc/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "evm-rpc" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -jsonrpc-core = "15.0.0" -jsonrpc-derive = "15.0.0" -ethereum-types = "0.12.0" -rustc-hex = "2.1.0" -serde = { version = "1.0.124", features = ["derive"] } - -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -sp-storage = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } - -module-evm-rpc-runtime-api = { path = "runtime_api" } -module-evm = { path = ".." } - -[dev-dependencies] -serde_json = "1.0.64" - -[features] -default = [ "rpc_binary_search_estimate" ] -rpc_binary_search_estimate = [] diff --git a/blockchain/modules/evm/rpc/runtime_api/Cargo.toml b/blockchain/modules/evm/rpc/runtime_api/Cargo.toml index f4440211..a83fef68 100644 --- a/blockchain/modules/evm/rpc/runtime_api/Cargo.toml +++ b/blockchain/modules/evm/rpc/runtime_api/Cargo.toml @@ -1,25 +1,22 @@ -[package] -name = "module-evm-rpc-runtime-api" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[dependencies] -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -ethereum-types = { version = "0.12", default-features = false } - -primitives = { package = "setheum-primitives", path = "../../../../primitives", default-features = false } - -[features] -default = ["std"] -std = [ - "sp-runtime/std", - "sp-api/std", - "sp-std/std", - "sp-core/std", - "ethereum-types/std", - "primitives/std", -] +[package] +name = "module-evm-rpc-runtime-api" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +sp-runtime = { workspace = true } +sp-api = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +primitives = { workspace = true } + +[features] +default = ["std"] +std = [ + "sp-runtime/std", + "sp-api/std", + "sp-std/std", + "sp-core/std", + "primitives/std", +] diff --git a/blockchain/modules/evm/rpc/runtime_api/src/lib.rs b/blockchain/modules/evm/rpc/runtime_api/src/lib.rs index 1d6e51e8..e6698730 100644 --- a/blockchain/modules/evm/rpc/runtime_api/src/lib.rs +++ b/blockchain/modules/evm/rpc/runtime_api/src/lib.rs @@ -1,57 +1,84 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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_attr(not(feature = "std"), no_std)] -#![allow(clippy::all)] - -use ethereum_types::H160; -use primitives::evm::{CallInfo, CreateInfo, EstimateResourcesRequest}; -use sp_runtime::{ - codec::Codec, - traits::{MaybeDisplay, MaybeFromStr}, -}; -use sp_std::vec::Vec; - -sp_api::decl_runtime_apis! { - pub trait EVMRuntimeRPCApi where - Balance: Codec + MaybeDisplay + MaybeFromStr, - { - fn call( - from: H160, - to: H160, - data: Vec, - value: Balance, - gas_limit: u64, - storage_limit: u32, - estimate: bool, - ) -> Result; - - fn create( - from: H160, - data: Vec, - value: Balance, - gas_limit: u64, - storage_limit: u32, - estimate: bool, - ) -> Result; - - fn get_estimate_resources_request(data: Vec) -> Result; - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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_attr(not(feature = "std"), no_std)] +#![allow(clippy::all)] + +use primitives::evm::{AccessListItem, BlockLimits, CallInfo, CreateInfo, EstimateResourcesRequest}; +use sp_core::H160; +use sp_runtime::{ + codec::Codec, + traits::{MaybeDisplay, MaybeFromStr}, +}; +use sp_std::vec::Vec; + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait EVMRuntimeRPCApi where + Balance: Codec + MaybeDisplay + MaybeFromStr, + AccountId: Codec + MaybeDisplay + MaybeFromStr, + { + fn call( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + estimate: bool, + ) -> Result; + + fn create( + from: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + estimate: bool, + ) -> Result; + + fn get_estimate_resources_request(data: Vec) -> Result; + + fn block_limits() -> BlockLimits; + + fn account_call( + from: AccountId, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + estimate: bool, + ) -> Result; + + fn account_create( + from: AccountId, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + estimate: bool, + ) -> Result; + } +} diff --git a/blockchain/modules/evm/rpc/src/lib.rs b/blockchain/modules/evm/rpc/src/lib.rs deleted file mode 100644 index 3bd0cb41..00000000 --- a/blockchain/modules/evm/rpc/src/lib.rs +++ /dev/null @@ -1,453 +0,0 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -#![allow(clippy::upper_case_acronyms)] - -use ethereum_types::{H160, U256}; -use frame_support::log; -use jsonrpc_core::{Error, ErrorCode, Result, Value}; -use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi; -use rustc_hex::ToHex; -use sc_rpc_api::DenyUnsafe; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_core::{Bytes, Decode}; -use sp_rpc::number::NumberOrHex; -use sp_runtime::{ - codec::Codec, - generic::BlockId, - traits::{self, Block as BlockT, MaybeDisplay, MaybeFromStr}, - SaturatedConversion, -}; -use std::convert::{TryFrom, TryInto}; -use std::{marker::PhantomData, sync::Arc}; - -use call_request::{CallRequest, EstimateResourcesResponse}; -pub use module_evm::{ExitError, ExitReason}; -pub use module_evm_rpc_runtime_api::EVMRuntimeRPCApi; - -pub use crate::evm_api::{EVMApi as EVMApiT, EVMApiServer}; - -mod call_request; -mod evm_api; - -fn internal_err(message: T) -> Error { - Error { - code: ErrorCode::InternalError, - message: message.to_string(), - data: None, - } -} - -#[allow(dead_code)] -fn error_on_execution_failure(reason: &ExitReason, data: &[u8]) -> Result<()> { - match reason { - ExitReason::Succeed(_) => Ok(()), - ExitReason::Error(e) => { - if *e == ExitError::OutOfGas { - // `ServerError(0)` will be useful in estimate gas - return Err(Error { - code: ErrorCode::ServerError(0), - message: "out of gas".to_string(), - data: None, - }); - } - Err(Error { - code: ErrorCode::InternalError, - message: format!("execution error: {:?}", e), - data: Some(Value::String("0x".to_string())), - }) - } - ExitReason::Revert(_) => Err(Error { - code: ErrorCode::InternalError, - message: decode_revert_message(data) - .map_or("execution revert".into(), |data| format!("execution revert: {}", data)), - data: Some(Value::String(format!("0x{}", data.to_hex::()))), - }), - ExitReason::Fatal(e) => Err(Error { - code: ErrorCode::InternalError, - message: format!("execution fatal: {:?}", e), - data: Some(Value::String("0x".to_string())), - }), - } -} - -fn decode_revert_message(data: &[u8]) -> Option { - // A minimum size of error function selector (4) + offset (32) + string length - // (32) should contain a utf-8 encoded revert reason. - let msg_start: usize = 68; - if data.len() > msg_start { - let message_len = U256::from(&data[36..msg_start]).saturated_into::(); - let msg_end = msg_start.checked_add(message_len)?; - - if data.len() < msg_end { - return None; - } - let body: &[u8] = &data[msg_start..msg_end]; - if let Ok(reason) = std::str::from_utf8(body) { - return Some(reason.to_string()); - } - } - None -} - -pub struct EVMApi { - client: Arc, - deny_unsafe: DenyUnsafe, - _marker: PhantomData<(B, Balance)>, -} - -impl EVMApi { - pub fn new(client: Arc, deny_unsafe: DenyUnsafe) -> Self { - Self { - client, - deny_unsafe, - _marker: Default::default(), - } - } -} - -fn to_u128(val: NumberOrHex) -> std::result::Result { - val.into_u256().try_into().map_err(|_| ()) -} - -// Default `gas and storage` limits: -// limits only apply to call() API -// create() API takes values from the caller and is unlimited -// -// 20M. TODO: use value from runtime -const MAX_GAS_LIMIT: u64 = 20_000_000; -// 4M. TODO: use value from runtime -const MAX_STORAGE_LIMIT: u32 = 4 * 1024 * 1024; - -impl EVMApiT<::Hash> for EVMApi -where - B: BlockT, - C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, - C::Api: EVMRuntimeRPCApi, - C::Api: TransactionPaymentApi, - Balance: Codec + MaybeDisplay + MaybeFromStr + Default + Send + Sync + 'static + TryFrom + Into, -{ - fn call(&self, request: CallRequest, at: Option<::Hash>) -> Result { - self.deny_unsafe.check_if_safe()?; - - let hash = at.unwrap_or_else(|| self.client.info().best_hash); - - let CallRequest { - from, - to, - gas_limit, - storage_limit, - value, - data, - } = request; - - let gas_limit = gas_limit.unwrap_or(MAX_GAS_LIMIT); - let storage_limit = storage_limit.unwrap_or(MAX_STORAGE_LIMIT); - let data = data.map(|d| d.0).unwrap_or_default(); - - let api = self.client.runtime_api(); - - let balance_value = if let Some(value) = value { - to_u128(value).and_then(|v| TryInto::::try_into(v).map_err(|_| ())) - } else { - Ok(Default::default()) - }; - - let balance_value = balance_value.map_err(|_| Error { - code: ErrorCode::InvalidParams, - message: format!("Invalid parameter value: {:?}", value), - data: None, - })?; - - match to { - Some(to) => { - let info = api - .call( - &BlockId::Hash(hash), - from.unwrap_or_default(), - to, - data, - balance_value, - gas_limit, - storage_limit, - true, - ) - .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? - .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - - log::debug!( - target: "evm", - "rpc call, info.exit_reason: {:?}, info.value: {:?}", - info.exit_reason, info.value, - ); - error_on_execution_failure(&info.exit_reason, &info.value)?; - - Ok(Bytes(info.value)) - } - None => Err(Error { - code: ErrorCode::InternalError, - message: "Not supported".into(), - data: None, - }), - } - } - - fn estimate_resources( - &self, - from: H160, - unsigned_extrinsic: Bytes, - at: Option<::Hash>, - ) -> Result { - self.deny_unsafe.check_if_safe()?; - - let hash = at.unwrap_or_else(|| self.client.info().best_hash); - let request = self - .client - .runtime_api() - .get_estimate_resources_request(&BlockId::Hash(hash), unsigned_extrinsic.to_vec()) - .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? - .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - - let request = CallRequest { - from: Some(from), - to: request.to, - gas_limit: request.gas_limit, - storage_limit: request.storage_limit, - value: request.value.map(|v| NumberOrHex::Hex(U256::from(v))), - data: request.data.map(Bytes), - }; - - let calculate_gas_used = |request| -> Result<(U256, i32)> { - let CallRequest { - from, - to, - gas_limit, - storage_limit, - value, - data, - } = request; - - let gas_limit = gas_limit.unwrap_or_else(u64::max_value); // TODO: set a limit - let storage_limit = storage_limit.unwrap_or_else(u32::max_value); // TODO: set a limit - let data = data.map(|d| d.0).unwrap_or_default(); - - let balance_value = if let Some(value) = value { - to_u128(value).and_then(|v| TryInto::::try_into(v).map_err(|_| ())) - } else { - Ok(Default::default()) - }; - - let balance_value = balance_value.map_err(|_| Error { - code: ErrorCode::InvalidParams, - message: format!("Invalid parameter value: {:?}", value), - data: None, - })?; - - let (used_gas, used_storage) = match to { - Some(to) => { - let info = self - .client - .runtime_api() - .call( - &BlockId::Hash(hash), - from.unwrap_or_default(), - to, - data, - balance_value, - gas_limit, - storage_limit, - true, - ) - .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? - .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - - error_on_execution_failure(&info.exit_reason, &info.value)?; - - (info.used_gas, info.used_storage) - } - None => { - let info = self - .client - .runtime_api() - .create( - &BlockId::Hash(hash), - from.unwrap_or_default(), - data, - balance_value, - gas_limit, - storage_limit, - true, - ) - .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? - .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - - error_on_execution_failure(&info.exit_reason, &[])?; - - (info.used_gas, info.used_storage) - } - }; - - Ok((used_gas, used_storage)) - }; - - if cfg!(feature = "rpc_binary_search_estimate") { - let mut lower = U256::from(21_000); - // TODO: get a good upper limit, but below U64::max to operation overflow - let mut upper = U256::from(1_000_000_000); - let mut mid = upper; - let mut best = mid; - let mut old_best: U256; - let mut storage: i32 = Default::default(); - - // if the gas estimation depends on the gas limit, then we want to binary - // search until the change is under some threshold. but if not dependent, - // we want to stop immediately. - let mut change_pct = U256::from(100); - let threshold_pct = U256::from(10); - - // invariant: lower <= mid <= upper - while change_pct > threshold_pct { - let mut test_request = request.clone(); - test_request.gas_limit = Some(mid.as_u64()); - match calculate_gas_used(test_request) { - // if Ok -- try to reduce the gas used - Ok((used_gas, used_storage)) => { - log::debug!( - target: "evm", - "calculate_gas_used ok, used_gas: {:?}, used_storage: {:?}", - used_gas, used_storage, - ); - - old_best = best; - best = used_gas; - change_pct = (U256::from(100) * (old_best - best)) - .checked_div(old_best) - .unwrap_or_default(); - upper = mid; - mid = (lower + upper + 1) / 2; - storage = used_storage; - } - - Err(err) => { - log::debug!( - target: "evm", - "calculate_gas_used err: {:?}, lower: {:?}, upper: {:?}, mid: {:?}", - err, lower, upper, mid - ); - - // if Err == OutofGas, we need more gas - if err.code == ErrorCode::ServerError(0) { - lower = mid; - mid = (lower + upper + 1) / 2; - if mid == lower { - break; - } - } else { - // Other errors, return directly - return Err(err); - } - } - } - } - - let uxt: ::Extrinsic = - Decode::decode(&mut &*unsigned_extrinsic).map_err(|e| Error { - code: ErrorCode::InternalError, - message: "Unable to dry run extrinsic.".into(), - data: Some(format!("{:?}", e).into()), - })?; - - let fee = self - .client - .runtime_api() - .query_fee_details(&BlockId::Hash(hash), uxt, unsigned_extrinsic.len() as u32) - .map_err(|e| Error { - code: ErrorCode::InternalError, - message: "Unable to query fee details.".into(), - data: Some(format!("{:?}", e).into()), - })?; - - let adjusted_weight_fee = fee - .inclusion_fee - .map_or_else(Default::default, |inclusion| inclusion.adjusted_weight_fee); - - Ok(EstimateResourcesResponse { - gas: best, - storage, - weight_fee: adjusted_weight_fee.into(), - }) - } else { - let (used_gas, used_storage) = calculate_gas_used(request)?; - - let uxt: ::Extrinsic = - Decode::decode(&mut &*unsigned_extrinsic).map_err(|e| Error { - code: ErrorCode::InternalError, - message: "Unable to dry run extrinsic.".into(), - data: Some(format!("{:?}", e).into()), - })?; - - let fee = self - .client - .runtime_api() - .query_fee_details(&BlockId::Hash(hash), uxt, unsigned_extrinsic.len() as u32) - .map_err(|e| Error { - code: ErrorCode::InternalError, - message: "Unable to query fee details.".into(), - data: Some(format!("{:?}", e).into()), - })?; - - let adjusted_weight_fee = fee - .inclusion_fee - .map_or_else(Default::default, |inclusion| inclusion.adjusted_weight_fee); - - Ok(EstimateResourcesResponse { - gas: used_gas, - storage: used_storage, - weight_fee: adjusted_weight_fee.into(), - }) - } - } -} - -#[test] -fn decode_revert_message_should_work() { - use sp_core::bytes::from_hex; - assert_eq!(decode_revert_message(&vec![]), None); - - let data = from_hex("0x8c379a00000000000000000000000000000000000000000000000000000000000000020").unwrap(); - assert_eq!(decode_revert_message(&data), None); - - let data = from_hex("0x8c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d6572726f72206d65737361676").unwrap(); - assert_eq!(decode_revert_message(&data), None); - - let data = from_hex("0x8c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d6572726f72206d65737361676500000000000000000000000000000000000000").unwrap(); - assert_eq!(decode_revert_message(&data), Some("error message".into())); - - // ensures we protect against msg_start + message_len overflow - let data = from_hex("0x9850188c1837189a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000018d618571827182618f718220618d6185718371836161876").unwrap(); - assert_eq!(decode_revert_message(&data), None); - // ensures we protect against msg_start + message_len overflow - let data = from_hex("0x9860189818501818188c181818371818189a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000181818d6181818571818182718181826181818f71818182206181818d61818185718181837181818361618181876").unwrap(); - assert_eq!(decode_revert_message(&data), None); - // ensures we protect against msg_start + message_len overflow - let data = from_hex("0x98640818c3187918a0000000000000000000000000000000000000000000000000000000000000001820000000000000000000000000000000000000000000000000000000000000000d186518721872186f18721820186d18651873187318611867186500000000000000000000000000000000000000").unwrap(); - assert_eq!(decode_revert_message(&data), None); -} diff --git a/blockchain/modules/evm/src/bench/mock.rs b/blockchain/modules/evm/src/bench/mock.rs new file mode 100644 index 00000000..438da8b7 --- /dev/null +++ b/blockchain/modules/evm/src/bench/mock.rs @@ -0,0 +1,273 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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(any(feature = "std", feature = "wasm-bench"))] + +use super::super::*; + +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, FindAuthor, Nothing}, + weights::{ConstantMultiplier, IdentityFee}, + ConsensusEngineId, PalletId, +}; +use frame_system::EnsureSignedBy; +use module_support::{ + mocks::{MockAddressMapping, MockErc20InfoMapping}, + SwapDexIncentives, Price, PriceProvider, SpecificJointsSwap, +}; +use orml_traits::{parameter_type_with_key, MultiReservableCurrency}; +pub use primitives::{Address, Amount, BlockNumber, CurrencyId, Header, Multiplier, ReserveIdentifier, Signature, + TokenSymbol, +}; +use sp_core::H160; +use sp_runtime::{ + generic, + traits::{AccountIdConversion, BlockNumberProvider, IdentityLookup}, + AccountId32, FixedU128, Percent, +}; + +type Balance = u128; +type Ratio = FixedU128; +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); + +mod evm_module { + pub use super::super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; + type BlockHashCount = ConstU32<10>; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = module_support::SystemAccountStore; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1000>; + type WeightInfo = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = ReserveIdentifier; + type DustRemovalWhitelist = Nothing; +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +} + +impl orml_currencies::Config for Runtime { + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +pub struct GasToWeight; +impl Convert for GasToWeight { + fn convert(a: u64) -> Weight { + Weight::from_parts(a, 0) + } +} + +pub struct AuthorGiven; +impl FindAuthor for AuthorGiven { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(AccountId32::from([1; 32])) + } +} + +parameter_types! { + pub NetworkContractSource: H160 = H160::from_low_u64_be(1); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); + pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); + pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); + pub const StorageDepositPerByte: Balance = convert_decimals_to_evm(10); +} + +impl Config for Runtime { + type AddressMapping = MockAddressMapping; + type Currency = Balances; + type TransferAll = Currencies; + type NewContractExtraBytes = ConstU32<100>; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = ConstU128<20_000_000>; + + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = GasToWeight; + type ChargeTransactionPayment = module_transaction_payment::ChargeTransactionPayment; + + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + type DeveloperDeposit = ConstU128<1000>; + type PublicationFee = ConstU128<200>; + type TreasuryAccount = TreasuryAccount; + type FreePublicationOrigin = EnsureSignedBy; + + type Runner = crate::runner::stack::Runner; + type FindAuthor = AuthorGiven; + type WeightInfo = (); +} + +parameter_types! { + pub const GetStableCurrencyId: CurrencyId = USSD; + pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::one(); + pub const TreasuryPalletId: PalletId = PalletId(*b"set/trsy"); + pub const TransactionPaymentPalletId: PalletId = PalletId(*b"set/fees"); + pub SetheumTreasuryAccount: AccountId32 = TreasuryPalletId::get().into_account_truncating(); + pub const CustomFeeSurplus: Percent = Percent::from_percent(50); + pub const AlternativeFeeSurplus: Percent = Percent::from_percent(25); + pub DefaultFeeTokens: Vec = vec![USSD]; + pub const TradingPathLimit: u32 = 4; + pub const ExistenceRequirement: u128 = 1; + pub AlternativeSwapPathJointList: Vec> = vec![]; +} +ord_parameter_types! { + pub const ListingOrigin: AccountId32 = AccountId32::new([1u8; 32]); +} +pub struct MockPriceSource; +impl PriceProvider for MockPriceSource { + fn get_relative_price(_base: CurrencyId, _quote: CurrencyId) -> Option { + Some(Price::one()) + } + + fn get_price(_currency_id: CurrencyId) -> Option { + Some(Price::one()) + } +} + +impl module_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type NativeCurrencyId = GetNativeCurrencyId; + type Currency = Balances; + type MultiCurrency = Currencies; + type OnTransactionPayment = (); + type OperationalFeeMultiplier = ConstU64<5>; + type TipPerWeightStep = ConstU128<1>; + type MaxTipsOfPriority = ConstU128<1000>; + type AlternativeFeeSwapDeposit = ExistenceRequirement; + type WeightToFee = IdentityFee; + type LengthToFee = ConstantMultiplier>; + type FeeMultiplierUpdate = (); + type Swap = SpecificJointsSwap; + type MaxSwapSlippageCompareToOracle = MaxSwapSlippageCompareToOracle; + type TradingPathLimit = TradingPathLimit; + type PriceSource = MockPriceSource; + type WeightInfo = (); + type PalletId = TransactionPaymentPalletId; + type TreasuryAccount = TreasuryAccount; + type UpdateOrigin = EnsureSignedBy; + type CustomFeeSurplus = CustomFeeSurplus; + type AlternativeFeeSurplus = AlternativeFeeSurplus; + type DefaultFeeTokens = DefaultFeeTokens; +} + +pub struct MockSwapDexIncentives; +impl SwapDexIncentives for MockSwapDexIncentives { + fn do_deposit_dex_share(who: &AccountId32, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult { + Tokens::reserve(lp_currency_id, who, amount) + } + + fn do_withdraw_dex_share(who: &AccountId32, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult { + let _ = Tokens::unreserve(lp_currency_id, who, amount); + Ok(()) + } +} + +parameter_types! { + pub const GetExchangeFee: (u32, u32) = (1, 100); + pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); +} + +impl edfis_swap_module::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Tokens; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = TradingPathLimit; + type PalletId = DEXPalletId; + type Erc20InfoMapping = MockErc20InfoMapping; + type WeightInfo = (); + type SwapDexIncentives = MockSwapDexIncentives; + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU32<0>; + type OnLiquidityPoolUpdated = (); +} + +pub type SignedExtra = (frame_system::CheckWeight,); +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + Dex: edfis_swap_module, + EVM: evm_module, + Tokens: orml_tokens, + Balances: pallet_balances, + Currencies: orml_currencies, + TransactionPayment: module_transaction_payment, + } +); diff --git a/blockchain/modules/evm/src/bench/mod.rs b/blockchain/modules/evm/src/bench/mod.rs new file mode 100644 index 00000000..5f270d20 --- /dev/null +++ b/blockchain/modules/evm/src/bench/mod.rs @@ -0,0 +1,274 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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(feature = "wasm-bench")] +#![allow(dead_code)] + +pub mod mock; + +use crate::{ + code_hash, evm::Runtime as EVMRuntime, module::*, runner::Runner, Context, StackExecutor, StackSubstateMetadata, + SubstrateStackState, +}; +use frame_support::{assert_ok, BoundedVec}; +use hex::FromHex; +use mock::*; +use module_support::mocks::MockAddressMapping; +use module_support::AddressMapping; +use primitives::evm::Vicinity; +use serde_json::Value; +use sp_core::{H160, H256, U256}; +use sp_std::{convert::TryInto, prelude::*, rc::Rc, str::FromStr}; +use wasm_bencher::{benches, Bencher}; + +fn get_bench_info(name: &str) -> (Vec, H160, Vec, u64, Vec) { + let benches_str = include_str!("../../../../evm-bench/build/benches.json"); + let evm_benches: Value = serde_json::from_str(benches_str).unwrap(); + let info = evm_benches[name].clone(); + + let code_str = info["code"].as_str().unwrap(); + let input_str = info["input"].as_str().unwrap_or_default(); + let output_str = info["output"].as_str().unwrap_or_default(); + + let code = Vec::from_hex(code_str).unwrap(); + let input = Vec::from_hex(input_str).unwrap(); + let output = Vec::from_hex(output_str).unwrap(); + + let from = H160::from_str(info["from"].as_str().unwrap()).unwrap(); + let used_gas = info["used_gas"].as_u64().unwrap(); + + (code, from, input, used_gas, output) +} + +fn faucet(address: &H160) { + let account_id = MockAddressMapping::get_account_id(&address); + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + account_id, + 1_000_000_000_000_000, + )); +} + +fn whitelist_keys(b: &mut Bencher, from: H160, code: Vec) -> H160 { + let address = H160::from_str("2000000000000000000000000000000000000001").unwrap(); + let vicinity = Vicinity { + gas_price: U256::one(), + ..Default::default() + }; + let context = Context { + caller: from, + address: address.clone(), + apparent_value: Default::default(), + }; + let config = ::config(); + let metadata = StackSubstateMetadata::new(21_000_000, 1_000_000, config); + let state = SubstrateStackState::::new(&vicinity, metadata); + let mut executor = StackExecutor::new_with_precompiles(state, config, &()); + + let mut runtime = EVMRuntime::new( + Rc::new(code.clone()), + Rc::new(Vec::new()), + context, + config.stack_limit, + config.memory_limit, + ); + let reason = executor.execute(&mut runtime); + + assert!(reason.is_succeed(), "{:?}", reason); + + let out = runtime.machine().return_value(); + let bounded_code: BoundedVec = out.try_into().unwrap(); + let code_hash = code_hash(bounded_code.as_slice()); + + // unknown key + b.whitelist( + hex_literal::hex!("3a7472616e73616374696f6e5f6c6576656c3a").to_vec(), + true, + true, + ); + + // non-existent contract will end up reading this key + b.whitelist( + Codes::::hashed_key_for(&H256::from_slice(&hex_literal::hex!( + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ))), + true, + true, + ); + b.whitelist(Codes::::hashed_key_for(&code_hash), true, true); + b.whitelist(CodeInfos::::hashed_key_for(&code_hash), true, true); + b.whitelist(Accounts::::hashed_key_for(&from), true, true); + b.whitelist(Accounts::::hashed_key_for(&address), true, true); + b.whitelist(ContractStorageSizes::::hashed_key_for(&address), true, true); + let from_account = ::AddressMapping::get_account_id(&from); + let address_account = ::AddressMapping::get_account_id(&address); + b.whitelist( + pallet_balances::Reserves::::hashed_key_for(&from_account), + true, + true, + ); + b.whitelist( + pallet_balances::Reserves::::hashed_key_for(&address_account), + true, + true, + ); + b.whitelist( + frame_system::Account::::hashed_key_for(&from_account), + true, + true, + ); + b.whitelist( + frame_system::Account::::hashed_key_for(&address_account), + true, + true, + ); + + // System::Number + b.whitelist( + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec(), + true, + true, + ); + + address +} + +macro_rules! evm_create { + ($name: ident) => { + fn $name(b: &mut Bencher) { + let (code, from, _, used_gas, _) = get_bench_info(stringify!($name)); + faucet(&from); + let contract_address = whitelist_keys(b, from, code.clone()); + + let result = b + .bench(|| { + // create contract + ::Runner::create_at_address( + from, + contract_address, + code.clone(), + 0, + 21_000_000, + 1_000_000, + vec![], + ::config(), + ) + }) + .unwrap(); + assert!( + result.exit_reason.is_succeed(), + "CREATE: Deploy contract failed with: {:?}", + result.exit_reason + ); + assert_eq!(result.used_gas, used_gas.into()); + } + }; +} + +macro_rules! evm_call { + ($name: ident) => { + fn $name(b: &mut Bencher) { + let (code, from, input, used_gas, output) = get_bench_info(stringify!($name)); + faucet(&from); + let contract_address = whitelist_keys(b, from, code.clone()); + + // create contract + let result = ::Runner::create_at_address( + from, + contract_address, + code.clone(), + 0, + 21_000_000, + 1_000_000, + vec![], + ::config(), + ) + .unwrap(); + + assert!( + result.exit_reason.is_succeed(), + "CALL: Deploy contract failed with: {:?}", + result.exit_reason + ); + assert_eq!(contract_address, result.value); + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + contract_address + )); + + let result = b + .bench(|| { + ::Runner::call( + from, + from, + contract_address, + input.clone(), + 0, + 21_000_000, + 1_000_000, + vec![], + ::config(), + ) + }) + .unwrap(); + + assert!( + result.exit_reason.is_succeed(), + "Call failed {:?}", + result.exit_reason + ); + assert_eq!(result.value, output); + assert_eq!(result.used_gas, used_gas.into()); + } + }; +} + +evm_create!(empty_deploy); +evm_call!(empty_noop); + +evm_create!(erc20_deploy); +evm_call!(erc20_approve); +evm_call!(erc20_approve_many); +evm_call!(erc20_transfer); +evm_call!(erc20_transfer_many); + +evm_create!(storage_deploy); +evm_call!(storage_store); +evm_call!(storage_store_many); + +evm_create!(ballot_deploy); +evm_call!(ballot_delegate); +evm_call!(ballot_vote); + +benches!( + empty_deploy, + empty_noop, + erc20_deploy, + erc20_approve, + erc20_approve_many, + erc20_transfer, + erc20_transfer_many, + storage_deploy, + storage_store, + storage_store_many, + ballot_deploy, + ballot_delegate, + ballot_vote +); diff --git a/blockchain/modules/evm/src/lib.rs b/blockchain/modules/evm/src/lib.rs index 6ff4dfad..056699cb 100644 --- a/blockchain/modules/evm/src/lib.rs +++ b/blockchain/modules/evm/src/lib.rs @@ -1,1487 +1,2174 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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_attr(not(feature = "std"), no_std)] -#![allow(clippy::too_many_arguments)] -#![allow(clippy::or_fun_call)] -#![allow(clippy::unused_unit)] -#![allow(clippy::upper_case_acronyms)] - -pub use crate::{ - precompiles::{Precompile, PrecompileSet}, - runner::{ - stack::SubstrateStackState, - state::{StackExecutor, StackSubstateMetadata}, - storage_meter::StorageMeter, - Runner, - }, -}; -use codec::{Decode, Encode, MaxEncodedLen}; -pub use evm::{Config as EvmConfig, Context, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed}; -use frame_support::{ - dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo}, - ensure, - error::BadOrigin, - log, - pallet_prelude::*, - parameter_types, - traits::{ - BalanceStatus, Currency, EnsureOrigin, ExistenceRequirement, FindAuthor, Get, NamedReservableCurrency, - OnKilledAccount, - }, - transactional, - weights::{Pays, PostDispatchInfo, Weight}, - BoundedVec, RuntimeDebug, -}; -use frame_system::{ensure_root, ensure_signed, pallet_prelude::*, EnsureOneOf, EnsureRoot, EnsureSigned}; -use hex_literal::hex; -pub use module_support::{ - AddressMapping, EVMStateRentTrait, ExecutionMode, InvokeContext, TransactionPayment, EVM as EVMTrait, -}; -pub use orml_traits::currency::TransferAll; -use primitive_types::{H160, H256, U256}; -pub use primitives::{ - evm::{Account, CallInfo, CreateInfo, EvmAddress, ExecutionInfo, Log, TransactionAction, Vicinity}, - ReserveIdentifier, H160_PREFIX_DEXSHARE, H160_PREFIX_TOKEN, MIRRORED_NFT_ADDRESS_START, PRECOMPILE_ADDRESS_START, - SYSTEM_CONTRACT_ADDRESS_PREFIX, -}; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sha3::{Digest, Keccak256}; -use sp_runtime::{ - traits::{ - Convert, DispatchInfoOf, One, PostDispatchInfoOf, Saturating, SignedExtension, UniqueSaturatedInto, Zero, - }, - transaction_validity::TransactionValidityError, - Either, TransactionOutcome, -}; -use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, fmt::Write, marker::PhantomData, prelude::*}; - -pub mod precompiles; -pub mod runner; - -mod mock; -mod tests; -pub mod weights; - -pub use module::*; -pub use weights::WeightInfo; - -/// Storage key size and storage value size. -pub const STORAGE_SIZE: u32 = 64; - -/// Type alias for currency balance. -pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -pub type NegativeImbalanceOf = - <::Currency as Currency<::AccountId>>::NegativeImbalance; -pub const RESERVE_ID_STORAGE_DEPOSIT: ReserveIdentifier = ReserveIdentifier::EvmStorageDeposit; -pub const RESERVE_ID_DEVELOPER_DEPOSIT: ReserveIdentifier = ReserveIdentifier::EvmDeveloperDeposit; - -// Initially based on Istanbul hard fork configuration. -static SETHEUM_CONFIG: EvmConfig = EvmConfig { - gas_ext_code: 700, - gas_ext_code_hash: 700, - gas_balance: 700, - gas_sload: 800, - gas_sstore_set: 20000, - gas_sstore_reset: 5000, - refund_sstore_clears: 0, // no gas refund - gas_suicide: 5000, - gas_suicide_new_account: 25000, - gas_call: 700, - gas_expbyte: 50, - gas_transaction_create: 53000, - gas_transaction_call: 21000, - gas_transaction_zero_data: 4, - gas_transaction_non_zero_data: 16, - sstore_gas_metering: false, // no gas refund - sstore_revert_under_stipend: false, // ignored - err_on_call_with_more_gas: false, - empty_considered_exists: false, - create_increase_nonce: true, - call_l64_after_gas: true, - stack_limit: 1024, - memory_limit: usize::max_value(), - call_stack_limit: 1024, - create_contract_limit: Some(MaxCodeSize::get() as usize), - call_stipend: 2300, - has_delegate_call: true, - has_create2: true, - has_revert: true, - has_return_data: true, - has_bitwise_shifting: true, - has_chain_id: true, - has_self_balance: true, - has_ext_code_hash: true, - estimate: false, -}; - -#[frame_support::pallet] -pub mod module { - use super::*; - - parameter_types! { - // Contract max code size. - pub const MaxCodeSize: u32 = 60 * 1024; - } - - /// EVM module trait - #[pallet::config] - pub trait Config: frame_system::Config + pallet_timestamp::Config { - /// Mapping from address to account id. - type AddressMapping: AddressMapping; - - /// Currency type for withdraw and balance storage. - type Currency: Currency - + NamedReservableCurrency; - - /// Merge free balance from source to dest. - type TransferAll: TransferAll; - - /// Charge extra bytes for creating a contract, would be reserved until - /// the contract deleted. - #[pallet::constant] - type NewContractExtraBytes: Get; - - /// Storage required for per byte. - #[pallet::constant] - type StorageDepositPerByte: Get>; - - /// The overarching event type. - type Event: From> + IsType<::Event>; - - /// Precompiles associated with this EVM engine. - type Precompiles: PrecompileSet; - - /// Chain ID of EVM. - #[pallet::constant] - type ChainId: Get; - - /// Convert gas to weight. - type GasToWeight: Convert; - - /// ChargeTransactionPayment convert weight to fee. - type ChargeTransactionPayment: TransactionPayment, NegativeImbalanceOf>; - - /// EVM config used in the module. - fn config() -> &'static EvmConfig { - &SETHEUM_CONFIG - } - - /// Required origin for creating system contract. - type NetworkContractOrigin: EnsureOrigin; - - /// The EVM address for creating system contract. - #[pallet::constant] - type NetworkContractSource: Get; - - /// Deposit for the developer. - #[pallet::constant] - type DeveloperDeposit: Get>; - - /// The fee for deploying the contract. - #[pallet::constant] - type DeploymentFee: Get>; - - #[pallet::constant] - type TreasuryAccount: Get; - - type FreeDeploymentOrigin: EnsureOrigin; - - /// EVM execution runner. - type Runner: Runner; - - /// Find author for the current block. - type FindAuthor: FindAuthor; - - /// Weight information for the extrinsics in this module. - type WeightInfo: WeightInfo; - } - - #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode)] - pub struct ContractInfo { - pub code_hash: H256, - pub maintainer: EvmAddress, - pub deployed: bool, - } - - #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode)] - pub struct AccountInfo { - pub nonce: T::Index, - pub contract_info: Option, - } - - impl AccountInfo { - pub fn new(nonce: T::Index, contract_info: Option) -> Self { - Self { nonce, contract_info } - } - } - - #[derive(Clone, Copy, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen)] - pub struct CodeInfo { - pub code_size: u32, - pub ref_count: u32, - } - - #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - /// Account definition used for genesis block construction. - pub struct GenesisAccount { - /// Account nonce. - pub nonce: Index, - /// Account balance. - pub balance: Balance, - /// Full account storage. - pub storage: BTreeMap, - /// Account code. - pub code: Vec, - } - - /// The EVM accounts info. - /// - /// Accounts: map EvmAddress => Option> - #[pallet::storage] - #[pallet::getter(fn accounts)] - pub type Accounts = StorageMap<_, Twox64Concat, EvmAddress, AccountInfo, OptionQuery>; - - /// The storage usage for contracts. Including code size, extra bytes and total AccountStorages - /// size. - /// - /// Accounts: map EvmAddress => u32 - #[pallet::storage] - #[pallet::getter(fn contract_storage_sizes)] - pub type ContractStorageSizes = StorageMap<_, Twox64Concat, EvmAddress, u32, ValueQuery>; - - /// The storages for EVM contracts. - /// - /// AccountStorages: double_map EvmAddress, H256 => H256 - #[pallet::storage] - #[pallet::getter(fn account_storages)] - pub type AccountStorages = - StorageDoubleMap<_, Twox64Concat, EvmAddress, Blake2_128Concat, H256, H256, ValueQuery>; - - /// The code for EVM contracts. - /// Key is Keccak256 hash of code. - /// - /// Codes: H256 => Vec - #[pallet::storage] - #[pallet::getter(fn codes)] - pub type Codes = StorageMap<_, Identity, H256, BoundedVec, ValueQuery>; - - /// The code info for EVM contracts. - /// Key is Keccak256 hash of code. - /// - /// CodeInfos: H256 => Option - #[pallet::storage] - #[pallet::getter(fn code_infos)] - pub type CodeInfos = StorageMap<_, Identity, H256, CodeInfo, OptionQuery>; - - /// Next available system contract address. - /// - /// NetworkContractIndex: u64 - #[pallet::storage] - #[pallet::getter(fn network_contract_index)] - pub type NetworkContractIndex = StorageValue<_, u64, ValueQuery>; - - /// Extrinsics origin for the current transaction. - /// - /// ExtrinsicOrigin: Option - #[pallet::storage] - #[pallet::getter(fn extrinsic_origin)] - pub type ExtrinsicOrigin = StorageValue<_, T::AccountId, OptionQuery>; - - #[pallet::genesis_config] - pub struct GenesisConfig { - pub accounts: BTreeMap, T::Index>>, - pub treasury: T::AccountId, - } - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - accounts: Default::default(), - treasury: Default::default(), - } - } - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - use sp_std::rc::Rc; - - // NOTE: Only applicable for newrome testnet, unit test and integration test. - // Use create_predeploy_contract to deploy predeploy contracts on the mainnet. - let source = T::NetworkContractSource::get(); - - self.accounts.iter().for_each(|(address, account)| { - let account_id = T::AddressMapping::get_account_id(address); - - let account_info = >::new(account.nonce, None); - >::insert(address, account_info); - - let amount = if account.balance.is_zero() { - T::Currency::minimum_balance() - } else { - account.balance - }; - T::Currency::deposit_creating(&account_id, amount); - - if !account.code.is_empty() { - // Transactions are not supported by BasicExternalities - // Use the EVM Runtime - let vicinity = Vicinity { - gas_price: U256::one(), - origin: Default::default(), - }; - let context = Context { - caller: source, - address: *address, - apparent_value: Default::default(), - }; - let metadata = - StackSubstateMetadata::new(210_000, 1000, T::NewContractExtraBytes::get(), T::config()); - let state = SubstrateStackState::::new(&vicinity, metadata); - let mut executor = StackExecutor::new(state, T::config()); - - let mut runtime = - evm::Runtime::new(Rc::new(account.code.clone()), Rc::new(Vec::new()), context, T::config()); - let reason = executor.execute(&mut runtime); - - assert!( - reason.is_succeed(), - "Genesis contract failed to execute, error: {:?}", - reason - ); - - let out = runtime.machine().return_value(); - >::create_contract(source, *address, out); - - #[cfg(not(feature = "with-ethereum-compatibility"))] - >::mark_deployed(*address, None).expect("Genesis contract failed to deploy"); - - for (index, value) in &account.storage { - AccountStorages::::insert(address, index, value); - } - } - }); - NetworkContractIndex::::put(MIRRORED_NFT_ADDRESS_START); - } - } - - /// EVM events - #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - #[pallet::metadata(T::AccountId = "AccountId")] - pub enum Event { - /// A contract has been created at given \[from, address, logs\]. - Created(EvmAddress, EvmAddress, Vec), - /// A contract was attempted to be created, but the execution failed. - /// \[from, contract, exit_reason, logs\] - CreatedFailed(EvmAddress, EvmAddress, ExitReason, Vec), - /// A contract has been executed successfully with states applied. \[from, contract, logs]\ - Executed(EvmAddress, EvmAddress, Vec), - /// A contract has been executed with errors. States are reverted with - /// only gas fees applied. \[from, contract, exit_reason, output, logs\] - ExecutedFailed(EvmAddress, EvmAddress, ExitReason, Vec, Vec), - /// Transferred maintainer. \[contract, address\] - TransferredMaintainer(EvmAddress, EvmAddress), - /// Enabled contract development. \[who\] - ContractDevelopmentEnabled(T::AccountId), - /// Disabled contract development. \[who\] - ContractDevelopmentDisabled(T::AccountId), - /// Deployed contract. \[contract\] - ContractDeployed(EvmAddress), - /// Set contract code. \[contract\] - ContractSetCode(EvmAddress), - /// Selfdestructed contract code. \[contract\] - ContractSelfdestructed(EvmAddress), - } - - #[pallet::error] - pub enum Error { - /// Address not mapped - AddressNotMapped, - /// Contract not found - ContractNotFound, - /// No permission - NoPermission, - /// Contract development is not enabled - ContractDevelopmentNotEnabled, - /// Contract development is already enabled - ContractDevelopmentAlreadyEnabled, - /// Contract already deployed - ContractAlreadyDeployed, - /// Contract exceeds max code size - ContractExceedsMaxCodeSize, - /// Contract already existed - ContractAlreadyExisted, - /// Storage usage exceeds storage limit - OutOfStorage, - /// Charge fee failed - ChargeFeeFailed, - /// Contract cannot be killed due to reference count - CannotKillContract, - /// Reserve storage failed - ReserveStorageFailed, - /// Unreserve storage failed - UnreserveStorageFailed, - /// Charge storage failed - ChargeStorageFailed, - } - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet {} - - #[pallet::call] - impl Pallet { - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] - #[transactional] - pub fn eth_call( - origin: OriginFor, - action: TransactionAction, - input: Vec, - #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: u64, - #[pallet::compact] storage_limit: u32, - #[pallet::compact] _valid_until: T::BlockNumber, // checked by tx validation logic - ) -> DispatchResultWithPostInfo { - match action { - TransactionAction::Call(target) => Self::call(origin, target, input, value, gas_limit, storage_limit), - TransactionAction::Create => Self::create(origin, input, value, gas_limit, storage_limit), - } - } - - /// Issue an EVM call operation. This is similar to a message call - /// transaction in Ethereum. - /// - /// - `target`: the contract address to call - /// - `input`: the data supplied for the call - /// - `value`: the amount sent for payable calls - /// - `gas_limit`: the maximum gas the call can use - /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] - #[transactional] - pub fn call( - origin: OriginFor, - target: EvmAddress, - input: Vec, - #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: u64, - #[pallet::compact] storage_limit: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let source = T::AddressMapping::get_or_create_evm_address(&who); - - let info = T::Runner::call( - source, - source, - target, - input, - value, - gas_limit, - storage_limit, - T::config(), - )?; - - let used_gas: u64 = info.used_gas.unique_saturated_into(); - - Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), - pays_fee: Pays::Yes, - }) - } - - /// Issue an EVM call operation on a scheduled contract call, and - /// refund the unused gas reserved when the call was scheduled. - /// - /// - `from`: the address the scheduled call originates from - /// - `target`: the contract address to call - /// - `input`: the data supplied for the call - /// - `value`: the amount sent for payable calls - /// - `gas_limit`: the maximum gas the call can use - /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] - #[transactional] - pub fn scheduled_call( - origin: OriginFor, - from: EvmAddress, - target: EvmAddress, - input: Vec, - #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: u64, - #[pallet::compact] storage_limit: u32, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - let _from_account = T::AddressMapping::get_account_id(&from); - let _payed: NegativeImbalanceOf; - #[cfg(not(feature = "with-ethereum-compatibility"))] - { - // unreserve the transaction fee for gas_limit - let weight = T::GasToWeight::convert(gas_limit); - let (_, imbalance) = T::ChargeTransactionPayment::unreserve_and_charge_fee(&_from_account, weight) - .map_err(|_| Error::::ChargeFeeFailed)?; - _payed = imbalance; - } - - let info = T::Runner::call(from, from, target, input, value, gas_limit, storage_limit, T::config())?; - - let used_gas: u64 = info.used_gas.unique_saturated_into(); - - #[cfg(not(feature = "with-ethereum-compatibility"))] - { - use sp_runtime::traits::Zero; - let refund_gas = gas_limit.saturating_sub(used_gas); - if !refund_gas.is_zero() { - // ignore the result to continue. if it fails, just the user will not - // be refunded, there will not increase user balance. - let res = T::ChargeTransactionPayment::refund_fee( - &_from_account, - T::GasToWeight::convert(refund_gas), - _payed, - ); - debug_assert!(res.is_ok()); - } - } - - Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), - pays_fee: Pays::Yes, - }) - } - - /// Issue an EVM create operation. This is similar to a contract - /// creation transaction in Ethereum. - /// - /// - `init`: the data supplied for the contract's constructor - /// - `value`: the amount sent to the contract upon creation - /// - `gas_limit`: the maximum gas the call can use - /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] - #[transactional] - pub fn create( - origin: OriginFor, - init: Vec, - #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: u64, - #[pallet::compact] storage_limit: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let source = T::AddressMapping::get_or_create_evm_address(&who); - - let info = T::Runner::create(source, init, value, gas_limit, storage_limit, T::config())?; - - let used_gas: u64 = info.used_gas.unique_saturated_into(); - - Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), - pays_fee: Pays::Yes, - }) - } - - /// Issue an EVM create2 operation. - /// - /// - `target`: the contract address to call - /// - `init`: the data supplied for the contract's constructor - /// - `salt`: used for generating the new contract's address - /// - `value`: the amount sent for payable calls - /// - `gas_limit`: the maximum gas the call can use - /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] - #[transactional] - pub fn create2( - origin: OriginFor, - init: Vec, - salt: H256, - #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: u64, - #[pallet::compact] storage_limit: u32, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let source = T::AddressMapping::get_or_create_evm_address(&who); - - let info = T::Runner::create2(source, init, salt, value, gas_limit, storage_limit, T::config())?; - - let used_gas: u64 = info.used_gas.unique_saturated_into(); - - Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), - pays_fee: Pays::Yes, - }) - } - - /// Issue an EVM create operation. The next available system contract - /// address will be used as created contract address. - /// - /// - `init`: the data supplied for the contract's constructor - /// - `value`: the amount sent for payable calls - /// - `gas_limit`: the maximum gas the call can use - /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] - #[transactional] - pub fn create_network_contract( - origin: OriginFor, - init: Vec, - #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: u64, - #[pallet::compact] storage_limit: u32, - ) -> DispatchResultWithPostInfo { - T::NetworkContractOrigin::ensure_origin(origin)?; - - let source = T::NetworkContractSource::get(); - let address = EvmAddress::from_low_u64_be(Self::network_contract_index()); - let info = - T::Runner::create_at_address(source, address, init, value, gas_limit, storage_limit, T::config())?; - - NetworkContractIndex::::mutate(|v| *v = v.saturating_add(One::one())); - - let used_gas: u64 = info.used_gas.unique_saturated_into(); - - Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), - pays_fee: Pays::Yes, - }) - } - - /// Issue an EVM create operation. The address specified - /// will be used as created contract address. - /// - /// - `target`: the address specified by the contract - /// - `init`: the data supplied for the contract's constructor - /// - `value`: the amount sent for payable calls - /// - `gas_limit`: the maximum gas the call can use - /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] - #[transactional] - pub fn create_predeploy_contract( - origin: OriginFor, - target: EvmAddress, - init: Vec, - #[pallet::compact] value: BalanceOf, - #[pallet::compact] gas_limit: u64, - #[pallet::compact] storage_limit: u32, - ) -> DispatchResultWithPostInfo { - T::NetworkContractOrigin::ensure_origin(origin)?; - - ensure!( - Pallet::::is_account_empty(&target), - Error::::ContractAlreadyExisted - ); - - let source = T::NetworkContractSource::get(); - - let info = if init.is_empty() { - // deposit ED for mirrored token - T::Currency::transfer( - &T::TreasuryAccount::get(), - &T::AddressMapping::get_account_id(&target), - T::Currency::minimum_balance(), - ExistenceRequirement::AllowDeath, - )?; - CreateInfo { - value: target, - exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), - used_gas: 0.into(), - used_storage: 0, - logs: vec![], - } - } else { - T::Runner::create_at_address(source, target, init, value, gas_limit, storage_limit, T::config())? - }; - - let used_gas: u64 = info.used_gas.unique_saturated_into(); - - Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), - pays_fee: Pays::Yes, - }) - } - - /// Transfers Contract maintainership to a new EVM Address. - /// - /// - `contract`: the contract whose maintainership is being transferred, the caller must be - /// the contract's maintainer - /// - `new_maintainer`: the address of the new maintainer - #[pallet::weight(::WeightInfo::transfer_maintainer())] - #[transactional] - pub fn transfer_maintainer( - origin: OriginFor, - contract: EvmAddress, - new_maintainer: EvmAddress, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - Self::do_transfer_maintainer(who, contract, new_maintainer)?; - - Pallet::::deposit_event(Event::::TransferredMaintainer(contract, new_maintainer)); - - Ok(().into()) - } - - /// Mark a given contract as deployed. - /// - /// - `contract`: The contract to mark as deployed, the caller must the contract's - /// maintainer - #[pallet::weight(::WeightInfo::deploy())] - #[transactional] - pub fn deploy(origin: OriginFor, contract: EvmAddress) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let address = T::AddressMapping::get_evm_address(&who).ok_or(Error::::AddressNotMapped)?; - T::Currency::transfer( - &who, - &T::TreasuryAccount::get(), - T::DeploymentFee::get(), - ExistenceRequirement::AllowDeath, - )?; - Self::mark_deployed(contract, Some(address))?; - Pallet::::deposit_event(Event::::ContractDeployed(contract)); - Ok(().into()) - } - - /// Mark a given contract as deployed without paying the deployment fee - /// - /// - `contract`: The contract to mark as deployed, the caller must be the contract's - /// maintainer. - #[pallet::weight(::WeightInfo::deploy_free())] - #[transactional] - pub fn deploy_free(origin: OriginFor, contract: EvmAddress) -> DispatchResultWithPostInfo { - T::FreeDeploymentOrigin::ensure_origin(origin)?; - Self::mark_deployed(contract, None)?; - Pallet::::deposit_event(Event::::ContractDeployed(contract)); - Ok(().into()) - } - - /// Mark the caller's address to allow contract development. - /// This allows the address to interact with non-deployed contracts. - #[pallet::weight(::WeightInfo::enable_contract_development())] - #[transactional] - pub fn enable_contract_development(origin: OriginFor) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - ensure!( - T::Currency::reserved_balance_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &who).is_zero(), - Error::::ContractDevelopmentAlreadyEnabled - ); - T::Currency::ensure_reserved_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &who, T::DeveloperDeposit::get())?; - Pallet::::deposit_event(Event::::ContractDevelopmentEnabled(who)); - Ok(().into()) - } - - /// Mark the caller's address to disable contract development. - /// This disallows the address to interact with non-deployed contracts. - #[pallet::weight(::WeightInfo::disable_contract_development())] - #[transactional] - pub fn disable_contract_development(origin: OriginFor) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - ensure!( - !T::Currency::reserved_balance_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &who).is_zero(), - Error::::ContractDevelopmentNotEnabled - ); - T::Currency::unreserve_all_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &who); - Pallet::::deposit_event(Event::::ContractDevelopmentDisabled(who)); - Ok(().into()) - } - - /// Set the code of a contract at a given address. - /// - /// - `contract`: The contract whose code is being set, must not be marked as deployed - /// - `code`: The new ABI bundle for the contract - #[pallet::weight(::WeightInfo::set_code())] - #[transactional] - pub fn set_code(origin: OriginFor, contract: EvmAddress, code: Vec) -> DispatchResultWithPostInfo { - let root_or_signed = Self::ensure_root_or_signed(origin)?; - Self::do_set_code(root_or_signed, contract, code)?; - - Pallet::::deposit_event(Event::::ContractSetCode(contract)); - - Ok(().into()) - } - - /// Remove a contract at a given address. - /// - /// - `contract`: The contract to remove, must not be marked as deployed - #[pallet::weight(::WeightInfo::selfdestruct())] - #[transactional] - pub fn selfdestruct(origin: OriginFor, contract: EvmAddress) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let maintainer = T::AddressMapping::get_evm_address(&who).ok_or(Error::::AddressNotMapped)?; - Self::do_selfdestruct(who, &maintainer, contract)?; - - Pallet::::deposit_event(Event::::ContractSelfdestructed(contract)); - - Ok(().into()) - } - } -} - -impl Pallet { - /// Check whether an account is empty. - pub fn is_account_empty(address: &H160) -> bool { - let account_id = T::AddressMapping::get_account_id(address); - let balance = T::Currency::total_balance(&account_id); - - if !balance.is_zero() { - return false; - } - - Self::accounts(address).map_or(true, |account_info| { - account_info.contract_info.is_none() && account_info.nonce.is_zero() - }) - } - - /// Remove an account if its empty. - /// Unused now. - pub fn remove_account_if_empty(address: &H160) { - if Self::is_account_empty(address) { - let res = Self::remove_account(address); - debug_assert!(res.is_ok()); - } - } - - #[transactional] - pub fn remove_contract(address: &EvmAddress) -> Result { - let address_account = T::AddressMapping::get_account_id(address); - - let size = Accounts::::try_mutate_exists(address, |account_info| -> Result { - let account_info = account_info.as_mut().ok_or(Error::::ContractNotFound)?; - let contract_info = account_info.contract_info.take().ok_or(Error::::ContractNotFound)?; - - let maintainer_account = T::AddressMapping::get_account_id(&contract_info.maintainer); - T::TransferAll::transfer_all(&address_account, &maintainer_account)?; - - CodeInfos::::mutate_exists(&contract_info.code_hash, |maybe_code_info| { - if let Some(code_info) = maybe_code_info.as_mut() { - code_info.ref_count = code_info.ref_count.saturating_sub(1); - if code_info.ref_count == 0 { - Codes::::remove(&contract_info.code_hash); - *maybe_code_info = None; - } - } else { - // code info removed while still having reference to it? - debug_assert!(false); - } - }); - - AccountStorages::::remove_prefix(address, None); - - let size = ContractStorageSizes::::take(address); - - Ok(size) - })?; - - // this should happen after `Accounts` is updated because this could trigger another updates on - // `Accounts` - frame_system::Pallet::::dec_providers(&address_account)?; - - Ok(size) - } - - /// Removes an account from Accounts and AccountStorages. - pub fn remove_account(address: &EvmAddress) -> DispatchResult { - // Deref code, and remove it if ref count is zero. - if let Some(AccountInfo { - contract_info: Some(contract_info), - .. - }) = Self::accounts(address) - { - CodeInfos::::mutate_exists(&contract_info.code_hash, |maybe_code_info| { - if let Some(code_info) = maybe_code_info.as_mut() { - code_info.ref_count = code_info.ref_count.saturating_sub(1); - if code_info.ref_count == 0 { - Codes::::remove(&contract_info.code_hash); - *maybe_code_info = None; - } - } - }); - } - - if let Some(AccountInfo { - contract_info: Some(_), .. - }) = Accounts::::take(address) - { - // remove_account can only be called when account is killed. i.e. providers == 0 - // but contract_info should maintain a provider - // so this should never happen - debug_assert!(false); - } - - Ok(()) - } - - /// Create an account. - /// - Create new account for the contract. - /// - Update codes info. - /// - Update maintainer of the contract. - /// - Save `code` if not saved yet. - pub fn create_contract(source: H160, address: H160, code: Vec) { - let bounded_code: BoundedVec = code - .try_into() - .expect("checked by create_contract_limit in SETHEUM_CONFIG; qed"); - if bounded_code.is_empty() { - return; - } - - // if source is account, the maintainer of the new contract is source. - // if source is contract, the maintainer of the new contract is the maintainer of the contract. - let maintainer = Self::accounts(source).map_or(source, |account_info| { - account_info - .contract_info - .map_or(source, |contract_info| contract_info.maintainer) - }); - - let code_hash = code_hash(bounded_code.as_slice()); - let code_size = bounded_code.len() as u32; - - let contract_info = ContractInfo { - code_hash, - maintainer, - #[cfg(feature = "with-ethereum-compatibility")] - deployed: true, - #[cfg(not(feature = "with-ethereum-compatibility"))] - deployed: false, - }; - - Self::update_contract_storage_size( - &address, - code_size.saturating_add(T::NewContractExtraBytes::get()) as i32, - ); - - CodeInfos::::mutate_exists(&code_hash, |maybe_code_info| { - if let Some(code_info) = maybe_code_info.as_mut() { - code_info.ref_count = code_info.ref_count.saturating_add(1); - } else { - let new = CodeInfo { - code_size, - ref_count: 1, - }; - *maybe_code_info = Some(new); - - Codes::::insert(&code_hash, bounded_code); - } - }); - - Accounts::::mutate(address, |maybe_account_info| { - if let Some(account_info) = maybe_account_info.as_mut() { - account_info.contract_info = Some(contract_info.clone()); - } else { - let account_info = AccountInfo::::new(Default::default(), Some(contract_info.clone())); - *maybe_account_info = Some(account_info); - } - }); - - frame_system::Pallet::::inc_providers(&T::AddressMapping::get_account_id(&address)); - } - - /// Get the account basic in EVM format. - pub fn account_basic(address: &EvmAddress) -> Account { - let account_id = T::AddressMapping::get_account_id(address); - - let nonce = Self::accounts(address).map_or(Default::default(), |account_info| account_info.nonce); - let balance = T::Currency::free_balance(&account_id); - - Account { - nonce: U256::from(UniqueSaturatedInto::::unique_saturated_into(nonce)), - balance: U256::from(UniqueSaturatedInto::::unique_saturated_into(balance)), - } - } - - /// Get the author using the FindAuthor trait. - pub fn find_author() -> H160 { - let digest = >::digest(); - let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); - - let author = T::FindAuthor::find_author(pre_runtime_digests).unwrap_or_default(); - T::AddressMapping::get_default_evm_address(&author) - } - - /// Get code hash at given address. - pub fn code_hash_at_address(address: &EvmAddress) -> H256 { - if let Some(AccountInfo { - contract_info: Some(contract_info), - .. - }) = Self::accounts(address) - { - contract_info.code_hash - } else { - // The same as `code_hash(&[])`, hardcode here. - H256::from_slice(&hex!( - "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" - )) - } - } - - /// Get code at given address. - pub fn code_at_address(address: &EvmAddress) -> BoundedVec { - Self::codes(&Self::code_hash_at_address(address)) - } - - pub fn update_contract_storage_size(address: &EvmAddress, change: i32) { - if change == 0 { - return; - } - ContractStorageSizes::::mutate(address, |val| { - if change > 0 { - *val = val.saturating_add(change as u32); - } else { - *val = val.saturating_sub((-change) as u32); - } - }); - } - - /// Sets a given contract's contract info to a new maintainer. - fn do_transfer_maintainer(who: T::AccountId, contract: EvmAddress, new_maintainer: EvmAddress) -> DispatchResult { - Accounts::::get(contract).map_or(Err(Error::::ContractNotFound), |account_info| { - account_info - .contract_info - .map_or(Err(Error::::ContractNotFound), |_| Ok(())) - })?; - - Accounts::::mutate(contract, |maybe_account_info| -> DispatchResult { - let account_info = maybe_account_info.as_mut().ok_or(Error::::ContractNotFound)?; - let contract_info = account_info - .contract_info - .as_mut() - .ok_or(Error::::ContractNotFound)?; - - let maintainer = T::AddressMapping::get_evm_address(&who).ok_or(Error::::AddressNotMapped)?; - ensure!(contract_info.maintainer == maintainer, Error::::NoPermission); - - contract_info.maintainer = new_maintainer; - Ok(()) - })?; - - Ok(()) - } - - /// Mark contract as deployed - /// - /// If maintainer is provider then it will check maintainer - fn mark_deployed(contract: EvmAddress, maintainer: Option) -> DispatchResult { - Accounts::::mutate(contract, |maybe_account_info| -> DispatchResult { - if let Some(AccountInfo { - contract_info: Some(contract_info), - .. - }) = maybe_account_info.as_mut() - { - if let Some(maintainer) = maintainer { - ensure!(contract_info.maintainer == maintainer, Error::::NoPermission); - } - ensure!(!contract_info.deployed, Error::::ContractAlreadyDeployed); - contract_info.deployed = true; - Ok(()) - } else { - Err(Error::::ContractNotFound.into()) - } - }) - } - - /// Set the code of a contract at a given address. - /// - /// - Ensures signer is maintainer or root. - /// - Update codes info. - /// - Save `code`if not saved yet. - fn do_set_code(root_or_signed: Either<(), T::AccountId>, contract: EvmAddress, code: Vec) -> DispatchResult { - Accounts::::mutate(contract, |maybe_account_info| -> DispatchResult { - let account_info = maybe_account_info.as_mut().ok_or(Error::::ContractNotFound)?; - let contract_info = account_info - .contract_info - .as_mut() - .ok_or(Error::::ContractNotFound)?; - - let source = if let Either::Right(signer) = root_or_signed { - let maintainer = T::AddressMapping::get_evm_address(&signer).ok_or(Error::::AddressNotMapped)?; - ensure!(contract_info.maintainer == maintainer, Error::::NoPermission); - ensure!(!contract_info.deployed, Error::::ContractAlreadyDeployed); - maintainer - } else { - T::NetworkContractSource::get() - }; - - let old_code_info = Self::code_infos(&contract_info.code_hash).ok_or(Error::::ContractNotFound)?; - - let bounded_code: BoundedVec = - code.try_into().map_err(|_| Error::::ContractExceedsMaxCodeSize)?; - let code_hash = code_hash(bounded_code.as_slice()); - let code_size = bounded_code.len() as u32; - // The code_hash of the same contract is definitely different. - // The `contract_info.code_hash` hashed by on_contract_initialization which constructored. - // Still check it here. - if code_hash == contract_info.code_hash { - return Ok(()); - } - - let storage_size_chainged: i32 = - code_size.saturating_add(T::NewContractExtraBytes::get()) as i32 - old_code_info.code_size as i32; - - if storage_size_chainged.is_positive() { - Self::reserve_storage(&source, storage_size_chainged as u32)?; - } - Self::charge_storage(&source, &contract, storage_size_chainged)?; - Self::update_contract_storage_size(&contract, storage_size_chainged); - - // try remove old codes - CodeInfos::::mutate_exists(&contract_info.code_hash, |maybe_code_info| -> DispatchResult { - let code_info = maybe_code_info.as_mut().ok_or(Error::::ContractNotFound)?; - code_info.ref_count = code_info.ref_count.saturating_sub(1); - if code_info.ref_count == 0 { - Codes::::remove(&contract_info.code_hash); - *maybe_code_info = None; - } - Ok(()) - })?; - - CodeInfos::::mutate_exists(&code_hash, |maybe_code_info| { - if let Some(code_info) = maybe_code_info.as_mut() { - code_info.ref_count = code_info.ref_count.saturating_add(1); - } else { - let new = CodeInfo { - code_size, - ref_count: 1, - }; - *maybe_code_info = Some(new); - - Codes::::insert(&code_hash, bounded_code); - } - }); - // update code_hash - contract_info.code_hash = code_hash; - - Ok(()) - }) - } - - /// Selfdestruct a contract at a given address. - fn do_selfdestruct(who: T::AccountId, maintainer: &EvmAddress, contract: EvmAddress) -> DispatchResult { - let account_info = Self::accounts(contract).ok_or(Error::::ContractNotFound)?; - let contract_info = account_info - .contract_info - .as_ref() - .ok_or(Error::::ContractNotFound)?; - - ensure!(contract_info.maintainer == *maintainer, Error::::NoPermission); - ensure!(!contract_info.deployed, Error::::ContractAlreadyDeployed); - - let storage = Self::remove_contract(&contract)?; - - let contract_account = T::AddressMapping::get_account_id(&contract); - - let amount = T::StorageDepositPerByte::get().saturating_mul(storage.into()); - let val = T::Currency::repatriate_reserved_named( - &RESERVE_ID_STORAGE_DEPOSIT, - &contract_account, - &who, - amount, - BalanceStatus::Free, - )?; - debug_assert!(val.is_zero()); - - Ok(()) - } - - fn ensure_root_or_signed(o: T::Origin) -> Result, BadOrigin> { - EnsureOneOf::, EnsureSigned>::try_origin(o) - .map_or(Err(BadOrigin), Ok) - } - - fn can_call_contract(address: &H160, caller: &H160) -> bool { - if let Some(AccountInfo { - contract_info: Some(ContractInfo { - deployed, maintainer, .. - }), - .. - }) = Accounts::::get(address) - { - deployed || maintainer == *caller || Self::is_developer_or_contract(caller) - } else { - // contract non exist, we don't override defualt evm behaviour - true - } - } - - fn is_developer_or_contract(caller: &H160) -> bool { - if let Some(AccountInfo { contract_info, .. }) = Accounts::::get(caller) { - let account_id = T::AddressMapping::get_account_id(caller); - contract_info.is_some() - || !T::Currency::reserved_balance_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &account_id).is_zero() - } else { - false - } - } - - fn reserve_storage(caller: &H160, limit: u32) -> DispatchResult { - if limit.is_zero() { - return Ok(()); - } - - let user = T::AddressMapping::get_account_id(caller); - let amount = T::StorageDepositPerByte::get().saturating_mul(limit.into()); - - log::debug!( - target: "evm", - "reserve_storage: [from: {:?}, account: {:?}, limit: {:?}, amount: {:?}]", - caller, user, limit, amount - ); - - T::Currency::reserve_named(&RESERVE_ID_STORAGE_DEPOSIT, &user, amount) - } - - fn unreserve_storage(caller: &H160, limit: u32, used: u32, refunded: u32) -> DispatchResult { - let total = limit.saturating_add(refunded); - let unused = total.saturating_sub(used); - if unused.is_zero() { - return Ok(()); - } - - let user = T::AddressMapping::get_account_id(caller); - let amount = T::StorageDepositPerByte::get().saturating_mul(unused.into()); - - log::debug!( - target: "evm", - "unreserve_storage: [from: {:?}, account: {:?}, used: {:?}, refunded: {:?}, unused: {:?}, amount: {:?}]", - caller, user, used, refunded, unused, amount - ); - - // should always be able to unreserve the amount - // but otherwise we will just ignore the issue here. - let err_amount = T::Currency::unreserve_named(&RESERVE_ID_STORAGE_DEPOSIT, &user, amount); - debug_assert!(err_amount.is_zero()); - Ok(()) - } - - fn charge_storage(caller: &H160, contract: &H160, storage: i32) -> DispatchResult { - if storage.is_zero() { - return Ok(()); - } - - let user = T::AddressMapping::get_account_id(caller); - let contract_acc = T::AddressMapping::get_account_id(contract); - let amount = T::StorageDepositPerByte::get().saturating_mul((storage.abs() as u32).into()); - - log::debug!( - target: "evm", - "charge_storage: [from: {:?}, account: {:?}, contract: {:?}, contract_acc: {:?}, storage: {:?}, amount: {:?}]", - caller, user, contract, contract_acc, storage, amount - ); - - if storage.is_positive() { - // `repatriate_reserved` requires beneficiary is an existing account but - // contract_acc could be a new account so we need to do - // unreserve/transfer/reserve. - // should always be able to unreserve the amount - // but otherwise we will just ignore the issue here. - let err_amount = T::Currency::unreserve_named(&RESERVE_ID_STORAGE_DEPOSIT, &user, amount); - debug_assert!(err_amount.is_zero()); - T::Currency::transfer(&user, &contract_acc, amount, ExistenceRequirement::AllowDeath)?; - T::Currency::reserve_named(&RESERVE_ID_STORAGE_DEPOSIT, &contract_acc, amount)?; - } else { - // user can't be a dead account - let val = T::Currency::repatriate_reserved_named( - &RESERVE_ID_STORAGE_DEPOSIT, - &contract_acc, - &user, - amount, - BalanceStatus::Reserved, - )?; - debug_assert!(val.is_zero()); - }; - - Ok(()) - } -} - -impl EVMTrait for Pallet { - type Balance = BalanceOf; - fn execute( - context: InvokeContext, - input: Vec, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - mode: ExecutionMode, - ) -> Result { - let mut config = T::config().clone(); - if let ExecutionMode::EstimateGas = mode { - config.estimate = true; - } - - frame_support::storage::with_transaction(|| { - let result = T::Runner::call( - context.sender, - context.origin, - context.contract, - input, - value, - gas_limit, - storage_limit, - &config, - ); - - match result { - Ok(info) => match mode { - ExecutionMode::Execute => { - if info.exit_reason.is_succeed() { - TransactionOutcome::Commit(Ok(info)) - } else { - TransactionOutcome::Rollback(Ok(info)) - } - } - ExecutionMode::View | ExecutionMode::EstimateGas => TransactionOutcome::Rollback(Ok(info)), - }, - Err(e) => TransactionOutcome::Rollback(Err(e)), - } - }) - } - - /// Get the real origin account and charge storage rent from the origin. - fn get_origin() -> Option { - ExtrinsicOrigin::::get() - } - - /// Provide a method to set origin for `on_initialize` - fn set_origin(origin: T::AccountId) { - ExtrinsicOrigin::::set(Some(origin)); - } -} - -impl EVMStateRentTrait> for Pallet { - fn query_new_contract_extra_bytes() -> u32 { - T::NewContractExtraBytes::get() - } - - fn query_storage_deposit_per_byte() -> BalanceOf { - T::StorageDepositPerByte::get() - } - - fn query_maintainer(contract: EvmAddress) -> Result { - Accounts::::get(contract).map_or(Err(Error::::ContractNotFound.into()), |account_info| { - account_info - .contract_info - .map_or(Err(Error::::ContractNotFound.into()), |v| Ok(v.maintainer)) - }) - } - - fn query_developer_deposit() -> BalanceOf { - T::DeveloperDeposit::get() - } - - fn query_deployment_fee() -> BalanceOf { - T::DeploymentFee::get() - } - - fn transfer_maintainer(from: T::AccountId, contract: EvmAddress, new_maintainer: EvmAddress) -> DispatchResult { - Pallet::::do_transfer_maintainer(from, contract, new_maintainer) - } -} - -pub struct CallKillAccount(PhantomData); -impl OnKilledAccount for CallKillAccount { - fn on_killed_account(who: &T::AccountId) { - if let Some(address) = T::AddressMapping::get_evm_address(who) { - let res = Pallet::::remove_account(&address); - debug_assert!(res.is_ok()); - } - let address = T::AddressMapping::get_default_evm_address(who); - let res = Pallet::::remove_account(&address); - debug_assert!(res.is_ok()); - } -} - -pub fn code_hash(code: &[u8]) -> H256 { - H256::from_slice(Keccak256::digest(code).as_slice()) -} - -fn encode_revert_message(e: &ExitError) -> Vec { - // A minimum size of error function selector (4) + offset (32) + string length - // (32) should contain a utf-8 encoded revert reason. - - let mut w = sp_std::Writer::default(); - let _ = core::write!(&mut w, "{:?}", e); - let msg = w.into_inner(); - - let mut data = Vec::with_capacity(68 + msg.len()); - data.extend_from_slice(&[0u8; 68]); - U256::from(msg.len()).to_big_endian(&mut data[36..68]); - data.extend_from_slice(&msg); - data -} - -#[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct SetEvmOrigin(PhantomData); - -impl sp_std::fmt::Debug for SetEvmOrigin { - #[cfg(feature = "std")] - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "SetEvmOrigin") - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - Ok(()) - } -} - -impl SetEvmOrigin { - pub fn new() -> Self { - Self(sp_std::marker::PhantomData) - } -} - -impl Default for SetEvmOrigin { - fn default() -> Self { - Self::new() - } -} - -impl SignedExtension for SetEvmOrigin { - const IDENTIFIER: &'static str = "SetEvmOrigin"; - type AccountId = T::AccountId; - type Call = T::Call; - type AdditionalSigned = (); - type Pre = (); - - fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { - Ok(()) - } - - fn pre_dispatch( - self, - who: &Self::AccountId, - _call: &Self::Call, - _info: &DispatchInfoOf, - _len: usize, - ) -> Result<(), TransactionValidityError> { - ExtrinsicOrigin::::set(Some(who.clone())); - Ok(()) - } - - fn post_dispatch( - _pre: Self::Pre, - _info: &DispatchInfoOf, - _post_info: &PostDispatchInfoOf, - _len: usize, - _result: &DispatchResult, - ) -> Result<(), TransactionValidityError> { - ExtrinsicOrigin::::kill(); - Ok(()) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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_attr(not(feature = "std"), no_std)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::or_fun_call)] +#![allow(clippy::unused_unit)] +#![allow(clippy::upper_case_acronyms)] + +pub use crate::runner::{ + stack::SubstrateStackState, + state::{PrecompileResult, StackExecutor, StackSubstateMetadata}, + storage_meter::StorageMeter, + Runner, +}; +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, Pays, PostDispatchInfo}, + ensure, + error::BadOrigin, + pallet_prelude::*, + parameter_types, + traits::{ + BalanceStatus, Currency, EitherOfDiverse, EnsureOrigin, ExistenceRequirement, FindAuthor, Get, + NamedReservableCurrency, OnKilledAccount, + }, + transactional, + weights::Weight, + BoundedVec, +}; +use frame_system::{ensure_root, ensure_signed, pallet_prelude::*, EnsureRoot, EnsureSigned}; +use hex_literal::hex; +pub use module_evm_utility::{ + ethereum::{AccessListItem, Log, TransactionAction}, + evm::{ + self, + executor::stack::{IsPrecompileResult, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet}, + Config as EvmConfig, Context, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed, ExternalOperation, + }, + Account, +}; +pub use module_support::{ + AddressMapping, EVMManager, ExecutionMode, InvokeContext, TransactionPayment, + EVM as EVMTrait, +}; +pub use orml_traits::{currency::TransferAll, MultiCurrency}; +use parity_scale_codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +pub use primitives::{ + evm::{ + convert_decimals_from_evm, convert_decimals_to_evm, decode_gas_limit, is_system_contract, CallInfo, CreateInfo, + EvmAddress, ExecutionInfo, Vicinity, MIRRORED_NFT_ADDRESS_START, MIRRORED_TOKENS_ADDRESS_START, + }, + Balance, CurrencyId, Nonce, ReserveIdentifier, +}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sha3::{Digest, Keccak256}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{ + traits::{Convert, DispatchInfoOf, One, PostDispatchInfoOf, SignedExtension, UniqueSaturatedInto, Zero}, + transaction_validity::TransactionValidityError, + DispatchError, Either, RuntimeDebug, SaturatedConversion, Saturating, TransactionOutcome, +}; +use sp_std::{cmp, collections::btree_map::BTreeMap, fmt::Debug, marker::PhantomData, prelude::*}; + +pub mod precompiles; +pub mod runner; + +pub mod bench; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +/// Storage key size and storage value size. +pub const STORAGE_SIZE: u32 = 64; +/// Remove contract item limit +pub const REMOVE_LIMIT: u32 = 100; +/// Immediate remove contract item limit 50 DB writes +pub const IMMEDIATE_REMOVE_LIMIT: u32 = 50; + +/// Type alias for currency balance. +pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub type NegativeImbalanceOf = + <::Currency as Currency<::AccountId>>::NegativeImbalance; +pub const RESERVE_ID_STORAGE_DEPOSIT: ReserveIdentifier = ReserveIdentifier::EvmStorageDeposit; +pub const RESERVE_ID_DEVELOPER_DEPOSIT: ReserveIdentifier = ReserveIdentifier::EvmDeveloperDeposit; + +// Initially based on shanghai hard fork configuration. +static SETHEUM_CONFIG: EvmConfig = EvmConfig { + refund_sstore_clears: 0, // no gas refund + sstore_gas_metering: false, // no gas refund + sstore_revert_under_stipend: false, // ignored + create_contract_limit: Some(MaxCodeSize::get() as usize), + ..module_evm_utility::evm::Config::shanghai() +}; + +/// Create an empty contract `contract Empty { }`. +pub const BASE_CREATE_GAS: u64 = 67_072; +/// Call function that just set a storage `function store(uint256 num) public { number = num; }`. +pub const BASE_CALL_GAS: u64 = 43_702; + +/// Helper method to calculate `create` weight. +fn create_weight(gas: u64) -> Weight { + ::WeightInfo::create() + // during `create` benchmark an additional of `BASE_CREATE_GAS` was used + // so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `create2` weight. +fn create2_weight(gas: u64) -> Weight { + ::WeightInfo::create2() + // during `create2` benchmark an additional of `BASE_CREATE_GAS` was used + // so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `create_predeploy_contract` weight. +fn create_predeploy_contract(gas: u64) -> Weight { + ::WeightInfo::create_predeploy_contract() + // during `create_predeploy_contract` benchmark an additional of `BASE_CREATE_GAS` + // was used so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `create_nft_contract` weight. +fn create_nft_contract(gas: u64) -> Weight { + ::WeightInfo::create_nft_contract() + // during `create_nft_contract` benchmark an additional of `BASE_CREATE_GAS` + // was used so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `call` weight. +fn call_weight(gas: u64) -> Weight { + ::WeightInfo::call() + // during `call` benchmark an additional of `BASE_CALL_GAS` was used + // so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CALL_GAS))) +} + +#[frame_support::pallet] +pub mod module { + use super::*; + + parameter_types! { + // Contract max code size. + pub const MaxCodeSize: u32 = 60 * 1024; + } + + /// EVM module trait + #[pallet::config] + pub trait Config: frame_system::Config + pallet_timestamp::Config { + /// Mapping from address to account id. + type AddressMapping: AddressMapping; + + /// Currency type for withdraw and balance storage. + type Currency: NamedReservableCurrency< + Self::AccountId, + ReserveIdentifier = ReserveIdentifier, + Balance = Balance, + >; + + /// Merge free balance from source to dest. + type TransferAll: TransferAll; + + /// Charge extra bytes for creating a contract, would be reserved until + /// the contract deleted. + #[pallet::constant] + type NewContractExtraBytes: Get; + + /// Storage required for per byte. + #[pallet::constant] + type StorageDepositPerByte: Get>; + + /// Tx fee required for per gas. + /// Provide to the client + #[pallet::constant] + type TxFeePerGas: Get>; + + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Precompiles associated with this EVM engine. + type PrecompilesType: PrecompileSet; + type PrecompilesValue: Get; + + /// Convert gas to weight. + type GasToWeight: Convert; + + /// ChargeTransactionPayment convert weight to fee. + type ChargeTransactionPayment: TransactionPayment, NegativeImbalanceOf>; + + /// EVM config used in the module. + fn config() -> &'static EvmConfig { + &SETHEUM_CONFIG + } + + /// Required origin for creating system contract. + type NetworkContractOrigin: EnsureOrigin; + + /// The EVM address for creating system contract. + #[pallet::constant] + type NetworkContractSource: Get; + + /// Deposit for the developer. + #[pallet::constant] + type DeveloperDeposit: Get>; + + /// The fee for publishing the contract. + #[pallet::constant] + type PublicationFee: Get>; + + #[pallet::constant] + type TreasuryAccount: Get; + + type FreePublicationOrigin: EnsureOrigin; + + /// EVM execution runner. + type Runner: Runner; + + /// Find author for the current block. + type FindAuthor: FindAuthor; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] + pub struct ContractInfo { + pub code_hash: H256, + pub maintainer: EvmAddress, + pub published: bool, + } + + #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] + pub struct AccountInfo { + pub nonce: Index, + pub contract_info: Option, + } + + impl AccountInfo { + pub fn new(nonce: Index, contract_info: Option) -> Self { + Self { nonce, contract_info } + } + } + + #[derive(Clone, Copy, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct CodeInfo { + pub code_size: u32, + pub ref_count: u32, + } + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, Default, Serialize, Deserialize)] + /// Account definition used for genesis block construction. + pub struct GenesisAccount { + /// Account nonce. + pub nonce: Index, + /// Account balance. + pub balance: Balance, + /// Full account storage. + pub storage: BTreeMap, + /// Account code. + pub code: Vec, + /// If the account should enable contract development mode + pub enable_contract_development: bool, + } + + /// The EVM Chain ID. + /// + /// ChainId: u64 + #[pallet::storage] + #[pallet::getter(fn chain_id)] + pub type ChainId = StorageValue<_, u64, ValueQuery>; + + /// The EVM accounts info. + /// + /// Accounts: map EvmAddress => Option> + #[pallet::storage] + #[pallet::getter(fn accounts)] + pub type Accounts = StorageMap<_, Twox64Concat, EvmAddress, AccountInfo, OptionQuery>; + + /// The storage usage for contracts. Including code size, extra bytes and total AccountStorages + /// size. + /// + /// Accounts: map EvmAddress => u32 + #[pallet::storage] + #[pallet::getter(fn contract_storage_sizes)] + pub type ContractStorageSizes = StorageMap<_, Twox64Concat, EvmAddress, u32, ValueQuery>; + + /// The storages for EVM contracts. + /// + /// AccountStorages: double_map EvmAddress, H256 => H256 + #[pallet::storage] + #[pallet::getter(fn account_storages)] + pub type AccountStorages = + StorageDoubleMap<_, Twox64Concat, EvmAddress, Blake2_128Concat, H256, H256, ValueQuery>; + + /// The code for EVM contracts. + /// Key is Keccak256 hash of code. + /// + /// Codes: H256 => Vec + #[pallet::storage] + #[pallet::getter(fn codes)] + pub type Codes = StorageMap<_, Identity, H256, BoundedVec, ValueQuery>; + + /// The code info for EVM contracts. + /// Key is Keccak256 hash of code. + /// + /// CodeInfos: H256 => Option + #[pallet::storage] + #[pallet::getter(fn code_infos)] + pub type CodeInfos = StorageMap<_, Identity, H256, CodeInfo, OptionQuery>; + + /// Next available system contract address. + /// + /// NetworkContractIndex: u64 + #[pallet::storage] + #[pallet::getter(fn network_contract_index)] + pub type NetworkContractIndex = StorageValue<_, u64, ValueQuery>; + + /// Extrinsics origin for the current transaction. + /// + /// ExtrinsicOrigin: Option + #[pallet::storage] + #[pallet::getter(fn extrinsic_origin)] + pub type ExtrinsicOrigin = StorageValue<_, T::AccountId, OptionQuery>; + + /// Xcm origin for the current transaction. + /// + /// XcmOrigin: Option> + #[pallet::storage] + #[pallet::getter(fn xcm_origin)] + pub type XcmOrigin = StorageValue<_, Vec, OptionQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub chain_id: u64, + pub accounts: BTreeMap, T::Nonce>>, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + use sp_std::rc::Rc; + + // NOTE: Only applicable for mandala testnet, unit test and integration test. + // Use create_predeploy_contract to deploy predeploy contracts on the mainnet. + let source = T::NetworkContractSource::get(); + + self.accounts.iter().for_each(|(address, account)| { + let account_id = T::AddressMapping::get_account_id(address); + + let account_info = >::new(account.nonce, None); + >::insert(address, account_info); + + let amount = if account.balance.is_zero() { + >::minimum_balance() + } else { + account.balance + }; + T::Currency::deposit_creating(&account_id, amount); + + if account.enable_contract_development { + T::Currency::ensure_reserved_named( + &RESERVE_ID_DEVELOPER_DEPOSIT, + &account_id, + T::DeveloperDeposit::get(), + ) + .expect("Failed to reserve developer deposit. Please make sure the account have enough balance."); + } + + if !account.code.is_empty() { + // init contract + + // Transactions are not supported by BasicExternalities + // Use the EVM Runtime + let vicinity = Vicinity { + gas_price: U256::one(), + ..Default::default() + }; + let context = Context { + caller: source, + address: *address, + apparent_value: Default::default(), + }; + let metadata = StackSubstateMetadata::new(210_000, 1000, T::config()); + let state = SubstrateStackState::::new(&vicinity, metadata); + let mut executor = StackExecutor::new_with_precompiles(state, T::config(), &()); + + let mut runtime = evm::Runtime::new( + Rc::new(account.code.clone()), + Rc::new(Vec::new()), + context, + T::config().stack_limit, + T::config().memory_limit, + ); + let reason = executor.execute(&mut runtime); + + assert!( + reason.is_succeed(), + "Genesis contract failed to execute, error: {:?}", + reason + ); + + let out = runtime.machine().return_value(); + >::create_contract(source, *address, true, out); + + for (index, value) in &account.storage { + AccountStorages::::insert(address, index, value); + } + } + }); + ChainId::::put(self.chain_id); + NetworkContractIndex::::put(MIRRORED_NFT_ADDRESS_START); + } + } + + /// EVM events + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// A contract has been created at given + Created { + from: EvmAddress, + contract: EvmAddress, + logs: Vec, + used_gas: u64, + used_storage: i32, + }, + /// A contract was attempted to be created, but the execution failed. + CreatedFailed { + from: EvmAddress, + contract: EvmAddress, + exit_reason: ExitReason, + logs: Vec, + used_gas: u64, + used_storage: i32, + }, + /// A contract has been executed successfully with states applied. + Executed { + from: EvmAddress, + contract: EvmAddress, + logs: Vec, + used_gas: u64, + used_storage: i32, + }, + /// A contract has been executed with errors. States are reverted with + /// only gas fees applied. + ExecutedFailed { + from: EvmAddress, + contract: EvmAddress, + exit_reason: ExitReason, + output: Vec, + logs: Vec, + used_gas: u64, + used_storage: i32, + }, + /// Transferred maintainer. + TransferredMaintainer { + contract: EvmAddress, + new_maintainer: EvmAddress, + }, + /// Enabled contract development. + ContractDevelopmentEnabled { who: T::AccountId }, + /// Disabled contract development. + ContractDevelopmentDisabled { who: T::AccountId }, + /// Published contract. + ContractPublished { contract: EvmAddress }, + /// Set contract code. + ContractSetCode { contract: EvmAddress }, + /// Selfdestructed contract code. + ContractSelfdestructed { contract: EvmAddress }, + } + + #[pallet::error] + pub enum Error { + /// Address not mapped + AddressNotMapped, + /// Contract not found + ContractNotFound, + /// No permission + NoPermission, + /// Contract development is not enabled + ContractDevelopmentNotEnabled, + /// Contract development is already enabled + ContractDevelopmentAlreadyEnabled, + /// Contract already published + ContractAlreadyPublished, + /// Contract exceeds max code size + ContractExceedsMaxCodeSize, + /// Contract already existed + ContractAlreadyExisted, + /// Storage usage exceeds storage limit + OutOfStorage, + /// Charge fee failed + ChargeFeeFailed, + /// Contract cannot be killed due to reference count + CannotKillContract, + /// Reserve storage failed + ReserveStorageFailed, + /// Unreserve storage failed + UnreserveStorageFailed, + /// Charge storage failed + ChargeStorageFailed, + /// Invalid decimals + InvalidDecimals, + /// Strict call failed + StrictCallFailed, + /// Caller is not externally owned account + NotEOA, + } + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + assert!(convert_decimals_from_evm(T::StorageDepositPerByte::get()).is_some()); + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(match *action { + TransactionAction::Call(_) => call_weight::(*gas_limit), + TransactionAction::Create => create_weight::(*gas_limit) + })] + #[allow(deprecated)] + #[deprecated(note = "please migrate to `eth_call_v2`")] + pub fn eth_call( + origin: OriginFor, + action: TransactionAction, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + #[pallet::compact] _valid_until: BlockNumberFor, // checked by tx validation logic + ) -> DispatchResultWithPostInfo { + match action { + TransactionAction::Call(target) => { + Self::call(origin, target, input, value, gas_limit, storage_limit, access_list) + } + TransactionAction::Create => Self::create(origin, input, value, gas_limit, storage_limit, access_list), + } + } + + #[pallet::call_index(15)] + #[pallet::weight(match *action { + TransactionAction::Call(_) => call_weight::(decode_gas_limit(*gas_limit).0), + TransactionAction::Create => create_weight::(decode_gas_limit(*gas_limit).0) + })] + pub fn eth_call_v2( + origin: OriginFor, + action: TransactionAction, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] _gas_price: u64, // checked by tx validation logic + #[pallet::compact] gas_limit: u64, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + let (actual_gas_limit, storage_limit) = decode_gas_limit(gas_limit); + + match action { + TransactionAction::Call(target) => Self::call( + origin, + target, + input, + value, + actual_gas_limit, + storage_limit, + access_list, + ), + TransactionAction::Create => { + Self::create(origin, input, value, actual_gas_limit, storage_limit, access_list) + } + } + } + + /// Issue an EVM call operation. This is similar to a message call + /// transaction in Ethereum. + /// + /// - `target`: the contract address to call + /// - `input`: the data supplied for the call + /// - `value`: the amount sent for payable calls + /// - `gas_limit`: the maximum gas the call can use + /// - `storage_limit`: the total bytes the contract's storage can increase by + #[pallet::call_index(1)] + #[pallet::weight(call_weight::(*gas_limit))] + pub fn call( + origin: OriginFor, + target: EvmAddress, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let source = T::AddressMapping::get_or_create_evm_address(&who); + + Self::ensure_eoa(&source)?; + + let outcome = T::Runner::call( + source, + source, + target, + input, + value, + gas_limit, + storage_limit, + access_list.into_iter().map(|v| (v.address, v.storage_keys)).collect(), + T::config(), + ); + + match outcome { + Err(e) => { + // EVM state changes reverted, increase nonce by ourselves + Self::inc_nonce(&source); + + Pallet::::deposit_event(Event::::ExecutedFailed { + from: source, + contract: target, + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(e).into())), + output: vec![], + logs: vec![], + used_gas: gas_limit, + used_storage: Default::default(), + }); + + Ok(().into()) + } + Ok(info) => { + let used_gas: u64 = info.used_gas.unique_saturated_into(); + + if info.exit_reason.is_succeed() { + Pallet::::deposit_event(Event::::Executed { + from: source, + contract: target, + logs: info.logs, + used_gas, + used_storage: info.used_storage, + }); + } else { + Pallet::::deposit_event(Event::::ExecutedFailed { + from: source, + contract: target, + exit_reason: info.exit_reason.clone(), + output: info.value.clone(), + logs: info.logs, + used_gas, + used_storage: Default::default(), + }); + } + + Ok(PostDispatchInfo { + actual_weight: Some(call_weight::(used_gas)), + pays_fee: Pays::Yes, + }) + } + } + } + + /// Issue an EVM call operation on a scheduled contract call, and + /// refund the unused gas reserved when the call was scheduled. + /// + /// - `from`: the address the scheduled call originates from + /// - `target`: the contract address to call + /// - `input`: the data supplied for the call + /// - `value`: the amount sent for payable calls + /// - `gas_limit`: the maximum gas the call can use + /// - `storage_limit`: the total bytes the contract's storage can increase by + #[pallet::call_index(2)] + #[pallet::weight(T::GasToWeight::convert(*gas_limit))] + // TODO: create benchmark + pub fn scheduled_call( + origin: OriginFor, + from: EvmAddress, + target: EvmAddress, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let _from_account = T::AddressMapping::get_account_id(&from); + let _payed: NegativeImbalanceOf; + #[cfg(not(feature = "with-ethereum-compatibility"))] + { + // unreserve the transaction fee for gas_limit + let weight = T::GasToWeight::convert(gas_limit); + let (_, imbalance) = T::ChargeTransactionPayment::unreserve_and_charge_fee(&_from_account, weight) + .map_err(|_| Error::::ChargeFeeFailed)?; + _payed = imbalance; + } + + match T::Runner::call( + from, + from, + target, + input, + value, + gas_limit, + storage_limit, + access_list.into_iter().map(|v| (v.address, v.storage_keys)).collect(), + T::config(), + ) { + Err(e) => { + Pallet::::deposit_event(Event::::ExecutedFailed { + from, + contract: target, + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(e).into())), + output: vec![], + logs: vec![], + used_gas: gas_limit, + used_storage: Default::default(), + }); + + Ok(().into()) + } + Ok(info) => { + let used_gas: u64 = info.used_gas.unique_saturated_into(); + + if info.exit_reason.is_succeed() { + Pallet::::deposit_event(Event::::Executed { + from, + contract: target, + logs: info.logs, + used_gas, + used_storage: info.used_storage, + }); + } else { + Pallet::::deposit_event(Event::::ExecutedFailed { + from, + contract: target, + exit_reason: info.exit_reason.clone(), + output: info.value.clone(), + logs: info.logs, + used_gas, + used_storage: Default::default(), + }); + } + + #[cfg(not(feature = "with-ethereum-compatibility"))] + { + use sp_runtime::traits::Zero; + let refund_gas = gas_limit.saturating_sub(used_gas); + if !refund_gas.is_zero() { + // ignore the result to continue. if it fails, just the user will not + // be refunded, there will not increase user balance. + let res = T::ChargeTransactionPayment::refund_fee( + &_from_account, + T::GasToWeight::convert(refund_gas), + _payed, + ); + debug_assert!(res.is_ok()); + } + } + + Ok(PostDispatchInfo { + actual_weight: Some(T::GasToWeight::convert(used_gas)), + pays_fee: Pays::Yes, + }) + } + } + } + + /// Issue an EVM create operation. This is similar to a contract + /// creation transaction in Ethereum. + /// + /// - `input`: the data supplied for the contract's constructor + /// - `value`: the amount sent to the contract upon creation + /// - `gas_limit`: the maximum gas the call can use + /// - `storage_limit`: the total bytes the contract's storage can increase by + #[pallet::call_index(3)] + #[pallet::weight(create_weight::(*gas_limit))] + pub fn create( + origin: OriginFor, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let source = T::AddressMapping::get_or_create_evm_address(&who); + + Self::ensure_eoa(&source)?; + + let outcome = T::Runner::create( + source, + input, + value, + gas_limit, + storage_limit, + access_list.into_iter().map(|v| (v.address, v.storage_keys)).collect(), + T::config(), + ); + + match outcome { + Err(e) => { + // EVM state changes reverted, increase nonce by ourselves + Self::inc_nonce(&source); + + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: H160::default(), + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(e).into())), + logs: vec![], + used_gas: gas_limit, + used_storage: Default::default(), + }); + + Ok(().into()) + } + Ok(info) => { + let used_gas: u64 = info.used_gas.unique_saturated_into(); + + if info.exit_reason.is_succeed() { + Pallet::::deposit_event(Event::::Created { + from: source, + contract: info.value, + logs: info.logs, + used_gas, + used_storage: info.used_storage, + }); + } else { + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: info.value, + exit_reason: info.exit_reason.clone(), + logs: info.logs, + used_gas, + used_storage: Default::default(), + }); + } + + Ok(PostDispatchInfo { + actual_weight: Some(create_weight::(used_gas)), + pays_fee: Pays::Yes, + }) + } + } + } + + /// Issue an EVM create2 operation. + /// + /// - `target`: the contract address to call + /// - `input`: the data supplied for the contract's constructor + /// - `salt`: used for generating the new contract's address + /// - `value`: the amount sent for payable calls + /// - `gas_limit`: the maximum gas the call can use + /// - `storage_limit`: the total bytes the contract's storage can increase by + #[pallet::call_index(4)] + #[pallet::weight(create2_weight::(*gas_limit))] + pub fn create2( + origin: OriginFor, + input: Vec, + salt: H256, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let source = T::AddressMapping::get_or_create_evm_address(&who); + + Self::ensure_eoa(&source)?; + + let outcome = T::Runner::create2( + source, + input, + salt, + value, + gas_limit, + storage_limit, + access_list.into_iter().map(|v| (v.address, v.storage_keys)).collect(), + T::config(), + ); + + match outcome { + Err(e) => { + // EVM state changes reverted, increase nonce by ourselves + Self::inc_nonce(&source); + + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: H160::default(), + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(e).into())), + logs: vec![], + used_gas: gas_limit, + used_storage: Default::default(), + }); + + Ok(().into()) + } + Ok(info) => { + let used_gas: u64 = info.used_gas.unique_saturated_into(); + + if info.exit_reason.is_succeed() { + Pallet::::deposit_event(Event::::Created { + from: source, + contract: info.value, + logs: info.logs, + used_gas, + used_storage: info.used_storage, + }); + } else { + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: info.value, + exit_reason: info.exit_reason.clone(), + logs: info.logs, + used_gas, + used_storage: Default::default(), + }); + } + + Ok(PostDispatchInfo { + actual_weight: Some(create2_weight::(used_gas)), + pays_fee: Pays::Yes, + }) + } + } + } + + /// Create mirrored NFT contract. The next available system contract + /// address will be used as created contract address. + /// + /// - `input`: the data supplied for the contract's constructor + /// - `value`: the amount sent for payable calls + /// - `gas_limit`: the maximum gas the call can use + /// - `storage_limit`: the total bytes the contract's storage can increase by + #[pallet::call_index(5)] + #[pallet::weight(create_nft_contract::(*gas_limit))] + pub fn create_nft_contract( + origin: OriginFor, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + T::NetworkContractOrigin::ensure_origin(origin)?; + + let source = T::NetworkContractSource::get(); + let source_account = T::AddressMapping::get_account_id(&source); + let address = MIRRORED_TOKENS_ADDRESS_START | EvmAddress::from_low_u64_be(Self::network_contract_index()); + + // ensure source has more than 10 SEE to deploy the contract. + let amount = T::Currency::minimum_balance().saturating_mul(100u32.into()); + if T::Currency::free_balance(&source_account) < amount { + T::Currency::transfer( + &T::TreasuryAccount::get(), + &source_account, + amount, + ExistenceRequirement::AllowDeath, + )?; + } + + match T::Runner::create_at_address( + source, + address, + input, + value, + gas_limit, + storage_limit, + access_list.into_iter().map(|v| (v.address, v.storage_keys)).collect(), + T::config(), + ) { + Err(e) => { + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: H160::default(), + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(e).into())), + logs: vec![], + used_gas: gas_limit, + used_storage: Default::default(), + }); + + Ok(().into()) + } + Ok(info) => { + let used_gas: u64 = info.used_gas.unique_saturated_into(); + + if info.exit_reason.is_succeed() { + NetworkContractIndex::::mutate(|v| *v = v.saturating_add(One::one())); + + Pallet::::deposit_event(Event::::Created { + from: source, + contract: info.value, + logs: info.logs, + used_gas, + used_storage: info.used_storage, + }); + } else { + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: info.value, + exit_reason: info.exit_reason.clone(), + logs: info.logs, + used_gas, + used_storage: Default::default(), + }); + } + + Ok(PostDispatchInfo { + actual_weight: Some(create_nft_contract::(used_gas)), + pays_fee: Pays::No, + }) + } + } + } + + /// Issue an EVM create operation. The address specified + /// will be used as created contract address. + /// + /// - `target`: the address specified by the contract + /// - `input`: the data supplied for the contract's constructor + /// - `value`: the amount sent for payable calls + /// - `gas_limit`: the maximum gas the call can use + /// - `storage_limit`: the total bytes the contract's storage can increase by + #[pallet::call_index(6)] + #[pallet::weight(create_predeploy_contract::(*gas_limit))] + pub fn create_predeploy_contract( + origin: OriginFor, + target: EvmAddress, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + T::NetworkContractOrigin::ensure_origin(origin)?; + + ensure!(Self::accounts(target).is_none(), Error::::ContractAlreadyExisted); + + let source = T::NetworkContractSource::get(); + let source_account = T::AddressMapping::get_account_id(&source); + // ensure source has more than 10 SEE to deploy the contract. + let amount = T::Currency::minimum_balance().saturating_mul(100u32.into()); + if T::Currency::free_balance(&source_account) < amount { + T::Currency::transfer( + &T::TreasuryAccount::get(), + &source_account, + amount, + ExistenceRequirement::AllowDeath, + )?; + } + + match T::Runner::create_at_address( + source, + target, + input, + value, + gas_limit, + storage_limit, + access_list.into_iter().map(|v| (v.address, v.storage_keys)).collect(), + T::config(), + ) { + Err(e) => { + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: H160::default(), + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(e).into())), + logs: vec![], + used_gas: gas_limit, + used_storage: Default::default(), + }); + + Ok(().into()) + } + Ok(info) => { + let used_gas: u64 = info.used_gas.unique_saturated_into(); + let contract = info.value; + + if info.exit_reason.is_succeed() { + Pallet::::deposit_event(Event::::Created { + from: source, + contract, + logs: info.logs, + used_gas, + used_storage: info.used_storage, + }); + } else { + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract, + exit_reason: info.exit_reason.clone(), + logs: info.logs, + used_gas, + used_storage: Default::default(), + }); + } + + if info.exit_reason.is_succeed() { + Self::mark_published(contract, Some(source))?; + Pallet::::deposit_event(Event::::ContractPublished { contract }); + } + + Ok(PostDispatchInfo { + actual_weight: Some(create_predeploy_contract::(used_gas)), + pays_fee: Pays::No, + }) + } + } + } + + /// Transfers Contract maintainership to a new EVM Address. + /// + /// - `contract`: the contract whose maintainership is being transferred, the caller must be + /// the contract's maintainer + /// - `new_maintainer`: the address of the new maintainer + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::transfer_maintainer())] + pub fn transfer_maintainer( + origin: OriginFor, + contract: EvmAddress, + new_maintainer: EvmAddress, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + Self::do_transfer_maintainer(who, contract, new_maintainer)?; + + Pallet::::deposit_event(Event::::TransferredMaintainer { + contract, + new_maintainer, + }); + + Ok(().into()) + } + + /// Mark a given contract as published. + /// + /// - `contract`: The contract to mark as published, the caller must the contract's + /// maintainer + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::publish_contract())] + pub fn publish_contract(origin: OriginFor, contract: EvmAddress) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + Self::do_publish_contract(who, contract)?; + + Pallet::::deposit_event(Event::::ContractPublished { contract }); + Ok(().into()) + } + + /// Mark a given contract as published without paying the publication fee + /// + /// - `contract`: The contract to mark as published, the caller must be the contract's + /// maintainer. + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::publish_free())] + pub fn publish_free(origin: OriginFor, contract: EvmAddress) -> DispatchResultWithPostInfo { + T::FreePublicationOrigin::ensure_origin(origin)?; + Self::mark_published(contract, None)?; + Pallet::::deposit_event(Event::::ContractPublished { contract }); + Ok(().into()) + } + + /// Mark the caller's address to allow contract development. + /// This allows the address to interact with non-published contracts. + #[pallet::call_index(10)] + #[pallet::weight(::WeightInfo::enable_contract_development())] + pub fn enable_contract_development(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + Self::do_enable_contract_development(&who)?; + + Pallet::::deposit_event(Event::::ContractDevelopmentEnabled { who }); + Ok(().into()) + } + + /// Mark the caller's address to disable contract development. + /// This disallows the address to interact with non-published contracts. + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::disable_contract_development())] + pub fn disable_contract_development(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + Self::do_disable_contract_development(&who)?; + + Pallet::::deposit_event(Event::::ContractDevelopmentDisabled { who }); + Ok(().into()) + } + + /// Set the code of a contract at a given address. + /// + /// - `contract`: The contract whose code is being set, must not be marked as published + /// - `code`: The new ABI bundle for the contract + #[pallet::call_index(12)] + #[pallet::weight(::WeightInfo::set_code(code.len() as u32))] + pub fn set_code(origin: OriginFor, contract: EvmAddress, code: Vec) -> DispatchResultWithPostInfo { + let root_or_signed = Self::ensure_root_or_signed(origin)?; + Self::do_set_code(root_or_signed, contract, code)?; + + Pallet::::deposit_event(Event::::ContractSetCode { contract }); + + Ok(().into()) + } + + /// Remove a contract at a given address. + /// + /// - `contract`: The contract to remove, must not be marked as published + #[pallet::call_index(13)] + #[pallet::weight(::WeightInfo::selfdestruct())] + pub fn selfdestruct(origin: OriginFor, contract: EvmAddress) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let caller = T::AddressMapping::get_evm_address(&who).ok_or(Error::::AddressNotMapped)?; + Self::do_selfdestruct(&caller, &contract)?; + + Pallet::::deposit_event(Event::::ContractSelfdestructed { contract }); + + Ok(().into()) + } + + /// Issue an EVM call operation in `Utility::batch_all`. This is same as the evm.call but + /// returns error when it failed. The current evm.call always success and emit event to + /// indicate it failed. + /// + /// - `target`: the contract address to call + /// - `input`: the data supplied for the call + /// - `value`: the amount sent for payable calls + /// - `gas_limit`: the maximum gas the call can use + /// - `storage_limit`: the total bytes the contract's storage can increase by + #[pallet::call_index(14)] + #[pallet::weight(call_weight::(*gas_limit))] + pub fn strict_call( + origin: OriginFor, + target: EvmAddress, + input: Vec, + #[pallet::compact] value: BalanceOf, + #[pallet::compact] gas_limit: u64, + #[pallet::compact] storage_limit: u32, + access_list: Vec, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let source = T::AddressMapping::get_or_create_evm_address(&who); + + Self::ensure_eoa(&source)?; + + match T::Runner::call( + source, + source, + target, + input, + value, + gas_limit, + storage_limit, + access_list.into_iter().map(|v| (v.address, v.storage_keys)).collect(), + T::config(), + ) { + Err(e) => Err(DispatchErrorWithPostInfo { + post_info: ().into(), + error: e, + }), + Ok(info) => { + let used_gas: u64 = info.used_gas.unique_saturated_into(); + + if info.exit_reason.is_succeed() { + Pallet::::deposit_event(Event::::Executed { + from: source, + contract: target, + logs: info.logs, + used_gas, + used_storage: info.used_storage, + }); + + Ok(PostDispatchInfo { + actual_weight: Some(call_weight::(used_gas)), + pays_fee: Pays::Yes, + }) + } else { + log::debug!( + target: "evm", + "batch_call failed: [from: {:?}, contract: {:?}, exit_reason: {:?}, output: {:?}, logs: {:?}, used_gas: {:?}]", + source, target, info.exit_reason, info.value, info.logs, used_gas + ); + Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(call_weight::(used_gas)), + pays_fee: Pays::Yes, + }, + error: Error::::StrictCallFailed.into(), + }) + } + } + } + } + } +} + +impl Pallet { + /// EIP-3607: https://eips.ethereum.org/EIPS/eip-3607 + /// Do not allow transactions for which `tx.sender` has any code deployed. + // + /// We extend the principle of this EIP to also prevent `tx.sender` to be the address + /// of a precompile. While mainnet Ethereum currently only has stateless precompiles, + /// Setheum EVM+ can have stateful precompiles that can manage funds or + /// which calls other contracts that expects this precompile address to be trustworthy. + fn ensure_eoa(caller: &EvmAddress) -> DispatchResult { + if is_system_contract(caller) || Self::is_contract(caller) { + return Err(Error::::NotEOA.into()); + } + Ok(()) + } + + /// Get StorageDepositPerByte of actual decimals + pub fn get_storage_deposit_per_byte() -> BalanceOf { + // StorageDepositPerByte decimals is 18, SEE decimals is 12, convert to 12 here. + convert_decimals_from_evm(T::StorageDepositPerByte::get()).expect("checked in integrity_test; qed") + } + + /// Check whether an account is empty. + pub fn is_account_empty(address: &H160) -> bool { + let account_id = T::AddressMapping::get_account_id(address); + let balance = T::Currency::total_balance(&account_id); + + if !balance.is_zero() { + return false; + } + + Self::accounts(address).map_or(true, |account_info| { + account_info.contract_info.is_none() && account_info.nonce.is_zero() + }) + } + + /// Remove an account if its empty. + /// NOTE: If the nonce is non-zero, it cannot be deleted to prevent the user from failing to + /// create a contract due to nonce reset + pub fn remove_account_if_empty(address: &H160) { + if Self::is_account_empty(address) { + Self::remove_account(address); + } + } + + #[transactional] + pub fn remove_contract(caller: &EvmAddress, contract: &EvmAddress) -> DispatchResult { + let contract_account = T::AddressMapping::get_account_id(contract); + let total_size = + Accounts::::try_mutate_exists(contract, |maybe_account_info| -> Result { + // We will keep the nonce until the storages are cleared. + // Only remove the `contract_info` + let account_info = maybe_account_info.as_mut().ok_or(Error::::ContractNotFound)?; + let contract_info = account_info.contract_info.take().ok_or(Error::::ContractNotFound)?; + + let maintainer = T::AddressMapping::get_account_id(contract_info.maintainer); + + let mut code_size: u32 = 0; + + CodeInfos::::mutate_exists(contract_info.code_hash, |maybe_code_info| { + if let Some(code_info) = maybe_code_info.as_mut() { + code_size = code_info.code_size; + code_info.ref_count = code_info.ref_count.saturating_sub(1); + if code_info.ref_count == 0 { + Codes::::remove(contract_info.code_hash); + *maybe_code_info = None; + } + } else { + // code info removed while still having reference to it? + debug_assert!(false); + } + }); + + let total_size = ContractStorageSizes::::take(contract); + + + let r = >::clear_prefix(contract, limit, None); + let count = r.backend; + + log::debug!( + target: "evm", + "remove_contract: [from: {:?}, contract: {:?}, maintainer: {:?}, count: {:?}]", + caller, contract, maintainer, count + ); + if r.maybe_cursor.is_none() { + // AllRemoved + let result = Pallet::::refund_storage(&caller, &contract, &maintainer); + // We also remove the contract if refund storage failed. + debug_assert!(result.is_ok()); + log::debug!( + target: "evm", + "remove_contract: [from: {:?}, contract: {:?}, maintainer: {:?}, result: {:?}]", + caller, contract, maintainer, result + ); + + // Remove account after all of the storages are cleared. + Pallet::::remove_account(&contract); + })?; + + // this should happen after `Accounts` is updated because this could trigger another updates on + // `Accounts` + frame_system::Pallet::::dec_providers(&contract_account)?; + + Ok(()) + } + + /// Removes an account from Accounts and AccountStorages. + /// NOTE: It will reset account nonce. + fn remove_account(address: &EvmAddress) { + // Deref code, and remove it if ref count is zero. + Accounts::::mutate_exists(address, |maybe_account| { + if let Some(account) = maybe_account { + if let Some(ContractInfo { code_hash, .. }) = account.contract_info { + CodeInfos::::mutate_exists(code_hash, |maybe_code_info| { + if let Some(code_info) = maybe_code_info { + code_info.ref_count = code_info.ref_count.saturating_sub(1); + if code_info.ref_count == 0 { + Codes::::remove(code_hash); + *maybe_code_info = None; + } + } + }); + + // remove_account can only be called when account is killed. i.e. providers == 0 + // but contract_info should maintain a provider + // so this should never happen + log::warn!( + target: "evm", + "remove_account: removed account {:?} while is still linked to contract info", + address + ); + debug_assert!(false, "removed account while is still linked to contract info"); + } + + *maybe_account = None; + } + }); + } + + /// Create an account. + /// - Create new account for the contract. + /// - Update codes info. + /// - Update maintainer of the contract. + /// - Save `code` if not saved yet. + pub fn create_contract(source: H160, address: H160, publish: bool, code: Vec) { + let bounded_code: BoundedVec = code + .try_into() + .expect("checked by create_contract_limit in SETHEUM_CONFIG; qed"); + if bounded_code.is_empty() { + return; + } + + // if source is account, the maintainer of the new contract is source. + // if source is contract, the maintainer of the new contract is the source contract. + let maintainer = source; + let code_hash = code_hash(bounded_code.as_slice()); + let code_size = bounded_code.len() as u32; + + let contract_info = ContractInfo { + code_hash, + maintainer, + #[cfg(feature = "with-ethereum-compatibility")] + published: true, + #[cfg(not(feature = "with-ethereum-compatibility"))] + published: publish, + }; + + CodeInfos::::mutate_exists(code_hash, |maybe_code_info| { + if let Some(code_info) = maybe_code_info.as_mut() { + code_info.ref_count = code_info.ref_count.saturating_add(1); + } else { + let new = CodeInfo { + code_size, + ref_count: 1, + }; + *maybe_code_info = Some(new); + + Codes::::insert(code_hash, bounded_code); + } + }); + + Accounts::::mutate(address, |maybe_account_info| { + if let Some(account_info) = maybe_account_info.as_mut() { + account_info.contract_info = Some(contract_info.clone()); + } else { + let account_info = AccountInfo::::new(Default::default(), Some(contract_info.clone())); + *maybe_account_info = Some(account_info); + } + }); + + let contract_account = T::AddressMapping::get_account_id(&address); + + // NOTE: inc providers occurs before receive and reserve storage fee for this `address`, + // it will directly `NewAccount`. If config `type AccountStore = System` when impl pallet_balances + // Config, System::Account exists, Balances::Account doesn't exist if AccountData is default(). So + // if runtime integrates module-evm, plz confirm config `type AccountStore = + // module_support::SystemAccountStore` for pallet_balances, it regards Balances::Account exists when + // System::Account exists. + frame_system::Pallet::::inc_providers(&contract_account); + } + + /// Get the account basic in EVM format. + pub fn account_basic(address: &EvmAddress) -> Account { + let account_id = T::AddressMapping::get_account_id(address); + + let nonce = Self::accounts(address).map_or(Default::default(), |account_info| account_info.nonce); + let balance = T::Currency::free_balance(&account_id); + + Account { + nonce: U256::from(UniqueSaturatedInto::::unique_saturated_into(nonce)), + balance: U256::from(UniqueSaturatedInto::::unique_saturated_into( + convert_decimals_to_evm(balance), + )), + } + } + + /// Get the author using the FindAuthor trait. + pub fn find_author() -> H160 { + let digest = >::digest(); + let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); + + if let Some(author) = T::FindAuthor::find_author(pre_runtime_digests) { + T::AddressMapping::get_default_evm_address(&author) + } else { + H160::default() + } + } + + /// Get code hash at given address. + pub fn code_hash_at_address(address: &EvmAddress) -> H256 { + if let Some(AccountInfo { + contract_info: Some(contract_info), + .. + }) = Self::accounts(address) + { + contract_info.code_hash + } else { + // The same as `code_hash(&[])`, hardcode here. + H256::from_slice(&hex!( + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + )) + } + } + + /// Get code size at given address. + pub fn code_size_at_address(address: &EvmAddress) -> U256 { + Self::code_infos(Self::code_hash_at_address(address)) + .map_or(U256::zero(), |code_info| U256::from(code_info.code_size)) + } + + /// Get code at given address. + pub fn code_at_address(address: &EvmAddress) -> BoundedVec { + Self::codes(Self::code_hash_at_address(address)) + } + + pub fn is_contract(address: &EvmAddress) -> bool { + matches!( + Self::accounts(address), + Some(AccountInfo { + contract_info: Some(_), + .. + }) + ) + } + + pub fn update_contract_storage_size(address: &EvmAddress, change: i32) { + if change == 0 { + return; + } + ContractStorageSizes::::mutate(address, |val| { + if change > 0 { + *val = val.saturating_add(change as u32); + } else { + *val = val.saturating_sub(change.unsigned_abs()); + } + }); + } + + /// Sets a given contract's contract info to a new maintainer. + fn do_transfer_maintainer(who: T::AccountId, contract: EvmAddress, new_maintainer: EvmAddress) -> DispatchResult { + Accounts::::mutate(contract, |maybe_account_info| -> DispatchResult { + let account_info = maybe_account_info.as_mut().ok_or(Error::::ContractNotFound)?; + let contract_info = account_info + .contract_info + .as_mut() + .ok_or(Error::::ContractNotFound)?; + + let maintainer = T::AddressMapping::get_evm_address(&who).ok_or(Error::::AddressNotMapped)?; + ensure!(contract_info.maintainer == maintainer, Error::::NoPermission); + + contract_info.maintainer = new_maintainer; + Ok(()) + })?; + + Ok(()) + } + + /// Puts a deposit down to allow account to interact with non-published contracts + fn do_enable_contract_development(who: &T::AccountId) -> DispatchResult { + ensure!( + T::Currency::reserved_balance_named(&RESERVE_ID_DEVELOPER_DEPOSIT, who).is_zero(), + Error::::ContractDevelopmentAlreadyEnabled + ); + T::Currency::ensure_reserved_named(&RESERVE_ID_DEVELOPER_DEPOSIT, who, T::DeveloperDeposit::get())?; + Ok(()) + } + + /// Returns deposit and disables account for contract development + fn do_disable_contract_development(who: &T::AccountId) -> DispatchResult { + ensure!( + !T::Currency::reserved_balance_named(&RESERVE_ID_DEVELOPER_DEPOSIT, who).is_zero(), + Error::::ContractDevelopmentNotEnabled + ); + T::Currency::unreserve_all_named(&RESERVE_ID_DEVELOPER_DEPOSIT, who); + Ok(()) + } + + /// Publishes the Contract + /// + /// Checks that `who` is the contract maintainer and takes the publication fee + fn do_publish_contract(who: T::AccountId, contract: EvmAddress) -> DispatchResult { + let address = T::AddressMapping::get_evm_address(&who).ok_or(Error::::AddressNotMapped)?; + T::Currency::transfer( + &who, + &T::TreasuryAccount::get(), + T::PublicationFee::get(), + ExistenceRequirement::AllowDeath, + )?; + Self::mark_published(contract, Some(address))?; + Ok(()) + } + + /// Mark contract as published + /// + /// If maintainer is provider then it will check maintainer + fn mark_published(contract: EvmAddress, maintainer: Option) -> DispatchResult { + Accounts::::mutate(contract, |maybe_account_info| -> DispatchResult { + if let Some(AccountInfo { + contract_info: Some(contract_info), + .. + }) = maybe_account_info.as_mut() + { + if let Some(maintainer) = maintainer { + ensure!(contract_info.maintainer == maintainer, Error::::NoPermission); + } + ensure!(!contract_info.published, Error::::ContractAlreadyPublished); + contract_info.published = true; + Ok(()) + } else { + Err(Error::::ContractNotFound.into()) + } + }) + } + + /// Set the code of a contract at a given address. + /// + /// - Ensures signer is maintainer or root. + /// - Update codes info. + /// - Save `code` if not saved yet. + fn do_set_code(root_or_signed: Either<(), T::AccountId>, contract: EvmAddress, code: Vec) -> DispatchResult { + Accounts::::mutate(contract, |maybe_account_info| -> DispatchResult { + let account_info = maybe_account_info.as_mut().ok_or(Error::::ContractNotFound)?; + let contract_info = account_info + .contract_info + .as_mut() + .ok_or(Error::::ContractNotFound)?; + + let source = if let Either::Right(signer) = root_or_signed { + let maintainer = T::AddressMapping::get_evm_address(&signer).ok_or(Error::::AddressNotMapped)?; + ensure!(contract_info.maintainer == maintainer, Error::::NoPermission); + ensure!(!contract_info.published, Error::::ContractAlreadyPublished); + maintainer + } else { + T::NetworkContractSource::get() + }; + + let old_code_info = Self::code_infos(contract_info.code_hash).ok_or(Error::::ContractNotFound)?; + + let bounded_code: BoundedVec = + code.try_into().map_err(|_| Error::::ContractExceedsMaxCodeSize)?; + let code_hash = code_hash(bounded_code.as_slice()); + let code_size = bounded_code.len() as u32; + // The code_hash of the same contract is definitely different. + // The `contract_info.code_hash` hashed by on_contract_initialization which constructed. + // Still check it here. + if code_hash == contract_info.code_hash { + return Ok(()); + } + + let storage_size_changed: i32 = + code_size.saturating_add(T::NewContractExtraBytes::get()) as i32 - old_code_info.code_size as i32; + + if storage_size_changed.is_positive() { + Self::reserve_storage(&source, storage_size_changed as u32)?; + } + Self::charge_storage(&source, &contract, storage_size_changed)?; + Self::update_contract_storage_size(&contract, storage_size_changed); + + // try remove old codes + CodeInfos::::mutate_exists(contract_info.code_hash, |maybe_code_info| -> DispatchResult { + let code_info = maybe_code_info.as_mut().ok_or(Error::::ContractNotFound)?; + code_info.ref_count = code_info.ref_count.saturating_sub(1); + if code_info.ref_count == 0 { + Codes::::remove(contract_info.code_hash); + *maybe_code_info = None; + } + Ok(()) + })?; + + CodeInfos::::mutate_exists(code_hash, |maybe_code_info| { + if let Some(code_info) = maybe_code_info.as_mut() { + code_info.ref_count = code_info.ref_count.saturating_add(1); + } else { + let new = CodeInfo { + code_size, + ref_count: 1, + }; + *maybe_code_info = Some(new); + + Codes::::insert(code_hash, bounded_code); + } + }); + // update code_hash + contract_info.code_hash = code_hash; + + Ok(()) + }) + } + + /// Selfdestruct a contract at a given address. + fn do_selfdestruct(caller: &EvmAddress, contract: &EvmAddress) -> DispatchResult { + let account_info = Self::accounts(contract).ok_or(Error::::ContractNotFound)?; + let contract_info = account_info + .contract_info + .as_ref() + .ok_or(Error::::ContractNotFound)?; + + ensure!(contract_info.maintainer == *caller, Error::::NoPermission); + ensure!(!contract_info.published, Error::::ContractAlreadyPublished); + + Self::remove_contract(caller, contract) + } + + fn ensure_root_or_signed(o: T::RuntimeOrigin) -> Result, BadOrigin> { + EitherOfDiverse::, EnsureSigned>::try_origin(o) + .map_or(Err(BadOrigin), Ok) + } + + fn can_call_contract(address: &H160, caller: &H160) -> bool { + if let Some(AccountInfo { + contract_info: Some(ContractInfo { + published, maintainer, .. + }), + .. + }) = Accounts::::get(address) + { + // when rpc is called, from is empty, allowing the call + published || maintainer == *caller || *caller == H160::default() || Self::is_developer_or_contract(caller) + } else { + // contract non exist, we don't override default evm behaviour + true + } + } + + fn is_developer_or_contract(caller: &H160) -> bool { + let account_id = T::AddressMapping::get_account_id(caller); + Self::query_developer_status(account_id) || Self::is_contract(caller) + } + + fn reserve_storage(caller: &H160, limit: u32) -> DispatchResult { + if limit.is_zero() { + return Ok(()); + } + + let user = T::AddressMapping::get_account_id(caller); + let amount = Self::get_storage_deposit_per_byte().saturating_mul(limit.into()); + + log::debug!( + target: "evm", + "reserve_storage: [from: {:?}, account: {:?}, limit: {:?}, amount: {:?}]", + caller, user, limit, amount + ); + + T::ChargeTransactionPayment::reserve_fee(&user, amount, Some(RESERVE_ID_STORAGE_DEPOSIT))?; + Ok(()) + } + + fn unreserve_storage(caller: &H160, limit: u32, used: u32, refunded: u32) -> DispatchResult { + let total = limit.saturating_add(refunded); + let unused = total.saturating_sub(used); + if unused.is_zero() { + return Ok(()); + } + + let user = T::AddressMapping::get_account_id(caller); + let amount = Self::get_storage_deposit_per_byte().saturating_mul(unused.into()); + + log::debug!( + target: "evm", + "unreserve_storage: [from: {:?}, account: {:?}, used: {:?}, refunded: {:?}, unused: {:?}, amount: {:?}]", + caller, user, used, refunded, unused, amount + ); + + // should always be able to unreserve the amount + // but otherwise we will just ignore the issue here. + let err_amount = T::ChargeTransactionPayment::unreserve_fee(&user, amount, Some(RESERVE_ID_STORAGE_DEPOSIT)); + debug_assert!(err_amount.is_zero()); + Ok(()) + } + + fn charge_storage(caller: &H160, contract: &H160, storage: i32) -> DispatchResult { + if storage.is_zero() { + return Ok(()); + } + + let user = T::AddressMapping::get_account_id(caller); + let contract_acc = T::AddressMapping::get_account_id(contract); + let amount = Self::get_storage_deposit_per_byte().saturating_mul(storage.unsigned_abs().into()); + + log::debug!( + target: "evm", + "charge_storage: [from: {:?}, account: {:?}, contract: {:?}, contract_acc: {:?}, storage: {:?}, amount: {:?}]", + caller, user, contract, contract_acc, storage, amount + ); + + if storage.is_positive() { + // `repatriate_reserved` requires beneficiary is an existing account, and create_contract did + // inc_provider for contract account. So here we can use `repatriate_reserved` instead of + // `unreserve` + `transfer` + `reserve`. + let err_amount = T::Currency::repatriate_reserved_named( + &RESERVE_ID_STORAGE_DEPOSIT, + &user, + &contract_acc, + amount, + BalanceStatus::Reserved, + )?; + debug_assert!(err_amount.is_zero()); + } else { + // user can't be a dead account + let val = T::Currency::repatriate_reserved_named( + &RESERVE_ID_STORAGE_DEPOSIT, + &contract_acc, + &user, + amount, + BalanceStatus::Reserved, + )?; + debug_assert!(val.is_zero()); + }; + + Ok(()) + } + + fn refund_storage(caller: &H160, contract: &H160, maintainer: &H160) -> DispatchResult { + let user = T::AddressMapping::get_account_id(caller); + let contract_acc = T::AddressMapping::get_account_id(contract); + let maintainer_acc = T::AddressMapping::get_account_id(maintainer); + let amount = T::Currency::reserved_balance_named(&RESERVE_ID_STORAGE_DEPOSIT, &contract_acc); + + log::debug!( + target: "evm", + "refund_storage: [from: {:?}, account: {:?}, contract: {:?}, contract_acc: {:?}, maintainer: {:?}, maintainer_acc: {:?}, amount: {:?}]", + caller, user, contract, contract_acc, maintainer, maintainer_acc, amount + ); + + // user can't be a dead account + let val = T::Currency::repatriate_reserved_named( + &RESERVE_ID_STORAGE_DEPOSIT, + &contract_acc, + &user, + amount, + BalanceStatus::Free, + )?; + debug_assert!(val.is_zero()); + + // transfer to treasury if maintainer is contract itself + let dest = if contract_acc == maintainer_acc { + T::TreasuryAccount::get() + } else { + maintainer_acc + }; + + T::TransferAll::transfer_all(&contract_acc, &dest)?; + + Ok(()) + } + + fn inc_nonce(origin: &H160) { + Accounts::::mutate(origin, |account| { + if let Some(info) = account.as_mut() { + info.nonce = info.nonce.saturating_add(T::Nonce::one()); + } else { + *account = Some(AccountInfo { + nonce: T::Nonce::one(), + contract_info: None, + }); + } + }); + } +} + +impl EVMTrait for Pallet { + type Balance = BalanceOf; + fn execute( + context: InvokeContext, + input: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + mode: ExecutionMode, + ) -> Result { + let mut config = T::config().clone(); + if let ExecutionMode::EstimateGas = mode { + config.estimate = true; + } + + frame_support::storage::with_transaction(|| { + let result = T::Runner::call( + context.sender, + context.origin, + context.contract, + input, + value, + gas_limit, + storage_limit, + vec![], + &config, + ); + + match result { + Ok(info) => match mode { + ExecutionMode::Execute => { + if info.exit_reason.is_succeed() { + Pallet::::deposit_event(Event::::Executed { + from: context.sender, + contract: context.contract, + logs: info.logs.clone(), + used_gas: info.used_gas.unique_saturated_into(), + used_storage: info.used_storage, + }); + TransactionOutcome::Commit(Ok(info)) + } else { + Pallet::::deposit_event(Event::::ExecutedFailed { + from: context.sender, + contract: context.contract, + exit_reason: info.exit_reason.clone(), + output: info.value.clone(), + logs: info.logs.clone(), + used_gas: info.used_gas.unique_saturated_into(), + used_storage: Default::default(), + }); + TransactionOutcome::Rollback(Ok(info)) + } + } + ExecutionMode::View | ExecutionMode::EstimateGas => TransactionOutcome::Rollback(Ok(info)), + }, + Err(e) => TransactionOutcome::Rollback(Err(e)), + } + }) + } + + /// Get the real origin account and charge storage rent from the origin. + fn get_origin() -> Option { + ExtrinsicOrigin::::get() + } + + /// Set the EVM origin + fn set_origin(origin: T::AccountId) { + ExtrinsicOrigin::::set(Some(origin)); + } + + // Kill the EVM origin + fn kill_origin() { + ExtrinsicOrigin::::kill(); + } + + // Set the EVM origin in xcm + fn push_xcm_origin(origin: T::AccountId) { + XcmOrigin::::mutate(|o| { + if let Some(o) = o { + o.push(origin); + } else { + *o = Some(vec![origin]); + } + }); + } + + // Pop the EVM origin in xcm + fn pop_xcm_origin() { + XcmOrigin::::mutate(|o| { + if let Some(arr) = o { + arr.pop(); + if arr.is_empty() { + *o = None; + } + } + }); + } + + // Kill the EVM origin in xcm + fn kill_xcm_origin() { + XcmOrigin::::kill(); + } + + // Get the real origin account or xcm origin and charge storage rent from the origin. + fn get_real_or_xcm_origin() -> Option { + ExtrinsicOrigin::::get().or_else(|| XcmOrigin::::get().and_then(|o| o.last().cloned())) + } +} + +pub struct EvmChainId(PhantomData); +impl Get for EvmChainId { + fn get() -> u64 { + Pallet::::chain_id() + } +} + +impl EVMManager> for Pallet { + fn query_new_contract_extra_bytes() -> u32 { + T::NewContractExtraBytes::get() + } + + fn query_storage_deposit_per_byte() -> BalanceOf { + // the decimals is already 18 + T::StorageDepositPerByte::get() + } + + fn query_maintainer(contract: EvmAddress) -> Result { + Accounts::::get(contract).map_or(Err(Error::::ContractNotFound.into()), |account_info| { + account_info + .contract_info + .map_or(Err(Error::::ContractNotFound.into()), |v| Ok(v.maintainer)) + }) + } + + fn query_developer_deposit() -> BalanceOf { + convert_decimals_to_evm(T::DeveloperDeposit::get()) + } + + fn query_publication_fee() -> BalanceOf { + convert_decimals_to_evm(T::PublicationFee::get()) + } + + fn transfer_maintainer(from: T::AccountId, contract: EvmAddress, new_maintainer: EvmAddress) -> DispatchResult { + Pallet::::do_transfer_maintainer(from, contract, new_maintainer) + } + + fn publish_contract_precompile(who: T::AccountId, contract: H160) -> DispatchResult { + Pallet::::do_publish_contract(who, contract) + } + + fn query_developer_status(who: T::AccountId) -> bool { + !T::Currency::reserved_balance_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &who).is_zero() + } + + fn enable_account_contract_development(who: T::AccountId) -> DispatchResult { + Pallet::::do_enable_contract_development(&who) + } + + fn disable_account_contract_development(who: T::AccountId) -> sp_runtime::DispatchResult { + Pallet::::do_disable_contract_development(&who) + } +} + +pub struct CallKillAccount(PhantomData); +impl OnKilledAccount for CallKillAccount { + fn on_killed_account(who: &T::AccountId) { + if let Some(address) = T::AddressMapping::get_evm_address(who) { + Pallet::::remove_account_if_empty(&address); + } + } +} + +pub fn code_hash(code: &[u8]) -> H256 { + H256::from_slice(Keccak256::digest(code).as_slice()) +} + +#[allow(dead_code)] +fn encode_revert_message(msg: &[u8]) -> Vec { + // A minimum size of error function selector (4) + offset (32) + string length + // (32) should contain a utf-8 encoded revert reason. + let mut data = Vec::with_capacity(68 + msg.len()); + data.extend_from_slice(&[0u8; 68]); + U256::from(msg.len()).to_big_endian(&mut data[36..68]); + data.extend_from_slice(msg); + data +} + +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct SetEvmOrigin(PhantomData); + +impl sp_std::fmt::Debug for SetEvmOrigin { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "SetEvmOrigin") + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SetEvmOrigin { + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl Default for SetEvmOrigin { + fn default() -> Self { + Self::new() + } +} + +impl SignedExtension for SetEvmOrigin { + const IDENTIFIER: &'static str = "SetEvmOrigin"; + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + ExtrinsicOrigin::::set(Some(who.clone())); + Ok(ValidTransaction::default()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result<(), TransactionValidityError> { + ExtrinsicOrigin::::set(Some(who.clone())); + Ok(()) + } + + fn post_dispatch( + _pre: Option, + _info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + ExtrinsicOrigin::::kill(); + XcmOrigin::::kill(); + Ok(()) + } +} diff --git a/blockchain/modules/evm/src/mock.rs b/blockchain/modules/evm/src/mock.rs index 83c643c6..b6a9c4a1 100644 --- a/blockchain/modules/evm/src/mock.rs +++ b/blockchain/modules/evm/src/mock.rs @@ -1,301 +1,293 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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)] - -use super::*; - -use frame_support::{construct_runtime, ord_parameter_types, parameter_types, traits::FindAuthor, ConsensusEngineId}; -use frame_system::EnsureSignedBy; -use module_support::mocks::MockAddressMapping; -use orml_traits::parameter_type_with_key; -use primitives::{Amount, BlockNumber, CurrencyId, ReserveIdentifier, TokenSymbol}; -use sp_core::{H160, H256}; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - AccountId32, -}; -use std::{collections::BTreeMap, str::FromStr}; - -mod evm_mod { - pub use super::super::*; -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -impl frame_system::Config for Runtime { - type BaseCallFilter = (); - type BlockWeights = (); - type BlockLength = (); - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId32; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = crate::CallKillAccount; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxReserves: u32 = 50; -} -impl pallet_balances::Config for Runtime { - type Balance = u64; - type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type MaxLocks = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = ReserveIdentifier; - type WeightInfo = (); -} - -parameter_types! { - pub const MinimumPeriod: u64 = 1000; -} -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> u64 { - Default::default() - }; -} - -impl orml_tokens::Config for Runtime { - type Event = Event; - type Balance = u64; - type Amount = Amount; - type CurrencyId = CurrencyId; - type WeightInfo = (); - type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); - type MaxLocks = (); - type DustRemovalWhitelist = (); -} - -parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); -} - -impl orml_currencies::Config for Runtime { - type Event = Event; - type MultiCurrency = Tokens; - type NativeCurrency = AdaptedBasicCurrency; - type GetNativeCurrencyId = GetNativeCurrencyId; - type WeightInfo = (); -} -pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; - -pub struct GasToWeight; - -impl Convert for GasToWeight { - fn convert(a: u64) -> u64 { - a - } -} - -pub struct AuthorGiven; -impl FindAuthor for AuthorGiven { - fn find_author<'a, I>(_digests: I) -> Option - where - I: 'a + IntoIterator, - { - Some(AccountId32::from_str("1234500000000000000000000000000000000000").unwrap()) - } -} - -parameter_types! { - pub NetworkContractSource: H160 = alice(); -} - -ord_parameter_types! { - pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); - pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); - pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); - pub const NewContractExtraBytes: u32 = 100; - pub const StorageDepositPerByte: u64 = 10; - pub const DeveloperDeposit: u64 = 1000; - pub const DeploymentFee: u64 = 200; - pub const ChainId: u64 = 1; -} - -impl Config for Runtime { - type AddressMapping = MockAddressMapping; - type Currency = Balances; - type TransferAll = Currencies; - type NewContractExtraBytes = NewContractExtraBytes; - type StorageDepositPerByte = StorageDepositPerByte; - - type Event = Event; - type Precompiles = (); - type ChainId = ChainId; - type GasToWeight = GasToWeight; - type ChargeTransactionPayment = (); - - type NetworkContractOrigin = EnsureSignedBy; - type NetworkContractSource = NetworkContractSource; - type DeveloperDeposit = DeveloperDeposit; - type DeploymentFee = DeploymentFee; - type TreasuryAccount = TreasuryAccount; - type FreeDeploymentOrigin = EnsureSignedBy; - - type Runner = crate::runner::stack::Runner; - type FindAuthor = AuthorGiven; - type WeightInfo = (); -} - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - EVM: evm_mod::{Pallet, Config, Call, Storage, Event}, - Tokens: orml_tokens::{Pallet, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Currencies: orml_currencies::{Pallet, Call, Event}, - } -); - -pub const INITIAL_BALANCE: u64 = 1_000_000_000_000; - -pub fn contract_a() -> H160 { - H160::from_str("2000000000000000000000000000000000000001").unwrap() -} - -pub fn contract_b() -> H160 { - H160::from_str("2000000000000000000000000000000000000002").unwrap() -} - -pub fn alice() -> H160 { - H160::from_str("1000000000000000000000000000000000000001").unwrap() -} - -pub fn bob() -> H160 { - H160::from_str("1000000000000000000000000000000000000002").unwrap() -} - -pub fn charlie() -> H160 { - H160::from_str("1000000000000000000000000000000000000003").unwrap() -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - let mut accounts = BTreeMap::new(); - - accounts.insert( - contract_a(), - GenesisAccount { - nonce: 1, - balance: Default::default(), - storage: Default::default(), - code: Default::default(), - }, - ); - accounts.insert( - contract_b(), - GenesisAccount { - nonce: 1, - balance: Default::default(), - storage: Default::default(), - code: Default::default(), - }, - ); - - accounts.insert( - alice(), - GenesisAccount { - nonce: 1, - balance: INITIAL_BALANCE, - storage: Default::default(), - code: Default::default(), - }, - ); - accounts.insert( - bob(), - GenesisAccount { - nonce: 1, - balance: INITIAL_BALANCE, - storage: Default::default(), - code: Default::default(), - }, - ); - - pallet_balances::GenesisConfig:: { - balances: vec![(TreasuryAccount::get(), INITIAL_BALANCE)], - } - .assimilate_storage(&mut t) - .unwrap(); - evm_mod::GenesisConfig:: { - accounts, - treasury: Default::default(), - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -pub fn balance(address: H160) -> u64 { - let account_id = ::AddressMapping::get_account_id(&address); - Balances::free_balance(account_id) -} - -pub fn reserved_balance(address: H160) -> u64 { - let account_id = ::AddressMapping::get_account_id(&address); - Balances::reserved_balance(account_id) -} - -#[cfg(not(feature = "with-ethereum-compatibility"))] -pub fn deploy_free(contract: H160) { - let _ = EVM::deploy_free(Origin::signed(CouncilAccount::get()), contract); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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)] + +use super::*; + +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, FindAuthor, Nothing}, + ConsensusEngineId, +}; +use frame_system::EnsureSignedBy; +use module_support::mocks::MockAddressMapping; +use orml_traits::parameter_type_with_key; +use primitives::{Amount, BlockNumber, CurrencyId, ReserveIdentifier, TokenSymbol}; +use sp_core::{bytes::from_hex, H160}; +use sp_runtime::{ + traits::{BlockNumberProvider, IdentityLookup}, + AccountId32, BuildStorage, +}; +use std::{collections::BTreeMap, str::FromStr}; + +type Balance = u128; + +pub mod evm_module { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<2>; + type AccountStore = module_support::SystemAccountStore; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1000>; + type WeightInfo = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = ReserveIdentifier; + type DustRemovalWhitelist = Nothing; +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +} + +impl orml_currencies::Config for Runtime { + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +pub struct GasToWeight; + +impl Convert for GasToWeight { + fn convert(a: u64) -> Weight { + Weight::from_parts(a, 0) + } +} + +pub struct AuthorGiven; +impl FindAuthor for AuthorGiven { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(::AddressMapping::get_account_id( + &H160::from_str("1234500000000000000000000000000000000000").unwrap(), + )) + } +} + +parameter_types! { + pub NetworkContractSource: H160 = alice(); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); + pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); + pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); + pub const StorageDepositPerByte: Balance = convert_decimals_to_evm(10); +} + +pub const NEW_CONTRACT_EXTRA_BYTES: u32 = 100; +pub const DEVELOPER_DEPOSIT: u128 = 1000; +pub const PUBLICATION_FEE: u128 = 200; +impl Config for Runtime { + type AddressMapping = MockAddressMapping; + type Currency = Balances; + type TransferAll = Currencies; + type NewContractExtraBytes = ConstU32; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = ConstU128<20_000_000>; + + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = GasToWeight; + type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; + + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + type DeveloperDeposit = ConstU128; + type PublicationFee = ConstU128; + type TreasuryAccount = TreasuryAccount; + type FreePublicationOrigin = EnsureSignedBy; + + type Runner = crate::runner::stack::Runner; + type FindAuthor = AuthorGiven; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + Timestamp: pallet_timestamp, + EVM: evm_module, + Tokens: orml_tokens, + Balances: pallet_balances, + Currencies: orml_currencies, + Utility: pallet_utility, + } +); + +pub const INITIAL_BALANCE: Balance = 1_000_000_000_000_000; + +pub fn contract_a() -> H160 { + H160::from_str("2000000000000000000000000000000000000001").unwrap() +} + +pub fn contract_b() -> H160 { + H160::from_str("2000000000000000000000000000000000000002").unwrap() +} + +pub fn alice() -> H160 { + H160::from_str("1000000000000000000000000000000000000001").unwrap() +} + +pub fn bob() -> H160 { + H160::from_str("1000000000000000000000000000000000000002").unwrap() +} + +pub fn charlie() -> H160 { + H160::from_str("1000000000000000000000000000000000000003").unwrap() +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut accounts = BTreeMap::new(); + + // pragma solidity >=0.8.2 <0.9.0; + // contract Test {} + let contract = from_hex( + "0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220199b6fd928fecd2e7ce866eb76c49927191c7a839fd75192acc84b773e5dbf1e64736f6c63430008120033" + ).unwrap(); + + accounts.insert( + contract_a(), + GenesisAccount { + nonce: 1, + code: contract.clone(), + ..Default::default() + }, + ); + accounts.insert( + contract_b(), + GenesisAccount { + nonce: 1, + ..Default::default() + }, + ); + + accounts.insert( + alice(), + GenesisAccount { + nonce: 1, + balance: INITIAL_BALANCE, + ..Default::default() + }, + ); + accounts.insert( + bob(), + GenesisAccount { + nonce: 1, + balance: INITIAL_BALANCE, + ..Default::default() + }, + ); + + pallet_balances::GenesisConfig:: { + balances: vec![(TreasuryAccount::get(), INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); + evm_module::GenesisConfig:: { chain_id: 1, accounts } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub fn balance(address: H160) -> Balance { + let account_id = ::AddressMapping::get_account_id(&address); + Balances::free_balance(account_id) +} + +pub fn eth_balance(address: H160) -> U256 { + EVM::account_basic(&address).balance +} + +pub fn reserved_balance(address: H160) -> Balance { + let account_id = ::AddressMapping::get_account_id(&address); + Balances::reserved_balance(account_id) +} + +#[cfg(not(feature = "with-ethereum-compatibility"))] +pub fn publish_free(contract: H160) { + let _ = EVM::publish_free(RuntimeOrigin::signed(CouncilAccount::get()), contract); +} diff --git a/blockchain/modules/evm/src/precompiles.rs b/blockchain/modules/evm/src/precompiles.rs deleted file mode 100644 index 7d037c13..00000000 --- a/blockchain/modules/evm/src/precompiles.rs +++ /dev/null @@ -1,424 +0,0 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! Builtin precompiles. - -use crate::runner::state::PrecompileOutput; -use evm::{Context, ExitError, ExitSucceed}; -use frame_support::log; -use impl_trait_for_tuples::impl_for_tuples; -use primitive_types::H160; -use ripemd160::Digest; -use sp_std::{cmp::min, marker::PhantomData, vec::Vec}; -use tiny_keccak::Hasher; - -/// Custom precompiles to be used by EVM engine. -pub trait PrecompileSet { - #![allow(clippy::type_complexity)] - /// Try to execute the code address as precompile. If the code address is - /// not a precompile or the precompile is not yet available, return `None`. - /// Otherwise, calculate the amount of gas needed with given `input` and - /// `target_gas`. Return `Some(Ok(status, output, gas_used))` if the - /// execution is successful. Otherwise return `Some(Err(_))`. - fn execute( - address: H160, - input: &[u8], - target_gas: Option, - context: &Context, - ) -> Option>; -} - -/// One single precompile used by EVM engine. -pub trait Precompile { - /// Try to execute the precompile. Calculate the amount of gas needed with - /// given `input` and `target_gas`. Return `Ok(status, output, gas_used)` if - /// the execution is successful. Otherwise return `Err(_)`. - fn execute( - input: &[u8], - target_gas: Option, - context: &Context, - ) -> core::result::Result; -} - -#[impl_for_tuples(16)] -#[tuple_types_no_default_trait_bound] -impl PrecompileSet for Tuple { - for_tuples!( where #( Tuple: Precompile )* ); - #[allow(clippy::type_complexity)] - fn execute( - address: H160, - input: &[u8], - target_gas: Option, - context: &Context, - ) -> Option> { - let mut index = 0; - - for_tuples!( #( - index += 1; - if address == H160::from_low_u64_be(index) { - return Some(Tuple::execute(input, target_gas, context)) - } - )* ); - - None - } -} - -pub struct EvmPrecompiles( - PhantomData<( - ECRecover, - Sha256, - Ripemd160, - Identity, - ECRecoverPublicKey, - Sha3FIPS256, - Sha3FIPS512, - )>, -); - -impl PrecompileSet - for EvmPrecompiles -where - ECRecover: Precompile, - Sha256: Precompile, - Ripemd160: Precompile, - Identity: Precompile, - ECRecoverPublicKey: Precompile, - Sha3FIPS256: Precompile, - Sha3FIPS512: Precompile, -{ - #[allow(clippy::type_complexity)] - fn execute( - address: H160, - input: &[u8], - target_gas: Option, - context: &Context, - ) -> Option> { - // https://github.com/ethereum/go-ethereum/blob/9357280fce5c5d57111d690a336cca5f89e34da6/core/vm/contracts.go#L83 - let result = if address == H160::from_low_u64_be(1) { - Some(ECRecover::execute(input, target_gas, context)) - } else if address == H160::from_low_u64_be(2) { - Some(Sha256::execute(input, target_gas, context)) - } else if address == H160::from_low_u64_be(3) { - Some(Ripemd160::execute(input, target_gas, context)) - } else if address == H160::from_low_u64_be(4) { - Some(Identity::execute(input, target_gas, context)) - } - // Non-standard precompile starts with 128 - else if address == H160::from_low_u64_be(128) { - Some(ECRecoverPublicKey::execute(input, target_gas, context)) - } else if address == H160::from_low_u64_be(129) { - Some(Sha3FIPS256::execute(input, target_gas, context)) - } else if address == H160::from_low_u64_be(130) { - Some(Sha3FIPS512::execute(input, target_gas, context)) - } else { - None - }; - - if result.is_some() { - log::debug!(target: "evm", "Precompile end, address: {:?}, input: {:?}, target_gas: {:?}, context: {:?}, result: {:?}", address, input, target_gas, context, result); - } - result - } -} - -pub trait LinearCostPrecompile { - const BASE: u64; - const WORD: u64; - - fn execute(input: &[u8], cost: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError>; -} - -impl Precompile for T { - fn execute( - input: &[u8], - target_gas: Option, - _: &Context, - ) -> core::result::Result { - let cost = ensure_linear_cost(target_gas, input.len() as u64, T::BASE, T::WORD)?; - - let (exit_status, output) = T::execute(input, cost)?; - Ok(PrecompileOutput { - exit_status, - cost, - output, - logs: Default::default(), - }) - } -} - -/// Linear gas cost -fn ensure_linear_cost(target_gas: Option, len: u64, base: u64, word: u64) -> Result { - let cost = base - .checked_add( - word.checked_mul(len.saturating_add(31) / 32) - .ok_or(ExitError::OutOfGas)?, - ) - .ok_or(ExitError::OutOfGas)?; - - if let Some(target_gas) = target_gas { - if cost > target_gas { - return Err(ExitError::OutOfGas); - } - } - - Ok(cost) -} - -/// The identity precompile. -pub struct Identity; - -impl LinearCostPrecompile for Identity { - const BASE: u64 = 15; - const WORD: u64 = 3; - - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { - Ok((ExitSucceed::Returned, input.to_vec())) - } -} - -/// The ecrecover precompile. -pub struct ECRecover; - -impl LinearCostPrecompile for ECRecover { - const BASE: u64 = 3000; - const WORD: u64 = 0; - - fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { - let mut input = [0u8; 128]; - input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); - - let mut msg = [0u8; 32]; - let mut sig = [0u8; 65]; - - msg[0..32].copy_from_slice(&input[0..32]); - sig[0..32].copy_from_slice(&input[64..96]); - sig[32..64].copy_from_slice(&input[96..128]); - sig[64] = input[63]; - - let result = match sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) { - Ok(pubkey) => { - let mut address = sp_io::hashing::keccak_256(&pubkey); - address[0..12].copy_from_slice(&[0u8; 12]); - address.to_vec() - } - Err(_) => [0u8; 0].to_vec(), - }; - - Ok((ExitSucceed::Returned, result)) - } -} - -/// The ripemd precompile. -pub struct Ripemd160; - -impl LinearCostPrecompile for Ripemd160 { - const BASE: u64 = 600; - const WORD: u64 = 120; - - fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { - let mut ret = [0u8; 32]; - ret[12..32].copy_from_slice(&ripemd160::Ripemd160::digest(input)); - Ok((ExitSucceed::Returned, ret.to_vec())) - } -} - -/// The sha256 precompile. -pub struct Sha256; - -impl LinearCostPrecompile for Sha256 { - const BASE: u64 = 60; - const WORD: u64 = 12; - - fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { - let ret = sp_io::hashing::sha2_256(input); - Ok((ExitSucceed::Returned, ret.to_vec())) - } -} - -/// The ecrecover precompile. -pub struct ECRecoverPublicKey; - -impl LinearCostPrecompile for ECRecoverPublicKey { - const BASE: u64 = 3000; - const WORD: u64 = 0; - - fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { - let mut input = [0u8; 128]; - input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); - - let mut msg = [0u8; 32]; - let mut sig = [0u8; 65]; - - msg[0..32].copy_from_slice(&input[0..32]); - sig[0..32].copy_from_slice(&input[64..96]); - sig[32..64].copy_from_slice(&input[96..128]); - sig[64] = input[63]; - - let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) - .map_err(|_| ExitError::Other("Public key recover failed".into()))?; - - Ok((ExitSucceed::Returned, pubkey.to_vec())) - } -} - -/// The Sha3FIPS256 precompile. -pub struct Sha3FIPS256; - -impl LinearCostPrecompile for Sha3FIPS256 { - const BASE: u64 = 60; - const WORD: u64 = 12; - - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { - let mut output = [0; 32]; - let mut sha3 = tiny_keccak::Sha3::v256(); - sha3.update(input); - sha3.finalize(&mut output); - Ok((ExitSucceed::Returned, output.to_vec())) - } -} - -/// The Sha3FIPS512 precompile. -pub struct Sha3FIPS512; - -impl LinearCostPrecompile for Sha3FIPS512 { - const BASE: u64 = 60; - const WORD: u64 = 12; - - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { - let mut output = [0; 64]; - let mut sha3 = tiny_keccak::Sha3::v512(); - sha3.update(input); - sha3.finalize(&mut output); - Ok((ExitSucceed::Returned, output.to_vec())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_empty_input() -> std::result::Result<(), ExitError> { - let input: [u8; 0] = []; - let expected = b"\ - \xa7\xff\xc6\xf8\xbf\x1e\xd7\x66\x51\xc1\x47\x56\xa0\x61\xd6\x62\ - \xf5\x80\xff\x4d\xe4\x3b\x49\xfa\x82\xd8\x0a\x4b\x80\xf8\x43\x4a\ - "; - - let cost: u64 = 1; - - match ::execute(&input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn hello_sha3_256() -> std::result::Result<(), ExitError> { - let input = b"hello"; - let expected = b"\ - \x33\x38\xbe\x69\x4f\x50\xc5\xf3\x38\x81\x49\x86\xcd\xf0\x68\x64\ - \x53\xa8\x88\xb8\x4f\x42\x4d\x79\x2a\xf4\xb9\x20\x23\x98\xf3\x92\ - "; - - let cost: u64 = 1; - - match ::execute(input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn long_string_sha3_256() -> std::result::Result<(), ExitError> { - let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - let expected = b"\ - \xbd\xe3\xf2\x69\x17\x5e\x1d\xcd\xa1\x38\x48\x27\x8a\xa6\x04\x6b\ - \xd6\x43\xce\xa8\x5b\x84\xc8\xb8\xbb\x80\x95\x2e\x70\xb6\xea\xe0\ - "; - - let cost: u64 = 1; - - match ::execute(input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn long_string_sha3_512() -> std::result::Result<(), ExitError> { - let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - let expected = b"\ - \xf3\x2a\x94\x23\x55\x13\x51\xdf\x0a\x07\xc0\xb8\xc2\x0e\xb9\x72\ - \x36\x7c\x39\x8d\x61\x06\x60\x38\xe1\x69\x86\x44\x8e\xbf\xbc\x3d\ - \x15\xed\xe0\xed\x36\x93\xe3\x90\x5e\x9a\x8c\x60\x1d\x9d\x00\x2a\ - \x06\x85\x3b\x97\x97\xef\x9a\xb1\x0c\xbd\xe1\x00\x9c\x7d\x0f\x09\ - "; - - let cost: u64 = 1; - - match ::execute(input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } - - #[test] - fn ecrecover() -> std::result::Result<(), ExitError> { - let input = sp_core::bytes::from_hex("0xe63325d74baa84af003dfb6a974f41672be881b56aa2c12c093f8259321bd460000000000000000000000000000000000000000000000000000000000000001c6273e55c6b942c7a701ae05195fa24395cd1db99e81c705b8c2eb4d7156ff85a3ecf6b591a30105fef03717fa608c887bd02ba548876e93ce818f90dc46ac374").unwrap(); - let expected = b"\ - \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ - \x6b\xe0\x2d\x1d\x36\x65\x66\x0d\x22\xff\x96\x24\xb7\xbe\x05\x51\xee\x1a\xc9\x1b\ - "; - - let cost: u64 = 1; - - match ::execute(&input, cost) { - Ok((_, out)) => { - assert_eq!(out, expected); - Ok(()) - } - Err(e) => { - panic!("Test not expected to fail: {:?}", e); - } - } - } -} diff --git a/blockchain/modules/evm/src/precompiles/blake2/eip_152.rs b/blockchain/modules/evm/src/precompiles/blake2/eip_152.rs new file mode 100644 index 00000000..c6b0601e --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/blake2/eip_152.rs @@ -0,0 +1,95 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +/// The precomputed values for BLAKE2b [from the spec](https://tools.ietf.org/html/rfc7693#section-2.7) +/// There are 10 16-byte arrays - one for each round +/// the entries are calculated from the sigma constants. +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// IV is the initialization vector for BLAKE2b. See https://tools.ietf.org/html/rfc7693#section-2.6 +/// for details. +const IV: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +#[inline(always)] +/// The G mixing function. See https://tools.ietf.org/html/rfc7693#section-3.1 +fn g(v: &mut [u64], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +/// The Blake2 compression function F. See https://tools.ietf.org/html/rfc7693#section-3.2 +/// Takes as an argument the state vector `h`, message block vector `m`, offset counter `t`, final +/// block indicator flag `f`, and number of rounds `rounds`. The state vector provided as the first +/// parameter is modified by the function. +pub fn compress(h: &mut [u64; 8], m: [u64; 16], t: [u64; 2], f: bool, rounds: usize) { + let mut v = [0u64; 16]; + v[..h.len()].copy_from_slice(h); // First half from state. + v[h.len()..].copy_from_slice(&IV); // Second half from IV. + + v[12] ^= t[0]; + v[13] ^= t[1]; + + if f { + v[14] = !v[14] // Invert all bits if the last-block-flag is set. + } + for i in 0..rounds { + // Message word selection permutation for this round. + let s = &SIGMA[i % 10]; + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } +} diff --git a/blockchain/modules/evm/src/precompiles/blake2/mod.rs b/blockchain/modules/evm/src/precompiles/blake2/mod.rs new file mode 100644 index 00000000..fc98ed91 --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/blake2/mod.rs @@ -0,0 +1,263 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::Precompile; +use crate::{PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult}; +use module_evm_utility::evm::{ExitError, ExitSucceed}; + +mod eip_152; + +pub struct Blake2F; + +impl Blake2F { + const GAS_COST_PER_ROUND: u64 = 1; // https://eips.ethereum.org/EIPS/eip-152#gas-costs-and-benchmarks +} + +impl Precompile for Blake2F { + /// Format of `input`: + /// [4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 + /// byte for f] + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + const BLAKE2_F_ARG_LEN: usize = 213; + + let input = handle.input(); + + if input.len() != BLAKE2_F_ARG_LEN { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + "input length for Blake2 F precompile should be exactly 213 bytes".into(), + ), + }); + } + + let mut rounds_buf: [u8; 4] = [0; 4]; + rounds_buf.copy_from_slice(&input[0..4]); + let rounds: u32 = u32::from_be_bytes(rounds_buf); + + let gas_cost: u64 = (rounds as u64) * Blake2F::GAS_COST_PER_ROUND; + handle.record_cost(gas_cost)?; + + let input = handle.input(); + + // we use from_le_bytes below to effectively swap byte order to LE if architecture is BE + + let mut h_buf: [u8; 64] = [0; 64]; + h_buf.copy_from_slice(&input[4..68]); + let mut h = [0u64; 8]; + let mut ctr = 0; + for state_word in &mut h { + let mut temp: [u8; 8] = Default::default(); + temp.copy_from_slice(&h_buf[(ctr * 8)..(ctr + 1) * 8]); + *state_word = u64::from_le_bytes(temp); + ctr += 1; + } + + let mut m_buf: [u8; 128] = [0; 128]; + m_buf.copy_from_slice(&input[68..196]); + let mut m = [0u64; 16]; + ctr = 0; + for msg_word in &mut m { + let mut temp: [u8; 8] = Default::default(); + temp.copy_from_slice(&m_buf[(ctr * 8)..(ctr + 1) * 8]); + *msg_word = u64::from_le_bytes(temp); + ctr += 1; + } + + let mut t_0_buf: [u8; 8] = [0; 8]; + t_0_buf.copy_from_slice(&input[196..204]); + let t_0 = u64::from_le_bytes(t_0_buf); + + let mut t_1_buf: [u8; 8] = [0; 8]; + t_1_buf.copy_from_slice(&input[204..212]); + let t_1 = u64::from_le_bytes(t_1_buf); + + let f = if input[212] == 1 { + true + } else if input[212] == 0 { + false + } else { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("incorrect final block indicator flag".into()), + }); + }; + + eip_152::compress(&mut h, m, [t_0, t_1], f, rounds as usize); + + let mut output_buf = [0u8; u64::BITS as usize]; + for (i, state_word) in h.iter().enumerate() { + output_buf[i * 8..(i + 1) * 8].copy_from_slice(&state_word.to_le_bytes()); + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: output_buf.to_vec(), + }) + } + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let mut handle = crate::precompiles::tests::MockPrecompileHandle::new(&input, target_gas, context, is_static); + let output = Self::execute(&mut handle)?; + + Ok((output, handle.gas_used)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::precompiles::tests::MockPrecompileHandle; + use frame_support::assert_ok; + use hex_literal::hex; + use module_evm_utility::evm::Context; + use sp_core::U256; + + fn get_context() -> Context { + Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + } + } + + #[test] + fn blake2f_cost() { + // 5 rounds + let input = hex!("0000000548c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let context = get_context(); + let mut mock_handle = MockPrecompileHandle::new(&input[..], None, &context, false); + assert_ok!(Blake2F::execute(&mut mock_handle)); + assert_eq!(mock_handle.gas_used, 5); + } + + #[test] + fn blake2f_invalid_length() { + let err = Err(PrecompileFailure::Error { + exit_status: ExitError::Other("input length for Blake2 F precompile should be exactly 213 bytes".into()), + }); + + // invalid input (too short) + let input = hex!("00"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + err + ); + + // Test vector 1 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-1 + let input = hex!("00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + err + ); + + // Test vector 2 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-2 + let input = hex!("000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + err + ); + } + + #[test] + fn blake2f_bad_finalization_flag() { + let err = Err(PrecompileFailure::Error { + exit_status: ExitError::Other("incorrect final block indicator flag".into()), + }); + + // Test vector 3 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-3 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + err + ); + } + + #[test] + fn blake2f_zero_rounds_is_ok_test_vector_4() { + // Test vector 4 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-4 + let input = hex!("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + #[test] + fn blake2_f_test_vector_5() { + // Test vector 5 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-5 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + #[test] + fn blake2_f_test_vector_6() { + // Test vector 6 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-6 + let input = hex!("0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000"); + let expected = hex!("75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + #[test] + fn blake2_f_test_vector_7() { + // Test vector 7 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-7 + let input = hex!("0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + #[ignore] + #[test] + fn blake2_f_test_vector_8() { + // Test vector 8 and expected output from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md#test-vector-8 + // Note this test is slow, 4294967295/0xffffffff rounds take a while. + let input = hex!("ffffffff48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let expected = hex!("fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615"); + assert_eq!( + Blake2F::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } +} diff --git a/blockchain/modules/evm/src/precompiles/bn128.rs b/blockchain/modules/evm/src/precompiles/bn128.rs new file mode 100644 index 00000000..9c132e6b --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/bn128.rs @@ -0,0 +1,449 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::Precompile; +use crate::{PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult}; +use module_evm_utility::evm::{ExitError, ExitSucceed}; +use sp_core::U256; +use sp_std::vec::Vec; + +fn read_fr(input: &[u8], start_inx: usize) -> Result { + let mut padded_input = Vec::from(input); + if padded_input.len() < start_inx + 32 { + padded_input.resize_with(start_inx + 32, Default::default); + } + + bn::Fr::from_slice(&padded_input[start_inx..(start_inx + 32)]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid field element".into()), + }) +} + +fn read_point(input: &[u8], start_inx: usize) -> Result { + use bn::{AffineG1, Fq, Group, G1}; + + let mut padded_input = Vec::from(input); + if padded_input.len() < start_inx + 64 { + padded_input.resize_with(start_inx + 64, Default::default); + } + + let px = Fq::from_slice(&padded_input[start_inx..(start_inx + 32)]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid point x coordinate".into()), + })?; + let py = + Fq::from_slice(&padded_input[(start_inx + 32)..(start_inx + 64)]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid point y coordinate".into()), + })?; + Ok(if px == Fq::zero() && py == Fq::zero() { + G1::zero() + } else { + AffineG1::new(px, py) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid curve point".into()), + })? + .into() + }) +} + +/// The Bn128Add builtin +pub struct Bn128Add; + +impl Bn128Add { + const GAS_COST: u64 = 150; // https://eips.ethereum.org/EIPS/eip-1108 +} + +impl Precompile for Bn128Add { + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + use bn::AffineG1; + + handle.record_cost(Bn128Add::GAS_COST)?; + + let input = handle.input(); + + let p1 = read_point(input, 0)?; + let p2 = read_point(input, 64)?; + + let mut buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p1 + p2) { + // point not at infinity + sum.x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()), + })?; + sum.y() + .to_big_endian(&mut buf[32..64]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()), + })?; + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: buf.to_vec(), + }) + } + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let mut handle = crate::precompiles::tests::MockPrecompileHandle::new(&input, target_gas, context, is_static); + let output = Self::execute(&mut handle)?; + + Ok((output, handle.gas_used)) + } +} + +/// The Bn128Mul builtin +pub struct Bn128Mul; + +impl Bn128Mul { + const GAS_COST: u64 = 6_000; // https://eips.ethereum.org/EIPS/eip-1108 +} + +impl Precompile for Bn128Mul { + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + use bn::AffineG1; + + handle.record_cost(Bn128Mul::GAS_COST)?; + + let input = handle.input(); + + let p = read_point(input, 0)?; + let fr = read_fr(input, 64)?; + + let mut buf = [0u8; 64]; + if let Some(sum) = AffineG1::from_jacobian(p * fr) { + // point not at infinity + sum.x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()), + })?; + sum.y() + .to_big_endian(&mut buf[32..64]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()), + })?; + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: buf.to_vec(), + }) + } + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let mut handle = crate::precompiles::tests::MockPrecompileHandle::new(&input, target_gas, context, is_static); + let output = Self::execute(&mut handle)?; + + Ok((output, handle.gas_used)) + } +} + +/// The Bn128Pairing builtin +pub struct Bn128Pairing; + +impl Bn128Pairing { + // https://eips.ethereum.org/EIPS/eip-1108 + const BASE_GAS_COST: u64 = 45_000; + const GAS_COST_PER_PAIRING: u64 = 34_000; +} + +impl Precompile for Bn128Pairing { + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + use bn::{pairing_batch, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; + + let input = handle.input(); + if input.len() % 192 != 0 { + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid input length, must be multiple of 192 (3 * (32*2))".into()), + }); + } + + let (ret_val, gas_cost) = if input.is_empty() { + (U256::one(), Bn128Pairing::BASE_GAS_COST) + } else { + // (a, b_a, b_b - each 64-byte affine coordinates) + let elements = input.len() / 192; + + let gas_cost: u64 = Bn128Pairing::BASE_GAS_COST + (elements as u64 * Bn128Pairing::GAS_COST_PER_PAIRING); + + let mut vals = Vec::new(); + for idx in 0..elements { + let a_x = Fq::from_slice(&input[idx * 192..idx * 192 + 32]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument x coordinate".into()), + })?; + + let a_y = + Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument y coordinate".into()), + })?; + + let b_a_y = + Fq::from_slice(&input[idx * 192 + 64..idx * 192 + 96]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument imaginary coeff x coordinate".into()), + })?; + + let b_a_x = + Fq::from_slice(&input[idx * 192 + 96..idx * 192 + 128]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument imaginary coeff y coordinate".into()), + })?; + + let b_b_y = + Fq::from_slice(&input[idx * 192 + 128..idx * 192 + 160]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument real coeff x coordinate".into()), + })?; + + let b_b_x = + Fq::from_slice(&input[idx * 192 + 160..idx * 192 + 192]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument real coeff y coordinate".into()), + })?; + + let b_a = Fq2::new(b_a_x, b_a_y); + let b_b = Fq2::new(b_b_x, b_b_y); + let b = if b_a.is_zero() && b_b.is_zero() { + G2::zero() + } else { + G2::from(AffineG2::new(b_a, b_b).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument - not on curve".into()), + })?) + }; + let a = if a_x.is_zero() && a_y.is_zero() { + G1::zero() + } else { + G1::from(AffineG1::new(a_x, a_y).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument - not on curve".into()), + })?) + }; + vals.push((a, b)); + } + + let mul = pairing_batch(&vals); + + if mul == Gt::one() { + (U256::one(), gas_cost) + } else { + (U256::zero(), gas_cost) + } + }; + + handle.record_cost(gas_cost)?; + + let mut buf = [0u8; 32]; + ret_val.to_big_endian(&mut buf); + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: buf.to_vec(), + }) + } + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let mut handle = crate::precompiles::tests::MockPrecompileHandle::new(&input, target_gas, context, is_static); + let output = Self::execute(&mut handle)?; + + Ok((output, handle.gas_used)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::precompiles::tests::MockPrecompileHandle; + use hex_literal::hex; + use module_evm_utility::evm::Context; + + fn get_context() -> Context { + Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + } + } + + #[test] + fn bn128_add() { + // zero-points additions + { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Add::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + // no input, should not fail + { + let input = [0u8; 0]; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Add::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + // should fail - point not on curve + { + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + "}; + + assert_eq!( + Bn128Add::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid curve point".into()) + }) + ); + } + } + + #[test] + fn bn128_mul() { + // zero-point multiplication + { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0200000000000000000000000000000000000000000000000000000000000000 + "}; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Mul::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + // should fail - point not on curve + { + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 0f00000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Bn128Mul::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid curve point".into()) + }) + ); + } + } + + #[test] + fn bn128_pairing_empty() { + // should not fail, because empty input is a valid input of 0 elements + let input = [0u8; 0]; + + let expected = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + "}; + + assert_eq!( + Bn128Pairing::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)) + .unwrap() + .output, + expected + ); + } + + #[test] + fn bn128_pairing_notcurve() { + // should fail - point not on curve + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + "}; + + assert_eq!( + Bn128Pairing::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid b argument - not on curve".into()) + }) + ); + } + + #[test] + fn bn128_pairing_fragmented() { + // should fail - input length is invalid + let input = hex! {" + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111111111111111111111111111111111111 + 111111111111111111111111111111 + "}; + + assert_eq!( + Bn128Pairing::execute(&mut MockPrecompileHandle::new(&input[..], None, &get_context(), false)), + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid input length, must be multiple of 192 (3 * (32*2))".into()) + }) + ); + } +} diff --git a/blockchain/modules/evm/src/precompiles/ecrecover.rs b/blockchain/modules/evm/src/precompiles/ecrecover.rs new file mode 100644 index 00000000..1788315a --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/ecrecover.rs @@ -0,0 +1,105 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::LinearCostPrecompile; +use crate::PrecompileFailure; +use module_evm_utility::evm::ExitSucceed; +use sp_std::{cmp::min, vec::Vec}; + +/// The ecrecover precompile. +pub struct ECRecover; + +impl LinearCostPrecompile for ECRecover { + const BASE: u64 = 3000; + const WORD: u64 = 0; + + fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut input = [0u8; 128]; + input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); + + let mut msg = [0u8; 32]; + let mut sig = [0u8; 65]; + + msg[0..32].copy_from_slice(&input[0..32]); + sig[0..32].copy_from_slice(&input[64..96]); // r + sig[32..64].copy_from_slice(&input[96..128]); // s + sig[64] = input[63]; // v + + // v can only be 27 or 28 on the full 32 bytes value. + // https://github.com/ethereum/go-ethereum/blob/a907d7e81aaeea15d80b2d3209ad8e08e3bf49e0/core/vm/contracts.go#L177 + if input[32..63] != [0u8; 31] || ![27, 28].contains(&input[63]) { + return Ok((ExitSucceed::Returned, [0u8; 0].to_vec())); + } + + let result = match sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) { + Ok(pubkey) => { + let mut address = sp_io::hashing::keccak_256(&pubkey); + address[0..12].copy_from_slice(&[0u8; 12]); + address.to_vec() + } + Err(_) => [0u8; 0].to_vec(), + }; + + Ok((ExitSucceed::Returned, result)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn handle_invalid_v() { + // V = 1 + let input = hex! {" + 18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c + 0000000000000000000000000000000000000000000000000000000000000001 + 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f + eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549 + "}; + let (exit, output) = ECRecover::execute(&input, 0).unwrap(); + assert_eq!(exit, ExitSucceed::Returned); + assert_eq!(output, [0u8; 0].to_vec()); + } + + #[test] + fn validate_v() { + // V = 28 + let mut input = hex! {" + 18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c + 000000000000000000000000000000000000000000000000000000000000001c + 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f + eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549 + "}; + + let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"); + + let (exit, output) = ECRecover::execute(&input, 0).unwrap(); + assert_eq!(exit, ExitSucceed::Returned); + assert_eq!(output, expected); + + // V = 27 + input[63] = 27; + let (exit, output) = ECRecover::execute(&input, 0).unwrap(); + assert_eq!(exit, ExitSucceed::Returned); + assert_ne!(output, expected); + } +} diff --git a/blockchain/modules/evm/src/precompiles/ecrecover_publickey.rs b/blockchain/modules/evm/src/precompiles/ecrecover_publickey.rs new file mode 100644 index 00000000..c476711f --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/ecrecover_publickey.rs @@ -0,0 +1,67 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::LinearCostPrecompile; +use crate::PrecompileFailure; +use module_evm_utility::evm::{ExitError, ExitSucceed}; +use sp_std::{cmp::min, vec::Vec}; + +/// The ecrecover precompile. +pub struct ECRecoverPublicKey; + +impl LinearCostPrecompile for ECRecoverPublicKey { + const BASE: u64 = 3000; + const WORD: u64 = 0; + + fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut input = [0u8; 128]; + input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); + + let mut msg = [0u8; 32]; + let mut sig = [0u8; 65]; + + msg[0..32].copy_from_slice(&input[0..32]); + sig[0..32].copy_from_slice(&input[64..96]); + sig[32..64].copy_from_slice(&input[96..128]); + sig[64] = input[63]; + + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Public key recover failed".into()), + })?; + + Ok((ExitSucceed::Returned, pubkey.to_vec())) + } +} + +#[test] +fn works() { + let input = hex_literal::hex! {" + 18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c + 000000000000000000000000000000000000000000000000000000000000001c + 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f + eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549 + "}; + + let expected = hex_literal::hex!("3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3"); + + let (exit, output) = ECRecoverPublicKey::execute(&input, 0).unwrap(); + assert_eq!(exit, ExitSucceed::Returned); + assert_eq!(output, expected); +} diff --git a/blockchain/modules/evm/rpc/src/evm_api.rs b/blockchain/modules/evm/src/precompiles/identity.rs similarity index 55% rename from blockchain/modules/evm/rpc/src/evm_api.rs rename to blockchain/modules/evm/src/precompiles/identity.rs index dec40c18..59641596 100644 --- a/blockchain/modules/evm/rpc/src/evm_api.rs +++ b/blockchain/modules/evm/src/precompiles/identity.rs @@ -1,47 +1,36 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! EVM rpc interface. - -use ethereum_types::H160; -use jsonrpc_core::Result; -use jsonrpc_derive::rpc; -use sp_core::Bytes; - -pub use rpc_impl_EVMApi::gen_server::EVMApi as EVMApiServer; - -use crate::call_request::{CallRequest, EstimateResourcesResponse}; - -/// EVM rpc interface. -#[rpc(server)] -pub trait EVMApi { - /// Call contract, returning the output data. - #[rpc(name = "evm_call")] - fn call(&self, _: CallRequest, at: Option) -> Result; - - /// Estimate resources needed for execution of given contract. - #[rpc(name = "evm_estimateResources")] - fn estimate_resources( - &self, - from: H160, - unsigned_extrinsic: Bytes, - at: Option, - ) -> Result; -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::LinearCostPrecompile; +use crate::PrecompileFailure; +use module_evm_utility::evm::ExitSucceed; +use sp_std::vec::Vec; + +/// The identity precompile. +pub struct Identity; + +impl LinearCostPrecompile for Identity { + const BASE: u64 = 15; + const WORD: u64 = 3; + + fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + Ok((ExitSucceed::Returned, input.to_vec())) + } +} diff --git a/blockchain/modules/evm/src/precompiles/mod.rs b/blockchain/modules/evm/src/precompiles/mod.rs new file mode 100644 index 00000000..1691b4ba --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/mod.rs @@ -0,0 +1,213 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Builtin precompiles. + +use crate::{PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult}; +use module_evm_utility::evm::{ExitError, ExitSucceed}; +use sp_std::vec::Vec; + +mod blake2; +mod bn128; +mod ecrecover; +mod ecrecover_publickey; +mod identity; +mod modexp; +mod ripemd; +mod sha256; +mod sha3fips; + +pub use self::ripemd::Ripemd160; +pub use blake2::Blake2F; +pub use bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; +pub use ecrecover::ECRecover; +pub use ecrecover_publickey::ECRecoverPublicKey; +pub use identity::Identity; +pub use modexp::{IstanbulModexp, Modexp}; +pub use sha256::Sha256; +pub use sha3fips::{Sha3FIPS256, Sha3FIPS512}; + +/// One single precompile used by EVM engine. +pub trait Precompile { + /// Try to execute the precompile. Calculate the amount of gas needed with given `input` and + /// `target_gas`. Return `Ok(status, output, gas_used)` if the execution is + /// successful. Otherwise return `Err(_)`. + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult; + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure>; +} + +pub trait LinearCostPrecompile { + const BASE: u64; + const WORD: u64; + + fn execute(input: &[u8], cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure>; +} + +impl Precompile for T { + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + let target_gas = handle.gas_limit(); + let cost = ensure_linear_cost(target_gas, handle.input().len() as u64, T::BASE, T::WORD)?; + + handle.record_cost(cost)?; + let (exit_status, output) = T::execute(handle.input(), cost)?; + Ok(PrecompileOutput { exit_status, output }) + } + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let cost = ensure_linear_cost(target_gas, input.len() as u64, T::BASE, T::WORD)?; + let (exit_status, output) = T::execute(input, cost)?; + Ok((PrecompileOutput { exit_status, output }, cost)) + } +} + +/// Linear gas cost +fn ensure_linear_cost(target_gas: Option, len: u64, base: u64, word: u64) -> Result { + let cost = base + .checked_add( + word.checked_mul(len.saturating_add(31) / 32) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?, + ) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?; + + if let Some(target_gas) = target_gas { + if cost > target_gas { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + Ok(cost) +} + +pub mod tests { + use crate::{ExitError, ExitReason, PrecompileHandle}; + use module_evm_utility::evm::{Context, Transfer}; + use sp_core::{H160, H256}; + use sp_std::vec::Vec; + + pub struct MockPrecompileHandle<'inner> { + pub input: &'inner [u8], + pub code_address: H160, + pub gas_limit: Option, + pub gas_used: u64, + pub context: &'inner Context, + pub is_static: bool, + } + + impl<'inner> MockPrecompileHandle<'inner> { + pub fn new(input: &'inner [u8], gas_limit: Option, context: &'inner Context, is_static: bool) -> Self { + Self { + input, + code_address: H160::default(), + gas_limit, + gas_used: 0, + context, + is_static, + } + } + } + + impl<'inner> PrecompileHandle for MockPrecompileHandle<'inner> { + fn call( + &mut self, + _: H160, + _: Option, + _: Vec, + _: Option, + _: bool, + _: &Context, + ) -> (ExitReason, Vec) { + unimplemented!() + } + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.gas_used += cost; + + if let Some(gas_limit) = self.gas_limit { + if self.gas_used > gas_limit { + Err(ExitError::OutOfGas) + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + _storage_growth: Option, + ) -> Result<(), ExitError> { + unimplemented!() + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) { + unimplemented!() + } + + fn remaining_gas(&self) -> u64 { + unimplemented!() + } + + fn log(&mut self, _address: H160, _topics: Vec, _data: Vec) -> Result<(), ExitError> { + unimplemented!() + } + + fn code_address(&self) -> H160 { + self.code_address + } + + fn input(&self) -> &[u8] { + self.input + } + + fn context(&self) -> &Context { + self.context + } + + fn is_static(&self) -> bool { + self.is_static + } + + fn gas_limit(&self) -> Option { + self.gas_limit + } + } +} diff --git a/blockchain/modules/evm/src/precompiles/modexp.rs b/blockchain/modules/evm/src/precompiles/modexp.rs new file mode 100644 index 00000000..de9cdedc --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/modexp.rs @@ -0,0 +1,751 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::Precompile; +use crate::{PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult}; +use module_evm_utility::evm::{ExitError, ExitSucceed}; +use num::{BigUint, One, Zero}; +use sp_core::U256; +use sp_runtime::traits::UniqueSaturatedInto; +use sp_std::{ + cmp::{max, min}, + vec::Vec, +}; + +const MAX_LENGTH: u64 = 1024; +const MIN_GAS_COST: u64 = 200; + +struct ModexpPricer; + +impl ModexpPricer { + fn adjusted_exp_len(len: u64, exp_low: U256) -> u64 { + let bit_index = if exp_low.is_zero() { + 0 + } else { + (255 - exp_low.leading_zeros()) as u64 + }; + if len <= 32 { + bit_index + } else { + 8 * (len - 32) + bit_index + } + } + + fn mult_complexity(x: u64) -> u64 { + match x { + x if x <= 64 => x * x, + x if x <= 1024 => (x * x) / 4 + 96 * x - 3072, + x => (x * x) / 16 + 480 * x - 199_680, + } + } + + fn read_lengths(input: &[u8]) -> (U256, U256, U256) { + let mut input = Vec::from(input); + if input.len() < 96 { + input.resize_with(96, Default::default); + } + let base_len = U256::from_big_endian(&input[..32]); + let exp_len = U256::from_big_endian(&input[32..64]); + let mod_len = U256::from_big_endian(&input[64..96]); + (base_len, exp_len, mod_len) + } + + fn read_exp(input: &[u8], base_len: U256, exp_len: U256) -> U256 { + let input_len = input.len(); + let base_len = if base_len > U256::from(u32::MAX) { + return U256::zero(); + } else { + UniqueSaturatedInto::::unique_saturated_into(base_len) + }; + if base_len + 96 >= input_len as u64 { + U256::zero() + } else { + let exp_start = 96 + base_len as usize; + let remaining_len = input_len - exp_start; + let mut reader = Vec::from(&input[exp_start..exp_start + remaining_len]); + let len = if exp_len < U256::from(32) { + UniqueSaturatedInto::::unique_saturated_into(exp_len) + } else { + 32 + }; + + if reader.len() < len { + reader.resize_with(len, Default::default); + } + + let mut buf: Vec = Vec::new(); + buf.resize_with(32 - len, Default::default); + buf.extend(&reader[..min(len, remaining_len)]); + buf.resize_with(32, Default::default); + U256::from_big_endian(&buf[..]) + } + } + + fn cost(divisor: u64, input: &[u8]) -> U256 { + // read lengths as U256 here for accurate gas calculation. + let (base_len, exp_len, mod_len) = Self::read_lengths(input); + + if mod_len.is_zero() && base_len.is_zero() { + return U256::zero(); + } + + let max_len = U256::from(MAX_LENGTH - 96); + if base_len > max_len || mod_len > max_len || exp_len > max_len { + return U256::max_value(); + } + + // read fist 32-byte word of the exponent. + let exp_low = Self::read_exp(input, base_len, exp_len); + + let (base_len, exp_len, mod_len) = ( + base_len.unique_saturated_into(), + exp_len.unique_saturated_into(), + mod_len.unique_saturated_into(), + ); + + let m = max(mod_len, base_len); + + let adjusted_exp_len = Self::adjusted_exp_len(exp_len, exp_low); + + let (gas, overflow) = Self::mult_complexity(m).overflowing_mul(max(adjusted_exp_len, 1)); + if overflow { + return U256::max_value(); + } + + (gas / divisor).into() + } + + fn eip_2565_mul_complexity(base_length: U256, modulus_length: U256) -> U256 { + let max_length = max(base_length, modulus_length); + let words = { + // div_ceil(max_length, 8); + let tmp = max_length / 8; + if (max_length % 8).is_zero() { + tmp + } else { + tmp + 1 + } + }; + words.saturating_mul(words) + } + + fn eip_2565_iter_count(exponent_length: U256, exponent: U256) -> U256 { + let thirty_two = U256::from(32); + let it = if exponent_length <= thirty_two && exponent.is_zero() { + U256::zero() + } else if exponent_length <= thirty_two { + U256::from(exponent.bits()) - U256::from(1) + } else { + // else > 32 + U256::from(8) + .saturating_mul(exponent_length - thirty_two) + .saturating_add(U256::from(exponent.bits()).saturating_sub(U256::from(1))) + }; + max(it, U256::one()) + } + + fn eip_2565_cost( + divisor: U256, + base_length: U256, + modulus_length: U256, + exponent_length: U256, + exponent: U256, + ) -> U256 { + let multiplication_complexity = Self::eip_2565_mul_complexity(base_length, modulus_length); + let iteration_count = Self::eip_2565_iter_count(exponent_length, exponent); + max( + U256::from(MIN_GAS_COST), + multiplication_complexity.saturating_mul(iteration_count) / divisor, + ) + } +} + +// ModExp expects the following as inputs: +// 1) 32 bytes expressing the length of base +// 2) 32 bytes expressing the length of exponent +// 3) 32 bytes expressing the length of modulus +// 4) base, size as described above +// 5) exponent, size as described above +// 6) modulus, size as described above +// +// +// NOTE: input sizes are bound to 1024 bytes, with the expectation +// that gas limits would be applied before actual computation. +// +// maximum stack size will also prevent abuse. +// +// see: https://eips.ethereum.org/EIPS/eip-198 + +pub trait ModexpImpl { + const DIVISOR: u64; + const EIP_2565: bool; + + fn execute_modexp(input: &[u8]) -> Vec { + let mut reader = Vec::from(input); + if reader.len() < 96 { + reader.resize_with(96, Default::default); + } + // read lengths as u64. + // ignoring the first 24 bytes might technically lead us to fall out of consensus, + // but so would running out of addressable memory! + let mut buf = [0u8; 8]; + buf.copy_from_slice(&reader[24..32]); + let base_len = u64::from_be_bytes(buf); + buf.copy_from_slice(&reader[32 + 24..64]); + let exp_len = u64::from_be_bytes(buf); + buf.copy_from_slice(&reader[64 + 24..96]); + let mod_len = u64::from_be_bytes(buf); + + // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle + // empty base first. + let r = if base_len == 0 && mod_len == 0 { + BigUint::zero() + } else { + let total_len = 96 + base_len + exp_len + mod_len; + if total_len > MAX_LENGTH { + return [0u8; 1].to_vec(); + } + let mut reader = Vec::from(input); + if reader.len() < total_len as usize { + reader.resize_with(total_len as usize, Default::default); + } + // read the numbers themselves. + let base_end = 96 + base_len as usize; + let base = BigUint::from_bytes_be(&reader[96..base_end]); + let exp_end = base_end + exp_len as usize; + let exponent = BigUint::from_bytes_be(&reader[base_end..exp_end]); + let mod_end = exp_end + mod_len as usize; + let modulus = BigUint::from_bytes_be(&reader[exp_end..mod_end]); + + if modulus.is_zero() || modulus.is_one() { + BigUint::zero() + } else { + base.modpow(&exponent, &modulus) + } + }; + + // write output to given memory, left padded and same length as the modulus. + let bytes = r.to_bytes_be(); + + // always true except in the case of zero-length modulus, which leads to + // output of length and value 1. + if bytes.len() as u64 <= mod_len { + let mut ret = Vec::with_capacity(mod_len as usize); + ret.extend(core::iter::repeat(0).take(mod_len as usize - bytes.len())); + ret.extend_from_slice(&bytes[..]); + ret.to_vec() + } else { + [0u8; 0].to_vec() + } + } +} + +pub struct IstanbulModexp; +pub struct Modexp; + +impl ModexpImpl for IstanbulModexp { + const DIVISOR: u64 = 20; + const EIP_2565: bool = false; +} + +impl ModexpImpl for Modexp { + const DIVISOR: u64 = 3; + const EIP_2565: bool = true; +} + +impl Precompile for IstanbulModexp { + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + let input = handle.input(); + let target_gas = handle.gas_limit(); + + if input.len() as u64 > MAX_LENGTH { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + let cost = ModexpPricer::cost(Self::DIVISOR, input); + if let Some(target_gas) = target_gas { + if cost > U256::from(u64::MAX) || target_gas < cost.as_u64() { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + let output = Self::execute_modexp(input); + handle.record_cost(cost.as_u64())?; + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output, + }) + } + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let mut handle = crate::precompiles::tests::MockPrecompileHandle::new(&input, target_gas, context, is_static); + let output = Self::execute(&mut handle)?; + + Ok((output, handle.gas_used)) + } +} + +impl Precompile for Modexp { + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + let input = handle.input(); + let target_gas = handle.gas_limit(); + + if input.len() as u64 > MAX_LENGTH { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + + if let Some(target_gas) = target_gas { + if target_gas < MIN_GAS_COST { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + let (base_len, exp_len, mod_len) = ModexpPricer::read_lengths(input); + let exp = ModexpPricer::read_exp(input, base_len, exp_len); + let cost = ModexpPricer::eip_2565_cost(U256::from(Self::DIVISOR), base_len, mod_len, exp_len, exp); + if let Some(target_gas) = target_gas { + if cost > U256::from(u64::MAX) || target_gas < cost.as_u64() { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + let output = Self::execute_modexp(input); + handle.record_cost(cost.as_u64())?; + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output, + }) + } + + #[cfg(feature = "evm-tests")] + fn execute_ext( + input: &[u8], + target_gas: Option, + context: &crate::Context, + is_static: bool, + ) -> Result<(PrecompileOutput, u64), PrecompileFailure> { + let mut handle = crate::precompiles::tests::MockPrecompileHandle::new(&input, target_gas, context, is_static); + let output = Self::execute(&mut handle)?; + + Ok((output, handle.gas_used)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::precompiles::tests::MockPrecompileHandle; + use hex_literal::hex; + use module_evm_utility::evm::Context; + + fn get_context() -> Context { + Context { + address: Default::default(), + caller: Default::default(), + apparent_value: U256::zero(), + } + } + + #[test] + fn handle_min_gas() { + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new(&[], Some(199), &get_context(), false)), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas + }) + ); + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new(&[], Some(200), &get_context(), false)), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: [0u8; 0].to_vec(), + }) + ); + } + + #[test] + fn test_empty_input() { + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new(&[], None, &get_context(), false)), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: [0u8; 0].to_vec(), + }) + ); + } + + #[test] + fn test_insufficient_input() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + "}; + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new(&input, None, &get_context(), false)), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: [0u8; 1].to_vec(), + }) + ); + } + + #[test] + fn test_excessive_input() { + let input = hex! {" + 1000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + "}; + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + } + + #[test] + fn exp_len_overflow() { + let input = hex! {" + 00000000000000000000000000000000000000000000000000000000000000ff + 2a1e530000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + "}; + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + } + + #[test] + fn gas_cost_multiplication_overflow() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 000000000000000000000000000000000000000000000000000000003b27bafd + 00000000000000000000000000000000000000000000000000000000503c8ac3 + "}; + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + } + + #[test] + fn test_simple_inputs() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000001 + 03 + 05 + 07 + "}; + + // 3 ^ 5 % 7 == 5 + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: vec![5], + }) + ); + } + + #[test] + fn test_large_inputs() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000EA5F + 0000000000000000000000000000000000000000000000000000000000000015 + 0000000000000000000000000000000000000000000000000000000000003874 + "}; + + // 59999 ^ 21 % 14452 = 10055 + + let mut output = [0u8; 32]; + U256::from(10055u64).to_big_endian(&mut output); + + assert_eq!( + IstanbulModexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: output.to_vec(), + }) + ); + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: output.to_vec(), + }) + ); + } + + #[test] + fn test_large_computation() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 03 + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e + fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f + "}; + + let mut output = [0u8; 32]; + U256::from(1u64).to_big_endian(&mut output); + + assert_eq!( + IstanbulModexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: output.to_vec(), + }) + ); + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: output.to_vec(), + }) + ); + } + + #[test] + fn zero_padding() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000020 + 03 + ffff + 80 + "}; + + let expected = hex!("3b01b01ac41f2d6e917c6d6a221ce793802469026d9ab7578fa2e79e4da6aaab"); + + assert_eq!( + IstanbulModexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: expected.to_vec(), + }) + ); + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: expected.to_vec(), + }) + ); + } + + #[test] + fn zero_length_modulus() { + let input = hex! {" + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + 03 + ffff + "}; + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: [0u8; 0].to_vec(), + }) + ); + } + + #[test] + fn test_zero_exp_with_33_length() { + // This is a regression test which ensures that the 'iteration_count' calculation + // in 'calculate_iteration_count' cannot underflow. + // + // In debug mode, this underflow could cause a panic. Otherwise, it causes N**0 to + // be calculated at more-than-normal expense. + // + // TODO: cite security advisory + + let input = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: [0u8; 1].to_vec(), + }) + ); + } + + #[test] + fn large_input() { + let input = vec![0u8; 1025]; + + assert_eq!( + IstanbulModexp::execute(&mut MockPrecompileHandle::new( + &input[..1024], + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: [0u8; 0].to_vec(), + }) + ); + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input[..1024], + Some(100_000), + &get_context(), + false + )), + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: [0u8; 0].to_vec(), + }) + ); + + assert_eq!( + IstanbulModexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + + assert_eq!( + Modexp::execute(&mut MockPrecompileHandle::new( + &input, + Some(100_000), + &get_context(), + false + )), + Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }) + ); + } +} diff --git a/blockchain/modules/evm/src/precompiles/ripemd.rs b/blockchain/modules/evm/src/precompiles/ripemd.rs new file mode 100644 index 00000000..b1c9307f --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/ripemd.rs @@ -0,0 +1,39 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::LinearCostPrecompile; +use crate::PrecompileFailure; +use module_evm_utility::evm::ExitSucceed; +use sha3::Digest; +use sp_std::vec::Vec; + +/// The ripemd precompile. +pub struct Ripemd160; + +impl LinearCostPrecompile for Ripemd160 { + const BASE: u64 = 600; + const WORD: u64 = 120; + + fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut ret = [0u8; 32]; + ret[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input)); + Ok((ExitSucceed::Returned, ret.to_vec())) + } +} diff --git a/blockchain/modules/evm/src/precompiles/sha256.rs b/blockchain/modules/evm/src/precompiles/sha256.rs new file mode 100644 index 00000000..962a7187 --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/sha256.rs @@ -0,0 +1,37 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::LinearCostPrecompile; +use crate::PrecompileFailure; +use module_evm_utility::evm::ExitSucceed; +use sp_std::vec::Vec; + +/// The sha256 precompile. +pub struct Sha256; + +impl LinearCostPrecompile for Sha256 { + const BASE: u64 = 60; + const WORD: u64 = 12; + + fn execute(input: &[u8], _cost: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let ret = sp_io::hashing::sha2_256(input); + Ok((ExitSucceed::Returned, ret.to_vec())) + } +} diff --git a/blockchain/modules/evm/src/precompiles/sha3fips.rs b/blockchain/modules/evm/src/precompiles/sha3fips.rs new file mode 100644 index 00000000..d4703170 --- /dev/null +++ b/blockchain/modules/evm/src/precompiles/sha3fips.rs @@ -0,0 +1,149 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 super::LinearCostPrecompile; +use crate::PrecompileFailure; +use module_evm_utility::evm::ExitSucceed; +use sp_std::vec::Vec; +use tiny_keccak::Hasher; + +/// The Sha3FIPS256 precompile. +pub struct Sha3FIPS256; + +impl LinearCostPrecompile for Sha3FIPS256 { + const BASE: u64 = 60; + const WORD: u64 = 12; + + fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut output = [0; 32]; + let mut sha3 = tiny_keccak::Sha3::v256(); + sha3.update(input); + sha3.finalize(&mut output); + Ok((ExitSucceed::Returned, output.to_vec())) + } +} + +/// The Sha3FIPS512 precompile. +pub struct Sha3FIPS512; + +impl LinearCostPrecompile for Sha3FIPS512 { + const BASE: u64 = 60; + const WORD: u64 = 12; + + fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { + let mut output = [0; 64]; + let mut sha3 = tiny_keccak::Sha3::v512(); + sha3.update(input); + sha3.finalize(&mut output); + Ok((ExitSucceed::Returned, output.to_vec())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use module_evm_utility::evm::ExitError; + + #[test] + fn test_empty_input() -> std::result::Result<(), ExitError> { + let input: [u8; 0] = []; + let expected = b"\ + \xa7\xff\xc6\xf8\xbf\x1e\xd7\x66\x51\xc1\x47\x56\xa0\x61\xd6\x62\ + \xf5\x80\xff\x4d\xe4\x3b\x49\xfa\x82\xd8\x0a\x4b\x80\xf8\x43\x4a\ + "; + + let cost: u64 = 1; + + match ::execute(&input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } + + #[test] + fn hello_sha3_256() -> std::result::Result<(), ExitError> { + let input = b"hello"; + let expected = b"\ + \x33\x38\xbe\x69\x4f\x50\xc5\xf3\x38\x81\x49\x86\xcd\xf0\x68\x64\ + \x53\xa8\x88\xb8\x4f\x42\x4d\x79\x2a\xf4\xb9\x20\x23\x98\xf3\x92\ + "; + + let cost: u64 = 1; + + match ::execute(input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } + + #[test] + fn long_string_sha3_256() -> std::result::Result<(), ExitError> { + let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + let expected = b"\ + \xbd\xe3\xf2\x69\x17\x5e\x1d\xcd\xa1\x38\x48\x27\x8a\xa6\x04\x6b\ + \xd6\x43\xce\xa8\x5b\x84\xc8\xb8\xbb\x80\x95\x2e\x70\xb6\xea\xe0\ + "; + + let cost: u64 = 1; + + match ::execute(input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } + + #[test] + fn long_string_sha3_512() -> std::result::Result<(), ExitError> { + let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + let expected = b"\ + \xf3\x2a\x94\x23\x55\x13\x51\xdf\x0a\x07\xc0\xb8\xc2\x0e\xb9\x72\ + \x36\x7c\x39\x8d\x61\x06\x60\x38\xe1\x69\x86\x44\x8e\xbf\xbc\x3d\ + \x15\xed\xe0\xed\x36\x93\xe3\x90\x5e\x9a\x8c\x60\x1d\x9d\x00\x2a\ + \x06\x85\x3b\x97\x97\xef\x9a\xb1\x0c\xbd\xe1\x00\x9c\x7d\x0f\x09\ + "; + + let cost: u64 = 1; + + match ::execute(input, cost) { + Ok((_, out)) => { + assert_eq!(out, expected); + Ok(()) + } + Err(e) => { + panic!("Test not expected to fail: {:?}", e); + } + } + } +} diff --git a/blockchain/modules/evm/src/runner/mod.rs b/blockchain/modules/evm/src/runner/mod.rs index 6571a49c..5fc56e94 100644 --- a/blockchain/modules/evm/src/runner/mod.rs +++ b/blockchain/modules/evm/src/runner/mod.rs @@ -1,99 +1,101 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 stack; -pub mod state; -pub mod storage_meter; - -use crate::{BalanceOf, CallInfo, Config, CreateInfo, ExitError}; -use evm::{backend::Backend, Transfer}; -use frame_support::dispatch::DispatchError; -pub use primitives::{ - evm::{Account, EvmAddress, Log, Vicinity}, - ReserveIdentifier, MIRRORED_NFT_ADDRESS_START, -}; -use sp_core::{H160, H256}; -use sp_std::vec::Vec; -use state::StackSubstateMetadata; - -pub trait Runner { - fn call( - source: H160, - origin: H160, - target: H160, - input: Vec, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result; - - fn create( - source: H160, - init: Vec, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result; - - fn create2( - source: H160, - init: Vec, - salt: H256, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result; - - fn create_at_address( - source: H160, - address: H160, - init: Vec, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result; -} - -pub trait StackState<'config>: Backend { - fn metadata(&self) -> &StackSubstateMetadata<'config>; - fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config>; - - fn enter(&mut self, gas_limit: u64, is_static: bool); - fn exit_commit(&mut self) -> Result<(), ExitError>; - fn exit_revert(&mut self) -> Result<(), ExitError>; - fn exit_discard(&mut self) -> Result<(), ExitError>; - - fn is_empty(&self, address: H160) -> bool; - fn deleted(&self, address: H160) -> bool; - - fn inc_nonce(&mut self, address: H160); - fn set_storage(&mut self, address: H160, key: H256, value: H256); - fn reset_storage(&mut self, address: H160); - fn log(&mut self, address: H160, topics: Vec, data: Vec); - fn set_deleted(&mut self, address: H160); - fn set_code(&mut self, address: H160, code: Vec); - fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError>; - fn reset_balance(&mut self, address: H160); - fn touch(&mut self, address: H160); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 stack; +pub mod state; +pub mod storage_meter; +pub mod tagged_runtime; + +use crate::{BalanceOf, CallInfo, Config, CreateInfo}; +use module_evm_utility::evm; +pub use primitives::evm::{EvmAddress, Vicinity}; +use sp_core::{H160, H256}; +use sp_runtime::DispatchError; +use sp_std::vec::Vec; + +pub trait Runner { + fn call( + source: H160, + origin: H160, + target: H160, + input: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result; + + fn create( + source: H160, + init: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result; + + fn create2( + source: H160, + init: Vec, + salt: H256, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result; + + fn create_at_address( + source: H160, + address: H160, + init: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result; +} + +pub trait RunnerExtended: Runner { + fn rpc_call( + source: H160, + origin: H160, + target: H160, + input: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result; + + fn rpc_create( + source: H160, + init: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result; +} diff --git a/blockchain/modules/evm/src/runner/stack.rs b/blockchain/modules/evm/src/runner/stack.rs index 89aafb7d..472d76e1 100644 --- a/blockchain/modules/evm/src/runner/stack.rs +++ b/blockchain/modules/evm/src/runner/stack.rs @@ -1,686 +1,929 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! EVM stack-based runner. -// Synchronize with https://github.com/paritytech/frontier/blob/master/frame/evm/src/runner/stack.rs - -use crate::{ - precompiles::PrecompileSet, - runner::{ - state::{StackExecutor, StackSubstateMetadata}, - Runner as RunnerT, StackState as StackStateT, - }, - AccountInfo, AccountStorages, Accounts, BalanceOf, CallInfo, Config, ContractStorageSizes, CreateInfo, Error, - Event, ExecutionInfo, One, Pallet, STORAGE_SIZE, -}; -use evm::{backend::Backend as BackendT, ExitError, ExitReason, Transfer}; -use frame_support::{ - dispatch::DispatchError, - ensure, log, - traits::{Currency, ExistenceRequirement, Get}, -}; -use module_support::AddressMapping; -pub use primitives::{ - evm::{Account, EvmAddress, Log, Vicinity}, - ReserveIdentifier, MIRRORED_NFT_ADDRESS_START, -}; -use sha3::{Digest, Keccak256}; -use sp_core::{H160, H256, U256}; -use sp_io::KillStorageResult::{AllRemoved, SomeRemaining}; -use sp_runtime::traits::UniqueSaturatedInto; -use sp_std::{boxed::Box, collections::btree_set::BTreeSet, marker::PhantomData, mem, vec::Vec}; - -#[derive(Default)] -pub struct Runner { - _marker: PhantomData, -} - -impl Runner { - /// Execute an EVM operation. - pub fn execute<'config, F, R>( - source: H160, - origin: H160, - value: U256, - gas_limit: u64, - storage_limit: u32, - config: &'config evm::Config, - f: F, - ) -> Result, sp_runtime::DispatchError> - where - F: FnOnce(&mut StackExecutor<'config, SubstrateStackState<'_, 'config, T>>) -> (ExitReason, R), - { - let gas_price = U256::one(); - let vicinity = Vicinity { gas_price, origin }; - - let metadata = StackSubstateMetadata::new(gas_limit, storage_limit, T::NewContractExtraBytes::get(), config); - let state = SubstrateStackState::new(&vicinity, metadata); - let mut executor = StackExecutor::new_with_precompile(state, config, T::Precompiles::execute); - - // NOTE: charge from transaction-payment - // let total_fee = gas_price - // .checked_mul(U256::from(gas_limit)) - // .ok_or(Error::::FeeOverflow)?; - // let total_payment = value.checked_add(total_fee).ok_or(Error::::PaymentOverflow)?; - // let source_account = Pallet::::account_basic(&source); - // ensure!(source_account.balance >= total_payment, Error::::BalanceLow); - - // Deduct fee from the `source` account. - // let fee = T::ChargeTransactionPayment::withdraw_fee(&source, total_fee)?; - - if !config.estimate { - Pallet::::reserve_storage(&origin, storage_limit).map_err(|e| { - log::debug!( - target: "evm", - "ReserveStorageFailed {:?} [source: {:?}, storage_limit: {:?}]", - e, - origin, - storage_limit - ); - Error::::ReserveStorageFailed - })?; - } - - // Execute the EVM call. - let (reason, retv) = f(&mut executor); - - let used_gas = U256::from(executor.used_gas()); - let actual_fee = executor.fee(gas_price); - log::debug!( - target: "evm", - "Execution {:?} [source: {:?}, value: {}, gas_limit: {}, actual_fee: {}]", - reason, - source, - value, - gas_limit, - actual_fee - ); - - // NOTE: refund from transaction-payment - // Refund fees to the `source` account if deducted more before, - // T::OnChargeTransaction::correct_and_deposit_fee(&source, actual_fee, fee)?; - - let state = executor.into_state(); - - // charge storage - let actual_storage = state - .metadata() - .storage_meter() - .finish() - .ok_or(Error::::OutOfStorage)?; - let used_storage = state.metadata().storage_meter().total_used(); - let refunded_storage = state.metadata().storage_meter().total_refunded(); - for (target, storage) in &state.substate.storage_logs { - if !config.estimate { - Pallet::::charge_storage(&origin, target, *storage).map_err(|e| { - log::debug!( - target: "evm", - "ChargeStorageFailed {:?} [source: {:?}, target: {:?}, storage: {:?}]", - e, - origin, - target, - storage - ); - Error::::ChargeStorageFailed - })?; - } - } - if !config.estimate { - Pallet::::unreserve_storage(&origin, storage_limit, used_storage, refunded_storage).map_err(|e| { - log::debug!( - target: "evm", - "UnreserveStorageFailed {:?} [source: {:?}, storage_limit: {:?}, used_storage: {:?}, refunded_storage: {:?}]", - e, - origin, - storage_limit, - used_storage, - refunded_storage - ); - Error::::UnreserveStorageFailed - })?; - } - - for address in state.substate.deletes { - log::debug!( - target: "evm", - "Deleting account at {:?}", - address - ); - Pallet::::remove_contract(&address).map_err(|e| { - log::debug!( - target: "evm", - "CannotKillContract address {:?}, reason: {:?}", - address, - e - ); - Error::::CannotKillContract - })?; - } - - log::debug!( - target: "evm", - "Execution logs {:?}", - state.substate.logs - ); - - Ok(ExecutionInfo { - value: retv, - exit_reason: reason, - used_gas, - used_storage: actual_storage, - logs: state.substate.logs, - }) - } -} - -impl RunnerT for Runner { - fn call( - source: H160, - origin: H160, - target: H160, - input: Vec, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result { - // if the contract not deployed, the caller must be developer or contract or maintainer. - // if the contract not exists, let evm try to execute it and handle the error. - ensure!( - Pallet::::can_call_contract(&target, &source), - Error::::NoPermission - ); - - let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); - let info = Self::execute(source, origin, value, gas_limit, storage_limit, config, |executor| { - executor.transact_call(source, target, value, input, gas_limit) - })?; - - if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Executed(source, target, info.logs.clone())); - } else { - Pallet::::deposit_event(Event::::ExecutedFailed( - source, - target, - info.exit_reason.clone(), - info.value.clone(), - info.logs.clone(), - )); - } - - Ok(info) - } - - fn create( - source: H160, - init: Vec, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result { - let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); - let info = Self::execute(source, source, value, gas_limit, storage_limit, config, |executor| { - let address = executor - .create_address(evm::CreateScheme::Legacy { caller: source }) - .unwrap_or_default(); // transact_create will check the address - (executor.transact_create(source, value, init, gas_limit), address) - })?; - - if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Created(source, info.value, info.logs.clone())); - } else { - Pallet::::deposit_event(Event::::CreatedFailed( - source, - info.value, - info.exit_reason.clone(), - info.logs.clone(), - )); - } - - Ok(info) - } - - fn create2( - source: H160, - init: Vec, - salt: H256, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result { - let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); - let code_hash = H256::from_slice(Keccak256::digest(&init).as_slice()); - let info = Self::execute(source, source, value, gas_limit, storage_limit, config, |executor| { - let address = executor - .create_address(evm::CreateScheme::Create2 { - caller: source, - code_hash, - salt, - }) - .unwrap_or_default(); // transact_create2 will check the address - (executor.transact_create2(source, value, init, salt, gas_limit), address) - })?; - - if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Created(source, info.value, info.logs.clone())); - } else { - Pallet::::deposit_event(Event::::CreatedFailed( - source, - info.value, - info.exit_reason.clone(), - info.logs.clone(), - )); - } - - Ok(info) - } - - fn create_at_address( - source: H160, - address: H160, - init: Vec, - value: BalanceOf, - gas_limit: u64, - storage_limit: u32, - config: &evm::Config, - ) -> Result { - let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); - let info = Self::execute(source, source, value, gas_limit, storage_limit, config, |executor| { - ( - executor.transact_create_at_address(source, address, value, init, gas_limit), - address, - ) - })?; - - if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Created(source, info.value, info.logs.clone())); - } else { - Pallet::::deposit_event(Event::::CreatedFailed( - source, - info.value, - info.exit_reason.clone(), - info.logs.clone(), - )); - } - - Ok(info) - } -} - -struct SubstrateStackSubstate<'config> { - metadata: StackSubstateMetadata<'config>, - deletes: BTreeSet, - logs: Vec, - storage_logs: Vec<(H160, i32)>, - parent: Option>>, -} - -impl<'config> SubstrateStackSubstate<'config> { - pub fn metadata(&self) -> &StackSubstateMetadata<'config> { - &self.metadata - } - - pub fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> { - &mut self.metadata - } - - pub fn enter(&mut self, gas_limit: u64, is_static: bool) { - let mut entering = Self { - metadata: self.metadata.spit_child(gas_limit, is_static), - parent: None, - deletes: BTreeSet::new(), - logs: Vec::new(), - storage_logs: Vec::new(), - }; - mem::swap(&mut entering, self); - - self.parent = Some(Box::new(entering)); - - sp_io::storage::start_transaction(); - } - - pub fn exit_commit(&mut self) -> Result<(), ExitError> { - let mut exited = *self.parent.take().expect("Cannot commit on root substate"); - mem::swap(&mut exited, self); - - let target = self.metadata().target().expect("Storage target is none"); - let storage = exited.metadata().storage_meter().used_storage(); - - self.metadata.swallow_commit(exited.metadata).map_err(|e| { - sp_io::storage::rollback_transaction(); - e - })?; - self.logs.append(&mut exited.logs); - self.deletes.append(&mut exited.deletes); - - exited.storage_logs.push((target, storage)); - self.storage_logs.append(&mut exited.storage_logs); - - sp_io::storage::commit_transaction(); - Ok(()) - } - - pub fn exit_revert(&mut self) -> Result<(), ExitError> { - let mut exited = *self.parent.take().expect("Cannot discard on root substate"); - mem::swap(&mut exited, self); - self.metadata.swallow_revert(exited.metadata).map_err(|e| { - sp_io::storage::rollback_transaction(); - e - })?; - - sp_io::storage::rollback_transaction(); - Ok(()) - } - - pub fn exit_discard(&mut self) -> Result<(), ExitError> { - let mut exited = *self.parent.take().expect("Cannot discard on root substate"); - mem::swap(&mut exited, self); - self.metadata.swallow_discard(exited.metadata).map_err(|e| { - sp_io::storage::rollback_transaction(); - e - })?; - - sp_io::storage::rollback_transaction(); - Ok(()) - } - - pub fn deleted(&self, address: H160) -> bool { - if self.deletes.contains(&address) { - return true; - } - - if let Some(parent) = self.parent.as_ref() { - return parent.deleted(address); - } - - false - } - - pub fn set_deleted(&mut self, address: H160) { - self.deletes.insert(address); - } - - pub fn log(&mut self, address: H160, topics: Vec, data: Vec) { - self.logs.push(Log { address, topics, data }); - } -} - -/// Substrate backend for EVM. -pub struct SubstrateStackState<'vicinity, 'config, T> { - vicinity: &'vicinity Vicinity, - substate: SubstrateStackSubstate<'config>, - _marker: PhantomData, -} - -impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { - /// Create a new backend with given vicinity. - pub fn new(vicinity: &'vicinity Vicinity, metadata: StackSubstateMetadata<'config>) -> Self { - Self { - vicinity, - substate: SubstrateStackSubstate { - metadata, - deletes: BTreeSet::new(), - logs: Vec::new(), - storage_logs: Vec::new(), - parent: None, - }, - _marker: PhantomData, - } - } -} - -impl<'vicinity, 'config, T: Config> BackendT for SubstrateStackState<'vicinity, 'config, T> { - fn gas_price(&self) -> U256 { - self.vicinity.gas_price - } - fn origin(&self) -> H160 { - self.vicinity.origin - } - - fn block_hash(&self, number: U256) -> H256 { - if number > U256::from(u32::max_value()) { - H256::default() - } else { - let number = T::BlockNumber::from(number.as_u32()); - H256::from_slice(frame_system::Pallet::::block_hash(number).as_ref()) - } - } - - fn block_number(&self) -> U256 { - let number: u128 = frame_system::Pallet::::block_number().unique_saturated_into(); - U256::from(number) - } - - fn block_coinbase(&self) -> H160 { - Pallet::::find_author() - } - - fn block_timestamp(&self) -> U256 { - let now: u128 = pallet_timestamp::Pallet::::get().unique_saturated_into(); - U256::from(now / 1000) - } - - fn block_difficulty(&self) -> U256 { - U256::zero() - } - - fn block_gas_limit(&self) -> U256 { - U256::zero() - } - - fn chain_id(&self) -> U256 { - U256::from(T::ChainId::get()) - } - - fn exists(&self, _address: H160) -> bool { - true - } - - fn basic(&self, address: H160) -> evm::backend::Basic { - let account = Pallet::::account_basic(&address); - - evm::backend::Basic { - balance: account.balance, - nonce: account.nonce, - } - } - - fn code(&self, address: H160) -> Vec { - Pallet::::code_at_address(&address).into_inner() - } - - fn storage(&self, address: H160, index: H256) -> H256 { - >::get(address, index) - } - - fn original_storage(&self, _address: H160, _index: H256) -> Option { - None - } -} - -impl<'vicinity, 'config, T: Config> StackStateT<'config> for SubstrateStackState<'vicinity, 'config, T> { - fn metadata(&self) -> &StackSubstateMetadata<'config> { - self.substate.metadata() - } - - fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> { - self.substate.metadata_mut() - } - - fn enter(&mut self, gas_limit: u64, is_static: bool) { - self.substate.enter(gas_limit, is_static) - } - - fn exit_commit(&mut self) -> Result<(), ExitError> { - self.substate.exit_commit() - } - - fn exit_revert(&mut self) -> Result<(), ExitError> { - self.substate.exit_revert() - } - - fn exit_discard(&mut self) -> Result<(), ExitError> { - self.substate.exit_discard() - } - - fn is_empty(&self, address: H160) -> bool { - Pallet::::is_account_empty(&address) - } - - fn deleted(&self, address: H160) -> bool { - self.substate.deleted(address) - } - - fn inc_nonce(&mut self, address: H160) { - Accounts::::mutate(&address, |maybe_account| { - if let Some(account) = maybe_account.as_mut() { - account.nonce += One::one() - } else { - let mut account_info = >::new(Default::default(), None); - account_info.nonce += One::one(); - *maybe_account = Some(account_info); - } - }); - } - - fn set_storage(&mut self, address: H160, index: H256, value: H256) { - if value == H256::default() { - log::debug!( - target: "evm", - "Removing storage for {:?} [index: {:?}]", - address, - index, - ); - >::remove(address, index); - Pallet::::update_contract_storage_size(&address, -(STORAGE_SIZE as i32)); - self.substate.metadata.storage_meter_mut().refund(STORAGE_SIZE); - } else { - log::debug!( - target: "evm", - "Updating storage for {:?} [index: {:?}, value: {:?}]", - address, - index, - value, - ); - >::insert(address, index, value); - Pallet::::update_contract_storage_size(&address, STORAGE_SIZE as i32); - self.substate.metadata.storage_meter_mut().charge(STORAGE_SIZE); - } - } - - fn reset_storage(&mut self, address: H160) { - match >::remove_prefix(address, None) { - AllRemoved(count) | SomeRemaining(count) => { - // should not happen - let storage = count.saturating_mul(STORAGE_SIZE); - Pallet::::update_contract_storage_size(&address, -(storage as i32)); - self.substate.metadata.storage_meter_mut().refund(storage); - } - } - } - - fn log(&mut self, address: H160, topics: Vec, data: Vec) { - self.substate.log(address, topics, data) - } - - fn set_deleted(&mut self, address: H160) { - self.substate.set_deleted(address); - let size = ContractStorageSizes::::get(address); - self.substate.metadata.storage_meter_mut().refund(size); - } - - fn set_code(&mut self, address: H160, code: Vec) { - log::debug!( - target: "evm", - "Inserting code ({} bytes) at {:?}", - code.len(), - address - ); - - let caller: H160; - let mut substate = &self.substate; - - loop { - if let Some(c) = substate.metadata().caller() { - // the caller maybe is contract and not deployed. - // get the parent's maintainer. - if Pallet::::is_account_empty(c) { - if substate.parent.is_none() { - log::error!( - target: "evm", - "get parent's maintainer failed. caller: {:?}, address: {:?}", - c, - address - ); - debug_assert!(false); - return; - } - substate = substate.parent.as_ref().expect("has checked; qed"); - } else { - caller = *c; - break; - } - } else { - log::error!( - target: "evm", - "get maintainer failed. address: {:?}", - address - ); - debug_assert!(false); - return; - } - } - - Pallet::::create_contract(caller, address, code); - } - - fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError> { - if transfer.value.is_zero() { - return Ok(()); - } - let source = T::AddressMapping::get_account_id(&transfer.source); - let target = T::AddressMapping::get_account_id(&transfer.target); - - T::Currency::transfer( - &source, - &target, - transfer.value.low_u128().unique_saturated_into(), - ExistenceRequirement::AllowDeath, - ) - .map_err(|e| ExitError::Other(Into::<&str>::into(e).into())) - } - - fn reset_balance(&mut self, _address: H160) { - // Do nothing on reset balance in Substrate. - // - // This function exists in EVM because a design issue - // (arguably a bug) in SELFDESTRUCT that can cause total - // issurance to be reduced. We do not need to replicate this. - } - - fn touch(&mut self, _address: H160) { - // Do nothing on touch in Substrate. - // - // EVM pallet considers all accounts to exist, and distinguish - // only empty and non-empty accounts. This avoids many of the - // subtle issues in EIP-161. - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! EVM stack-based runner. +// Synchronize with https://github.com/paritytech/frontier/blob/bcae569524/frame/evm/src/runner/stack.rs + +use crate::{ + runner::{ + state::{Accessed, StackExecutor, StackState as StackStateT, StackSubstateMetadata}, + Runner as RunnerT, RunnerExtended, + }, + AccountStorages, BalanceOf, CallInfo, Config, CreateInfo, Error, ExecutionInfo, Pallet, STORAGE_SIZE, +}; +use frame_support::{ + ensure, + traits::{Currency, ExistenceRequirement, Get}, + transactional, +}; +use frame_system::pallet_prelude::*; +use module_evm_utility::{ + ethereum::Log, + evm::{self, backend::Backend as BackendT, ExitError, ExitReason, Transfer}, +}; +use module_support::{AddressMapping, EVM}; +pub use primitives::{ + evm::{convert_decimals_from_evm, EvmAddress, Vicinity, MIRRORED_NFT_ADDRESS_START}, + ReserveIdentifier, +}; +use sp_core::{defer, H160, H256, U256}; +use sp_runtime::{ + traits::{UniqueSaturatedInto, Zero}, + DispatchError, +}; +use sp_std::{ + boxed::Box, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + marker::PhantomData, + mem, + vec::Vec, +}; + +#[derive(Default)] +pub struct Runner { + _marker: PhantomData, +} + +impl Runner { + /// Execute an EVM operation. + pub fn execute<'config, 'precompiles, F, R>( + source: H160, + origin: H160, + value: U256, + gas_limit: u64, + storage_limit: u32, + config: &'config evm::Config, + skip_storage_rent: bool, + precompiles: &'precompiles T::PrecompilesType, + f: F, + ) -> Result, sp_runtime::DispatchError> + where + F: FnOnce( + &mut StackExecutor<'config, 'precompiles, SubstrateStackState<'_, 'config, T>, T::PrecompilesType>, + ) -> (ExitReason, R), + { + let gas_price = U256::one(); + let vicinity = Vicinity { + gas_price, + origin, + ..Default::default() + }; + + let metadata = StackSubstateMetadata::new(gas_limit, storage_limit, config); + let state = SubstrateStackState::new(&vicinity, metadata); + let mut executor = StackExecutor::new_with_precompiles(state, config, precompiles); + + ensure!( + convert_decimals_from_evm( + TryInto::>::try_into(value).map_err(|_| Error::::InvalidDecimals)? + ) + .is_some(), + Error::::InvalidDecimals + ); + + if !skip_storage_rent { + Pallet::::reserve_storage(&origin, storage_limit).map_err(|e| { + log::debug!( + target: "evm", + "ReserveStorageFailed {:?} [source: {:?}, storage_limit: {:?}]", + e, + origin, + storage_limit + ); + Error::::ReserveStorageFailed + })?; + } + + // Execute the EVM call. + let (reason, retv) = f(&mut executor); + + let used_gas = U256::from(executor.used_gas()); + log::debug!( + target: "evm", + "Execution {:?} [source: {:?}, value: {}, gas_limit: {}, used_gas: {}]", + reason, + source, + value, + gas_limit, + used_gas, + ); + + let state = executor.into_state(); + + // charge storage + let actual_storage = state + .metadata() + .storage_meter() + .finish() + .ok_or(Error::::OutOfStorage)?; + let used_storage = state.metadata().storage_meter().total_used(); + let refunded_storage = state.metadata().storage_meter().total_refunded(); + log::debug!( + target: "evm", + "Storage logs: {:?}", + state.substate.storage_logs + ); + let mut sum_storage: i32 = 0; + for (target, storage) in &state.substate.storage_logs.into_iter().fold( + BTreeMap::::new(), + |mut bmap, (target, storage)| { + bmap.entry(target) + .and_modify(|x| *x = x.saturating_add(storage)) + .or_insert(storage); + bmap + }, + ) { + if !skip_storage_rent { + Pallet::::charge_storage(&origin, target, *storage).map_err(|e| { + log::debug!( + target: "evm", + "ChargeStorageFailed {:?} [source: {:?}, target: {:?}, storage: {:?}]", + e, + origin, + target, + storage + ); + Error::::ChargeStorageFailed + })?; + } + sum_storage = sum_storage.saturating_add(*storage); + } + if actual_storage != sum_storage { + log::debug!( + target: "evm", + "ChargeStorageFailed [actual_storage: {:?}, sum_storage: {:?}]", + actual_storage, sum_storage + ); + return Err(Error::::ChargeStorageFailed.into()); + } + + if !skip_storage_rent { + Pallet::::unreserve_storage(&origin, storage_limit, used_storage, refunded_storage).map_err(|e| { + log::debug!( + target: "evm", + "UnreserveStorageFailed {:?} [source: {:?}, storage_limit: {:?}, used_storage: {:?}, refunded_storage: {:?}]", + e, + origin, + storage_limit, + used_storage, + refunded_storage + ); + Error::::UnreserveStorageFailed + })?; + } + + for address in state.substate.deletes { + log::debug!( + target: "evm", + "Deleting account at {:?}", + address + ); + Pallet::::remove_contract(&origin, &address).map_err(|e| { + log::debug!( + target: "evm", + "CannotKillContract address {:?}, reason: {:?}", + address, + e + ); + Error::::CannotKillContract + })?; + } + + log::debug!( + target: "evm", + "Execution logs {:?}", + state.substate.logs + ); + + Ok(ExecutionInfo { + value: retv, + exit_reason: reason, + used_gas, + used_storage: actual_storage, + logs: state.substate.logs, + }) + } +} + +impl RunnerT for Runner { + /// Require transactional here. Always need to send events. + #[transactional] + fn call( + source: H160, + origin: H160, + target: H160, + input: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result { + // if the contract not published, the caller must be developer or contract or maintainer. + // if the contract not exists, let evm try to execute it and handle the error. + ensure!( + Pallet::::can_call_contract(&target, &source), + Error::::NoPermission + ); + + let precompiles = T::PrecompilesValue::get(); + let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); + Self::execute( + source, + origin, + value, + gas_limit, + storage_limit, + config, + false, + &precompiles, + |executor| executor.transact_call(source, target, value, input, gas_limit, access_list), + ) + } + + /// Require transactional here. Always need to send events. + #[transactional] + fn create( + source: H160, + init: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result { + let precompiles = T::PrecompilesValue::get(); + let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); + Self::execute( + source, + source, + value, + gas_limit, + storage_limit, + config, + false, + &precompiles, + |executor| { + let address = executor + .create_address(evm::CreateScheme::Legacy { caller: source }) + .unwrap_or_default(); // transact_create will check the address + let (reason, _) = executor.transact_create(source, value, init, gas_limit, access_list); + (reason, address) + }, + ) + } + + /// Require transactional here. Always need to send events. + #[transactional] + fn create2( + source: H160, + init: Vec, + salt: H256, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result { + let precompiles = T::PrecompilesValue::get(); + let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); + let code_hash = H256::from(sp_io::hashing::keccak_256(&init)); + Self::execute( + source, + source, + value, + gas_limit, + storage_limit, + config, + false, + &precompiles, + |executor| { + let address = executor + .create_address(evm::CreateScheme::Create2 { + caller: source, + code_hash, + salt, + }) + .unwrap_or_default(); // transact_create2 will check the address + let (reason, _) = executor.transact_create2(source, value, init, salt, gas_limit, access_list); + (reason, address) + }, + ) + } + + /// Require transactional here. Always need to send events. + #[transactional] + fn create_at_address( + source: H160, + address: H160, + init: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result { + let precompiles = T::PrecompilesValue::get(); + let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); + Self::execute( + source, + source, + value, + gas_limit, + storage_limit, + config, + false, + &precompiles, + |executor| { + let (reason, _) = + executor.transact_create_at_address(source, address, value, init, gas_limit, access_list); + (reason, address) + }, + ) + } +} + +impl RunnerExtended for Runner { + /// Special method for rpc call which won't charge for storage rent + /// Same as call but with skip_storage_rent: true + fn rpc_call( + source: H160, + origin: H160, + target: H160, + input: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result { + // Ensure eth_call has evm origin, otherwise xcm charge rent fee will fail. + Pallet::::set_origin(T::AddressMapping::get_account_id(&origin)); + defer!(Pallet::::kill_origin()); + + let precompiles = T::PrecompilesValue::get(); + let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); + Self::execute( + source, + origin, + value, + gas_limit, + storage_limit, + config, + true, + &precompiles, + |executor| executor.transact_call(source, target, value, input, gas_limit, access_list), + ) + } + + /// Special method for rpc create which won't charge for storage rent + /// Same as create but with skip_storage_rent: true + fn rpc_create( + source: H160, + init: Vec, + value: BalanceOf, + gas_limit: u64, + storage_limit: u32, + access_list: Vec<(H160, Vec)>, + config: &evm::Config, + ) -> Result { + let precompiles = T::PrecompilesValue::get(); + let value = U256::from(UniqueSaturatedInto::::unique_saturated_into(value)); + Self::execute( + source, + source, + value, + gas_limit, + storage_limit, + config, + true, + &precompiles, + |executor| { + let address = executor + .create_address(evm::CreateScheme::Legacy { caller: source }) + .unwrap_or_default(); // transact_create will check the address + let (reason, _) = executor.transact_create(source, value, init, gas_limit, access_list); + (reason, address) + }, + ) + } +} + +struct SubstrateStackSubstate<'config> { + metadata: StackSubstateMetadata<'config>, + deletes: BTreeSet, + logs: Vec, + storage_logs: Vec<(H160, i32)>, + parent: Option>>, + known_original_storage: BTreeMap<(H160, H256), H256>, +} + +impl<'config> SubstrateStackSubstate<'config> { + pub fn metadata(&self) -> &StackSubstateMetadata<'config> { + &self.metadata + } + + pub fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> { + &mut self.metadata + } + + pub fn enter(&mut self, gas_limit: u64, is_static: bool) { + let mut entering = Self { + metadata: self.metadata.spit_child(gas_limit, is_static), + parent: None, + deletes: BTreeSet::new(), + logs: Vec::new(), + storage_logs: Vec::new(), + known_original_storage: BTreeMap::new(), + }; + mem::swap(&mut entering, self); + + self.parent = Some(Box::new(entering)); + + sp_io::storage::start_transaction(); + } + + pub fn exit_commit(&mut self) -> Result<(), ExitError> { + let mut exited = *self.parent.take().expect("Cannot commit on root substate"); + mem::swap(&mut exited, self); + + let target = self.metadata().target().expect("Storage target is none"); + let storage = exited.metadata().storage_meter().used_storage(); + + self.metadata.swallow_commit(exited.metadata).map_err(|e| { + sp_io::storage::rollback_transaction(); + e + })?; + self.logs.append(&mut exited.logs); + self.deletes.append(&mut exited.deletes); + + exited.storage_logs.push((target, storage)); + self.storage_logs.append(&mut exited.storage_logs); + + sp_io::storage::commit_transaction(); + Ok(()) + } + + pub fn exit_revert(&mut self) -> Result<(), ExitError> { + let mut exited = *self.parent.take().expect("Cannot discard on root substate"); + mem::swap(&mut exited, self); + self.metadata.swallow_revert(exited.metadata).map_err(|e| { + sp_io::storage::rollback_transaction(); + e + })?; + + sp_io::storage::rollback_transaction(); + Ok(()) + } + + pub fn exit_discard(&mut self) -> Result<(), ExitError> { + let mut exited = *self.parent.take().expect("Cannot discard on root substate"); + mem::swap(&mut exited, self); + self.metadata.swallow_discard(exited.metadata).map_err(|e| { + sp_io::storage::rollback_transaction(); + e + })?; + + sp_io::storage::rollback_transaction(); + Ok(()) + } + + pub fn deleted(&self, address: H160) -> bool { + if self.deletes.contains(&address) { + return true; + } + + if let Some(parent) = self.parent.as_ref() { + return parent.deleted(address); + } + + false + } + + pub fn set_deleted(&mut self, address: H160) { + self.deletes.insert(address); + } + + pub fn log(&mut self, address: H160, topics: Vec, data: Vec) { + self.logs.push(Log { address, topics, data }); + } + + fn recursive_is_cold bool>(&self, f: &F) -> bool { + let local_is_accessed = self.metadata.accessed().as_ref().map(f).unwrap_or(false); + if local_is_accessed { + false + } else { + self.parent.as_ref().map(|p| p.recursive_is_cold(f)).unwrap_or(true) + } + } + + pub fn known_original_storage(&self, address: H160, index: H256) -> Option { + if let Some(parent) = self.parent.as_ref() { + return parent.known_original_storage(address, index); + } + self.known_original_storage.get(&(address, index)).copied() + } + + pub fn set_known_original_storage(&mut self, address: H160, index: H256, value: H256) { + if let Some(ref mut parent) = self.parent { + return parent.set_known_original_storage(address, index, value); + } + self.known_original_storage.insert((address, index), value); + } +} + +#[cfg(feature = "evm-tests")] +impl<'config> SubstrateStackSubstate<'config> { + pub fn mark_account_dirty(&self, address: H160) { + // https://github.com/ethereum/go-ethereum/blob/v1.10.16/core/state/state_object.go#L143 + // insert in parent to make sure it doesn't get discarded + if address == H160::from_low_u64_be(3) { + if let Some(parent) = self.parent.as_ref() { + return parent.mark_account_dirty(address); + } + } + self.metadata().dirty_accounts.borrow_mut().insert(address); + } + + pub fn is_account_dirty(&self, address: H160) -> bool { + if self.metadata().dirty_accounts.borrow().contains(&address) { + return true; + } + if let Some(parent) = self.parent.as_ref() { + return parent.is_account_dirty(address); + } + false + } +} + +/// Substrate backend for EVM. +pub struct SubstrateStackState<'vicinity, 'config, T> { + vicinity: &'vicinity Vicinity, + substate: SubstrateStackSubstate<'config>, + _marker: PhantomData, +} + +impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { + /// Create a new backend with given vicinity. + pub fn new(vicinity: &'vicinity Vicinity, metadata: StackSubstateMetadata<'config>) -> Self { + Self { + vicinity, + substate: SubstrateStackSubstate { + metadata, + deletes: BTreeSet::new(), + logs: Vec::new(), + storage_logs: Vec::new(), + parent: None, + known_original_storage: BTreeMap::new(), + }, + _marker: PhantomData, + } + } +} + +#[cfg(feature = "evm-tests")] +impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { + pub fn deleted_accounts(&self) -> Vec { + self.substate.deletes.iter().copied().collect() + } + + pub fn empty_accounts(&self) -> Vec { + self.metadata() + .dirty_accounts + .borrow() + .iter() + .filter(|x| self.is_empty(**x)) + .copied() + .collect() + } +} + +impl<'vicinity, 'config, T: Config> BackendT for SubstrateStackState<'vicinity, 'config, T> { + fn gas_price(&self) -> U256 { + self.vicinity.gas_price + } + fn origin(&self) -> H160 { + self.vicinity.origin + } + + fn block_randomness(&self) -> Option { + self.vicinity.block_randomness + } + + fn block_hash(&self, number: U256) -> H256 { + if number > U256::from(u32::MAX) { + H256::default() + } else { + let number = BlockNumberFor::::from(number.as_u32()); + H256::from_slice(frame_system::Pallet::::block_hash(number).as_ref()) + } + } + + fn block_number(&self) -> U256 { + let number: u128 = frame_system::Pallet::::block_number().unique_saturated_into(); + U256::from(number) + } + + fn block_coinbase(&self) -> H160 { + self.vicinity.block_coinbase.unwrap_or(Pallet::::find_author()) + } + + fn block_timestamp(&self) -> U256 { + let now: u128 = pallet_timestamp::Pallet::::get().unique_saturated_into(); + U256::from(now / 1000) + } + + fn block_difficulty(&self) -> U256 { + self.vicinity.block_difficulty.unwrap_or_default() + } + + fn block_gas_limit(&self) -> U256 { + self.vicinity.block_gas_limit.unwrap_or_default() + } + + fn chain_id(&self) -> U256 { + U256::from(Pallet::::chain_id()) + } + + #[cfg(feature = "evm-tests")] + fn exists(&self, address: H160) -> bool { + crate::Accounts::::contains_key(&address) || self.substate.is_account_dirty(address) + } + + #[cfg(not(feature = "evm-tests"))] + fn exists(&self, _address: H160) -> bool { + true + } + + fn basic(&self, address: H160) -> evm::backend::Basic { + let account = Pallet::::account_basic(&address); + + evm::backend::Basic { + balance: account.balance, + nonce: account.nonce, + } + } + + fn code(&self, address: H160) -> Vec { + Pallet::::code_at_address(&address).into_inner() + } + + fn storage(&self, address: H160, index: H256) -> H256 { + AccountStorages::::get(address, index) + } + + fn original_storage(&self, address: H160, index: H256) -> Option { + if let Some(value) = self.substate.known_original_storage(address, index) { + Some(value) + } else { + Some(self.storage(address, index)) + } + } + + fn block_base_fee_per_gas(&self) -> sp_core::U256 { + self.vicinity.block_base_fee_per_gas.unwrap_or(U256::one()) + } +} + +impl<'vicinity, 'config, T: Config> StackStateT<'config> for SubstrateStackState<'vicinity, 'config, T> { + fn metadata(&self) -> &StackSubstateMetadata<'config> { + self.substate.metadata() + } + + fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> { + self.substate.metadata_mut() + } + + fn enter(&mut self, gas_limit: u64, is_static: bool) { + self.substate.enter(gas_limit, is_static) + } + + fn exit_commit(&mut self) -> Result<(), ExitError> { + self.substate.exit_commit() + } + + fn exit_revert(&mut self) -> Result<(), ExitError> { + self.substate.exit_revert() + } + + fn exit_discard(&mut self) -> Result<(), ExitError> { + self.substate.exit_discard() + } + + fn is_empty(&self, address: H160) -> bool { + Pallet::::is_account_empty(&address) + } + + fn deleted(&self, address: H160) -> bool { + self.substate.deleted(address) + } + + fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> { + Pallet::::inc_nonce(&address); + Ok(()) + } + + fn set_storage(&mut self, address: H160, index: H256, value: H256) { + let current = >::get(address, index); + + // keep track of original storage + if self.substate.known_original_storage(address, index).is_none() { + self.substate.set_known_original_storage(address, index, current); + }; + + if value == H256::default() { + log::debug!( + target: "evm", + "Removing storage for {:?} [index: {:?}]", + address, + index, + ); + >::remove(address, index); + + // storage meter + if !current.is_zero() { + Pallet::::update_contract_storage_size(&address, -(STORAGE_SIZE as i32)); + self.substate.metadata.storage_meter_mut().refund(STORAGE_SIZE); + } + } else { + log::debug!( + target: "evm", + "Updating storage for {:?} [index: {:?}, value: {:?}]", + address, + index, + value, + ); + >::insert(address, index, value); + + // storage meter + if current.is_zero() { + Pallet::::update_contract_storage_size(&address, STORAGE_SIZE as i32); + self.substate.metadata.storage_meter_mut().charge(STORAGE_SIZE); + } + } + } + + fn reset_storage(&mut self, address: H160) { + // use drain_prefix to avoid wasm-bencher counting limit as write operation + >::drain_prefix(address).for_each(drop); + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) { + self.substate.log(address, topics, data) + } + + fn set_deleted(&mut self, address: H160) { + self.substate.set_deleted(address) + } + + fn set_code(&mut self, address: H160, code: Vec) { + log::debug!( + target: "evm", + "Inserting code ({} bytes) at {:?}", + code.len(), + address + ); + + // get maintainer from parent caller `enter_substate` will do `spit_child` + let parent = match self.substate.parent { + Some(ref parent) => parent, + None => { + log::error!( + target: "evm", + "get parent's maintainer failed. address: {:?}", + address + ); + debug_assert!(false); + return; + } + }; + + let caller = match parent.metadata().caller() { + Some(ref caller) => caller, + None => { + log::error!( + target: "evm", + "get parent's caller failed. address: {:?}", + address + ); + debug_assert!(false); + return; + } + }; + + let is_published = self.substate.metadata.origin_code_address().map_or(false, |addr| { + Pallet::::accounts(addr).map_or(false, |account| account.contract_info.map_or(false, |v| v.published)) + }); + + log::debug!( + target: "evm", + "set_code: address: {:?}, maintainer: {:?}, publish: {:?}", + address, + caller, + is_published + ); + + let code_size = code.len() as u32; + Pallet::::create_contract(*caller, address, is_published, code); + + let used_storage = code_size.saturating_add(T::NewContractExtraBytes::get()); + Pallet::::update_contract_storage_size(&address, used_storage as i32); + self.substate.metadata.storage_meter_mut().charge(used_storage); + } + + fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError> { + self.touch(transfer.target); + if transfer.value.is_zero() { + return Ok(()); + } + let source = T::AddressMapping::get_account_id(&transfer.source); + let target = T::AddressMapping::get_account_id(&transfer.target); + let amount = convert_decimals_from_evm( + TryInto::>::try_into(transfer.value).map_err(|_| ExitError::OutOfFund)?, + ) + .ok_or(ExitError::Other(Into::<&str>::into(Error::::InvalidDecimals).into()))?; + + log::debug!( + target: "evm", + "transfer [source: {:?}, target: {:?}, amount: {:?}]", + source, + target, + amount + ); + + if T::Currency::free_balance(&source) < amount { + return Err(ExitError::OutOfFund); + } + + T::Currency::transfer(&source, &target, amount, ExistenceRequirement::AllowDeath) + .map_err(|e| ExitError::Other(Into::<&str>::into(e).into())) + } + + fn reset_balance(&mut self, address: H160) { + // Address and target can be the same during SELFDESTRUCT. In that case we transfer the + // remaining balance to treasury + let source = T::AddressMapping::get_account_id(&address); + let balance = T::Currency::free_balance(&source); + if !balance.is_zero() { + if let Err(e) = T::Currency::transfer( + &source, + &T::TreasuryAccount::get(), + balance, + ExistenceRequirement::AllowDeath, + ) { + debug_assert!( + false, + "Failed to transfer remaining balance to treasury with error: {:?}", + e + ); + } + } + } + + fn touch(&mut self, _address: H160) { + // Do nothing on touch in Substrate. + // + // EVM pallet considers all accounts to exist, and distinguish + // only empty and non-empty accounts. This avoids many of the + // subtle issues in EIP-161. + + // this is needed only for evm-tests to keep track of dirty accounts + #[cfg(feature = "evm-tests")] + self.substate.mark_account_dirty(_address); + } + + fn is_cold(&self, address: H160) -> bool { + self.substate + .recursive_is_cold(&|a| a.accessed_addresses.contains(&address)) + } + + fn is_storage_cold(&self, address: H160, key: H256) -> bool { + self.substate + .recursive_is_cold(&|a: &Accessed| a.accessed_storage.contains(&(address, key))) + } + + fn code_size(&self, address: H160) -> U256 { + Pallet::::code_size_at_address(&address) + } + + fn code_hash(&self, address: H160) -> H256 { + Pallet::::code_hash_at_address(&address) + } +} diff --git a/blockchain/modules/evm/src/runner/state.rs b/blockchain/modules/evm/src/runner/state.rs index 69a860fa..5c0806b0 100644 --- a/blockchain/modules/evm/src/runner/state.rs +++ b/blockchain/modules/evm/src/runner/state.rs @@ -1,950 +1,1666 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -// Synchronize with https://github.com/rust-blockchain/evm/blob/master/src/executor/stack/mod.rs - -use crate::{encode_revert_message, runner::StackState, StorageMeter}; -use core::{cmp::min, convert::Infallible}; -use evm::{ - Capture, Config, Context, CreateScheme, ExitError, ExitReason, ExitRevert, ExitSucceed, Opcode, Runtime, Stack, - Transfer, -}; -use evm_gasometer::{self as gasometer, Gasometer}; -use evm_runtime::Handler; -use frame_support::log; -use primitive_types::{H160, H256, U256}; -pub use primitives::{ - evm::{Account, EvmAddress, Log, Vicinity}, - ReserveIdentifier, H160_PREFIX_DEXSHARE, H160_PREFIX_TOKEN, MIRRORED_NFT_ADDRESS_START, PREDEPLOY_ADDRESS_START, - SYSTEM_CONTRACT_ADDRESS_PREFIX, -}; -use sha3::{Digest, Keccak256}; -use sp_std::{rc::Rc, vec::Vec}; - -macro_rules! event { - ($x:expr) => {}; -} - -#[cfg(feature = "tracing")] -mod tracing { - pub struct Tracer; - impl evm_runtime::tracing::EventListener for Tracer { - fn event(&mut self, event: evm_runtime::tracing::Event) { - frame_support::log::debug!( - target: "evm", "evm_runtime tracing: {:?}", event - ); - } - } -} - -#[cfg(feature = "tracing")] -use tracing::*; - -pub enum StackExitKind { - Succeeded, - Reverted, - Failed, -} - -pub struct StackSubstateMetadata<'config> { - gasometer: Gasometer<'config>, - storage_meter: StorageMeter, - is_static: bool, - depth: Option, - // save the caller which called `inner_create` to get maintainer - caller: Option, - // save the contract to charge storage - target: Option, -} - -impl<'config> StackSubstateMetadata<'config> { - pub fn new(gas_limit: u64, storage_limit: u32, extra_bytes: u32, config: &'config Config) -> Self { - Self { - gasometer: Gasometer::new(gas_limit, config), - storage_meter: StorageMeter::new(storage_limit, extra_bytes), - is_static: false, - depth: None, - caller: None, - target: None, - } - } - - pub fn swallow_commit(&mut self, other: Self) -> Result<(), ExitError> { - self.gasometer_mut().record_stipend(other.gasometer().gas())?; - self.gasometer.record_refund(other.gasometer().refunded_gas())?; - - // merge child meter into parent meter - self.storage_meter.merge(other.storage_meter()); - - Ok(()) - } - - pub fn swallow_revert(&mut self, other: Self) -> Result<(), ExitError> { - self.gasometer_mut().record_stipend(other.gasometer().gas())?; - - Ok(()) - } - - pub fn swallow_discard(&mut self, _other: Self) -> Result<(), ExitError> { - Ok(()) - } - - pub fn spit_child(&self, gas_limit: u64, is_static: bool) -> Self { - Self { - gasometer: Gasometer::new(gas_limit, self.gasometer().config()), - storage_meter: StorageMeter::new( - self.storage_meter().available_storage(), - self.storage_meter().extra_bytes(), - ), - is_static: is_static || self.is_static, - depth: match self.depth { - None => Some(0), - Some(n) => Some(n + 1), - }, - caller: None, - target: None, - } - } - - pub fn gasometer(&self) -> &Gasometer<'config> { - &self.gasometer - } - - pub fn gasometer_mut(&mut self) -> &mut Gasometer<'config> { - &mut self.gasometer - } - - pub fn storage_meter(&self) -> &StorageMeter { - &self.storage_meter - } - - pub fn storage_meter_mut(&mut self) -> &mut StorageMeter { - &mut self.storage_meter - } - - pub fn is_static(&self) -> bool { - self.is_static - } - - pub fn depth(&self) -> Option { - self.depth - } - - pub fn caller(&self) -> &Option { - &self.caller - } - - pub fn caller_mut(&mut self) -> &mut Option { - &mut self.caller - } - - pub fn target(&self) -> &Option { - &self.target - } - - pub fn target_mut(&mut self) -> &mut Option { - &mut self.target - } -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PrecompileOutput { - pub exit_status: ExitSucceed, - pub cost: u64, - pub output: Vec, - pub logs: Vec, -} - -/// Precompiles function signature. Expected input arguments are: -/// * Address -/// * Input -/// * Context -pub type PrecompileFn = fn(H160, &[u8], Option, &Context) -> Option>; - -/// Stack-based executor. -pub struct StackExecutor<'config, S> { - config: &'config Config, - precompile: PrecompileFn, - state: S, -} - -fn no_precompile( - _address: H160, - _input: &[u8], - _target_gas: Option, - _context: &Context, -) -> Option> { - None -} - -impl<'config, S: StackState<'config>> StackExecutor<'config, S> { - /// Create a new stack-based executor. - pub fn new(state: S, config: &'config Config) -> Self { - Self::new_with_precompile(state, config, no_precompile) - } - - /// Return a reference of the Config. - pub fn config(&self) -> &'config Config { - self.config - } - - /// Create a new stack-based executor with given precompiles. - pub fn new_with_precompile(state: S, config: &'config Config, precompile: PrecompileFn) -> Self { - Self { - config, - precompile, - state, - } - } - - pub fn state(&self) -> &S { - &self.state - } - - pub fn state_mut(&mut self) -> &mut S { - &mut self.state - } - - pub fn into_state(self) -> S { - self.state - } - - /// Create a substate executor from the current executor. - pub fn enter_substate(&mut self, gas_limit: u64, is_static: bool) { - self.state.enter(gas_limit, is_static); - } - - /// Exit a substate. Panic if it results an empty substate stack. - pub fn exit_substate(&mut self, kind: StackExitKind) -> Result<(), ExitError> { - match kind { - StackExitKind::Succeeded => self.state.exit_commit(), - StackExitKind::Reverted => self.state.exit_revert(), - StackExitKind::Failed => self.state.exit_discard(), - } - } - - /// Execute the runtime until it returns. - pub fn execute(&mut self, runtime: &mut Runtime) -> ExitReason { - match runtime.run(self) { - Capture::Exit(s) => s, - Capture::Trap(_) => unreachable!("Trap is Infallible"), - } - } - - /// Get remaining gas. - pub fn gas(&self) -> u64 { - self.state.metadata().gasometer().gas() - } - - /// Execute a `CREATE` transaction. - pub fn transact_create(&mut self, caller: H160, value: U256, init_code: Vec, gas_limit: u64) -> ExitReason { - let transaction_cost = gasometer::create_transaction_cost(&init_code); - match self - .state - .metadata_mut() - .gasometer_mut() - .record_transaction(transaction_cost) - { - Ok(()) => (), - Err(e) => return e.into(), - } - - match self.create_inner( - caller, - CreateScheme::Legacy { caller }, - value, - init_code, - Some(gas_limit), - false, - ) { - Capture::Exit((s, _, _)) => s, - Capture::Trap(_) => unreachable!(), - } - } - - /// Execute a `CREATE2` transaction. - pub fn transact_create2( - &mut self, - caller: H160, - value: U256, - init_code: Vec, - salt: H256, - gas_limit: u64, - ) -> ExitReason { - let transaction_cost = gasometer::create_transaction_cost(&init_code); - match self - .state - .metadata_mut() - .gasometer_mut() - .record_transaction(transaction_cost) - { - Ok(()) => (), - Err(e) => return e.into(), - } - let code_hash = H256::from_slice(Keccak256::digest(&init_code).as_slice()); - - match self.create_inner( - caller, - CreateScheme::Create2 { - caller, - code_hash, - salt, - }, - value, - init_code, - Some(gas_limit), - false, - ) { - Capture::Exit((s, _, _)) => s, - Capture::Trap(_) => unreachable!(), - } - } - - /// Execute a `CREATE` transaction with specific address. - pub fn transact_create_at_address( - &mut self, - caller: H160, - address: H160, - value: U256, - init_code: Vec, - gas_limit: u64, - ) -> ExitReason { - let transaction_cost = gasometer::create_transaction_cost(&init_code); - match self - .state - .metadata_mut() - .gasometer_mut() - .record_transaction(transaction_cost) - { - Ok(()) => (), - Err(e) => return e.into(), - } - - match self.create_inner( - caller, - CreateScheme::Fixed(address), - value, - init_code, - Some(gas_limit), - false, - ) { - Capture::Exit((s, _, _)) => s, - Capture::Trap(_) => unreachable!(), - } - } - - /// Execute a `CALL` transaction. - pub fn transact_call( - &mut self, - caller: H160, - address: H160, - value: U256, - data: Vec, - gas_limit: u64, - ) -> (ExitReason, Vec) { - let transaction_cost = gasometer::call_transaction_cost(&data); - match self - .state - .metadata_mut() - .gasometer_mut() - .record_transaction(transaction_cost) - { - Ok(()) => (), - Err(e) => return (e.into(), Vec::new()), - } - - self.state.inc_nonce(caller); - - let context = Context { - caller, - address, - apparent_value: value, - }; - - match self.call_inner( - address, - Some(Transfer { - source: caller, - target: address, - value, - }), - data, - Some(gas_limit), - false, - false, - false, - context, - ) { - Capture::Exit((s, v)) => (s, v), - Capture::Trap(_) => unreachable!(), - } - } - - /// Get used gas for the current executor, given the price. - pub fn used_gas(&self) -> u64 { - self.state.metadata().gasometer().total_used_gas() - - min( - self.state.metadata().gasometer().total_used_gas() / 2, - self.state.metadata().gasometer().refunded_gas() as u64, - ) - } - - /// Get fee needed for the current executor, given the price. - pub fn fee(&self, price: U256) -> U256 { - let used_gas = self.used_gas(); - U256::from(used_gas) * price - } - - /// Get account nonce. - pub fn nonce(&self, address: H160) -> U256 { - self.state.basic(address).nonce - } - - pub fn handle_mirrored_token(&self, address: H160) -> H160 { - log::debug!( - target: "evm", - "handle_mirrored_token: address: {:?}", - address, - ); - - let addr = address.as_bytes(); - if !addr.starts_with(&SYSTEM_CONTRACT_ADDRESS_PREFIX) { - return address; - } - - if addr.starts_with(&H160_PREFIX_TOKEN) || addr.starts_with(&H160_PREFIX_DEXSHARE) { - // `Token` predeploy contract. - let token_address = H160::from_low_u64_be(PREDEPLOY_ADDRESS_START); - log::debug!( - target: "evm", - "handle_mirrored_token: origin address: {:?}, token address: {:?}", - address, - token_address - ); - token_address - } else { - address - } - } - - /// Get the create address from given scheme. - pub fn create_address(&self, scheme: CreateScheme) -> Result { - let address = match scheme { - CreateScheme::Create2 { - caller, - code_hash, - salt, - } => { - let mut hasher = Keccak256::new(); - hasher.update(&[0xff]); - hasher.update(&caller[..]); - hasher.update(&salt[..]); - hasher.update(&code_hash[..]); - H256::from_slice(hasher.finalize().as_slice()).into() - } - CreateScheme::Legacy { caller } => { - let nonce = self.nonce(caller); - let mut stream = rlp::RlpStream::new_list(2); - stream.append(&caller); - stream.append(&nonce); - H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() - } - CreateScheme::Fixed(naddress) => naddress, - }; - - match scheme { - CreateScheme::Create2 { .. } | CreateScheme::Legacy { .. } => { - if address.as_bytes().starts_with(&SYSTEM_CONTRACT_ADDRESS_PREFIX) { - Err(ExitError::Other("ConflictContractAddress".into())) - } else { - Ok(address) - } - } - _ => Ok(address), - } - } - - fn create_inner( - &mut self, - caller: H160, - scheme: CreateScheme, - value: U256, - init_code: Vec, - target_gas: Option, - take_l64: bool, - ) -> Capture<(ExitReason, Option, Vec), Infallible> { - macro_rules! try_or_fail { - ( $e:expr ) => { - match $e { - Ok(v) => v, - Err(e) => return Capture::Exit((e.into(), None, Vec::new())), - } - }; - } - - fn l64(gas: u64) -> u64 { - gas - gas / 64 - } - - let address = match self.create_address(scheme) { - Err(e) => { - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); - } - Ok(address) => address, - }; - - *self.state.metadata_mut().caller_mut() = Some(caller); - *self.state.metadata_mut().target_mut() = Some(address); - - event!(Create { - caller, - address, - scheme, - value, - init_code: &init_code, - target_gas - }); - - if let Some(depth) = self.state.metadata().depth() { - if depth > self.config.call_stack_limit { - return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())); - } - } - - if self.balance(caller) < value { - return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())); - } - - let after_gas = if take_l64 && self.config.call_l64_after_gas { - if self.config.estimate { - let initial_after_gas = self.state.metadata().gasometer().gas(); - let diff = initial_after_gas - l64(initial_after_gas); - try_or_fail!(self.state.metadata_mut().gasometer_mut().record_cost(diff)); - self.state.metadata().gasometer().gas() - } else { - l64(self.state.metadata().gasometer().gas()) - } - } else { - self.state.metadata().gasometer().gas() - }; - - let target_gas = target_gas.unwrap_or(after_gas); - - let gas_limit = min(after_gas, target_gas); - try_or_fail!(self.state.metadata_mut().gasometer_mut().record_cost(gas_limit)); - - self.state.inc_nonce(caller); - - self.enter_substate(gas_limit, false); - - { - if self.code_size(address) != U256::zero() { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); - } - - if self.nonce(address) > U256::zero() { - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); - } - - self.state.reset_storage(address); - } - - let context = Context { - address, - caller, - apparent_value: value, - }; - let transfer = Transfer { - source: caller, - target: address, - value, - }; - match self.state.transfer(transfer) { - Ok(()) => (), - Err(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); - } - } - - if self.config.create_increase_nonce { - self.state.inc_nonce(address); - } - - let mut runtime = Runtime::new(Rc::new(init_code), Rc::new(Vec::new()), context, self.config); - - let reason = self.execute(&mut runtime); - log::debug!(target: "evm", "Create execution using address {}: {:?}", address, reason); - - match reason { - ExitReason::Succeed(s) => { - let out = runtime.machine().return_value(); - - if let Some(limit) = self.config.create_contract_limit { - if out.len() > limit { - self.state.metadata_mut().gasometer_mut().fail(); - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateContractLimit.into(), None, Vec::new())); - } - } - - match self.state.metadata_mut().gasometer_mut().record_deposit(out.len()) { - Ok(()) => { - self.state - .metadata_mut() - .storage_meter_mut() - .charge_with_extra_bytes(out.len() as u32); - let e = self.exit_substate(StackExitKind::Succeeded); - try_or_fail!(e); - self.state.set_code(address, out); - Capture::Exit((ExitReason::Succeed(s), Some(address), Vec::new())) - } - Err(e) => { - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), None, Vec::new())) - } - } - } - ExitReason::Error(e) => { - self.state.metadata_mut().gasometer_mut().fail(); - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), None, Vec::new())) - } - ExitReason::Revert(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - Capture::Exit((ExitReason::Revert(e), None, runtime.machine().return_value())) - } - ExitReason::Fatal(e) => { - self.state.metadata_mut().gasometer_mut().fail(); - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Fatal(e), None, Vec::new())) - } - } - } - - #[allow(clippy::too_many_arguments)] - fn call_inner( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - take_l64: bool, - take_stipend: bool, - context: Context, - ) -> Capture<(ExitReason, Vec), Infallible> { - macro_rules! try_or_fail { - ( $e:expr ) => { - match $e { - Ok(v) => v, - Err(e) => return Capture::Exit((e.into(), Vec::new())), - } - }; - } - - fn l64(gas: u64) -> u64 { - gas - gas / 64 - } - - *self.state.metadata_mut().target_mut() = Some(code_address); - - event!(Call { - code_address, - transfer: &transfer, - input: &input, - target_gas, - is_static, - context: &context, - }); - - let after_gas = if take_l64 && self.config.call_l64_after_gas { - if self.config.estimate { - let initial_after_gas = self.state.metadata().gasometer().gas(); - let diff = initial_after_gas - l64(initial_after_gas); - try_or_fail!(self.state.metadata_mut().gasometer_mut().record_cost(diff)); - self.state.metadata().gasometer().gas() - } else { - l64(self.state.metadata().gasometer().gas()) - } - } else { - self.state.metadata().gasometer().gas() - }; - - let target_gas = target_gas.unwrap_or(after_gas); - let mut gas_limit = min(target_gas, after_gas); - - try_or_fail!(self.state.metadata_mut().gasometer_mut().record_cost(gas_limit)); - - if let Some(transfer) = transfer.as_ref() { - if take_stipend && transfer.value != U256::zero() { - gas_limit = gas_limit.saturating_add(self.config.call_stipend); - } - } - - let code = self.code(code_address); - - self.enter_substate(gas_limit, is_static); - self.state.touch(context.address); - - if let Some(depth) = self.state.metadata().depth() { - if depth > self.config.call_stack_limit { - let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())); - } - } - - if let Some(transfer) = transfer { - match self.state.transfer(transfer) { - Ok(()) => (), - Err(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitReason::Error(e), Vec::new())); - } - } - } - - if let Some(ret) = (self.precompile)(code_address, &input, Some(gas_limit), &context) { - match ret { - Ok(PrecompileOutput { - exit_status, - output, - cost, - logs, - }) => { - for Log { address, topics, data } in logs { - match self.log(address, topics, data) { - Ok(_) => continue, - Err(error) => { - return Capture::Exit((ExitReason::Error(error), output)); - } - } - } - - let _ = self.state.metadata_mut().gasometer_mut().record_cost(cost); - let e = self.exit_substate(StackExitKind::Succeeded); - try_or_fail!(e); - return Capture::Exit((ExitReason::Succeed(exit_status), output)); - } - Err(e) => { - // return the error to contract - let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitReason::Revert(ExitRevert::Reverted), encode_revert_message(&e))); - } - } - } - - let mut runtime = Runtime::new(Rc::new(code), Rc::new(input), context, self.config); - - #[cfg(not(feature = "tracing"))] - let reason = self.execute(&mut runtime); - #[cfg(feature = "tracing")] - let reason = evm_runtime::tracing::using(&mut Tracer, || self.execute(&mut runtime)); - - log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, reason); - - match reason { - ExitReason::Succeed(s) => { - let e = self.exit_substate(StackExitKind::Succeeded); - try_or_fail!(e); - Capture::Exit((ExitReason::Succeed(s), runtime.machine().return_value())) - } - ExitReason::Error(e) => { - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), Vec::new())) - } - ExitReason::Revert(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - Capture::Exit((ExitReason::Revert(e), runtime.machine().return_value())) - } - ExitReason::Fatal(e) => { - self.state.metadata_mut().gasometer_mut().fail(); - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Fatal(e), Vec::new())) - } - } - } -} - -impl<'config, S: StackState<'config>> Handler for StackExecutor<'config, S> { - type CreateInterrupt = Infallible; - type CreateFeedback = Infallible; - type CallInterrupt = Infallible; - type CallFeedback = Infallible; - - fn balance(&self, address: H160) -> U256 { - self.state.basic(address).balance - } - - fn code_size(&self, address: H160) -> U256 { - let addr = self.handle_mirrored_token(address); - U256::from(self.state.code(addr).len()) - } - - fn code_hash(&self, address: H160) -> H256 { - if !self.exists(address) { - return H256::default(); - } - - let addr = self.handle_mirrored_token(address); - H256::from_slice(Keccak256::digest(&self.state.code(addr)).as_slice()) - } - - fn code(&self, address: H160) -> Vec { - let addr = self.handle_mirrored_token(address); - self.state.code(addr) - } - - fn storage(&self, address: H160, index: H256) -> H256 { - self.state.storage(address, index) - } - - fn original_storage(&self, address: H160, index: H256) -> H256 { - self.state.original_storage(address, index).unwrap_or_default() - } - - fn exists(&self, address: H160) -> bool { - if self.config.empty_considered_exists { - self.state.exists(address) - } else { - self.state.exists(address) && !self.state.is_empty(address) - } - } - - fn gas_left(&self) -> U256 { - U256::from(self.state.metadata().gasometer().gas()) - } - - fn gas_price(&self) -> U256 { - self.state.gas_price() - } - fn origin(&self) -> H160 { - self.state.origin() - } - fn block_hash(&self, number: U256) -> H256 { - self.state.block_hash(number) - } - fn block_number(&self) -> U256 { - self.state.block_number() - } - fn block_coinbase(&self) -> H160 { - self.state.block_coinbase() - } - fn block_timestamp(&self) -> U256 { - self.state.block_timestamp() - } - fn block_difficulty(&self) -> U256 { - self.state.block_difficulty() - } - fn block_gas_limit(&self) -> U256 { - self.state.block_gas_limit() - } - fn chain_id(&self) -> U256 { - self.state.chain_id() - } - - fn deleted(&self, address: H160) -> bool { - self.state.deleted(address) - } - - fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError> { - self.state.set_storage(address, index, value); - Ok(()) - } - - fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { - self.state.log(address, topics, data); - Ok(()) - } - - fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError> { - let balance = self.balance(address); - - event!(Suicide { - target, - address, - balance, - }); - - self.state.transfer(Transfer { - source: address, - target, - value: balance, - })?; - self.state.reset_balance(address); - self.state.set_deleted(address); - - Ok(()) - } - - fn create( - &mut self, - caller: H160, - scheme: CreateScheme, - value: U256, - init_code: Vec, - target_gas: Option, - ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { - self.create_inner(caller, scheme, value, init_code, target_gas, true) - } - - fn call( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: Context, - ) -> Capture<(ExitReason, Vec), Self::CallInterrupt> { - self.call_inner( - code_address, - transfer, - input, - target_gas, - is_static, - true, - true, - context, - ) - } - - #[inline] - fn pre_validate(&mut self, context: &Context, opcode: Opcode, stack: &Stack) -> Result<(), ExitError> { - // log::trace!(target: "evm", "Running opcode: {:?}, Pre gas-left: {:?}", opcode, - // gasometer().gas()); - - if let Some(cost) = gasometer::static_opcode_cost(opcode) { - self.state.metadata_mut().gasometer_mut().record_cost(cost)?; - } else { - let is_static = self.state.metadata().is_static(); - let (gas_cost, memory_cost) = - gasometer::dynamic_opcode_cost(context.address, opcode, stack, is_static, self.config, self)?; - - let gasometer = &mut self.state.metadata_mut().gasometer_mut(); - - gasometer.record_dynamic_cost(gas_cost, memory_cost)?; - } - - Ok(()) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +// Synchronize with https://github.com/rust-blockchain/evm/blob/d543f10/src/executor/stack/executor.rs + +use crate::{ + encode_revert_message, + runner::tagged_runtime::{RuntimeKind, TaggedRuntime}, + IsPrecompileResult, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet, StorageMeter, +}; +use core::{cmp::min, convert::Infallible}; +use module_evm_utility::{ + evm::{ + backend::Backend, maybe_borrowed::MaybeBorrowed, Capture, Config, Context, CreateScheme, ExitError, ExitFatal, + ExitReason, Opcode, Resolve, Runtime, Stack, Transfer, + }, + evm_gasometer::{self as gasometer, Gasometer, StorageTarget}, + evm_runtime::Handler, +}; +pub use primitives::{ + currency::CurrencyIdType, + evm::{ + EvmAddress, Vicinity, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_TOKEN_NFT, MIRRORED_NFT_ADDRESS_START, + SYSTEM_CONTRACT_ADDRESS_PREFIX, + }, + ReserveIdentifier, +}; +use sha3::{Digest, Keccak256}; +use sp_core::{H160, H256, U256}; +use sp_runtime::traits::Zero; +use sp_std::{collections::btree_set::BTreeSet, rc::Rc, vec::Vec}; + +macro_rules! event { + ($x:expr) => {}; +} + +#[cfg(feature = "tracing")] +mod tracing { + pub struct Tracer; + impl module_evm_utility::evm::tracing::EventListener for Tracer { + fn event(&mut self, event: module_evm_utility::evm::tracing::Event) { + frame_support::log::debug!( + target: "evm", "evm tracing: {:?}", event + ); + } + } + impl module_evm_utility::evm_runtime::tracing::EventListener for Tracer { + fn event(&mut self, event: module_evm_utility::evm_runtime::tracing::Event) { + frame_support::log::debug!( + target: "evm", "evm_runtime tracing: {:?}", event + ); + } + } + impl module_evm_utility::evm_gasometer::tracing::EventListener for Tracer { + fn event(&mut self, event: module_evm_utility::evm_gasometer::tracing::Event) { + frame_support::log::debug!( + target: "evm", "evm_gasometer tracing: {:?}", event + ); + } + } +} + +#[cfg(feature = "tracing")] +use tracing::*; + +macro_rules! emit_exit { + ($reason:expr) => {{ + let reason = $reason; + event!(Exit { + reason: &reason, + return_value: &Vec::new(), + }); + reason + }}; + ($reason:expr, $return_value:expr) => {{ + let reason = $reason; + let return_value = $return_value; + event!(Exit { + reason: &reason, + return_value: &return_value, + }); + (reason, return_value) + }}; +} + +// Default call stack capacity that can be used to +// execute the stack without reallocating. +const DEFAULT_CALL_STACK_CAPACITY: usize = 4; + +pub enum StackExitKind { + Succeeded, + Reverted, + Failed, +} + +#[derive(Default, Clone, Debug)] +pub struct Accessed { + pub accessed_addresses: BTreeSet, + pub accessed_storage: BTreeSet<(H160, H256)>, +} + +impl Accessed { + pub fn access_address(&mut self, address: H160) { + self.accessed_addresses.insert(address); + } + + pub fn access_addresses(&mut self, addresses: I) + where + I: Iterator, + { + for address in addresses { + self.accessed_addresses.insert(address); + } + } + + pub fn access_storages(&mut self, storages: I) + where + I: Iterator, + { + for storage in storages { + self.accessed_storage.insert((storage.0, storage.1)); + } + } +} + +#[derive(Clone, Debug)] +pub struct StackSubstateMetadata<'config> { + gasometer: Gasometer<'config>, + storage_meter: StorageMeter, + is_static: bool, + depth: Option, + accessed: Option, + // save the caller which called `inner_create` to get maintainer + caller: Option, + // save the contract to charge storage + target: Option, + // save the call contract address, publish status will sync from it. + origin_code_address: Option, + // this is needed only for evm-tests to keep track of dirty accounts + #[cfg(feature = "evm-tests")] + pub dirty_accounts: std::cell::RefCell>, +} + +impl<'config> StackSubstateMetadata<'config> { + pub fn new(gas_limit: u64, storage_limit: u32, config: &'config Config) -> Self { + let accessed = if config.increase_state_access_gas { + Some(Accessed::default()) + } else { + None + }; + Self { + gasometer: Gasometer::new(gas_limit, config), + storage_meter: StorageMeter::new(storage_limit), + is_static: false, + depth: None, + accessed, + caller: None, + target: None, + origin_code_address: None, + #[cfg(feature = "evm-tests")] + dirty_accounts: std::cell::RefCell::new(BTreeSet::new()), + } + } + + pub fn swallow_commit(&mut self, other: Self) -> Result<(), ExitError> { + self.gasometer.record_stipend(other.gasometer.gas())?; + self.gasometer.record_refund(other.gasometer.refunded_gas())?; + + if let (Some(mut other_accessed), Some(self_accessed)) = (other.accessed, self.accessed.as_mut()) { + self_accessed + .accessed_addresses + .append(&mut other_accessed.accessed_addresses); + self_accessed + .accessed_storage + .append(&mut other_accessed.accessed_storage); + } + + // merge child meter into parent meter + self.storage_meter.merge(&other.storage_meter); + + #[cfg(feature = "evm-tests")] + self.dirty_accounts + .borrow_mut() + .append(&mut other.dirty_accounts.borrow().iter().copied().collect()); + + Ok(()) + } + + pub fn swallow_revert(&mut self, other: Self) -> Result<(), ExitError> { + self.gasometer.record_stipend(other.gasometer.gas())?; + + Ok(()) + } + + pub fn swallow_discard(&mut self, _other: Self) -> Result<(), ExitError> { + Ok(()) + } + + pub fn spit_child(&self, gas_limit: u64, is_static: bool) -> Self { + Self { + gasometer: Gasometer::new(gas_limit, self.gasometer.config()), + storage_meter: StorageMeter::new(self.storage_meter.available_storage()), + is_static: is_static || self.is_static, + depth: match self.depth { + None => Some(0), + Some(n) => Some(n + 1), + }, + accessed: self.accessed.as_ref().map(|_| Accessed::default()), + caller: None, + target: None, + origin_code_address: self.origin_code_address, + #[cfg(feature = "evm-tests")] + dirty_accounts: std::cell::RefCell::new(BTreeSet::new()), + } + } + + pub fn gasometer(&self) -> &Gasometer<'config> { + &self.gasometer + } + + pub fn gasometer_mut(&mut self) -> &mut Gasometer<'config> { + &mut self.gasometer + } + + pub fn storage_meter(&self) -> &StorageMeter { + &self.storage_meter + } + + pub fn storage_meter_mut(&mut self) -> &mut StorageMeter { + &mut self.storage_meter + } + + pub fn is_static(&self) -> bool { + self.is_static + } + + pub fn depth(&self) -> Option { + self.depth + } + + pub fn access_address(&mut self, address: H160) { + if let Some(accessed) = &mut self.accessed { + accessed.access_address(address) + } + } + + pub fn access_addresses(&mut self, addresses: I) + where + I: Iterator, + { + if let Some(accessed) = &mut self.accessed { + accessed.access_addresses(addresses); + } + } + + pub fn access_storage(&mut self, address: H160, key: H256) { + if let Some(accessed) = &mut self.accessed { + accessed.accessed_storage.insert((address, key)); + } + } + + pub fn access_storages(&mut self, storages: I) + where + I: Iterator, + { + if let Some(accessed) = &mut self.accessed { + accessed.access_storages(storages); + } + } + + pub fn accessed(&self) -> &Option { + &self.accessed + } + + pub fn caller(&self) -> &Option { + &self.caller + } + + pub fn caller_mut(&mut self) -> &mut Option { + &mut self.caller + } + + pub fn target(&self) -> &Option { + &self.target + } + + pub fn target_mut(&mut self) -> &mut Option { + &mut self.target + } + + pub fn origin_code_address(&mut self) -> &Option { + &self.origin_code_address + } + + pub fn origin_code_address_mut(&mut self) -> &mut Option { + &mut self.origin_code_address + } +} + +pub trait StackState<'config>: Backend { + fn metadata(&self) -> &StackSubstateMetadata<'config>; + fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config>; + + fn enter(&mut self, gas_limit: u64, is_static: bool); + fn exit_commit(&mut self) -> Result<(), ExitError>; + fn exit_revert(&mut self) -> Result<(), ExitError>; + fn exit_discard(&mut self) -> Result<(), ExitError>; + + fn is_empty(&self, address: H160) -> bool; + fn deleted(&self, address: H160) -> bool; + fn is_cold(&self, address: H160) -> bool; + fn is_storage_cold(&self, address: H160, key: H256) -> bool; + + fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError>; + fn set_storage(&mut self, address: H160, key: H256, value: H256); + fn reset_storage(&mut self, address: H160); + fn log(&mut self, address: H160, topics: Vec, data: Vec); + fn set_deleted(&mut self, address: H160); + fn set_code(&mut self, address: H160, code: Vec); + fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError>; + fn reset_balance(&mut self, address: H160); + fn touch(&mut self, address: H160); + + /// Fetch the code size of an address. + /// Provide a default implementation by fetching the code, but + /// can be customized to use a more performant approach that don't need to + /// fetch the code. + fn code_size(&self, address: H160) -> U256 { + U256::from(self.code(address).len()) + } + + /// Fetch the code hash of an address. + /// Provide a default implementation by fetching the code, but + /// can be customized to use a more performant approach that don't need to + /// fetch the code. + fn code_hash(&self, address: H160) -> H256 { + H256::from_slice(Keccak256::digest(self.code(address)).as_slice()) + } + + fn record_external_operation(&mut self, _op: crate::ExternalOperation) -> Result<(), ExitError> { + Ok(()) + } + + fn record_external_dynamic_opcode_cost( + &mut self, + _opcode: Opcode, + _gas_cost: gasometer::GasCost, + _target: StorageTarget, + ) -> Result<(), ExitError> { + Ok(()) + } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + _storage_growth: Option, + ) -> Result<(), ExitError> { + Ok(()) + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} +} + +/// A precompile result. +pub type PrecompileResult = Result; + +/// Stack-based executor. +pub struct StackExecutor<'config, 'precompiles, S, P> { + config: &'config Config, + state: S, + precompile_set: &'precompiles P, +} + +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecutor<'config, 'precompiles, S, P> { + /// Return a reference of the Config. + pub fn config(&self) -> &'config Config { + self.config + } + + /// Return a reference to the precompile set. + pub fn precompiles(&self) -> &'precompiles P { + self.precompile_set + } + + /// Create a new stack-based executor with given precompiles. + pub fn new_with_precompiles(state: S, config: &'config Config, precompile_set: &'precompiles P) -> Self { + Self { + config, + state, + precompile_set, + } + } + + pub fn state(&self) -> &S { + &self.state + } + + pub fn state_mut(&mut self) -> &mut S { + &mut self.state + } + + pub fn into_state(self) -> S { + self.state + } + + /// Create a substate executor from the current executor. + pub fn enter_substate(&mut self, gas_limit: u64, is_static: bool) { + self.state.enter(gas_limit, is_static); + } + + /// Exit a substate. Panic if it results an empty substate stack. + pub fn exit_substate(&mut self, kind: StackExitKind) -> Result<(), ExitError> { + match kind { + StackExitKind::Succeeded => self.state.exit_commit(), + StackExitKind::Reverted => self.state.exit_revert(), + StackExitKind::Failed => self.state.exit_discard(), + } + } + + /// Execute the runtime until it returns. + pub fn execute(&mut self, runtime: &mut Runtime) -> ExitReason { + let mut call_stack = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + call_stack.push(TaggedRuntime { + kind: RuntimeKind::Execute, + inner: MaybeBorrowed::Borrowed(runtime), + }); + let (reason, _, _) = self.execute_with_call_stack(&mut call_stack); + reason + } + + /// Execute using Runtimes on the call_stack until it returns. + fn execute_with_call_stack( + &mut self, + call_stack: &mut Vec>, + ) -> (ExitReason, Option, Vec) { + // This `interrupt_runtime` is used to pass the runtime obtained from the + // `Capture::Trap` branch in the match below back to the top of the call stack. + // The reason we can't simply `push` the runtime directly onto the stack in the + // `Capture::Trap` branch is because the borrow-checker complains that the stack + // is already borrowed as long as we hold a pointer on the last element + // (i.e. the currently executing runtime). + let mut interrupt_runtime = None; + loop { + if let Some(rt) = interrupt_runtime.take() { + call_stack.push(rt); + } + let runtime = match call_stack.last_mut() { + Some(runtime) => runtime, + None => { + return (ExitReason::Fatal(ExitFatal::UnhandledInterrupt), None, Vec::new()); + } + }; + let reason = { + let inner_runtime = &mut runtime.inner; + match inner_runtime.run(self) { + Capture::Exit(reason) => reason, + Capture::Trap(Resolve::Call(rt, _)) => { + interrupt_runtime = Some(rt.0); + continue; + } + Capture::Trap(Resolve::Create(rt, _)) => { + interrupt_runtime = Some(rt.0); + continue; + } + } + }; + let runtime_kind = runtime.kind; + let (reason, maybe_address, return_data) = match runtime_kind { + RuntimeKind::Create(created_address) => { + let (reason, maybe_address, return_data) = + self.cleanup_for_create(created_address, reason, runtime.inner.machine().return_value()); + (reason, maybe_address, return_data) + } + RuntimeKind::Call(code_address) => { + let return_data = + self.cleanup_for_call(code_address, &reason, runtime.inner.machine().return_value()); + (reason, None, return_data) + } + RuntimeKind::Execute => (reason, None, runtime.inner.machine().return_value()), + }; + // We're done with that runtime now, so can pop it off the call stack + call_stack.pop(); + // Now pass the results from that runtime on to the next one in the stack + let runtime = match call_stack.last_mut() { + Some(r) => r, + None => return (reason, None, return_data), + }; + emit_exit!(&reason, &return_data); + let inner_runtime = &mut runtime.inner; + let maybe_error = match runtime_kind { + RuntimeKind::Create(_) => inner_runtime.finish_create(reason, maybe_address, return_data), + RuntimeKind::Call(_) => inner_runtime.finish_call(reason, return_data), + RuntimeKind::Execute => inner_runtime.finish_call(reason, return_data), + }; + // Early exit if passing on the result caused an error + if let Err(e) = maybe_error { + return (e, None, Vec::new()); + } + } + } + + /// Get remaining gas. + pub fn gas(&self) -> u64 { + self.state.metadata().gasometer.gas() + } + + fn record_create_transaction_cost( + &mut self, + init_code: &[u8], + access_list: &[(H160, Vec)], + ) -> Result<(), ExitError> { + let transaction_cost = gasometer::create_transaction_cost(init_code, access_list); + let gasometer = &mut self.state.metadata_mut().gasometer; + gasometer.record_transaction(transaction_cost) + } + + fn maybe_record_init_code_cost(&mut self, init_code: &[u8]) -> Result<(), ExitError> { + if let Some(limit) = self.config.max_initcode_size { + // EIP-3860 + if init_code.len() > limit { + self.state.metadata_mut().gasometer.fail(); + return Err(ExitError::CreateContractLimit); + } + return self + .state + .metadata_mut() + .gasometer + .record_cost(gasometer::init_code_cost(init_code)); + } + Ok(()) + } + + /// Execute a `CREATE` transaction. + pub fn transact_create( + &mut self, + caller: H160, + value: U256, + init_code: Vec, + gas_limit: u64, + access_list: Vec<(H160, Vec)>, // See EIP-2930 + ) -> (ExitReason, Vec) { + event!(TransactCreate { + caller, + value, + init_code: &init_code, + gas_limit, + address: self.create_address(CreateScheme::Legacy { caller }), + }); + + if let Some(limit) = self.config.max_initcode_size { + if init_code.len() > limit { + self.state.metadata_mut().gasometer.fail(); + return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new()); + } + } + + if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { + return emit_exit!(e.into(), Vec::new()); + } + self.initialize_with_access_list(access_list); + + match self.create_inner( + caller, + CreateScheme::Legacy { caller }, + value, + init_code, + Some(gas_limit), + false, + ) { + Capture::Exit((s, _, v)) => emit_exit!(s, v), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } + } + } + + /// Execute a `CREATE2` transaction. + pub fn transact_create2( + &mut self, + caller: H160, + value: U256, + init_code: Vec, + salt: H256, + gas_limit: u64, + access_list: Vec<(H160, Vec)>, // See EIP-2930 + ) -> (ExitReason, Vec) { + if let Some(limit) = self.config.max_initcode_size { + if init_code.len() > limit { + self.state.metadata_mut().gasometer.fail(); + return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new()); + } + } + + let code_hash = H256::from_slice(Keccak256::digest(&init_code).as_slice()); + event!(TransactCreate2 { + caller, + value, + init_code: &init_code, + salt, + gas_limit, + address: self.create_address(CreateScheme::Create2 { + caller, + code_hash, + salt, + }), + }); + + if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { + return emit_exit!(e.into(), Vec::new()); + } + self.initialize_with_access_list(access_list); + + match self.create_inner( + caller, + CreateScheme::Create2 { + caller, + code_hash, + salt, + }, + value, + init_code, + Some(gas_limit), + false, + ) { + Capture::Exit((s, _, v)) => emit_exit!(s, v), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } + } + } + + /// Execute a `CREATE` transaction with specific address. + pub fn transact_create_at_address( + &mut self, + caller: H160, + address: H160, + value: U256, + init_code: Vec, + gas_limit: u64, + access_list: Vec<(H160, Vec)>, + ) -> (ExitReason, Vec) { + if let Some(limit) = self.config.max_initcode_size { + if init_code.len() > limit { + self.state.metadata_mut().gasometer.fail(); + return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new()); + } + } + + event!(TransactCreate { + caller, + value, + init_code: &init_code, + gas_limit, + address, + }); + + if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { + return emit_exit!(e.into(), Vec::new()); + } + self.initialize_with_access_list(access_list); + + match self.create_inner( + caller, + CreateScheme::Fixed(address), + value, + init_code, + Some(gas_limit), + false, + ) { + Capture::Exit((s, _, v)) => emit_exit!(s, v), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } + } + } + + /// Execute a `CALL` transaction with a given caller, address, value and + /// gas limit and data. + /// + /// Takes in an additional `access_list` parameter for EIP-2930 which was + /// introduced in the Ethereum Berlin hard fork. If you do not wish to use + /// this functionality, just pass in an empty vector. + pub fn transact_call( + &mut self, + caller: H160, + address: H160, + value: U256, + data: Vec, + gas_limit: u64, + access_list: Vec<(H160, Vec)>, + ) -> (ExitReason, Vec) { + if let Err(e) = self.state.inc_nonce(caller) { + return (e.into(), Vec::new()); + } + + event!(TransactCall { + caller, + address, + value, + data: &data, + gas_limit, + }); + + let transaction_cost = gasometer::call_transaction_cost(&data, &access_list); + let gasometer = &mut self.state.metadata_mut().gasometer; + match gasometer.record_transaction(transaction_cost) { + Ok(()) => (), + Err(e) => return emit_exit!(e.into(), Vec::new()), + } + // set origin_code_address publish status will sync from it. + *self.state.metadata_mut().origin_code_address_mut() = Some(address); + + // Initialize initial addresses for EIP-2929 + if self.config.increase_state_access_gas { + if self.config.warm_coinbase_address { + // Warm coinbase address for EIP-3651 + let addresses = core::iter::once(caller) + .chain(core::iter::once(address)) + .chain(core::iter::once(self.block_coinbase())); + self.state.metadata_mut().access_addresses(addresses); + } else { + let addresses = core::iter::once(caller).chain(core::iter::once(address)); + self.state.metadata_mut().access_addresses(addresses); + } + + self.initialize_with_access_list(access_list); + } + + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { + return (e.into(), Vec::new()); + } + + let context = Context { + caller, + address, + apparent_value: value, + }; + + match self.call_inner( + address, + Some(Transfer { + source: caller, + target: address, + value, + }), + data, + Some(gas_limit), + false, + false, + false, + context, + ) { + Capture::Exit((s, v)) => emit_exit!(s, v), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } + } + } + + /// Get used gas for the current executor, given the price. + pub fn used_gas(&self) -> u64 { + self.state.metadata().gasometer.total_used_gas() + - min( + self.state.metadata().gasometer.total_used_gas() / self.config.max_refund_quotient, + self.state.metadata().gasometer.refunded_gas() as u64, + ) + } + + /// Get fee needed for the current executor, given the price. + pub fn fee(&self, price: U256) -> U256 { + let used_gas = self.used_gas(); + U256::from(used_gas).saturating_mul(price) + } + + /// Get account nonce. + pub fn nonce(&self, address: H160) -> U256 { + self.state.basic(address).nonce + } + + /// Get the create address from given scheme. + pub fn create_address(&self, scheme: CreateScheme) -> Result { + let address = match scheme { + CreateScheme::Create2 { + caller, + code_hash, + salt, + } => { + let mut hasher = Keccak256::new(); + hasher.update([0xff]); + hasher.update(&caller[..]); + hasher.update(&salt[..]); + hasher.update(&code_hash[..]); + H256::from_slice(hasher.finalize().as_slice()).into() + } + CreateScheme::Legacy { caller } => { + let nonce = self.nonce(caller); + let mut stream = rlp::RlpStream::new_list(2); + stream.append(&caller); + stream.append(&nonce); + H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() + } + CreateScheme::Fixed(naddress) => naddress, + }; + + match scheme { + CreateScheme::Create2 { .. } | CreateScheme::Legacy { .. } => { + if address.as_bytes().starts_with(&SYSTEM_CONTRACT_ADDRESS_PREFIX) { + Err(ExitError::CreateCollision) + } else { + Ok(address) + } + } + _ => Ok(address), + } + } + + pub fn initialize_with_access_list(&mut self, access_list: Vec<(H160, Vec)>) { + let addresses = access_list.iter().map(|a| a.0); + self.state.metadata_mut().access_addresses(addresses); + + let storage_keys = access_list + .into_iter() + .flat_map(|(address, keys)| keys.into_iter().map(move |key| (address, key))); + self.state.metadata_mut().access_storages(storage_keys); + } + + fn create_inner( + &mut self, + caller: H160, + scheme: CreateScheme, + value: U256, + init_code: Vec, + target_gas: Option, + take_l64: bool, + ) -> Capture<(ExitReason, Option, Vec), StackExecutorCreateInterrupt<'static>> { + macro_rules! try_or_fail { + ( $e:expr ) => { + match $e { + Ok(v) => v, + Err(e) => return Capture::Exit((e.into(), None, Vec::new())), + } + }; + } + + fn l64(gas: u64) -> u64 { + gas - gas / 64 + } + + let address = match self.create_address(scheme) { + Err(e) => { + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } + Ok(address) => address, + }; + + *self.state.metadata_mut().caller_mut() = Some(caller); + *self.state.metadata_mut().target_mut() = Some(address); + + self.state.metadata_mut().access_address(caller); + self.state.metadata_mut().access_address(address); + + event!(Create { + caller, + address, + scheme, + value, + init_code: &init_code, + target_gas + }); + + if let Some(depth) = self.state.metadata().depth { + if depth >= self.config.call_stack_limit { + return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())); + } + } + + if self.balance(caller) < value { + return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())); + } + + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } + + if let Err(e) = self.state.inc_nonce(caller) { + return Capture::Exit((e.into(), None, Vec::new())); + } + + let after_gas = if take_l64 && self.config.call_l64_after_gas { + if self.config.estimate { + let initial_after_gas = self.state.metadata().gasometer.gas(); + let diff = initial_after_gas - l64(initial_after_gas); + try_or_fail!(self.state.metadata_mut().gasometer.record_cost(diff)); + self.state.metadata().gasometer.gas() + } else { + l64(self.state.metadata().gasometer.gas()) + } + } else { + self.state.metadata().gasometer.gas() + }; + + let target_gas = target_gas.unwrap_or(after_gas); + + let gas_limit = min(after_gas, target_gas); + try_or_fail!(self.state.metadata_mut().gasometer.record_cost(gas_limit)); + + self.enter_substate(gas_limit, false); + + { + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AddressCodeRead(address)) { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } + let code_size = self.code_size(address); + if code_size != U256::zero() { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); + } + + // We will keep the nonce until the storages are cleared. + if self.nonce(address) > U256::zero() { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); + } + + // Still do this, although it is superfluous. + self.state.reset_storage(address); + } + + let context = Context { + address, + caller, + apparent_value: value, + }; + let transfer = Transfer { + source: caller, + target: address, + value, + }; + match self.state.transfer(transfer) { + Ok(()) => (), + Err(e) => { + let _ = self.exit_substate(StackExitKind::Reverted); + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } + } + + if self.config.create_increase_nonce { + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } + if let Err(e) = self.state.inc_nonce(address) { + return Capture::Exit((e.into(), None, Vec::new())); + } + } + + let runtime = Runtime::new( + Rc::new(init_code), + Rc::new(Vec::new()), + context, + self.config.stack_limit, + self.config.memory_limit, + ); + + Capture::Trap(StackExecutorCreateInterrupt(TaggedRuntime { + kind: RuntimeKind::Create(address), + inner: MaybeBorrowed::Owned(runtime), + })) + } + + #[allow(clippy::too_many_arguments)] + fn call_inner( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + take_l64: bool, + take_stipend: bool, + context: Context, + ) -> Capture<(ExitReason, Vec), StackExecutorCallInterrupt<'static>> { + macro_rules! try_or_fail { + ( $e:expr ) => { + match $e { + Ok(v) => v, + Err(e) => return Capture::Exit((e.into(), Vec::new())), + } + }; + } + + fn l64(gas: u64) -> u64 { + gas - gas / 64 + } + + // Set target address + *self.state.metadata_mut().target_mut() = Some(code_address); + + event!(Call { + code_address, + transfer: &transfer, + input: &input, + target_gas, + is_static, + context: &context, + }); + + let after_gas = if take_l64 && self.config.call_l64_after_gas { + if self.config.estimate { + let initial_after_gas = self.state.metadata().gasometer.gas(); + let diff = initial_after_gas - l64(initial_after_gas); + try_or_fail!(self.state.metadata_mut().gasometer.record_cost(diff)); + self.state.metadata().gasometer.gas() + } else { + l64(self.state.metadata().gasometer.gas()) + } + } else { + self.state.metadata().gasometer.gas() + }; + + let target_gas = target_gas.unwrap_or(after_gas); + let mut gas_limit = min(target_gas, after_gas); + + try_or_fail!(self.state.metadata_mut().gasometer.record_cost(gas_limit)); + + if let Some(transfer) = transfer.as_ref() { + if take_stipend && transfer.value != U256::zero() { + gas_limit = gas_limit.saturating_add(self.config.call_stipend); + } + } + + self.enter_substate(gas_limit, is_static); + self.state.touch(context.address); + + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AddressCodeRead(code_address)) { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), Vec::new())); + } + let code = self.code(code_address); + + if let Some(depth) = self.state.metadata().depth { + if depth > self.config.call_stack_limit { + let _ = self.exit_substate(StackExitKind::Reverted); + return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())); + } + } + + if let Some(transfer) = transfer { + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), Vec::new())); + } + match self.state.transfer(transfer) { + Ok(()) => (), + Err(e) => { + let _ = self.exit_substate(StackExitKind::Reverted); + return Capture::Exit((ExitReason::Error(e), Vec::new())); + } + } + } + + // At this point, the state has been modified in enter_substate to + // reflect both the is_static parameter of this call and the is_static + // of the caller context. + let precompile_is_static = self.state.metadata().is_static(); + if let Some(result) = self.precompile_set.execute(&mut StackExecutorHandle { + executor: self, + code_address, + input: &input, + gas_limit: Some(gas_limit), + context: &context, + is_static: precompile_is_static, + }) { + return match result { + Ok(PrecompileOutput { exit_status, output }) => { + // let _ = self.exit_substate(StackExitKind::Succeeded); + let e = self.exit_substate(StackExitKind::Succeeded); + try_or_fail!(e); + Capture::Exit((ExitReason::Succeed(exit_status), output)) + } + Err(PrecompileFailure::Error { exit_status }) => { + let _ = self.exit_substate(StackExitKind::Failed); + Capture::Exit((ExitReason::Error(exit_status), Vec::new())) + } + Err(PrecompileFailure::Revert { exit_status, output }) => { + let _ = self.exit_substate(StackExitKind::Reverted); + // Capture::Exit((ExitReason::Revert(exit_status), output)) + Capture::Exit((ExitReason::Revert(exit_status), encode_revert_message(&output))) + } + Err(PrecompileFailure::Fatal { exit_status }) => { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + Capture::Exit((ExitReason::Fatal(exit_status), Vec::new())) + } + }; + } + + let runtime = Runtime::new( + Rc::new(code), + Rc::new(input), + context, + self.config.stack_limit, + self.config.memory_limit, + ); + + Capture::Trap(StackExecutorCallInterrupt(TaggedRuntime { + kind: RuntimeKind::Call(code_address), + inner: MaybeBorrowed::Owned(runtime), + })) + } + + fn cleanup_for_create( + &mut self, + created_address: H160, + reason: ExitReason, + return_data: Vec, + ) -> (ExitReason, Option, Vec) { + fn check_first_byte(config: &Config, code: &[u8]) -> Result<(), ExitError> { + if config.disallow_executable_format && Some(&Opcode::EOFMAGIC.as_u8()) == code.first() { + return Err(ExitError::InvalidCode(Opcode::EOFMAGIC)); + } + Ok(()) + } + + log::debug!(target: "evm", "Create execution using address {}: {:?}", created_address, reason); + + match reason { + ExitReason::Succeed(s) => { + let out = return_data; + let address = created_address; + // As of EIP-3541 code starting with 0xef cannot be deployed + if let Err(e) = check_first_byte(self.config, &out) { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + return (e.into(), None, Vec::new()); + } + + if let Some(limit) = self.config.create_contract_limit { + if out.len() > limit { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + return (ExitError::CreateContractLimit.into(), None, Vec::new()); + } + } + + match self.state.metadata_mut().gasometer.record_deposit(out.len()) { + Ok(()) => { + let code_len = out.len(); + self.state.set_code(address, out); + let exit_result = self.exit_substate(StackExitKind::Succeeded); + if let Err(e) = + self.record_external_operation(crate::ExternalOperation::Write(U256::from(code_len))) + { + return (e.into(), None, Vec::new()); + } + if let Err(e) = exit_result { + return (e.into(), None, Vec::new()); + } + (ExitReason::Succeed(s), Some(address), Vec::new()) + } + Err(e) => { + let _ = self.exit_substate(StackExitKind::Failed); + (ExitReason::Error(e), None, Vec::new()) + } + } + } + ExitReason::Error(e) => { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + (ExitReason::Error(e), None, Vec::new()) + } + ExitReason::Revert(e) => { + let _ = self.exit_substate(StackExitKind::Reverted); + (ExitReason::Revert(e), None, return_data) + } + ExitReason::Fatal(e) => { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + (ExitReason::Fatal(e), None, Vec::new()) + } + } + } + + fn cleanup_for_call(&mut self, code_address: H160, reason: &ExitReason, return_data: Vec) -> Vec { + log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, reason); + match reason { + ExitReason::Succeed(_) => { + let _ = self.exit_substate(StackExitKind::Succeeded); + return_data + } + ExitReason::Error(_) => { + let _ = self.exit_substate(StackExitKind::Failed); + Vec::new() + } + ExitReason::Revert(_) => { + let _ = self.exit_substate(StackExitKind::Reverted); + return_data + } + ExitReason::Fatal(_) => { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + Vec::new() + } + } + } +} + +pub struct StackExecutorCallInterrupt<'borrow>(TaggedRuntime<'borrow>); +pub struct StackExecutorCreateInterrupt<'borrow>(TaggedRuntime<'borrow>); + +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler + for StackExecutor<'config, 'precompiles, S, P> +{ + type CreateInterrupt = StackExecutorCreateInterrupt<'static>; + type CreateFeedback = Infallible; + type CallInterrupt = StackExecutorCallInterrupt<'static>; + type CallFeedback = Infallible; + + fn balance(&self, address: H160) -> U256 { + self.state.basic(address).balance + } + + fn code_size(&self, address: H160) -> U256 { + self.state.code_size(address) + } + + fn code_hash(&self, address: H160) -> H256 { + if !self.exists(address) { + return H256::default(); + } + + self.state.code_hash(address) + } + + fn code(&self, address: H160) -> Vec { + let code = self.state.code(address); + if code.len().is_zero() { + if let IsPrecompileResult::Answer { + is_precompile: false, .. + } = self.precompile_set.is_precompile(address, u64::zero()) + { + log::debug!( + target: "evm", + "contract does not exist, address: {:?}", + address + ); + } + } + code + } + + fn storage(&self, address: H160, index: H256) -> H256 { + self.state.storage(address, index) + } + + fn original_storage(&self, address: H160, index: H256) -> H256 { + self.state.original_storage(address, index).unwrap_or_default() + } + + fn exists(&self, address: H160) -> bool { + if self.config.empty_considered_exists { + self.state.exists(address) + } else { + self.state.exists(address) && !self.state.is_empty(address) + } + } + + fn is_cold(&mut self, address: H160, maybe_index: Option) -> Result { + Ok(match maybe_index { + None => { + let is_precompile = match self + .precompile_set + .is_precompile(address, self.state.metadata().gasometer.gas()) + { + IsPrecompileResult::Answer { + is_precompile, + extra_cost, + } => { + self.state.metadata_mut().gasometer.record_cost(extra_cost)?; + is_precompile + } + IsPrecompileResult::OutOfGas => return Err(ExitError::OutOfGas), + }; + + !is_precompile && self.state.is_cold(address) + } + Some(index) => self.state.is_storage_cold(address, index), + }) + } + + fn gas_left(&self) -> U256 { + U256::from(self.state.metadata().gasometer.gas()) + } + + fn gas_price(&self) -> U256 { + self.state.gas_price() + } + fn origin(&self) -> H160 { + self.state.origin() + } + fn block_hash(&self, number: U256) -> H256 { + self.state.block_hash(number) + } + fn block_number(&self) -> U256 { + self.state.block_number() + } + fn block_coinbase(&self) -> H160 { + self.state.block_coinbase() + } + fn block_timestamp(&self) -> U256 { + self.state.block_timestamp() + } + fn block_difficulty(&self) -> U256 { + self.state.block_difficulty() + } + fn block_randomness(&self) -> Option { + self.state.block_randomness() + } + fn block_gas_limit(&self) -> U256 { + self.state.block_gas_limit() + } + fn block_base_fee_per_gas(&self) -> U256 { + self.state.block_base_fee_per_gas() + } + fn chain_id(&self) -> U256 { + self.state.chain_id() + } + + fn deleted(&self, address: H160) -> bool { + self.state.deleted(address) + } + + fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError> { + self.state.set_storage(address, index, value); + Ok(()) + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + self.state.log(address, topics, data); + Ok(()) + } + + fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError> { + let balance = self.balance(address); + + event!(Suicide { + target, + address, + balance, + }); + + self.state.transfer(Transfer { + source: address, + target, + value: balance, + })?; + self.state.reset_balance(address); + self.state.set_deleted(address); + + Ok(()) + } + + #[cfg(not(feature = "tracing"))] + fn create( + &mut self, + caller: H160, + scheme: CreateScheme, + value: U256, + init_code: Vec, + target_gas: Option, + ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { + if let Err(e) = self.maybe_record_init_code_cost(&init_code) { + let reason: ExitReason = e.into(); + emit_exit!(reason.clone()); + return Capture::Exit((reason, None, Vec::new())); + } + + self.create_inner(caller, scheme, value, init_code, target_gas, true) + } + + #[cfg(feature = "tracing")] + fn create( + &mut self, + caller: H160, + scheme: CreateScheme, + value: U256, + init_code: Vec, + target_gas: Option, + ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { + if let Err(e) = self.maybe_record_init_code_cost(&init_code) { + let reason: ExitReason = e.into(); + emit_exit!(reason.clone()); + return Capture::Exit((reason, None, Vec::new())); + } + + let capture = self.create_inner(caller, scheme, value, init_code, target_gas, true); + + if let Capture::Exit((ref reason, _, ref return_value)) = capture { + emit_exit!(reason, return_value); + } + + capture + } + + #[cfg(not(feature = "tracing"))] + fn call( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: Context, + ) -> Capture<(ExitReason, Vec), Self::CallInterrupt> { + self.call_inner( + code_address, + transfer, + input, + target_gas, + is_static, + true, + true, + context, + ) + } + + #[cfg(feature = "tracing")] + fn call( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: Context, + ) -> Capture<(ExitReason, Vec), Self::CallInterrupt> { + let capture = self.call_inner( + code_address, + transfer, + input, + target_gas, + is_static, + true, + true, + context, + ); + + if let Capture::Exit((ref reason, ref return_value)) = capture { + emit_exit!(reason, return_value); + } + + capture + } + + #[inline] + fn pre_validate(&mut self, context: &Context, opcode: Opcode, stack: &Stack) -> Result<(), ExitError> { + // log::trace!(target: "evm", "Running opcode: {:?}, Pre gas-left: {:?}", opcode, gasometer.gas()); + + if let Some(cost) = gasometer::static_opcode_cost(opcode) { + self.state.metadata_mut().gasometer.record_cost(cost)?; + } else { + let is_static = self.state.metadata().is_static; + let (gas_cost, target, memory_cost) = + gasometer::dynamic_opcode_cost(context.address, opcode, stack, is_static, self.config, self)?; + + let gasometer = &mut self.state.metadata_mut().gasometer; + + gasometer.record_dynamic_cost(gas_cost, memory_cost)?; + + self.state + .record_external_dynamic_opcode_cost(opcode, gas_cost, target)?; + + match target { + StorageTarget::Address(address) => self.state.metadata_mut().access_address(address), + StorageTarget::Slot(address, key) => self.state.metadata_mut().access_storage(address, key), + StorageTarget::None => (), + } + } + + Ok(()) + } + + fn record_external_operation(&mut self, op: crate::ExternalOperation) -> Result<(), ExitError> { + self.state.record_external_operation(op) + } +} + +pub struct StackExecutorHandle<'inner, 'config, 'precompiles, S, P> { + executor: &'inner mut StackExecutor<'config, 'precompiles, S, P>, + code_address: H160, + input: &'inner [u8], + gas_limit: Option, + context: &'inner Context, + is_static: bool, +} + +impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle + for StackExecutorHandle<'inner, 'config, 'precompiles, S, P> +{ + // Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. + fn call( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + gas_limit: Option, + is_static: bool, + context: &Context, + ) -> (ExitReason, Vec) { + // For normal calls the cost is recorded at opcode level. + // Since we don't go through opcodes we need manually record the call + // cost. Not doing so will make the code panic as recording the call stipend + // will do an underflow. + let target_is_cold = match self.executor.is_cold(code_address, None) { + Ok(x) => x, + Err(err) => return (ExitReason::Error(err), Vec::new()), + }; + + let target_exists = self.executor.exists(code_address); + + let gas_cost = gasometer::GasCost::Call { + value: transfer.clone().map(|x| x.value).unwrap_or_else(U256::zero), + gas: U256::from(gas_limit.unwrap_or(u64::MAX)), + target_is_cold, + target_exists, + }; + + // We record the length of the input. + let memory_cost = Some(gasometer::MemoryCost { + offset: U256::zero(), + len: input.len().into(), + }); + + if let Err(error) = self + .executor + .state + .metadata_mut() + .gasometer + .record_dynamic_cost(gas_cost, memory_cost) + { + return (ExitReason::Error(error), Vec::new()); + } + + event!(PrecompileSubcall { + code_address: code_address.clone(), + transfer: &transfer, + input: &input, + target_gas: gas_limit, + is_static, + context + }); + + // Perform the subcall + match Handler::call( + self.executor, + code_address, + transfer, + input, + gas_limit, + is_static, + context.clone(), + ) { + Capture::Exit((s, v)) => (s, v), + Capture::Trap(rt) => { + // Ideally this would pass the interrupt back to the executor so it could be + // handled like any other call, however the type signature of this function does + // not allow it. For now we'll make a recursive call instead of making a breaking + // change to the precompile API. But this means a custom precompile could still + // potentially cause a stack overflow if you're not careful. + let mut call_stack = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + call_stack.push(rt.0); + let (reason, _, return_data) = self.executor.execute_with_call_stack(&mut call_stack); + emit_exit!(reason, return_data) + } + } + } + + /// Record cost to the Runtime gasometer. + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.executor.state.metadata_mut().gasometer.record_cost(cost) + } + + /// Record Substrate specific cost. + fn record_external_cost( + &mut self, + ref_time: Option, + proof_size: Option, + storage_growth: Option, + ) -> Result<(), ExitError> { + self.executor + .state + .record_external_cost(ref_time, proof_size, storage_growth) + } + + /// Refund Substrate specific cost. + fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option) { + self.executor.state.refund_external_cost(ref_time, proof_size); + } + + /// Retreive the remaining gas. + fn remaining_gas(&self) -> u64 { + self.executor.state.metadata().gasometer.gas() + } + + /// Record a log. + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + Handler::log(self.executor, address, topics, data) + } + + /// Retreive the code address (what is the address of the precompile being called). + fn code_address(&self) -> H160 { + self.code_address + } + + /// Retreive the input data the precompile is called with. + fn input(&self) -> &[u8] { + self.input + } + + /// Retreive the context in which the precompile is executed. + fn context(&self) -> &Context { + self.context + } + + /// Is the precompile call is done statically. + fn is_static(&self) -> bool { + self.is_static + } + + /// Retreive the gas limit of this call. + fn gas_limit(&self) -> Option { + self.gas_limit + } +} diff --git a/blockchain/modules/evm/src/runner/storage_meter.rs b/blockchain/modules/evm/src/runner/storage_meter.rs index 20888d4d..5824b5ee 100644 --- a/blockchain/modules/evm/src/runner/storage_meter.rs +++ b/blockchain/modules/evm/src/runner/storage_meter.rs @@ -1,311 +1,282 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 frame_support::log; - -pub struct StorageMeter { - limit: u32, - extra_bytes: u32, - used: u32, - refunded: u32, - // save storage of children - child_used: u32, - child_refunded: u32, -} - -impl StorageMeter { - /// Create a new storage_meter with given storage limit. - pub fn new(limit: u32, extra_bytes: u32) -> Self { - Self { - limit, - extra_bytes, - used: 0, - refunded: 0, - child_used: 0, - child_refunded: 0, - } - } - - pub fn child_meter(&mut self) -> Self { - let storage = self.available_storage(); - StorageMeter::new(storage, self.extra_bytes) - } - - pub fn storage_limit(&self) -> u32 { - self.limit - } - - pub fn extra_bytes(&self) -> u32 { - self.extra_bytes - } - - pub fn used(&self) -> u32 { - self.used - } - - pub fn refunded(&self) -> u32 { - self.refunded - } - - pub fn total_used(&self) -> u32 { - self.used.saturating_add(self.child_used) - } - - pub fn total_refunded(&self) -> u32 { - self.refunded.saturating_add(self.child_refunded) - } - - pub fn available_storage(&self) -> u32 { - self.limit - .saturating_add(self.refunded) - .saturating_add(self.child_refunded) - .saturating_sub(self.used) - .saturating_sub(self.child_used) - } - - pub fn used_storage(&self) -> i32 { - if self.used > self.refunded { - (self.used - self.refunded) as i32 - } else { - -((self.refunded - self.used) as i32) - } - } - - pub fn finish(&self) -> Option { - let total_used = self.total_used(); - let total_refunded = self.total_refunded(); - log::trace!( - target: "evm", - "StorageMeter: finish: used {:?} refunded {:?}", - total_used, total_refunded - ); - if self.limit < total_used.saturating_sub(total_refunded) { - // OutOfStorage - return None; - } - - if total_used > total_refunded { - Some((total_used - total_refunded) as i32) - } else { - Some(-((total_refunded - total_used) as i32)) - } - } - - pub fn charge(&mut self, storage: u32) { - log::trace!( - target: "evm", - "StorageMeter: charge: storage {:?}", - storage - ); - self.used = self.used.saturating_add(storage); - } - - pub fn charge_with_extra_bytes(&mut self, storage: u32) { - log::trace!( - target: "evm", - "StorageMeter: charge: storage {:?}", - storage - ); - self.used = self.used.saturating_add(storage).saturating_add(self.extra_bytes); - } - - pub fn uncharge(&mut self, storage: u32) { - log::trace!( - target: "evm", - "StorageMeter: uncharge: storage {:?}", - storage - ); - self.used = self.used.saturating_sub(storage); - } - - pub fn refund(&mut self, storage: u32) { - log::trace!( - target: "evm", - "StorageMeter: refund: storage {:?}", - storage - ); - self.refunded = self.refunded.saturating_add(storage); - } - - pub fn merge(&mut self, other: &Self) { - self.child_used = self.child_used.saturating_add(other.total_used()); - self.child_refunded = self.child_refunded.saturating_add(other.total_refunded()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_storage_with_limit_zero() { - let mut storage_meter = StorageMeter::new(0, 0); - assert_eq!(storage_meter.available_storage(), 0); - assert_eq!(storage_meter.storage_limit(), 0); - - // refund - storage_meter.refund(1); - assert_eq!(storage_meter.total_used(), 0); - assert_eq!(storage_meter.total_refunded(), 1); - assert_eq!(storage_meter.used_storage(), -1); - assert_eq!(storage_meter.available_storage(), 1); - - // charge - storage_meter.charge(1); - assert_eq!(storage_meter.total_used(), 1); - assert_eq!(storage_meter.total_refunded(), 1); - assert_eq!(storage_meter.used_storage(), 0); - assert_eq!(storage_meter.available_storage(), 0); - - // uncharge - storage_meter.uncharge(1); - assert_eq!(storage_meter.total_used(), 0); - assert_eq!(storage_meter.total_refunded(), 1); - assert_eq!(storage_meter.used_storage(), -1); - assert_eq!(storage_meter.available_storage(), 1); - - // finish - assert_eq!(storage_meter.finish(), Some(-1)); - } - - #[test] - fn test_with_extra_bytes() { - let mut storage_meter = StorageMeter::new(1000, 100); - assert_eq!(storage_meter.available_storage(), 1000); - assert_eq!(storage_meter.extra_bytes(), 100); - - storage_meter.charge(200); - assert_eq!(storage_meter.finish(), Some(200)); - - storage_meter.charge_with_extra_bytes(200); - assert_eq!(storage_meter.finish(), Some(500)); - } - - #[test] - fn test_out_of_storage() { - let mut storage_meter = StorageMeter::new(1000, 0); - assert_eq!(storage_meter.available_storage(), 1000); - - storage_meter.charge(200); - assert_eq!(storage_meter.finish(), Some(200)); - - storage_meter.charge(2000); - assert_eq!(storage_meter.finish(), None); - - storage_meter.refund(2000); - assert_eq!(storage_meter.finish(), Some(200)); - } - - #[test] - fn test_high_use_and_refund() { - let mut storage_meter = StorageMeter::new(1000, 0); - assert_eq!(storage_meter.available_storage(), 1000); - - storage_meter.charge(1000); - assert_eq!(storage_meter.available_storage(), 0); - - storage_meter.charge(100); - assert_eq!(storage_meter.available_storage(), 0); - storage_meter.refund(200); - assert_eq!(storage_meter.available_storage(), 100); - - let child_meter = storage_meter.child_meter(); - assert_eq!(storage_meter.available_storage(), 100); - - assert_eq!(child_meter.finish(), Some(0)); - assert_eq!(storage_meter.finish(), Some(900)); - } - - #[test] - fn test_child_meter() { - let mut storage_meter = StorageMeter::new(1000, 0); - storage_meter.charge(100); - - let mut child_meter = storage_meter.child_meter(); - assert_eq!(child_meter.available_storage(), 900); - - child_meter.charge(100); - assert_eq!(child_meter.available_storage(), 800); - - child_meter.refund(50); - assert_eq!(child_meter.available_storage(), 850); - - let mut child_meter_2 = child_meter.child_meter(); - assert_eq!(child_meter_2.available_storage(), 850); - - child_meter_2.charge(20); - assert_eq!(child_meter_2.available_storage(), 830); - - assert_eq!(child_meter_2.finish(), Some(20)); - - assert_eq!(child_meter.finish(), Some(50)); - - let mut child_meter_3 = storage_meter.child_meter(); - assert_eq!(child_meter_3.available_storage(), 900); - - child_meter_3.charge(30); - assert_eq!(child_meter_3.available_storage(), 870); - assert_eq!(child_meter_3.finish(), Some(30)); - - assert_eq!(storage_meter.available_storage(), 900); - assert_eq!(storage_meter.finish(), Some(100)); - } - - #[test] - fn test_merge() { - let mut storage_meter = StorageMeter::new(1000, 0); - storage_meter.charge(100); - - let mut child_meter = storage_meter.child_meter(); - assert_eq!(child_meter.available_storage(), 900); - - child_meter.charge(100); - assert_eq!(child_meter.available_storage(), 800); - - child_meter.refund(50); - assert_eq!(child_meter.available_storage(), 850); - - let mut child_meter_2 = child_meter.child_meter(); - assert_eq!(child_meter_2.available_storage(), 850); - - child_meter_2.charge(20); - assert_eq!(child_meter_2.available_storage(), 830); - - assert_eq!(child_meter_2.finish(), Some(20)); - - assert_eq!(child_meter.finish(), Some(50)); - child_meter.merge(&child_meter_2); - assert_eq!(child_meter.available_storage(), 830); - - let mut child_meter_3 = storage_meter.child_meter(); - assert_eq!(child_meter_3.available_storage(), 900); - - child_meter_3.charge(30); - assert_eq!(child_meter_3.available_storage(), 870); - assert_eq!(child_meter_3.finish(), Some(30)); - storage_meter.merge(&child_meter_3); - - assert_eq!(storage_meter.available_storage(), 870); - assert_eq!(child_meter.finish(), Some(70)); - assert_eq!(storage_meter.finish(), Some(130)); - storage_meter.merge(&child_meter); - assert_eq!(storage_meter.available_storage(), 800); - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +#[derive(Default, Clone, Debug)] +pub struct StorageMeter { + limit: u32, + used: u32, + refunded: u32, + // save storage of children + child_used: u32, + child_refunded: u32, +} + +impl StorageMeter { + /// Create a new storage_meter with given storage limit. + pub fn new(limit: u32) -> Self { + Self { + limit, + used: 0, + refunded: 0, + child_used: 0, + child_refunded: 0, + } + } + + pub fn child_meter(&mut self) -> Self { + let storage = self.available_storage(); + StorageMeter::new(storage) + } + + pub fn storage_limit(&self) -> u32 { + self.limit + } + + pub fn used(&self) -> u32 { + self.used + } + + pub fn refunded(&self) -> u32 { + self.refunded + } + + pub fn total_used(&self) -> u32 { + self.used.saturating_add(self.child_used) + } + + pub fn total_refunded(&self) -> u32 { + self.refunded.saturating_add(self.child_refunded) + } + + pub fn available_storage(&self) -> u32 { + self.limit + .saturating_add(self.refunded) + .saturating_add(self.child_refunded) + .saturating_sub(self.used) + .saturating_sub(self.child_used) + } + + pub fn used_storage(&self) -> i32 { + if self.used > self.refunded { + (self.used - self.refunded) as i32 + } else { + -((self.refunded - self.used) as i32) + } + } + + pub fn finish(&self) -> Option { + let total_used = self.total_used(); + let total_refunded = self.total_refunded(); + log::trace!( + target: "evm", + "StorageMeter: finish: used {:?} refunded {:?}", + total_used, total_refunded + ); + if self.limit < total_used.saturating_sub(total_refunded) { + // OutOfStorage + return None; + } + + if total_used > total_refunded { + Some((total_used - total_refunded) as i32) + } else { + Some(-((total_refunded - total_used) as i32)) + } + } + + pub fn charge(&mut self, storage: u32) { + log::trace!( + target: "evm", + "StorageMeter: charge: storage {:?}", + storage + ); + self.used = self.used.saturating_add(storage); + } + + pub fn uncharge(&mut self, storage: u32) { + log::trace!( + target: "evm", + "StorageMeter: uncharge: storage {:?}", + storage + ); + self.used = self.used.saturating_sub(storage); + } + + pub fn refund(&mut self, storage: u32) { + log::trace!( + target: "evm", + "StorageMeter: refund: storage {:?}", + storage + ); + self.refunded = self.refunded.saturating_add(storage); + } + + pub fn merge(&mut self, other: &Self) { + self.child_used = self.child_used.saturating_add(other.total_used()); + self.child_refunded = self.child_refunded.saturating_add(other.total_refunded()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_storage_with_limit_zero() { + let mut storage_meter = StorageMeter::new(0); + assert_eq!(storage_meter.available_storage(), 0); + assert_eq!(storage_meter.storage_limit(), 0); + + // refund + storage_meter.refund(1); + assert_eq!(storage_meter.total_used(), 0); + assert_eq!(storage_meter.total_refunded(), 1); + assert_eq!(storage_meter.used_storage(), -1); + assert_eq!(storage_meter.available_storage(), 1); + + // charge + storage_meter.charge(1); + assert_eq!(storage_meter.total_used(), 1); + assert_eq!(storage_meter.total_refunded(), 1); + assert_eq!(storage_meter.used_storage(), 0); + assert_eq!(storage_meter.available_storage(), 0); + + // uncharge + storage_meter.uncharge(1); + assert_eq!(storage_meter.total_used(), 0); + assert_eq!(storage_meter.total_refunded(), 1); + assert_eq!(storage_meter.used_storage(), -1); + assert_eq!(storage_meter.available_storage(), 1); + + // finish + assert_eq!(storage_meter.finish(), Some(-1)); + } + + #[test] + fn test_out_of_storage() { + let mut storage_meter = StorageMeter::new(1000); + assert_eq!(storage_meter.available_storage(), 1000); + + storage_meter.charge(200); + assert_eq!(storage_meter.finish(), Some(200)); + + storage_meter.charge(2000); + assert_eq!(storage_meter.finish(), None); + + storage_meter.refund(2000); + assert_eq!(storage_meter.finish(), Some(200)); + } + + #[test] + fn test_high_use_and_refund() { + let mut storage_meter = StorageMeter::new(1000); + assert_eq!(storage_meter.available_storage(), 1000); + + storage_meter.charge(1000); + assert_eq!(storage_meter.available_storage(), 0); + + storage_meter.charge(100); + assert_eq!(storage_meter.available_storage(), 0); + storage_meter.refund(200); + assert_eq!(storage_meter.available_storage(), 100); + + let child_meter = storage_meter.child_meter(); + assert_eq!(storage_meter.available_storage(), 100); + + assert_eq!(child_meter.finish(), Some(0)); + assert_eq!(storage_meter.finish(), Some(900)); + } + + #[test] + fn test_child_meter() { + let mut storage_meter = StorageMeter::new(1000); + storage_meter.charge(100); + + let mut child_meter = storage_meter.child_meter(); + assert_eq!(child_meter.available_storage(), 900); + + child_meter.charge(100); + assert_eq!(child_meter.available_storage(), 800); + + child_meter.refund(50); + assert_eq!(child_meter.available_storage(), 850); + + let mut child_meter_2 = child_meter.child_meter(); + assert_eq!(child_meter_2.available_storage(), 850); + + child_meter_2.charge(20); + assert_eq!(child_meter_2.available_storage(), 830); + + assert_eq!(child_meter_2.finish(), Some(20)); + + assert_eq!(child_meter.finish(), Some(50)); + + let mut child_meter_3 = storage_meter.child_meter(); + assert_eq!(child_meter_3.available_storage(), 900); + + child_meter_3.charge(30); + assert_eq!(child_meter_3.available_storage(), 870); + assert_eq!(child_meter_3.finish(), Some(30)); + + assert_eq!(storage_meter.available_storage(), 900); + assert_eq!(storage_meter.finish(), Some(100)); + } + + #[test] + fn test_merge() { + let mut storage_meter = StorageMeter::new(1000); + storage_meter.charge(100); + + let mut child_meter = storage_meter.child_meter(); + assert_eq!(child_meter.available_storage(), 900); + + child_meter.charge(100); + assert_eq!(child_meter.available_storage(), 800); + + child_meter.refund(50); + assert_eq!(child_meter.available_storage(), 850); + + let mut child_meter_2 = child_meter.child_meter(); + assert_eq!(child_meter_2.available_storage(), 850); + + child_meter_2.charge(20); + assert_eq!(child_meter_2.available_storage(), 830); + + assert_eq!(child_meter_2.finish(), Some(20)); + + assert_eq!(child_meter.finish(), Some(50)); + child_meter.merge(&child_meter_2); + assert_eq!(child_meter.available_storage(), 830); + + let mut child_meter_3 = storage_meter.child_meter(); + assert_eq!(child_meter_3.available_storage(), 900); + + child_meter_3.charge(30); + assert_eq!(child_meter_3.available_storage(), 870); + assert_eq!(child_meter_3.finish(), Some(30)); + storage_meter.merge(&child_meter_3); + + assert_eq!(storage_meter.available_storage(), 870); + assert_eq!(child_meter.finish(), Some(70)); + assert_eq!(storage_meter.finish(), Some(130)); + storage_meter.merge(&child_meter); + assert_eq!(storage_meter.available_storage(), 800); + } +} diff --git a/blockchain/modules/evm/rpc/src/call_request.rs b/blockchain/modules/evm/src/runner/tagged_runtime.rs similarity index 51% rename from blockchain/modules/evm/rpc/src/call_request.rs rename to blockchain/modules/evm/src/runner/tagged_runtime.rs index 9f129a17..d578baef 100644 --- a/blockchain/modules/evm/rpc/src/call_request.rs +++ b/blockchain/modules/evm/src/runner/tagged_runtime.rs @@ -1,55 +1,35 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 ethereum_types::{H160, U256}; -use serde::{Deserialize, Serialize}; -use sp_core::Bytes; -use sp_rpc::number::NumberOrHex; - -/// Call request -#[derive(Debug, Default, PartialEq, Deserialize, Clone)] -#[serde(deny_unknown_fields)] -#[serde(rename_all = "camelCase")] -pub struct CallRequest { - /// From - pub from: Option, - /// To - pub to: Option, - /// Gas Limit - pub gas_limit: Option, - /// Storage Limit - pub storage_limit: Option, - /// Value - pub value: Option, - /// Data - pub data: Option, -} - -/// EstimateResources response -#[derive(Debug, Eq, PartialEq, Default, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct EstimateResourcesResponse { - /// Used gas - pub gas: U256, - /// Used storage - pub storage: i32, - /// Adjusted weight fee - pub weight_fee: U256, -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 module_evm_utility::evm::{maybe_borrowed::MaybeBorrowed, Runtime}; +use sp_core::H160; + +pub struct TaggedRuntime<'borrow> { + pub kind: RuntimeKind, + pub inner: MaybeBorrowed<'borrow, Runtime>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RuntimeKind { + Create(H160), + Call(H160), + /// Special variant used only in `StackExecutor::execute` + Execute, +} diff --git a/blockchain/modules/evm/src/tests.rs b/blockchain/modules/evm/src/tests.rs index cbe6b539..8ec9d6c3 100644 --- a/blockchain/modules/evm/src/tests.rs +++ b/blockchain/modules/evm/src/tests.rs @@ -1,1643 +1,2969 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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)] - -use super::*; -use mock::{Event, *}; - -use crate::runner::{ - stack::SubstrateStackState, - state::{StackExecutor, StackSubstateMetadata}, - StackState, -}; -use frame_support::{assert_noop, assert_ok, dispatch::DispatchErrorWithPostInfo}; -use module_support::AddressMapping; -use sp_core::{ - bytes::{from_hex, to_hex}, - H160, -}; -use sp_runtime::{traits::BadOrigin, AccountId32}; -use std::str::FromStr; - -#[test] -fn fail_call_return_ok() { - new_test_ext().execute_with(|| { - let mut data = [0u8; 32]; - data[0..4].copy_from_slice(b"evm:"); - let signer: AccountId32 = AccountId32::from(data).into(); - - let origin = Origin::signed(signer); - assert_ok!(EVM::call(origin.clone(), contract_a(), Vec::new(), 0, 1000000, 0)); - assert_ok!(EVM::call(origin, contract_b(), Vec::new(), 0, 1000000, 0)); - }); -} - -#[test] -fn should_calculate_contract_address() { - new_test_ext().execute_with(|| { - let addr = H160::from_str("bec02ff0cbf20042a37d964c33e89f1a2be7f068").unwrap(); - - let vicinity = Vicinity { - gas_price: U256::one(), - origin: Default::default(), - }; - let metadata = StackSubstateMetadata::new(1000, 1000, NewContractExtraBytes::get(), &SETHEUM_CONFIG); - let state = SubstrateStackState::::new(&vicinity, metadata); - let mut executor = StackExecutor::new(state, &SETHEUM_CONFIG); - - assert_eq!( - executor.create_address(evm::CreateScheme::Legacy { caller: addr }), - Ok(H160::from_str("d654cB21c05cb14895baae28159b1107e9DbD6E4").unwrap()) - ); - - executor.state_mut().inc_nonce(addr); - assert_eq!( - executor.create_address(evm::CreateScheme::Legacy { caller: addr }), - Ok(H160::from_str("97784910F057B07bFE317b0552AE23eF34644Aed").unwrap()) - ); - - executor.state_mut().inc_nonce(addr); - assert_eq!( - executor.create_address(evm::CreateScheme::Legacy { caller: addr }), - Ok(H160::from_str("82155a21E0Ccaee9D4239a582EB2fDAC1D9237c5").unwrap()) - ); - - assert_eq!( - executor.create_address(evm::CreateScheme::Fixed( - H160::from_str("0x0000000000000000000000000000000000000000").unwrap() - )), - Ok(H160::from_str("0x0000000000000000000000000000000000000000").unwrap()) - ); - }); -} - -#[test] -fn should_create_and_call_contract() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function multiply(uint a, uint b) public pure returns(uint) { - // return a * b; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - // deploy contract - let caller = alice(); - let result = ::Runner::create( - caller.clone(), - contract, - 0, - 1000000, - 1000000, - ::config(), - ).unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - - let contract_address = result.value; - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(contract_address); - - assert_eq!(contract_address, H160::from_str("5f8bd49cd9f0cb2bd5bb9d4320dfe9b61023249d").unwrap()); - - assert_eq!(Pallet::::account_basic(&caller).nonce, 2.into()); - - // multiply(2, 3) - let multiply = from_hex( - "0x165c4a1600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" - ).unwrap(); - - // call method `multiply` - let result = ::Runner::call( - alice(), - alice(), - contract_address, - multiply, - 0, - 1000000, - 1000000, - ::config(), - ).unwrap(); - assert_eq!( - U256::from(result.value.as_slice()), - 6.into(), - ); - - assert_eq!(Pallet::::account_basic(&caller).nonce, 3.into()); - - let code_hash = H256::from_str("164981e02df203a0fb32a0af7c2cd1cc7f9df7bb49a4d2b0219307bb68a4b603").unwrap(); - let code_size = 184u32; - - assert_eq!(Accounts::::get(&contract_address), Some(AccountInfo { - nonce: 1, - contract_info: Some(ContractInfo { - code_hash, - maintainer: alice(), - deployed: true - }) - })); - - assert_eq!(ContractStorageSizes::::get(&contract_address), code_size + NewContractExtraBytes::get()); - assert_eq!(CodeInfos::::get(&code_hash), Some(CodeInfo { - code_size, - ref_count: 1, - })); - assert!(Codes::::contains_key(&code_hash)); - }); -} - -#[test] -fn create_reverts_with_message() { - // pragma solidity ^0.5.0; - // - // contract Foo { - // constructor() public { - // require(false, "error message"); - // } - // } - let contract = from_hex( - "0x6080604052348015600f57600080fd5b5060006083576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f6572726f72206d6573736167650000000000000000000000000000000000000081525060200191505060405180910390fd5b603e8060906000396000f3fe6080604052600080fdfea265627a7a723158204741083d83bf4e3ee8099dd0b3471c81061237c2e8eccfcb513dfa4c04634b5b64736f6c63430005110032" - ).unwrap(); - new_test_ext().execute_with(|| { - let result = ::Runner::create( - alice(), - contract, - 0, - 12_000_000, - 12_000_000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Revert(ExitRevert::Reverted)); - assert_eq!( - result.value, - H160::from_str("0x5f8bd49cd9f0cb2bd5bb9d4320dfe9b61023249d").unwrap() - ); - }); -} - -#[test] -fn call_reverts_with_message() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function foo() public pure { - // require(false, "error message"); - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b60336035565b005b600060a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f6572726f72206d6573736167650000000000000000000000000000000000000081525060200191505060405180910390fd5b56fea265627a7a7231582066b3ee33bedba8a318d0d66610145030fdc0f982b11f5160d366e15e4d8ba2ef64736f6c63430005110032" - ).unwrap(); - let caller = alice(); - - new_test_ext().execute_with(|| { - // deploy contract - let result = ::Runner::create( - caller, - contract, - 0, - 1000000, - 1000000, - ::config(), - ).unwrap(); - - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - - let alice_balance = INITIAL_BALANCE - 323 * ::StorageDepositPerByte::get(); - - assert_eq!(balance(alice()), alice_balance); - - let contract_address = result.value; - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(contract_address); - - // call method `foo` - let foo = from_hex("0xc2985578").unwrap(); - let result = ::Runner::call( - caller, - caller, - contract_address, - foo, - 0, - 1000000, - 1000000, - ::config(), - ).unwrap(); - - assert_eq!(balance(alice()), alice_balance); - assert_eq!(result.exit_reason, ExitReason::Revert(ExitRevert::Reverted)); - assert_eq!( - to_hex(&result.value, true), - "0x8c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d6572726f72206d65737361676500000000000000000000000000000000000000" - ); - - let message = String::from_utf8_lossy(&result.value); - assert!(message.contains("error message")); - - assert_eq!(Pallet::::account_basic(&caller).nonce, 3.into()); - }); -} - -#[test] -fn should_deploy_payable_contract() { - // pragma solidity ^0.5.0; - // - // contract Test { - // uint value; - // constructor(uint a) public payable { - // value = a; - // } - // - // function getValue() public payable returns (uint) { - // return value; - // } - // } - let mut contract = from_hex( - "0x60806040526040516100c73803806100c783398181016040526020811015602557600080fd5b81019080805190602001909291905050508060008190555050607b8061004c6000396000f3fe608060405260043610601c5760003560e01c806320965255146021575b600080fd5b6027603d565b6040518082815260200191505060405180910390f35b6000805490509056fea265627a7a72315820b832564a9db725638dcef03d07bfbdd2dc818020ea359630317e2126e95c314964736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - let amount = 1000u64; - - let stored_value: Vec = - from_hex("0x000000000000000000000000000000000000000000000000000000000000007b").unwrap(); - contract.append(&mut stored_value.clone()); - - let result = ::Runner::create( - alice(), - contract, - amount, - 1000000, - 100000, - ::config(), - ) - .unwrap(); - let contract_address = result.value; - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(contract_address); - - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.used_storage, 287); - - let alice_balance = INITIAL_BALANCE - amount - 287 * ::StorageDepositPerByte::get(); - assert_eq!(balance(alice()), alice_balance); - assert_eq!(balance(contract_address), amount); - - // call getValue() - let result = ::Runner::call( - alice(), - alice(), - contract_address, - from_hex("0x20965255").unwrap(), - amount, - 100000, - 100000, - ::config(), - ) - .unwrap(); - - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.value, stored_value); - assert_eq!(result.used_storage, 0); - - assert_eq!(balance(alice()), alice_balance - amount); - assert_eq!(balance(contract_address), 2 * amount); - - assert_eq!( - AccountStorages::::iter_prefix(&contract_address).collect::>(), - vec![( - H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - H256::from_slice(stored_value.as_slice()) - )] - ); - }); -} - -#[test] -fn should_transfer_from_contract() { - // pragma solidity ^0.5.16; - // - // contract SendEther { - // function sendViaTransfer(address payable _to) public payable { - // // This function is no longer recommended for sending Ether. - // _to.transfer(msg.value); - // } - // - // function sendViaSend(address payable _to) public payable { - // // Send returns a boolean value indicating success or failure. - // // This function is not recommended for sending Ether. - // bool sent = _to.send(msg.value); - // require(sent, "Failed to send Ether"); - // } - // - // function sendViaCall(address payable _to) public payable { - // // Call returns a boolean value indicating success or failure. - // // This is the current recommended method to use. - // (bool sent, bytes memory data) = _to.call.value(msg.value)(""); - // require(sent, "Failed to send Ether"); - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b50610318806100206000396000f3fe6080604052600436106100345760003560e01c8063636e082b1461003957806374be48061461007d578063830c29ae146100c1575b600080fd5b61007b6004803603602081101561004f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610105565b005b6100bf6004803603602081101561009357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061014f565b005b610103600480360360208110156100d757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101ff565b005b8073ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f1935050505015801561014b573d6000803e3d6000fd5b5050565b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806101fb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f4661696c656420746f2073656e6420457468657200000000000000000000000081525060200191505060405180910390fd5b5050565b600060608273ffffffffffffffffffffffffffffffffffffffff163460405180600001905060006040518083038185875af1925050503d8060008114610261576040519150601f19603f3d011682016040523d82523d6000602084013e610266565b606091505b5091509150816102de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f4661696c656420746f2073656e6420457468657200000000000000000000000081525060200191505060405180910390fd5b50505056fea265627a7a723158201b401be037c87d59ec386e75b0166702abb5a64f93ea20080904b6791bd88d1564736f6c63430005110032" - ).unwrap(); - new_test_ext().execute_with(|| { - let amount = 1000u64; - - let result = ::Runner::create( - alice(), - contract, - 0, - 10000000, - 10000000, - ::config(), - ) - .expect("create shouldn't fail"); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.used_storage, 892); - - let alice_balance = INITIAL_BALANCE - 892 * ::StorageDepositPerByte::get(); - assert_eq!(balance(alice()), alice_balance); - - let contract_address = result.value; - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(contract_address); - - // send via transfer - let mut via_transfer = from_hex("0x636e082b").unwrap(); - via_transfer.append(&mut Vec::from(H256::from(charlie()).as_bytes())); - - let result = ::Runner::call( - alice(), - alice(), - contract_address, - via_transfer, - amount, - 1000000, - 1000000, - ::config(), - ) - .unwrap(); - - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(balance(alice()), alice_balance - 1 * amount); - assert_eq!(balance(charlie()), 1 * amount); - - // send via send - let mut via_send = from_hex("0x74be4806").unwrap(); - via_send.append(&mut Vec::from(H256::from(charlie()).as_bytes())); - - let result = ::Runner::call( - alice(), - alice(), - contract_address, - via_send, - amount, - 1000000, - 1000000, - ::config(), - ) - .unwrap(); - - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(balance(charlie()), 2 * amount); - assert_eq!(balance(alice()), alice_balance - 2 * amount); - - // send via call - let mut via_call = from_hex("0x830c29ae").unwrap(); - via_call.append(&mut Vec::from(H256::from(charlie()).as_bytes())); - - let result = ::Runner::call( - alice(), - alice(), - contract_address, - via_call, - amount, - 1000000, - 1000000, - ::config(), - ) - .unwrap(); - - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(balance(charlie()), 3 * amount); - assert_eq!(balance(alice()), alice_balance - 3 * amount); - }) -} - -#[test] -fn contract_should_deploy_contracts() { - // pragma solidity ^0.5.0; - // - // contract Factory { - // Contract[] newContracts; - // - // function createContract () public payable { - // Contract newContract = new Contract(); - // newContracts.push(newContract); - // } - // } - // - // contract Contract {} - let contract = from_hex( - "0x608060405234801561001057600080fd5b5061016f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063412a5a6d14610046575b600080fd5b61004e610050565b005b600061005a6100e2565b604051809103906000f080158015610076573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b6040516052806100f28339019056fe6080604052348015600f57600080fd5b50603580601d6000396000f3fe6080604052600080fdfea165627a7a7230582092dc1966a8880ddf11e067f9dd56a632c11a78a4afd4a9f05924d427367958cc0029a165627a7a723058202b2cc7384e11c452cdbf39b68dada2d5e10a632cc0174a354b8b8c83237e28a40029" - ).unwrap(); - new_test_ext().execute_with(|| { - let result = ::Runner::create( - alice(), - contract.clone(), - 0, - 1000000000, - 1000000000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.used_storage, 467); - - let alice_balance = INITIAL_BALANCE - 467 * ::StorageDepositPerByte::get(); - - assert_eq!(balance(alice()), alice_balance); - let factory_contract_address = result.value; - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(factory_contract_address); - - assert_eq!(balance(factory_contract_address), 0); - assert_eq!( - reserved_balance(factory_contract_address), - 467 * ::StorageDepositPerByte::get() - ); - - // Factory.createContract - let amount = 1000000000; - let create_contract = from_hex("0x412a5a6d").unwrap(); - let result = ::Runner::call( - alice(), - alice(), - factory_contract_address, - create_contract, - amount, - 1000000000, - 1000000000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(result.used_storage, 281); - - assert_eq!( - balance(alice()), - alice_balance - amount - 281 * ::StorageDepositPerByte::get() - ); - assert_eq!(balance(factory_contract_address), amount); - assert_eq!(reserved_balance(factory_contract_address), 5950); - let contract_address = H160::from_str("7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(); - assert_eq!(balance(contract_address), 0); - assert_eq!(reserved_balance(contract_address), 1530); - }); -} - -#[test] -fn contract_should_deploy_contracts_without_payable() { - // pragma solidity ^0.5.0; - // - // contract Factory { - // Contract[] newContracts; - // - // function createContract () public { - // Contract newContract = new Contract(); - // newContracts.push(newContract); - // } - // } - // - // contract Contract {} - let contract = from_hex( - "0x608060405234801561001057600080fd5b5061016c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063412a5a6d14610030575b600080fd5b61003861003a565b005b6000604051610048906100d0565b604051809103906000f080158015610064573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b605b806100dd8339019056fe6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582094976cee5af14bf59c4bae67c79c12eb15de19bc18ad6038f3ee0898273c9c0564736f6c63430005110032a265627a7a72315820e19ae28dbf01eae11c526295a1ac533ea341c74d5724efe43171f6010fc98b3964736f6c63430005110032" - ).unwrap(); - new_test_ext().execute_with(|| { - let result = ::Runner::create( - alice(), - contract.clone(), - 0, - 1000000000, - 1000000000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - - let alice_balance = INITIAL_BALANCE - 464 * ::StorageDepositPerByte::get(); - - assert_eq!(balance(alice()), alice_balance); - let factory_contract_address = result.value; - assert_eq!(balance(factory_contract_address), 0); - assert_eq!(reserved_balance(factory_contract_address), 4640); - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(factory_contract_address); - - // Factory.createContract - let create_contract = from_hex("0x412a5a6d").unwrap(); - let result = ::Runner::call( - alice(), - alice(), - factory_contract_address, - create_contract, - 0, - 1000000000, - 1000000000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(result.used_storage, 290); - assert_eq!( - balance(alice()), - alice_balance - (result.used_storage as u64 * ::StorageDepositPerByte::get()) - ); - assert_eq!(balance(factory_contract_address), 0); - assert_eq!(reserved_balance(factory_contract_address), 5920); - }); -} - -#[test] -fn deploy_factory() { - // pragma solidity ^0.5.0; - // - // contract Factory { - // Contract c; - // constructor() public { - // c = new Contract(); - // c.foo(); - // } - // } - // - // contract Contract { - // function foo() public pure returns (uint) { - // return 123; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060405161001d90610121565b604051809103906000f080158015610039573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b815260040160206040518083038186803b1580156100e057600080fd5b505afa1580156100f4573d6000803e3d6000fd5b505050506040513d602081101561010a57600080fd5b81019080805190602001909291905050505061012d565b60a58061017983390190565b603e8061013b6000396000f3fe6080604052600080fdfea265627a7a7231582064177030ee644a03aaf8d65027df9e0331c8bc4b161de25bfb8aa3142848e0f864736f6c634300051100326080604052348015600f57600080fd5b5060878061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000607b90509056fea265627a7a7231582031e5a4abae00962cfe9875df1b5b0d3ce6624e220cb8c714a948794fcddb6b4f64736f6c63430005110032" - ).unwrap(); - new_test_ext().execute_with(|| { - let result = - ::Runner::create(alice(), contract, 0, 2_000_000, 5000, ::config()) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.used_gas.as_u64(), 156_479u64); - assert_eq!(result.used_storage, 461); - assert_eq!( - balance(alice()), - INITIAL_BALANCE - (result.used_storage as u64 * ::StorageDepositPerByte::get()) - ); - }); -} - -#[test] -fn create_network_contract_works() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function multiply(uint a, uint b) public pure returns(uint) { - // return a * b; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - // deploy contract - assert_ok!(EVM::create_network_contract( - Origin::signed(NetworkContractAccount::get()), - contract, - 0, - 1000000, - 1000000, - )); - - assert_eq!( - Pallet::::account_basic(&NetworkContractSource::get()).nonce, - 2.into() - ); - System::assert_last_event(Event::EVM(crate::Event::Created( - NetworkContractSource::get(), - H160::from_low_u64_be(MIRRORED_NFT_ADDRESS_START), - vec![], - ))); - assert_eq!(EVM::network_contract_index(), MIRRORED_NFT_ADDRESS_START + 1); - }); -} - -#[test] -fn create_network_contract_fails_if_non_network_contract_origin() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function multiply(uint a, uint b) public pure returns(uint) { - // return a * b; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - assert_noop!( - EVM::create_network_contract( - Origin::signed(AccountId32::from([1u8; 32])), - contract, - 0, - 1000000, - 1000000 - ), - BadOrigin - ); - }); -} - -#[test] -fn create_predeploy_contract_works() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function multiply(uint a, uint b) public pure returns(uint) { - // return a * b; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - let addr = H160::from_str("1111111111111111111111111111111111111111").unwrap(); - - assert_eq!(Pallet::::is_account_empty(&addr), true); - - // deploy contract - assert_ok!(EVM::create_predeploy_contract( - Origin::signed(NetworkContractAccount::get()), - addr, - contract, - 0, - 1000000, - 1000000, - )); - - assert_eq!(Pallet::::is_account_empty(&addr), false); - - System::assert_last_event(Event::EVM(crate::Event::Created( - NetworkContractSource::get(), - addr, - vec![], - ))); - - assert_noop!( - EVM::create_predeploy_contract( - Origin::signed(NetworkContractAccount::get()), - addr, - vec![], - 0, - 1000000, - 1000000, - ), - Error::::ContractAlreadyExisted - ); - - // deploy mirrored token - let addr = H160::from_str("2222222222222222222222222222222222222222").unwrap(); - assert_ok!(EVM::create_predeploy_contract( - Origin::signed(NetworkContractAccount::get()), - addr, - vec![], - 0, - 1000000, - 1000000, - )); - let account_id = ::AddressMapping::get_account_id(&addr); - assert_eq!(Balances::free_balance(account_id), Balances::minimum_balance()); - assert_eq!( - Balances::free_balance(TreasuryAccount::get()), - INITIAL_BALANCE - Balances::minimum_balance() - ); - }); -} - -#[test] -fn should_transfer_maintainer() { - // pragma solidity ^0.5.0; - // - // contract Factory { - // Contract c; - // constructor() public { - // c = new Contract(); - // c.foo(); - // } - // } - // - // contract Contract { - // function foo() public pure returns (uint) { - // return 123; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060405161001d90610121565b604051809103906000f080158015610039573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b815260040160206040518083038186803b1580156100e057600080fd5b505afa1580156100f4573d6000803e3d6000fd5b505050506040513d602081101561010a57600080fd5b81019080805190602001909291905050505061012d565b60a58061017983390190565b603e8061013b6000396000f3fe6080604052600080fdfea265627a7a7231582064177030ee644a03aaf8d65027df9e0331c8bc4b161de25bfb8aa3142848e0f864736f6c634300051100326080604052348015600f57600080fd5b5060878061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000607b90509056fea265627a7a7231582031e5a4abae00962cfe9875df1b5b0d3ce6624e220cb8c714a948794fcddb6b4f64736f6c63430005110032" - ).unwrap(); - new_test_ext().execute_with(|| { - let result = ::Runner::create( - alice(), - contract, - 0, - 12_000_000, - 12_000_000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.used_storage, 461); - let alice_balance = INITIAL_BALANCE - 461 * ::StorageDepositPerByte::get(); - let contract_address = result.value; - - assert_eq!(balance(alice()), alice_balance); - - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - let bob_account_id = ::AddressMapping::get_account_id(&bob()); - assert_eq!(balance(bob()), INITIAL_BALANCE); - // transfer_maintainer - assert_ok!(EVM::transfer_maintainer( - Origin::signed(alice_account_id.clone()), - contract_address, - bob() - )); - System::assert_last_event(Event::EVM(crate::Event::TransferredMaintainer(contract_address, bob()))); - assert_eq!(balance(bob()), INITIAL_BALANCE); - - assert_noop!( - EVM::transfer_maintainer(Origin::signed(bob_account_id.clone()), H160::default(), alice()), - Error::::ContractNotFound - ); - - assert_noop!( - EVM::transfer_maintainer(Origin::signed(alice_account_id.clone()), contract_address, bob()), - Error::::NoPermission - ); - assert_eq!(balance(alice()), alice_balance); - }); -} - -#[test] -fn should_deploy() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function multiply(uint a, uint b) public pure returns(uint) { - // return a * b; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - let bob_account_id = ::AddressMapping::get_account_id(&bob()); - - // contract not created yet - assert_noop!(EVM::deploy(Origin::signed(alice_account_id.clone()), H160::default()), Error::::ContractNotFound); - - // if the contract not exists, evm will return ExitSucceed::Stopped. - let result = ::Runner::call( - alice(), - alice(), - EvmAddress::default(), - vec![], - 0, - 1000000, - 1000000, - ::config(), - ).unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(result.used_storage, 0); - - // create contract - let result = ::Runner::create(alice(), contract, 0, 21_000_000, 21_000_000, ::config()).unwrap(); - let contract_address = result.value; - - assert_eq!(result.used_storage, 284); - let alice_balance = INITIAL_BALANCE - 284 * ::StorageDepositPerByte::get(); - - assert_eq!(balance(alice()), alice_balance); - - // multiply(2, 3) - let multiply = from_hex( - "0x165c4a1600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" - ).unwrap(); - - // contract maintainer can call - assert_ok!(::Runner::call( - alice(), - alice(), - contract_address, - multiply.clone(), - 0, - 1000000, - 1000000, - ::config(), - )); - - // call method `multiply` will fail, not deployed yet - assert_noop!(EVM::call( - Origin::signed(bob_account_id.clone()), - contract_address, - multiply.clone(), - 0, - 1000000, - 1000000, - ), Error::::NoPermission); - - // developer can call the undeployed contract - assert_ok!(EVM::enable_contract_development(Origin::signed(bob_account_id.clone()))); - assert_ok!(::Runner::call( - bob(), - bob(), - contract_address, - vec![], - 0, - 1000000, - 1000000, - ::config(), - )); - - // not maintainer - assert_noop!(EVM::deploy(Origin::signed(bob_account_id), contract_address), Error::::NoPermission); - - assert_ok!(EVM::deploy(Origin::signed(alice_account_id.clone()), contract_address)); - let code_size = Accounts::::get(contract_address).map_or(0, |account_info| -> u32 { - account_info.contract_info.map_or(0, |contract_info| CodeInfos::::get(contract_info.code_hash).map_or(0, |code_info| code_info.code_size)) - }); - assert_eq!(balance(alice()), INITIAL_BALANCE - DeploymentFee::get() - ((NewContractExtraBytes::get() + code_size) as u64 * StorageDepositPerByte::get())); - assert_eq!(Balances::free_balance(TreasuryAccount::get()), INITIAL_BALANCE + DeploymentFee::get()); - - // call method `multiply` will work - assert_ok!(::Runner::call( - alice(), - alice(), - contract_address, - multiply, - 0, - 1000000, - 1000000, - ::config(), - )); - - // contract already deployed - assert_noop!(EVM::deploy(Origin::signed(alice_account_id), contract_address), Error::::ContractAlreadyDeployed); - }); -} - -#[test] -fn should_deploy_free() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function multiply(uint a, uint b) public pure returns(uint) { - // return a * b; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - // contract not created yet - assert_noop!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), H160::default()), Error::::ContractNotFound); - - // create contract - let result = ::Runner::create(alice(), contract, 0, 21_000_000, 21_000_000, ::config()).unwrap(); - let contract_address = result.value; - - // multiply(2, 3) - let multiply = from_hex( - "0x165c4a1600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" - ).unwrap(); - - // call method `multiply` will fail, not deployed yet - let bob_account_id = ::AddressMapping::get_account_id(&bob()); - assert_noop!(EVM::call( - Origin::signed(bob_account_id), - contract_address, - multiply.clone(), - 0, - 1000000, - 1000000, - ), Error::::NoPermission); - - assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), contract_address)); - - // call method `multiply` - assert_ok!(::Runner::call( - bob(), - alice(), - contract_address, - multiply.clone(), - 0, - 1000000, - 1000000, - ::config(), - )); - - // contract already deployed - assert_noop!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), contract_address), Error::::ContractAlreadyDeployed); - }); -} - -#[test] -fn should_enable_contract_development() { - new_test_ext().execute_with(|| { - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - assert_eq!(reserved_balance(alice()), 0); - assert_ok!(EVM::enable_contract_development(Origin::signed(alice_account_id))); - assert_eq!(reserved_balance(alice()), DeveloperDeposit::get()); - assert_eq!(balance(alice()), INITIAL_BALANCE - DeveloperDeposit::get()); - }); -} - -#[test] -fn should_disable_contract_development() { - new_test_ext().execute_with(|| { - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - - // contract development is not enabled yet - assert_noop!( - EVM::disable_contract_development(Origin::signed(alice_account_id.clone())), - Error::::ContractDevelopmentNotEnabled - ); - assert_eq!(balance(alice()), INITIAL_BALANCE); - - // enable contract development - assert_eq!(reserved_balance(alice()), 0); - assert_ok!(EVM::enable_contract_development(Origin::signed( - alice_account_id.clone() - ))); - assert_eq!(reserved_balance(alice()), DeveloperDeposit::get()); - - // deposit reserved - assert_eq!(balance(alice()), INITIAL_BALANCE - DeveloperDeposit::get()); - - // disable contract development - assert_ok!(EVM::disable_contract_development(Origin::signed( - alice_account_id.clone() - ))); - // deposit unreserved - assert_eq!(balance(alice()), INITIAL_BALANCE); - - // contract development already disabled - assert_noop!( - EVM::disable_contract_development(Origin::signed(alice_account_id)), - Error::::ContractDevelopmentNotEnabled - ); - }); -} - -#[test] -fn should_set_code() { - // pragma solidity ^0.5.0; - // - // contract Test { - // function multiply(uint a, uint b) public pure returns(uint) { - // return a * b; - // } - // } - let contract = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - let contract_err = from_hex( - "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - let bob_account_id = ::AddressMapping::get_account_id(&bob()); - - // create contract - let result = ::Runner::create( - alice(), - contract.clone(), - 0, - 21_000_000, - 21_000_000, - ::config(), - ) - .unwrap(); - let contract_address = result.value; - assert_eq!(result.used_storage, 284); - let alice_balance = INITIAL_BALANCE - 284 * ::StorageDepositPerByte::get(); - - assert_eq!(balance(alice()), alice_balance); - assert_eq!(reserved_balance(contract_address), 2840); - - let code_hash = H256::from_str("164981e02df203a0fb32a0af7c2cd1cc7f9df7bb49a4d2b0219307bb68a4b603").unwrap(); - assert_eq!( - Accounts::::get(&contract_address), - Some(AccountInfo { - nonce: 1, - contract_info: Some(ContractInfo { - code_hash, - maintainer: alice(), - deployed: false - }) - }) - ); - assert_eq!( - CodeInfos::::get(&code_hash), - Some(CodeInfo { - code_size: 184, - ref_count: 1, - }) - ); - - assert_noop!( - EVM::set_code(Origin::signed(bob_account_id), contract_address, contract.clone()), - Error::::NoPermission - ); - assert_ok!(EVM::set_code( - Origin::signed(alice_account_id.clone()), - contract_address, - contract.clone() - )); - assert_ok!(EVM::set_code(Origin::root(), contract_address, contract)); - - assert_eq!(reserved_balance(contract_address), 4150); - - let new_code_hash = H256::from_str("9061d510f6235de4eae304e1a2a2ae22e1610ba893c018b7fabc1f1635f49877").unwrap(); - assert_eq!( - Accounts::::get(&contract_address), - Some(AccountInfo { - nonce: 1, - contract_info: Some(ContractInfo { - code_hash: new_code_hash, - maintainer: alice(), - deployed: false - }) - }) - ); - assert_eq!(CodeInfos::::get(&code_hash), None); - assert_eq!( - CodeInfos::::get(&new_code_hash), - Some(CodeInfo { - code_size: 215, - ref_count: 1, - }) - ); - assert_eq!(Codes::::contains_key(&code_hash), false); - assert_eq!(Codes::::contains_key(&new_code_hash), true); - - assert_ok!(EVM::set_code(Origin::root(), contract_address, vec![])); - let new_code_hash = H256::from_str("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").unwrap(); - assert_eq!( - Accounts::::get(&contract_address), - Some(AccountInfo { - nonce: 1, - contract_info: Some(ContractInfo { - code_hash: new_code_hash, - maintainer: alice(), - deployed: false - }) - }) - ); - assert_eq!( - CodeInfos::::get(&new_code_hash), - Some(CodeInfo { - code_size: 0, - ref_count: 1, - }) - ); - assert_eq!(reserved_balance(contract_address), 3000); - - assert_noop!( - EVM::set_code( - Origin::signed(alice_account_id.clone()), - contract_address, - [8u8; (MaxCodeSize::get() + 1) as usize].to_vec(), - ), - Error::::ContractExceedsMaxCodeSize - ); - - assert_ok!(EVM::deploy_free( - Origin::signed(CouncilAccount::get()), - contract_address - )); - - assert_noop!( - EVM::set_code(Origin::signed(alice_account_id), contract_address, contract_err), - Error::::ContractAlreadyDeployed - ); - }); -} - -#[test] -fn should_selfdestruct() { - // pragma solidity ^0.5.0; - // - // contract Test { - // uint value; - // constructor(uint a) public payable { - // value = a; - // } - // - // function getValue() public payable returns (uint) { - // return value; - // } - // } - let mut contract = from_hex( - "0x60806040526040516100c73803806100c783398181016040526020811015602557600080fd5b81019080805190602001909291905050508060008190555050607b8061004c6000396000f3fe608060405260043610601c5760003560e01c806320965255146021575b600080fd5b6027603d565b6040518082815260200191505060405180910390f35b6000805490509056fea265627a7a72315820b832564a9db725638dcef03d07bfbdd2dc818020ea359630317e2126e95c314964736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - let bob_account_id = ::AddressMapping::get_account_id(&bob()); - - let amount = 1000u64; - - let stored_value: Vec = - from_hex("0x000000000000000000000000000000000000000000000000000000000000007b").unwrap(); - contract.append(&mut stored_value.clone()); - - // create contract - let result = ::Runner::create( - alice(), - contract, - amount, - 1000000, - 100000, - ::config(), - ) - .unwrap(); - - let contract_address = result.value; - assert_eq!(result.used_storage, 287); - let alice_balance = INITIAL_BALANCE - 287 * ::StorageDepositPerByte::get() - amount; - - assert_eq!(balance(alice()), alice_balance); - - let code_hash = H256::from_str("21fe816097a50d298f819bc6d40cff473c43c87d99bcd7d3c3b2b85417f66f5a").unwrap(); - let code_size = 123u32; - - assert_eq!( - ContractStorageSizes::::get(&contract_address), - code_size + NewContractExtraBytes::get() + STORAGE_SIZE - ); - assert_eq!( - CodeInfos::::get(&code_hash), - Some(CodeInfo { - code_size, - ref_count: 1, - }) - ); - assert!(Codes::::contains_key(&code_hash)); - - assert_noop!( - EVM::selfdestruct(Origin::signed(bob_account_id), contract_address), - Error::::NoPermission - ); - let contract_account_id = ::AddressMapping::get_account_id(&contract_address); - assert_eq!(System::providers(&contract_account_id), 2); - assert_ok!(EVM::selfdestruct(Origin::signed(alice_account_id), contract_address)); - - assert_eq!(System::providers(&contract_account_id), 0); - assert!(!System::account_exists(&contract_account_id)); - assert!(!Accounts::::contains_key(&contract_address)); - assert!(!ContractStorageSizes::::contains_key(&contract_address)); - assert_eq!(AccountStorages::::iter_prefix(&contract_address).count(), 0); - assert!(!CodeInfos::::contains_key(&code_hash)); - assert!(!Codes::::contains_key(&code_hash)); - }); -} - -#[test] -fn storage_limit_should_work() { - // pragma solidity ^0.5.0; - - // contract Factory { - // Contract[] newContracts; - - // function createContract (uint num) public payable { - // for(uint i = 0; i < num; i++) { - // Contract newContract = new Contract(); - // newContracts.push(newContract); - // } - // } - // } - - // contract Contract {} - let contract = from_hex( - "0x608060405234801561001057600080fd5b506101a0806100206000396000f3fe60806040526004361061001e5760003560e01c80639db8d7d514610023575b600080fd5b61004f6004803603602081101561003957600080fd5b8101908080359060200190929190505050610051565b005b60008090505b8181101561010057600060405161006d90610104565b604051809103906000f080158015610089573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050508080600101915050610057565b5050565b605b806101118339019056fe6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582035666e9471716d6d05ed9f0c1ab13d0371f49d536270f905bff06cd98212dcb064736f6c63430005110032a265627a7a723158203b6aaf6588bc3e6a35986612a62f715255430eab09ffb24401e5f18eb58a05d564736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - let result = ::Runner::create( - alice(), - contract.clone(), - 0, - 200_000, - 1000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.used_storage, 516); - let alice_balance = INITIAL_BALANCE - 516 * ::StorageDepositPerByte::get(); - assert_eq!(balance(alice()), alice_balance); - - let factory_contract_address = result.value; - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(factory_contract_address); - - assert_eq!(balance(factory_contract_address), 0); - assert_eq!( - reserved_balance(factory_contract_address), - 516 * ::StorageDepositPerByte::get() - ); - - // Factory.createContract(1) - let amount = 1000000000; - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - assert_noop!( - EVM::call( - Origin::signed(alice_account_id.clone()), - factory_contract_address, - create_contract, - amount, - 1000000000, - 0, - ), - DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { - actual_weight: None, - pays_fee: Pays::Yes, - }, - error: DispatchError::from(Error::::OutOfStorage) - } - ); - - // Factory.createContract(1) - let amount = 1000000000; - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); - let result = ::Runner::call( - alice(), - alice(), - factory_contract_address, - create_contract, - amount, - 1000000000, - 1000000000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(result.used_storage, 290); - - // Factory.createContract(2) - let amount = 1000000000; - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000002").unwrap(); - assert_noop!( - EVM::call( - Origin::signed(alice_account_id), - factory_contract_address, - create_contract, - amount, - 1000000000, - 127, - ), - DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { - actual_weight: None, - pays_fee: Pays::Yes, - }, - error: DispatchError::from(Error::::OutOfStorage) - } - ); - - // Factory.createContract(2) - let amount = 1000000000; - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000002").unwrap(); - let result = ::Runner::call( - alice(), - alice(), - factory_contract_address, - create_contract, - amount, - 1000000000, - 1000000000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); - assert_eq!(result.used_storage, 580); - }); -} - -#[test] -fn evm_execute_mode_should_work() { - // pragma solidity ^0.5.0; - - // contract Factory { - // Contract[] newContracts; - - // function createContract (uint num) public payable { - // for(uint i = 0; i < num; i++) { - // Contract newContract = new Contract(); - // newContracts.push(newContract); - // } - // } - // } - - // contract Contract {} - let contract = from_hex( - "0x608060405234801561001057600080fd5b506101a0806100206000396000f3fe60806040526004361061001e5760003560e01c80639db8d7d514610023575b600080fd5b61004f6004803603602081101561003957600080fd5b8101908080359060200190929190505050610051565b005b60008090505b8181101561010057600060405161006d90610104565b604051809103906000f080158015610089573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050508080600101915050610057565b5050565b605b806101118339019056fe6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582035666e9471716d6d05ed9f0c1ab13d0371f49d536270f905bff06cd98212dcb064736f6c63430005110032a265627a7a723158203b6aaf6588bc3e6a35986612a62f715255430eab09ffb24401e5f18eb58a05d564736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - let mut alice_balance = INITIAL_BALANCE - 516 * ::StorageDepositPerByte::get(); - - let result = ::Runner::create( - alice(), - contract.clone(), - 0, - 1000000000, - 1000000000, - ::config(), - ) - .unwrap(); - assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - assert_eq!(result.used_storage, 516); - assert_eq!(balance(alice()), alice_balance); - let factory_contract_address = result.value; - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(factory_contract_address); - - let context = InvokeContext { - contract: factory_contract_address, - sender: alice(), - origin: alice(), - }; - - // ExecutionMode::EstimateGas - // Factory.createContract(1) - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); - let result = EVM::execute( - context, - create_contract, - Default::default(), - 2_100_000, - 1000, - ExecutionMode::EstimateGas, - ) - .unwrap(); - assert_eq!( - result, - CallInfo { - exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), - value: vec![], - used_gas: U256::from(139845), - used_storage: 290, - logs: vec![] - } - ); - - // Factory.createContract(2) - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000002").unwrap(); - let result = EVM::execute( - context, - create_contract, - Default::default(), - 2_100_000, - 2_100_000, - ExecutionMode::EstimateGas, - ) - .unwrap(); - assert_eq!( - result, - CallInfo { - exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), - value: vec![], - used_gas: U256::from(256402), - used_storage: 580, - logs: vec![] - } - ); - assert_eq!(balance(alice()), alice_balance); - - // ExecutionMode::Execute - // Factory.createContract(1) - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); - assert_noop!( - EVM::execute( - context, - create_contract, - Default::default(), - 2_100_000, - 0, - ExecutionMode::Execute, - ), - Error::::OutOfStorage - ); - assert_eq!(balance(alice()), alice_balance); - - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); - let result = EVM::execute( - context, - create_contract, - Default::default(), - 2_100_000, - 2_100_000, - ExecutionMode::Execute, - ) - .unwrap(); - assert_eq!( - result, - CallInfo { - exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), - value: vec![], - used_gas: U256::from(107869), - used_storage: 290, - logs: vec![] - } - ); - - alice_balance -= 290 * ::StorageDepositPerByte::get(); - - assert_eq!(balance(alice()), alice_balance); - - // ExecutionMode::View - // Discard any state changes - let create_contract = - from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); - let result = EVM::execute( - context, - create_contract, - Default::default(), - 2_100_000, - 2_100_000, - ExecutionMode::View, - ) - .unwrap(); - assert_eq!( - result, - CallInfo { - exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), - value: vec![], - used_gas: U256::from(92869), - used_storage: 290, - logs: vec![] - } - ); - - assert_eq!(balance(alice()), alice_balance); - }); -} - -#[test] -fn should_update_storage() { - // pragma solidity ^0.5.0; - // - // contract Test { - // mapping(address => uint256) public values; - // - // constructor() public { - // values[msg.sender] = 42; - // } - // - // function set(uint val) public { - // values[msg.sender] = val; - // } - // } - - let contract = from_hex( - "0x608060405234801561001057600080fd5b50602a6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610154806100646000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806354fe9fd71461003b57806360fe47b114610093575b600080fd5b61007d6004803603602081101561005157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c1565b6040518082815260200191505060405180910390f35b6100bf600480360360208110156100a957600080fd5b81019080803590602001909291905050506100d9565b005b60006020528060005260406000206000915090505481565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505056fea265627a7a723158207ab6991e97c9c12f57d81df0c7f955435418354adeb26116b581d7f2f035ca8f64736f6c63430005110032" - ).unwrap(); - - new_test_ext().execute_with(|| { - // create contract - let result = - ::Runner::create(alice(), contract, 0, 500000, 100000, ::config()) - .unwrap(); - - let contract_address = result.value; - - let code_size = 340u32; - - let mut used_storage = code_size + NewContractExtraBytes::get() + STORAGE_SIZE; - - assert_eq!(result.used_storage, used_storage as i32); - - assert_eq!(ContractStorageSizes::::get(&contract_address), used_storage); - - #[cfg(not(feature = "with-ethereum-compatibility"))] - deploy_free(contract_address); - - // call method `set(123)` - let alice_account_id = ::AddressMapping::get_account_id(&alice()); - assert_noop!( - EVM::call( - Origin::signed(alice_account_id), - contract_address, - from_hex("0x60fe47b1000000000000000000000000000000000000000000000000000000000000007b").unwrap(), - 0, - 1000000, - 0, - ), - DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { - actual_weight: None, - pays_fee: Pays::Yes, - }, - error: DispatchError::from(Error::::OutOfStorage) - } - ); - - // call method `set(123)` - let result = ::Runner::call( - bob(), - alice(), - contract_address, - from_hex("0x60fe47b1000000000000000000000000000000000000000000000000000000000000007b").unwrap(), - 0, - 1000000, - STORAGE_SIZE, - ::config(), - ) - .unwrap(); - - used_storage += STORAGE_SIZE; - - assert_eq!(result.used_storage, STORAGE_SIZE as i32); - assert_eq!(ContractStorageSizes::::get(&contract_address), used_storage); - - // call method `set(0)` - let result = ::Runner::call( - bob(), - alice(), - contract_address, - from_hex("0x60fe47b10000000000000000000000000000000000000000000000000000000000000000").unwrap(), - 0, - 1000000, - STORAGE_SIZE, - ::config(), - ) - .unwrap(); - - used_storage -= STORAGE_SIZE; - - assert_eq!(result.used_storage, -(STORAGE_SIZE as i32)); - assert_eq!(ContractStorageSizes::::get(&contract_address), used_storage); - }); -} - -#[test] -fn code_hash_with_non_existent_address_should_work() { - new_test_ext().execute_with(|| { - assert_eq!( - EVM::code_hash_at_address(&H160::from_str("0x0000000000000000000000000000000000000000").unwrap()), - code_hash(&[]) - ); - }); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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)] + +use super::*; +use mock::{evm_module, RuntimeCall, RuntimeEvent, *}; + +use crate::runner::{ + stack::SubstrateStackState, + state::{StackExecutor, StackState, StackSubstateMetadata}, +}; +use frame_support::{assert_noop, assert_ok, dispatch::DispatchErrorWithPostInfo}; +use module_support::{mocks::MockAddressMapping, AddressMapping}; +use sp_core::{ + bytes::{from_hex, to_hex}, + H160, +}; +use sp_runtime::{traits::BadOrigin, AccountId32}; +use std::str::FromStr; + +#[test] +fn fail_call_return_ok_and_inc_nonce() { + new_test_ext().execute_with(|| { + let alice = alice(); + let account = MockAddressMapping::get_account_id(&alice); + let origin = RuntimeOrigin::signed(account); + + // nonce starts with 1 + assert_eq!(EVM::account_basic(&alice).nonce, U256::from(1)); + + // out of gas + assert_ok!(EVM::call(origin.clone(), contract_a(), Vec::new(), 0, 100, 0, vec![])); + // nonce inc by 1 + assert_eq!(EVM::account_basic(&alice).nonce, U256::from(2)); + + // success call + assert_ok!(EVM::call( + origin.clone(), + contract_b(), + Vec::new(), + 0, + 1000000, + 0, + vec![] + )); + // nonce inc by 1 + assert_eq!(EVM::account_basic(&alice).nonce, U256::from(3)); + + // invalid decimals + assert_ok!(EVM::call(origin, contract_b(), Vec::new(), 1111, 1000000, 0, vec![])); + // nonce inc by 1 + assert_eq!(EVM::account_basic(&alice).nonce, U256::from(4)); + }); +} + +#[test] +fn inc_nonce_with_revert() { + // pragma solidity ^0.5.0; + // + // contract Foo { + // constructor() public { + // require(false, "error message"); + // } + // } + let contract = from_hex( + "0x6080604052348015600f57600080fd5b5060006083576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f6572726f72206d6573736167650000000000000000000000000000000000000081525060200191505060405180910390fd5b603e8060906000396000f3fe6080604052600080fdfea265627a7a723158204741083d83bf4e3ee8099dd0b3471c81061237c2e8eccfcb513dfa4c04634b5b64736f6c63430005110032").unwrap(); + + new_test_ext().execute_with(|| { + let alice = alice(); + let account = MockAddressMapping::get_account_id(&alice); + let origin = RuntimeOrigin::signed(account); + + // alice starts with nonce 1 + assert_eq!(EVM::account_basic(&alice).nonce, U256::from(1)); + + // revert call + assert_ok!(EVM::create(origin.clone(), contract, 0, 1000000, 0, vec![])); + + // nonce inc by 1 + assert_eq!(EVM::account_basic(&alice).nonce, U256::from(2)); + }); +} + +#[test] +fn should_calculate_contract_address() { + new_test_ext().execute_with(|| { + let addr = H160::from_str("bec02ff0cbf20042a37d964c33e89f1a2be7f068").unwrap(); + + let vicinity = Vicinity { + gas_price: U256::one(), + ..Default::default() + }; + let metadata = StackSubstateMetadata::new(1000, 1000, &SETHEUM_CONFIG); + let state = SubstrateStackState::::new(&vicinity, metadata); + let mut executor = StackExecutor::new_with_precompiles(state, &SETHEUM_CONFIG, &()); + + assert_eq!( + executor.create_address(evm::CreateScheme::Legacy { caller: addr }), + Ok(H160::from_str("d654cB21c05cb14895baae28159b1107e9DbD6E4").unwrap()) + ); + + assert_ok!(executor.state_mut().inc_nonce(addr)); + assert_eq!( + executor.create_address(evm::CreateScheme::Legacy { caller: addr }), + Ok(H160::from_str("97784910F057B07bFE317b0552AE23eF34644Aed").unwrap()) + ); + + assert_ok!(executor.state_mut().inc_nonce(addr)); + assert_eq!( + executor.create_address(evm::CreateScheme::Legacy { caller: addr }), + Ok(H160::from_str("82155a21E0Ccaee9D4239a582EB2fDAC1D9237c5").unwrap()) + ); + + assert_eq!( + executor.create_address(evm::CreateScheme::Fixed( + H160::from_str("0x0000000000000000000000000000000000000000").unwrap() + )), + Ok(H160::from_str("0x0000000000000000000000000000000000000000").unwrap()) + ); + }); +} + +#[test] +fn should_create_and_call_contract() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function multiply(uint a, uint b) public pure returns(uint) { + // return a * b; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + // publish contract + let caller = alice(); + let result = ::Runner::create( + caller, + contract, + 0, + 1000000, + 1000000, + vec![], + ::config(), + ).unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + + let contract_address = result.value; + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(contract_address); + + assert_eq!(contract_address, H160::from_str("5f8bd49cd9f0cb2bd5bb9d4320dfe9b61023249d").unwrap()); + + assert_eq!(Pallet::::account_basic(&caller).nonce, 2.into()); + + // multiply(2, 3) + let multiply = from_hex( + "0x165c4a1600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" + ).unwrap(); + + // call method `multiply` + let result = ::Runner::call( + alice(), + alice(), + contract_address, + multiply, + 0, + 1000000, + 1000000, + vec![], + ::config(), + ).unwrap(); + assert_eq!( + U256::from(result.value.as_slice()), + 6.into(), + ); + + assert_eq!(Pallet::::account_basic(&caller).nonce, 3.into()); + + let code_hash = H256::from_str("164981e02df203a0fb32a0af7c2cd1cc7f9df7bb49a4d2b0219307bb68a4b603").unwrap(); + let code_size = 184u32; + + assert_eq!(Accounts::::get(&contract_address), Some(AccountInfo { + nonce: 1, + contract_info: Some(ContractInfo { + code_hash, + maintainer: alice(), + published: true + }) + })); + + assert_eq!(ContractStorageSizes::::get(&contract_address), code_size + NEW_CONTRACT_EXTRA_BYTES); + assert_eq!(CodeInfos::::get(&code_hash), Some(CodeInfo { + code_size, + ref_count: 1, + })); + assert!(Codes::::contains_key(&code_hash)); + }); +} + +#[test] +fn create_reverts_with_message() { + // pragma solidity ^0.5.0; + // + // contract Foo { + // constructor() public { + // require(false, "error message"); + // } + // } + let contract = from_hex( + "0x6080604052348015600f57600080fd5b5060006083576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f6572726f72206d6573736167650000000000000000000000000000000000000081525060200191505060405180910390fd5b603e8060906000396000f3fe6080604052600080fdfea265627a7a723158204741083d83bf4e3ee8099dd0b3471c81061237c2e8eccfcb513dfa4c04634b5b64736f6c63430005110032" + ).unwrap(); + new_test_ext().execute_with(|| { + let result = ::Runner::create( + alice(), + contract, + 0, + 12_000_000, + 12_000_000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Revert(ExitRevert::Reverted)); + assert_eq!( + result.value, + H160::from_str("0x5f8bd49cd9f0cb2bd5bb9d4320dfe9b61023249d").unwrap() + ); + }); +} + +#[test] +fn call_reverts_with_message() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function foo() public pure { + // require(false, "error message"); + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b60336035565b005b600060a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f6572726f72206d6573736167650000000000000000000000000000000000000081525060200191505060405180910390fd5b56fea265627a7a7231582066b3ee33bedba8a318d0d66610145030fdc0f982b11f5160d366e15e4d8ba2ef64736f6c63430005110032" + ).unwrap(); + let caller = alice(); + + new_test_ext().execute_with(|| { + // publish contract + let result = ::Runner::create( + caller, + contract, + 0, + 1000000, + 1000000, + vec![], + ::config(), + ).unwrap(); + + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + + let alice_balance = INITIAL_BALANCE - 323 * EVM::get_storage_deposit_per_byte(); + + assert_eq!(balance(alice()), alice_balance); + + let contract_address = result.value; + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(contract_address); + + // call method `foo` + let foo = from_hex("0xc2985578").unwrap(); + let result = ::Runner::call( + caller, + caller, + contract_address, + foo, + 0, + 1000000, + 1000000, + vec![], + ::config(), + ).unwrap(); + + assert_eq!(balance(alice()), alice_balance); + assert_eq!(result.exit_reason, ExitReason::Revert(ExitRevert::Reverted)); + assert_eq!( + to_hex(&result.value, true), + "0x8c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d6572726f72206d65737361676500000000000000000000000000000000000000" + ); + + let message = String::from_utf8_lossy(&result.value); + assert!(message.contains("error message")); + + assert_eq!(Pallet::::account_basic(&caller).nonce, 3.into()); + }); +} + +#[test] +fn should_publish_payable_contract() { + // pragma solidity ^0.5.0; + // + // contract Test { + // uint value; + // constructor(uint a) public payable { + // value = a; + // } + // + // function getValue() public payable returns (uint) { + // return value; + // } + // } + let mut contract = from_hex( + "0x60806040526040516100c73803806100c783398181016040526020811015602557600080fd5b81019080805190602001909291905050508060008190555050607b8061004c6000396000f3fe608060405260043610601c5760003560e01c806320965255146021575b600080fd5b6027603d565b6040518082815260200191505060405180910390f35b6000805490509056fea265627a7a72315820b832564a9db725638dcef03d07bfbdd2dc818020ea359630317e2126e95c314964736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + let amount = 1000u128; + + let stored_value: Vec = + from_hex("0x000000000000000000000000000000000000000000000000000000000000007b").unwrap(); + contract.append(&mut stored_value.clone()); + + let result = ::Runner::create( + alice(), + contract.clone(), + convert_decimals_to_evm(amount), + 1000000, + 100000, + vec![], + ::config(), + ) + .unwrap(); + let contract_address = result.value; + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(contract_address); + + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!(result.used_storage, 287); + + let alice_balance = INITIAL_BALANCE - amount - 287 * EVM::get_storage_deposit_per_byte(); + assert_eq!(balance(alice()), alice_balance); + assert_eq!(balance(contract_address), amount); + + // call getValue() + let result = ::Runner::call( + alice(), + alice(), + contract_address, + from_hex("0x20965255").unwrap(), + convert_decimals_to_evm(amount), + 100000, + 100000, + vec![], + ::config(), + ) + .unwrap(); + + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!(result.value, stored_value); + assert_eq!(result.used_storage, 0); + + assert_eq!(balance(alice()), alice_balance - amount); + assert_eq!(balance(contract_address), 2 * amount); + + assert_eq!( + AccountStorages::::iter_prefix(&contract_address).collect::>(), + vec![( + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + H256::from_slice(stored_value.as_slice()) + )] + ); + }); +} + +#[test] +fn should_transfer_from_contract() { + // pragma solidity ^0.5.16; + // + // contract SendEther { + // function sendViaTransfer(address payable _to) public payable { + // // This function is no longer recommended for sending Ether. + // _to.transfer(msg.value); + // } + // + // function sendOneEthViaTransfer(address payable _to) public { + // // This function is no longer recommended for sending Ether. + // _to.transfer(1 ether); + // } + // + // function balanceOf(address _to) public view returns (uint256) { + // return _to.balance; + // } + // + // function sendViaSend(address payable _to) public payable { + // // Send returns a boolean value indicating success or failure. + // // This function is not recommended for sending Ether. + // bool sent = _to.send(msg.value); + // require(sent, "Failed to send Ether"); + // } + // + // function sendViaCall(address payable _to) public payable { + // // Call returns a boolean value indicating success or failure. + // // This is the current recommended method to use. + // (bool sent, bytes memory data) = _to.call.value(msg.value)(""); + // require(sent, "Failed to send Ether"); + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5061044a806100206000396000f3fe60806040526004361061004a5760003560e01c8063636e082b1461004f57806370a082311461009357806372005fce146100f857806374be48061461013c578063830c29ae14610180575b600080fd5b6100916004803603602081101561006557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101c4565b005b34801561009f57600080fd5b506100e2600480360360208110156100b657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061020e565b6040518082815260200191505060405180910390f35b61013a6004803603602081101561010e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061022f565b005b61017e6004803603602081101561015257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610281565b005b6101c26004803603602081101561019657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610331565b005b8073ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f1935050505015801561020a573d6000803e3d6000fd5b5050565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b8073ffffffffffffffffffffffffffffffffffffffff166108fc670de0b6b3a76400009081150290604051600060405180830381858888f1935050505015801561027d573d6000803e3d6000fd5b5050565b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f1935050505090508061032d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f4661696c656420746f2073656e6420457468657200000000000000000000000081525060200191505060405180910390fd5b5050565b600060608273ffffffffffffffffffffffffffffffffffffffff163460405180600001905060006040518083038185875af1925050503d8060008114610393576040519150601f19603f3d011682016040523d82523d6000602084013e610398565b606091505b509150915081610410576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f4661696c656420746f2073656e6420457468657200000000000000000000000081525060200191505060405180910390fd5b50505056fea265627a7a7231582021fdf580e8b027bad2c5950c8a3292801da3f6f119a9dddcf170592ed45c85f264736f6c63430005100032" + ).unwrap(); + new_test_ext().execute_with(|| { + let amount = 1000u128; + + let result = ::Runner::create( + alice(), + contract, + 0, + 10000000, + 10000000, + vec![], + ::config(), + ) + .expect("create shouldn't fail"); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!(result.used_storage, 1198); + + let alice_balance = INITIAL_BALANCE - 1198 * EVM::get_storage_deposit_per_byte(); + assert_eq!(balance(alice()), alice_balance); + assert_eq!( + eth_balance(alice()), + U256::from(convert_decimals_to_evm(balance(alice()))) + ); + + let contract_address = result.value; + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(contract_address); + + // send via transfer + let mut via_transfer = from_hex("0x636e082b").unwrap(); + via_transfer.append(&mut Vec::from(H256::from(charlie()).as_bytes())); + + let result = ::Runner::call( + alice(), + alice(), + contract_address, + via_transfer, + convert_decimals_to_evm(amount), + 1000000, + 1000000, + vec![], + ::config(), + ) + .unwrap(); + + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + assert_eq!(balance(alice()), alice_balance - amount); + assert_eq!( + eth_balance(alice()), + U256::from(convert_decimals_to_evm(balance(alice()))) + ); + assert_eq!(balance(charlie()), amount); + assert_eq!( + eth_balance(charlie()), + U256::from(convert_decimals_to_evm(balance(charlie()))) + ); + + // send via send + let mut via_send = from_hex("0x74be4806").unwrap(); + via_send.append(&mut Vec::from(H256::from(charlie()).as_bytes())); + + let result = ::Runner::call( + alice(), + alice(), + contract_address, + via_send, + convert_decimals_to_evm(amount), + 1000000, + 1000000, + vec![], + ::config(), + ) + .unwrap(); + + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + assert_eq!(balance(charlie()), 2 * amount); + assert_eq!( + eth_balance(charlie()), + U256::from(convert_decimals_to_evm(balance(charlie()))) + ); + assert_eq!(balance(alice()), alice_balance - 2 * amount); + assert_eq!( + eth_balance(alice()), + U256::from(convert_decimals_to_evm(balance(alice()))) + ); + + // send via call + let mut via_call = from_hex("0x830c29ae").unwrap(); + via_call.append(&mut Vec::from(H256::from(charlie()).as_bytes())); + + let result = ::Runner::call( + alice(), + alice(), + contract_address, + via_call, + convert_decimals_to_evm(amount), + 1000000, + 1000000, + vec![], + ::config(), + ) + .unwrap(); + + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + assert_eq!(balance(charlie()), 3 * amount); + assert_eq!( + eth_balance(charlie()), + U256::from(convert_decimals_to_evm(balance(charlie()))) + ); + assert_eq!(balance(alice()), alice_balance - 3 * amount); + assert_eq!( + eth_balance(alice()), + U256::from(convert_decimals_to_evm(balance(alice()))) + ); + + // send 1 eth via transfer + let dollar_see = 10u128.pow(12); + let mut one_eth_via_transfer = from_hex("0x72005fce").unwrap(); + one_eth_via_transfer.append(&mut Vec::from(H256::from(charlie()).as_bytes())); + + let result = ::Runner::call( + alice(), + alice(), + contract_address, + one_eth_via_transfer, + convert_decimals_to_evm(dollar_see), // 1 SEE + 1000000, + 1000000, + vec![], + ::config(), + ) + .unwrap(); + + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + assert_eq!(balance(charlie()), 3 * amount + dollar_see); + assert_eq!( + eth_balance(charlie()), + U256::from(convert_decimals_to_evm(balance(charlie()))) + ); + assert_eq!(balance(alice()), alice_balance - 3 * amount - dollar_see); + assert_eq!( + eth_balance(alice()), + U256::from(convert_decimals_to_evm(balance(alice()))) + ); + + // balanceOf + let mut one_eth_via_transfer = from_hex("0x70a08231").unwrap(); + one_eth_via_transfer.append(&mut Vec::from(H256::from(charlie()).as_bytes())); + + let result = ::Runner::call( + alice(), + alice(), + contract_address, + one_eth_via_transfer, + 0, + 1000000, + 1000000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!( + U256::from(result.value.as_slice()), + U256::from(convert_decimals_to_evm(balance(charlie()))) + ); + }) +} + +#[test] +fn contract_should_publish_contracts() { + // pragma solidity ^0.5.0; + // + // contract Factory { + // Contract[] newContracts; + // + // function createContract () public payable { + // Contract newContract = new Contract(); + // newContracts.push(newContract); + // } + // } + // + // contract Contract {} + let contract = from_hex( + "0x608060405234801561001057600080fd5b5061016f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063412a5a6d14610046575b600080fd5b61004e610050565b005b600061005a6100e2565b604051809103906000f080158015610076573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b6040516052806100f28339019056fe6080604052348015600f57600080fd5b50603580601d6000396000f3fe6080604052600080fdfea165627a7a7230582092dc1966a8880ddf11e067f9dd56a632c11a78a4afd4a9f05924d427367958cc0029a165627a7a723058202b2cc7384e11c452cdbf39b68dada2d5e10a632cc0174a354b8b8c83237e28a40029" + ).unwrap(); + new_test_ext().execute_with(|| { + let result = ::Runner::create( + alice(), + contract.clone(), + 0, + 1000000000, + 1000000000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!(result.used_storage, 467); + + let alice_balance = INITIAL_BALANCE - 467 * EVM::get_storage_deposit_per_byte(); + + assert_eq!(balance(alice()), alice_balance); + let factory_contract_address = result.value; + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(factory_contract_address); + + assert_eq!(balance(factory_contract_address), 0); + assert_eq!( + reserved_balance(factory_contract_address), + 467 * EVM::get_storage_deposit_per_byte() + ); + + // Factory.createContract + let amount = 1000u128; + let create_contract = from_hex("0x412a5a6d").unwrap(); + let result = ::Runner::call( + alice(), + alice(), + factory_contract_address, + create_contract, + convert_decimals_to_evm(amount), + 1000000000, + 1000000000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + assert_eq!(result.used_storage, 281); + + assert_eq!( + balance(alice()), + alice_balance - amount - 281 * EVM::get_storage_deposit_per_byte() + ); + assert_eq!(balance(factory_contract_address), amount); + assert_eq!( + reserved_balance(factory_contract_address), + (467 + 128) * EVM::get_storage_deposit_per_byte() + ); + let contract_address = H160::from_str("7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(); + assert_eq!(balance(contract_address), 0); + assert_eq!( + reserved_balance(contract_address), + 153 * EVM::get_storage_deposit_per_byte() + ); + }); +} + +#[test] +fn contract_should_publish_contracts_without_payable() { + // pragma solidity ^0.5.0; + // + // contract Factory { + // Contract[] newContracts; + // + // function createContract () public { + // Contract newContract = new Contract(); + // newContracts.push(newContract); + // } + // } + // + // contract Contract {} + let contract = from_hex( + "0x608060405234801561001057600080fd5b5061016c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063412a5a6d14610030575b600080fd5b61003861003a565b005b6000604051610048906100d0565b604051809103906000f080158015610064573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b605b806100dd8339019056fe6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582094976cee5af14bf59c4bae67c79c12eb15de19bc18ad6038f3ee0898273c9c0564736f6c63430005110032a265627a7a72315820e19ae28dbf01eae11c526295a1ac533ea341c74d5724efe43171f6010fc98b3964736f6c63430005110032" + ).unwrap(); + new_test_ext().execute_with(|| { + let result = ::Runner::create( + alice(), + contract.clone(), + 0, + 1000000000, + 1000000000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + + let alice_balance = INITIAL_BALANCE - 464 * EVM::get_storage_deposit_per_byte(); + + assert_eq!(balance(alice()), alice_balance); + let factory_contract_address = result.value; + assert_eq!(balance(factory_contract_address), 0); + assert_eq!(reserved_balance(factory_contract_address), 4640); + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(factory_contract_address); + + // Factory.createContract + let create_contract = from_hex("0x412a5a6d").unwrap(); + let result = ::Runner::call( + alice(), + alice(), + factory_contract_address, + create_contract, + 0, + 1000000000, + 1000000000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + assert_eq!(result.used_storage, 290); + assert_eq!( + balance(alice()), + alice_balance - (result.used_storage as u128 * EVM::get_storage_deposit_per_byte()) + ); + assert_eq!(balance(factory_contract_address), 0); + assert_eq!( + reserved_balance(factory_contract_address), + (464 + 128) * EVM::get_storage_deposit_per_byte() + ); + }); +} + +#[test] +fn publish_factory() { + // pragma solidity ^0.5.0; + // + // contract Factory { + // Contract c; + // constructor() public { + // c = new Contract(); + // c.foo(); + // } + // } + // + // contract Contract { + // function foo() public pure returns (uint) { + // return 123; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060405161001d90610121565b604051809103906000f080158015610039573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b815260040160206040518083038186803b1580156100e057600080fd5b505afa1580156100f4573d6000803e3d6000fd5b505050506040513d602081101561010a57600080fd5b81019080805190602001909291905050505061012d565b60a58061017983390190565b603e8061013b6000396000f3fe6080604052600080fdfea265627a7a7231582064177030ee644a03aaf8d65027df9e0331c8bc4b161de25bfb8aa3142848e0f864736f6c634300051100326080604052348015600f57600080fd5b5060878061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000607b90509056fea265627a7a7231582031e5a4abae00962cfe9875df1b5b0d3ce6624e220cb8c714a948794fcddb6b4f64736f6c63430005110032" + ).unwrap(); + new_test_ext().execute_with(|| { + let result = ::Runner::create( + alice(), + contract, + 0, + 2_000_000, + 5000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!(result.used_gas.as_u64(), 155925); + assert_eq!(result.used_storage, 461); + assert_eq!( + balance(alice()), + INITIAL_BALANCE - (result.used_storage as u128 * EVM::get_storage_deposit_per_byte()) + ); + }); +} + +#[test] +fn create_nft_contract_works() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function multiply(uint a, uint b) public pure returns(uint) { + // return a * b; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + // publish contract + assert_ok!(EVM::create_nft_contract( + RuntimeOrigin::signed(NetworkContractAccount::get()), + contract, + 0, + 1000000, + 1000000, + vec![], + )); + + assert_eq!( + Pallet::::account_basic(&NetworkContractSource::get()).nonce, + 2.into() + ); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Created { + from: NetworkContractSource::get(), + contract: MIRRORED_TOKENS_ADDRESS_START | H160::from_low_u64_be(MIRRORED_NFT_ADDRESS_START), + logs: vec![], + used_gas: 93197, + used_storage: 284, + })); + assert_eq!(EVM::network_contract_index(), MIRRORED_NFT_ADDRESS_START + 1); + }); +} + +#[test] +fn create_nft_contract_fails_if_non_network_contract_origin() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function multiply(uint a, uint b) public pure returns(uint) { + // return a * b; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + assert_noop!( + EVM::create_nft_contract( + RuntimeOrigin::signed(AccountId32::from([1u8; 32])), + contract, + 0, + 1000000, + 1000000, + vec![], + ), + BadOrigin + ); + }); +} + +#[test] +fn create_predeploy_contract_works() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function multiply(uint a, uint b) public pure returns(uint) { + // return a * b; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + let addr = H160::from_str("1111111111111111111111111111111111111111").unwrap(); + + assert_eq!(Pallet::::is_account_empty(&addr), true); + + // deploy contract + assert_ok!(EVM::create_predeploy_contract( + RuntimeOrigin::signed(NetworkContractAccount::get()), + addr, + contract.clone(), + 0, + 1000000, + 1000000, + vec![], + )); + + assert_eq!(Pallet::::is_account_empty(&addr), false); + + System::assert_has_event(RuntimeEvent::EVM(crate::Event::Created { + from: NetworkContractSource::get(), + contract: addr, + logs: vec![], + used_gas: 93197, + used_storage: 284, + })); + + System::assert_last_event(RuntimeEvent::EVM(crate::Event::ContractPublished { contract: addr })); + + assert_noop!( + EVM::create_predeploy_contract( + RuntimeOrigin::signed(NetworkContractAccount::get()), + addr, + vec![], + 0, + 1000000, + 1000000, + vec![], + ), + Error::::ContractAlreadyExisted + ); + + // deploy empty contract + let token_addr = H160::from_str("2222222222222222222222222222222222222222").unwrap(); + + // the call is ok, but actually deploy failed, will trige CreatedFailed event + // if contract is empty, will skip inc_provider for contract account, so it + // fail at charge storage. + assert_ok!(EVM::create_predeploy_contract( + RuntimeOrigin::signed(NetworkContractAccount::get()), + token_addr, + vec![], + 0, + 1000000, + 1000000, + vec![], + )); + System::assert_has_event(RuntimeEvent::EVM(crate::Event::CreatedFailed { + from: NetworkContractSource::get(), + contract: H160::from_str("0000000000000000000000000000000000000000").unwrap(), + exit_reason: ExitReason::Error(ExitError::Other( + Into::<&str>::into(Error::::ChargeStorageFailed).into(), + )), + logs: vec![], + used_gas: 1000000, + used_storage: 0, + })); + + assert_eq!(CodeInfos::::get(&EVM::code_hash_at_address(&token_addr)), None); + }); +} + +#[test] +fn should_transfer_maintainer() { + // pragma solidity ^0.5.0; + // + // contract Factory { + // Contract c; + // constructor() public { + // c = new Contract(); + // c.foo(); + // } + // } + // + // contract Contract { + // function foo() public pure returns (uint) { + // return 123; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060405161001d90610121565b604051809103906000f080158015610039573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c29855786040518163ffffffff1660e01b815260040160206040518083038186803b1580156100e057600080fd5b505afa1580156100f4573d6000803e3d6000fd5b505050506040513d602081101561010a57600080fd5b81019080805190602001909291905050505061012d565b60a58061017983390190565b603e8061013b6000396000f3fe6080604052600080fdfea265627a7a7231582064177030ee644a03aaf8d65027df9e0331c8bc4b161de25bfb8aa3142848e0f864736f6c634300051100326080604052348015600f57600080fd5b5060878061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000607b90509056fea265627a7a7231582031e5a4abae00962cfe9875df1b5b0d3ce6624e220cb8c714a948794fcddb6b4f64736f6c63430005110032" + ).unwrap(); + new_test_ext().execute_with(|| { + let result = ::Runner::create( + alice(), + contract, + 0, + 12_000_000, + 12_000_000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!(result.used_storage, 461); + let alice_balance = INITIAL_BALANCE - 461 * EVM::get_storage_deposit_per_byte(); + let contract_address = result.value; + + assert_eq!(balance(alice()), alice_balance); + + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + assert_eq!(balance(bob()), INITIAL_BALANCE); + // transfer_maintainer + assert_ok!(EVM::transfer_maintainer( + RuntimeOrigin::signed(alice_account_id.clone()), + contract_address, + bob() + )); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::TransferredMaintainer { + contract: contract_address, + new_maintainer: bob(), + })); + assert_eq!(balance(bob()), INITIAL_BALANCE); + + assert_noop!( + EVM::transfer_maintainer(RuntimeOrigin::signed(bob_account_id), H160::default(), alice()), + Error::::ContractNotFound + ); + + assert_noop!( + EVM::transfer_maintainer(RuntimeOrigin::signed(alice_account_id), contract_address, bob()), + Error::::NoPermission + ); + assert_eq!(balance(alice()), alice_balance); + }); +} + +#[test] +fn should_publish() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function multiply(uint a, uint b) public pure returns(uint) { + // return a * b; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + + // contract not created yet + assert_noop!(EVM::publish_contract(RuntimeOrigin::signed(alice_account_id.clone()), H160::default()), Error::::ContractNotFound); + + // if the contract not exists, evm will return ExitSucceed::Stopped. + let result = ::Runner::call( + alice(), + alice(), + EvmAddress::default(), + vec![], + 0, + 1000000, + 1000000, + vec![], + ::config(), + ).unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + assert_eq!(result.used_storage, 0); + + // create contract + let result = ::Runner::create(alice(), contract, 0, 21_000_000, 21_000_000, vec![],::config()).unwrap(); + let contract_address = result.value; + + assert_eq!(result.used_storage, 284); + let alice_balance = INITIAL_BALANCE - 284 * EVM::get_storage_deposit_per_byte(); + + assert_eq!(balance(alice()), alice_balance); + + // multiply(2, 3) + let multiply = from_hex( + "0x165c4a1600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" + ).unwrap(); + + // contract maintainer can call + assert_ok!(::Runner::call( + alice(), + alice(), + contract_address, + multiply.clone(), + 0, + 1000000, + 1000000, + vec![], + ::config(), + )); + + // call method `multiply` will fail, not published yet + assert_eq!(EVM::call( + RuntimeOrigin::signed(bob_account_id.clone()), + contract_address, + multiply.clone(), + 0, + 1000000, + 1000000, + vec![], + ), Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes })); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::ExecutedFailed { + from: bob(), + contract: contract_address, + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(Error::::NoPermission).into())), + output: vec![], + logs: vec![], + used_gas: 1000000, + used_storage: 0, + })); + + // developer can call the unpublished contract + assert_ok!(EVM::enable_contract_development(RuntimeOrigin::signed(bob_account_id.clone()))); + assert_ok!(::Runner::call( + bob(), + bob(), + contract_address, + vec![], + 0, + 1000000, + 1000000, + vec![], + ::config(), + )); + + // not maintainer + assert_noop!(EVM::publish_contract(RuntimeOrigin::signed(bob_account_id), contract_address), Error::::NoPermission); + + assert_ok!(EVM::publish_contract(RuntimeOrigin::signed(alice_account_id.clone()), contract_address)); + let code_size = Accounts::::get(contract_address).map_or(0, |account_info| -> u32 { + account_info.contract_info.map_or(0, |contract_info| CodeInfos::::get(contract_info.code_hash).map_or(0, |code_info| code_info.code_size)) + }); + assert_eq!(balance(alice()), INITIAL_BALANCE - PUBLICATION_FEE - ((NEW_CONTRACT_EXTRA_BYTES + code_size) as u128* EVM::get_storage_deposit_per_byte())); + assert_eq!(Balances::free_balance(TreasuryAccount::get()), INITIAL_BALANCE + PUBLICATION_FEE); + + // call method `multiply` will work + assert_ok!(::Runner::call( + alice(), + alice(), + contract_address, + multiply, + 0, + 1000000, + 1000000, + vec![], + ::config(), + )); + + // contract already published + assert_noop!(EVM::publish_contract(RuntimeOrigin::signed(alice_account_id), contract_address), Error::::ContractAlreadyPublished); + }); +} + +#[test] +fn should_publish_free() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function multiply(uint a, uint b) public pure returns(uint) { + // return a * b; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + // contract not created yet + assert_noop!(EVM::publish_free(RuntimeOrigin::signed(CouncilAccount::get()), H160::default()), Error::::ContractNotFound); + + // create contract + let result = ::Runner::create(alice(), contract, 0, 21_000_000, 21_000_000, vec![], ::config()).unwrap(); + let contract_address = result.value; + + // multiply(2, 3) + let multiply = from_hex( + "0x165c4a1600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" + ).unwrap(); + + // call method `multiply` will fail, not published yet + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + assert_eq!(EVM::call( + RuntimeOrigin::signed(bob_account_id), + contract_address, + multiply.clone(), + 0, + 1000000, + 1000000, + vec![], + ), Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes })); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::ExecutedFailed { + from: bob(), + contract: contract_address, + exit_reason: ExitReason::Error(ExitError::Other(Into::<&str>::into(Error::::NoPermission).into())), + output: vec![], + logs: vec![], + used_gas: 1000000, + used_storage: 0, + })); + + assert_ok!(EVM::publish_free(RuntimeOrigin::signed(CouncilAccount::get()), contract_address)); + + // call method `multiply` + assert_ok!(::Runner::call( + bob(), + alice(), + contract_address, + multiply, + 0, + 1000000, + 1000000, + vec![], + ::config(), + )); + + // contract already published + assert_noop!(EVM::publish_free(RuntimeOrigin::signed(CouncilAccount::get()), contract_address), Error::::ContractAlreadyPublished); + }); +} + +#[test] +fn should_enable_contract_development() { + new_test_ext().execute_with(|| { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + assert_eq!(reserved_balance(alice()), 0); + assert_ok!(EVM::enable_contract_development(RuntimeOrigin::signed( + alice_account_id + ))); + assert_eq!(reserved_balance(alice()), DEVELOPER_DEPOSIT); + assert_eq!(balance(alice()), INITIAL_BALANCE - DEVELOPER_DEPOSIT); + }); +} + +#[test] +fn should_disable_contract_development() { + new_test_ext().execute_with(|| { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + + // contract development is not enabled yet + assert_noop!( + EVM::disable_contract_development(RuntimeOrigin::signed(alice_account_id.clone())), + Error::::ContractDevelopmentNotEnabled + ); + assert_eq!(balance(alice()), INITIAL_BALANCE); + + // enable contract development + assert_eq!(reserved_balance(alice()), 0); + assert_ok!(EVM::enable_contract_development(RuntimeOrigin::signed( + alice_account_id.clone() + ))); + assert_eq!(reserved_balance(alice()), DEVELOPER_DEPOSIT); + + // deposit reserved + assert_eq!(balance(alice()), INITIAL_BALANCE - DEVELOPER_DEPOSIT); + + // disable contract development + assert_ok!(EVM::disable_contract_development(RuntimeOrigin::signed( + alice_account_id.clone() + ))); + // deposit unreserved + assert_eq!(balance(alice()), INITIAL_BALANCE); + + // contract development already disabled + assert_noop!( + EVM::disable_contract_development(RuntimeOrigin::signed(alice_account_id)), + Error::::ContractDevelopmentNotEnabled + ); + }); +} + +#[test] +fn should_set_code() { + // pragma solidity ^0.5.0; + // + // contract Test { + // function multiply(uint a, uint b) public pure returns(uint) { + // return a * b; + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + let contract_err = from_hex( + "0x608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032608060405234801561001057600080fd5b5060b88061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063165c4a1614602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506076565b6040518082815260200191505060405180910390f35b600081830290509291505056fea265627a7a723158201f3db7301354b88b310868daf4395a6ab6cd42d16b1d8e68cdf4fdd9d34fffbf64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + + // create contract + let result = ::Runner::create( + alice(), + contract.clone(), + 0, + 21_000_000, + 21_000_000, + vec![], + ::config(), + ) + .unwrap(); + let contract_address = result.value; + assert_eq!(result.used_storage, 284); + let alice_balance = INITIAL_BALANCE - 284 * EVM::get_storage_deposit_per_byte(); + + assert_eq!(balance(alice()), alice_balance); + assert_eq!(reserved_balance(contract_address), 2840); + + let code_hash = H256::from_str("164981e02df203a0fb32a0af7c2cd1cc7f9df7bb49a4d2b0219307bb68a4b603").unwrap(); + assert_eq!( + Accounts::::get(&contract_address), + Some(AccountInfo { + nonce: 1, + contract_info: Some(ContractInfo { + code_hash, + maintainer: alice(), + published: false + }) + }) + ); + assert_eq!( + CodeInfos::::get(&code_hash), + Some(CodeInfo { + code_size: 184, + ref_count: 1, + }) + ); + + assert_noop!( + EVM::set_code( + RuntimeOrigin::signed(bob_account_id), + contract_address, + contract.clone() + ), + Error::::NoPermission + ); + assert_ok!(EVM::set_code( + RuntimeOrigin::signed(alice_account_id.clone()), + contract_address, + contract.clone() + )); + assert_ok!(EVM::set_code(RuntimeOrigin::root(), contract_address, contract)); + + assert_eq!(reserved_balance(contract_address), 4150); + + let new_code_hash = H256::from_str("9061d510f6235de4eae304e1a2a2ae22e1610ba893c018b7fabc1f1635f49877").unwrap(); + assert_eq!( + Accounts::::get(&contract_address), + Some(AccountInfo { + nonce: 1, + contract_info: Some(ContractInfo { + code_hash: new_code_hash, + maintainer: alice(), + published: false + }) + }) + ); + assert_eq!(CodeInfos::::get(&code_hash), None); + assert_eq!( + CodeInfos::::get(&new_code_hash), + Some(CodeInfo { + code_size: 215, + ref_count: 1, + }) + ); + assert_eq!(Codes::::contains_key(&code_hash), false); + assert_eq!(Codes::::contains_key(&new_code_hash), true); + + assert_ok!(EVM::set_code(RuntimeOrigin::root(), contract_address, vec![])); + let new_code_hash = H256::from_str("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").unwrap(); + assert_eq!( + Accounts::::get(&contract_address), + Some(AccountInfo { + nonce: 1, + contract_info: Some(ContractInfo { + code_hash: new_code_hash, + maintainer: alice(), + published: false + }) + }) + ); + assert_eq!( + CodeInfos::::get(&new_code_hash), + Some(CodeInfo { + code_size: 0, + ref_count: 1, + }) + ); + assert_eq!(reserved_balance(contract_address), 3000); + + assert_noop!( + EVM::set_code( + RuntimeOrigin::signed(alice_account_id.clone()), + contract_address, + [8u8; (MaxCodeSize::get() + 1) as usize].to_vec(), + ), + Error::::ContractExceedsMaxCodeSize + ); + + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + contract_address + )); + + assert_noop!( + EVM::set_code(RuntimeOrigin::signed(alice_account_id), contract_address, contract_err), + Error::::ContractAlreadyPublished + ); + }); +} + +#[test] +fn should_selfdestruct_without_schedule_task() { + // pragma solidity ^0.5.0; + // + // contract Test { + // uint value; + // constructor(uint a) public payable { + // value = a; + // } + // + // function getValue() public payable returns (uint) { + // return value; + // } + // } + let mut contract = from_hex( + "0x60806040526040516100c73803806100c783398181016040526020811015602557600080fd5b81019080805190602001909291905050508060008190555050607b8061004c6000396000f3fe608060405260043610601c5760003560e01c806320965255146021575b600080fd5b6027603d565b6040518082815260200191505060405180910390f35b6000805490509056fea265627a7a72315820b832564a9db725638dcef03d07bfbdd2dc818020ea359630317e2126e95c314964736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + + let amount = 1000u128; + + let mut stored_value: Vec = + from_hex("0x000000000000000000000000000000000000000000000000000000000000007b").unwrap(); + contract.append(&mut stored_value); + + // create contract + let result = ::Runner::create( + alice(), + contract.clone(), + convert_decimals_to_evm(amount), + 1000000, + 100000, + vec![], + ::config(), + ) + .unwrap(); + + let contract_address = result.value; + assert_eq!(result.used_storage, 287); + let alice_balance = INITIAL_BALANCE - 287 * EVM::get_storage_deposit_per_byte() - amount; + + assert_eq!(balance(alice()), alice_balance); + + let code_hash = H256::from_str("21fe816097a50d298f819bc6d40cff473c43c87d99bcd7d3c3b2b85417f66f5a").unwrap(); + let code_size = 123u32; + + assert_eq!( + ContractStorageSizes::::get(&contract_address), + code_size + NEW_CONTRACT_EXTRA_BYTES + STORAGE_SIZE + ); + assert_eq!( + CodeInfos::::get(&code_hash), + Some(CodeInfo { + code_size, + ref_count: 1, + }) + ); + assert!(Codes::::contains_key(&code_hash)); + + assert_noop!( + EVM::selfdestruct(RuntimeOrigin::signed(bob_account_id), contract_address), + Error::::NoPermission + ); + let contract_account_id = ::AddressMapping::get_account_id(&contract_address); + assert_eq!(System::providers(&contract_account_id), 2); + assert_ok!(EVM::selfdestruct( + RuntimeOrigin::signed(alice_account_id), + contract_address + )); + + assert_eq!(System::providers(&contract_account_id), 0); + assert!(!System::account_exists(&contract_account_id)); + assert!(!Accounts::::contains_key(&contract_address)); + assert!(!ContractStorageSizes::::contains_key(&contract_address)); + assert_eq!(AccountStorages::::iter_prefix(&contract_address).count(), 0); + assert!(!CodeInfos::::contains_key(&code_hash)); + assert!(!Codes::::contains_key(&code_hash)); + + let reserved_amount = 287 * EVM::get_storage_deposit_per_byte(); + + // refund storage deposit + assert_eq!(balance(alice()), alice_balance + amount + reserved_amount); + assert_eq!(balance(contract_address), 0); + assert_eq!(reserved_balance(contract_address), 0); + + // can publish at the same address + assert_ok!(EVM::create_predeploy_contract( + RuntimeOrigin::signed(NetworkContractAccount::get()), + contract_address, + contract, + 0, + 1000000, + 1000000, + vec![], + )); + }); +} + +#[test] +fn should_selfdestruct_with_schedule_task() { + // pragma solidity ^0.8.0; + // + // contract Test { + // mapping(uint256 => uint256) private data; + // + // constructor() public payable {} + // + // function setValue(uint256 key, uint256 value) public { + // data[key] = value; + // } + // } + let contract = from_hex( + "0x6080604052610105806100136000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80637b8d56e314602d575b600080fd5b60436004803603810190603f91906096565b6045565b005b80600080848152602001908152602001600020819055505050565b600080fd5b6000819050919050565b6076816065565b8114608057600080fd5b50565b600081359050609081606f565b92915050565b6000806040838503121560aa5760a96060565b5b600060b6858286016083565b925050602060c5858286016083565b915050925092905056fea26469706673582212201cbfb5695481e8cf4c7a1206d22d0a707cb85907a10b47038ac14af0c386344464736f6c63430008120033" + ).unwrap(); + + new_test_ext().execute_with(|| { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + + let amount = 1000u128; + + // create contract + let result = ::Runner::create( + alice(), + contract, + convert_decimals_to_evm(amount), + 1000000, + 100000, + vec![], + ::config(), + ) + .unwrap(); + + let contract_address = result.value; + assert_eq!(result.used_storage, 361); + let alice_balance = INITIAL_BALANCE - 361 * EVM::get_storage_deposit_per_byte() - amount; + + assert_eq!(balance(alice()), alice_balance); + + let code_hash = H256::from_str("7c96b02b6e32519ac1f47de5dd18efa07efd70b7eb57fc7a3d599eafa8329cd1").unwrap(); + let code_size = 261u32; + assert_eq!( + Accounts::::get(&contract_address) + .unwrap() + .contract_info + .unwrap() + .code_hash, + code_hash + ); + + assert_eq!( + ContractStorageSizes::::get(&contract_address), + code_size + NEW_CONTRACT_EXTRA_BYTES + ); + assert_eq!( + CodeInfos::::get(&code_hash), + Some(CodeInfo { + code_size, + ref_count: 1, + }) + ); + assert!(Codes::::contains_key(&code_hash)); + + let storage_count: u32 = REMOVE_LIMIT + 1; + assert_eq!(AccountStorages::::iter_prefix(&contract_address).count(), 0); + for i in 1..=storage_count { + // setValue + let mut input: Vec = from_hex("0x7b8d56e3").unwrap(); + + let mut buf = [0u8; 32]; + U256::from(i).to_big_endian(&mut buf); + input.append(&mut H256::from_slice(&buf).as_bytes().to_vec()); // key + input.append(&mut H256::from_slice(&buf).as_bytes().to_vec()); // value + + assert_ok!(EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + contract_address, + input, + 0, + 1000000000, + 1000, + vec![], + )); + } + assert_eq!( + AccountStorages::::iter_prefix(&contract_address).count(), + storage_count as usize + ); + + assert_noop!( + EVM::selfdestruct(RuntimeOrigin::signed(bob_account_id), contract_address), + Error::::NoPermission + ); + let contract_account_id = ::AddressMapping::get_account_id(&contract_address); + assert_eq!(System::providers(&contract_account_id), 2); + assert_ok!(EVM::selfdestruct( + RuntimeOrigin::signed(alice_account_id), + contract_address + )); + + // TODO: wait new host function. Keys in the overlay are deleted without counting towards the + // `limit`. assert_eq!(System::providers(&contract_account_id), 1); + // assert!(System::account_exists(&contract_account_id)); + // assert!(Accounts::::contains_key(&contract_address)); + // assert!(!ContractStorageSizes::::contains_key(&contract_address)); + // assert_eq!(AccountStorages::::iter_prefix(&contract_address).count(), 101); + // assert!(!CodeInfos::::contains_key(&code_hash)); + // assert!(!Codes::::contains_key(&code_hash)); + + // let reserved_amount = (storage_count * STORAGE_SIZE) as u128 * + // EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance - + // reserved_amount); assert_eq!(balance(contract_address), 1000); + // assert_eq!( + // reserved_balance(contract_address), + // reserved_amount + 361 * EVM::get_storage_deposit_per_byte() + // ); + + // // can't publish at the same address + // assert_noop!( + // EVM::create_predeploy_contract( + // RuntimeOrigin::signed(NetworkContractAccount::get()), + // contract_address, + // vec![], + // 0, + // 1000000, + // 1000000, + // vec![], + // ), + // DispatchErrorWithPostInfo { + // post_info: PostDispatchInfo { + // actual_weight: None, + // pays_fee: Pays::Yes, + // }, + // error: Error::::ContractAlreadyExisted.into() + // } + // ); + + // refund storage deposit + assert_eq!( + balance(alice()), + alice_balance + amount + 361 * EVM::get_storage_deposit_per_byte() + ); + assert_eq!(balance(contract_address), 0); + assert_eq!(reserved_balance(contract_address), 0); + + assert_eq!(System::providers(&contract_account_id), 0); + assert!(!System::account_exists(&contract_account_id)); + assert!(!Accounts::::contains_key(&contract_address)); + assert_eq!(AccountStorages::::iter_prefix(&contract_address).count(), 0); + }); +} + +#[test] +fn storage_limit_should_work() { + // pragma solidity ^0.5.0; + + // contract Factory { + // Contract[] newContracts; + + // function createContract (uint num) public payable { + // for(uint i = 0; i < num; i++) { + // Contract newContract = new Contract(); + // newContracts.push(newContract); + // } + // } + // } + + // contract Contract {} + let contract = from_hex( + "0x608060405234801561001057600080fd5b506101a0806100206000396000f3fe60806040526004361061001e5760003560e01c80639db8d7d514610023575b600080fd5b61004f6004803603602081101561003957600080fd5b8101908080359060200190929190505050610051565b005b60008090505b8181101561010057600060405161006d90610104565b604051809103906000f080158015610089573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050508080600101915050610057565b5050565b605b806101118339019056fe6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582035666e9471716d6d05ed9f0c1ab13d0371f49d536270f905bff06cd98212dcb064736f6c63430005110032a265627a7a723158203b6aaf6588bc3e6a35986612a62f715255430eab09ffb24401e5f18eb58a05d564736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + let result = ::Runner::create( + alice(), + contract.clone(), + 0, + 200_000, + 1000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + assert_eq!(result.used_storage, 516); + let alice_balance = INITIAL_BALANCE - 516 * EVM::get_storage_deposit_per_byte(); + assert_eq!(balance(alice()), alice_balance); + + let factory_contract_address = result.value; + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(factory_contract_address); + + assert_eq!(balance(factory_contract_address), 0); + assert_eq!( + reserved_balance(factory_contract_address), + 516 * EVM::get_storage_deposit_per_byte() + ); + + // Factory.createContract(1) + let amount = 1000000000; + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + assert_eq!( + EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + factory_contract_address, + create_contract, + amount, + 1000000000, + 0, + vec![], + ), + Ok(PostDispatchInfo { + actual_weight: None, + pays_fee: Pays::Yes + }) + ); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::ExecutedFailed { + from: alice(), + contract: factory_contract_address, + exit_reason: ExitReason::Error(ExitError::Other( + Into::<&str>::into(Error::::OutOfStorage).into(), + )), + output: vec![], + logs: vec![], + used_gas: 1000000000, + used_storage: 0, + })); + + // Factory.createContract(1) + let amount = 1000000000; + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); + let result = ::Runner::call( + alice(), + alice(), + factory_contract_address, + create_contract, + amount, + 1000000000, + 1000000000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + + // code_size + array_update(2 items) + extra_size = 290, array_length is already set + let expected_used_storage = 62 + 2 * 64 + 100; + assert_eq!(expected_used_storage, 290); + assert_eq!(result.used_storage, expected_used_storage); + + // Factory.createContract(2) + let amount = 1000000000; + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000002").unwrap(); + assert_eq!( + EVM::call( + RuntimeOrigin::signed(alice_account_id), + factory_contract_address, + create_contract, + amount, + 1000000000, + 451, + vec![], + ), + Ok(PostDispatchInfo { + actual_weight: None, + pays_fee: Pays::Yes + }) + ); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::ExecutedFailed { + from: alice(), + contract: factory_contract_address, + exit_reason: ExitReason::Error(ExitError::Other( + Into::<&str>::into(Error::::OutOfStorage).into(), + )), + output: vec![], + logs: vec![], + used_gas: 1000000000, + used_storage: 0, + })); + + // Factory.createContract(2) + let amount = 1000000000; + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000002").unwrap(); + let result = ::Runner::call( + alice(), + alice(), + factory_contract_address, + create_contract, + amount, + 1000000000, + 452, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Stopped)); + + // 2 * code_size + array_update(2 items) + extra_size = 452, array_length is already set + let expected_used_storage = 2 * 62 + 2 * 64 + 2 * 100; + + assert_eq!(expected_used_storage, 452); + assert_eq!(result.used_storage, expected_used_storage); + }); +} + +#[test] +fn evm_execute_mode_should_work() { + // pragma solidity ^0.5.0; + + // contract Factory { + // Contract[] newContracts; + + // function createContract (uint num) public payable { + // for(uint i = 0; i < num; i++) { + // Contract newContract = new Contract(); + // newContracts.push(newContract); + // } + // } + // } + + // contract Contract {} + let contract = from_hex( + "0x608060405234801561001057600080fd5b506101a0806100206000396000f3fe60806040526004361061001e5760003560e01c80639db8d7d514610023575b600080fd5b61004f6004803603602081101561003957600080fd5b8101908080359060200190929190505050610051565b005b60008090505b8181101561010057600060405161006d90610104565b604051809103906000f080158015610089573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050508080600101915050610057565b5050565b605b806101118339019056fe6080604052348015600f57600080fd5b50603e80601d6000396000f3fe6080604052600080fdfea265627a7a7231582035666e9471716d6d05ed9f0c1ab13d0371f49d536270f905bff06cd98212dcb064736f6c63430005110032a265627a7a723158203b6aaf6588bc3e6a35986612a62f715255430eab09ffb24401e5f18eb58a05d564736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + let mut alice_balance = INITIAL_BALANCE - 516 * EVM::get_storage_deposit_per_byte(); + + let result = ::Runner::create( + alice(), + contract.clone(), + 0, + 1000000000, + 1000000000, + vec![], + ::config(), + ) + .unwrap(); + assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); + + let account = Accounts::::get(&result.value).unwrap(); + let code_info = CodeInfos::::get(account.contract_info.unwrap().code_hash).unwrap(); + + // code_size + extra_size = 516 + let expected_used_storage = 416 + 100; + assert_eq!(code_info.code_size, 416); + assert_eq!(result.used_storage, expected_used_storage); + assert_eq!(balance(alice()), alice_balance); + let factory_contract_address = result.value; + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(factory_contract_address); + + let context = InvokeContext { + contract: factory_contract_address, + sender: alice(), + origin: alice(), + }; + + // ExecutionMode::EstimateGas + // Factory.createContract(1) + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); + let result = EVM::execute( + context, + create_contract, + Default::default(), + 2_100_000, + 1000, + ExecutionMode::EstimateGas, + ) + .unwrap(); + // code_size + array_update(1 item + length) + extra_size = 290 + let expected_used_storage = 62 + 2 * 64 + 100; + assert_eq!(expected_used_storage, 290); + assert_eq!( + result, + CallInfo { + exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), + value: vec![], + used_gas: U256::from(142451), + used_storage: expected_used_storage, + logs: vec![] + } + ); + + // Factory.createContract(2) + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000002").unwrap(); + let result = EVM::execute( + context, + create_contract, + Default::default(), + 2_100_000, + 2_100_000, + ExecutionMode::EstimateGas, + ) + .unwrap(); + + // 2 * code_size + array_update(2 items + length) + extra_size = 516 + let expected_used_storage = 2 * 62 + 3 * 64 + 2 * 100; + assert_eq!(expected_used_storage, 516); + assert_eq!( + result, + CallInfo { + exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), + value: vec![], + used_gas: U256::from(259573), + used_storage: expected_used_storage, + logs: vec![] + } + ); + assert_eq!(balance(alice()), alice_balance); + + let code_hash = Accounts::::get(&factory_contract_address) + .unwrap() + .contract_info + .unwrap() + .code_hash; + + let contract_storage_size = ContractStorageSizes::::get(&factory_contract_address); + let storage_count = AccountStorages::::iter_prefix(&factory_contract_address).count() as u32; + let code_info = CodeInfos::::get(&code_hash).unwrap(); + assert_eq!(code_info.code_size, 416); + assert_eq!( + contract_storage_size, + NEW_CONTRACT_EXTRA_BYTES + code_info.code_size + storage_count * 64 + ); + assert_eq!(storage_count, 0); + + // ExecutionMode::Execute + // Factory.createContract(1) + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); + assert_noop!( + EVM::execute( + context, + create_contract, + Default::default(), + 2_100_000, + 0, + ExecutionMode::Execute, + ), + Error::::OutOfStorage + ); + assert_eq!(balance(alice()), alice_balance); + + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); + let result = EVM::execute( + context, + create_contract, + Default::default(), + 2_100_000, + 2_100_000, + ExecutionMode::Execute, + ) + .unwrap(); + + // code_size + array_update(1 item + length) + extra_size = 290 + let expected_used_storage = 62 + 2 * 64 + 100; + assert_eq!(expected_used_storage, 290); + assert_eq!( + result, + CallInfo { + exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), + value: vec![], + used_gas: U256::from(110475), + used_storage: expected_used_storage, + logs: vec![] + } + ); + + let contract_storage_size = ContractStorageSizes::::get(&factory_contract_address); + let storage_count = AccountStorages::::iter_prefix(&factory_contract_address).count() as u32; + let code_info = CodeInfos::::get(&code_hash).unwrap(); + assert_eq!(code_info.code_size, 416); + assert_eq!( + contract_storage_size, + NEW_CONTRACT_EXTRA_BYTES + code_info.code_size + storage_count * 64 + ); + // one address stored in array + array_length + assert_eq!(storage_count, 2); + + alice_balance -= expected_used_storage as u128 * EVM::get_storage_deposit_per_byte(); + + assert_eq!(balance(alice()), alice_balance); + + // ExecutionMode::View + // Discard any state changes + let create_contract = + from_hex("0x9db8d7d50000000000000000000000000000000000000000000000000000000000000001").unwrap(); + let result = EVM::execute( + context, + create_contract, + Default::default(), + 2_100_000, + 2_100_000, + ExecutionMode::View, + ) + .unwrap(); + + // code_size + array_update(1 item) + extra_size = 226, array_length is already set + let expected_used_storage = 62 + 64 + 100; + assert_eq!(expected_used_storage, 226); + assert_eq!( + result, + CallInfo { + exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), + value: vec![], + used_gas: U256::from(93375), + used_storage: expected_used_storage, + logs: vec![] + } + ); + + assert_eq!(balance(alice()), alice_balance); + }); +} + +#[test] +fn should_update_storage() { + // pragma solidity ^0.5.0; + // + // contract Test { + // mapping(address => uint256) public values; + // + // constructor() public { + // values[msg.sender] = 42; + // } + // + // function set(uint val) public { + // values[msg.sender] = val; + // } + // } + + let contract = from_hex( + "0x608060405234801561001057600080fd5b50602a6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610154806100646000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806354fe9fd71461003b57806360fe47b114610093575b600080fd5b61007d6004803603602081101561005157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c1565b6040518082815260200191505060405180910390f35b6100bf600480360360208110156100a957600080fd5b81019080803590602001909291905050506100d9565b005b60006020528060005260406000206000915090505481565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505056fea265627a7a723158207ab6991e97c9c12f57d81df0c7f955435418354adeb26116b581d7f2f035ca8f64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + // create contract + let result = ::Runner::create( + alice(), + contract, + 0, + 500000, + 100000, + vec![], + ::config(), + ) + .unwrap(); + + let contract_address = result.value; + + let code_size = 340u32; + + let mut used_storage = code_size + NEW_CONTRACT_EXTRA_BYTES + STORAGE_SIZE; + + assert_eq!(result.used_storage, used_storage as i32); + + assert_eq!(ContractStorageSizes::::get(&contract_address), used_storage); + + #[cfg(not(feature = "with-ethereum-compatibility"))] + publish_free(contract_address); + + // call method `set(123)` + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + assert_eq!( + EVM::call( + RuntimeOrigin::signed(bob_account_id), + contract_address, + from_hex("0x60fe47b1000000000000000000000000000000000000000000000000000000000000007b").unwrap(), + 0, + 1000000, + 0, + vec![], + ), + Ok(PostDispatchInfo { + actual_weight: None, + pays_fee: Pays::Yes + }) + ); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::ExecutedFailed { + from: bob(), + contract: contract_address, + exit_reason: ExitReason::Error(ExitError::Other( + Into::<&str>::into(Error::::OutOfStorage).into(), + )), + output: vec![], + logs: vec![], + used_gas: 1000000, + used_storage: 0, + })); + + // call method `set(123)` + let result = ::Runner::call( + bob(), + alice(), + contract_address, + from_hex("0x60fe47b1000000000000000000000000000000000000000000000000000000000000007b").unwrap(), + 0, + 1000000, + STORAGE_SIZE, + vec![], + ::config(), + ) + .unwrap(); + + used_storage += STORAGE_SIZE; + + assert_eq!(result.used_storage, STORAGE_SIZE as i32); + assert_eq!(ContractStorageSizes::::get(&contract_address), used_storage); + + // call method `set(0)` + let result = ::Runner::call( + bob(), + alice(), + contract_address, + from_hex("0x60fe47b10000000000000000000000000000000000000000000000000000000000000000").unwrap(), + 0, + 1000000, + STORAGE_SIZE, + vec![], + ::config(), + ) + .unwrap(); + + used_storage -= STORAGE_SIZE; + + assert_eq!(result.used_storage, -(STORAGE_SIZE as i32)); + assert_eq!(ContractStorageSizes::::get(&contract_address), used_storage); + }); +} + +#[test] +fn code_hash_with_non_existent_address_should_work() { + new_test_ext().execute_with(|| { + assert_eq!( + EVM::code_hash_at_address(&H160::from_str("0x0000000000000000000000000000000000000000").unwrap()), + code_hash(&[]) + ); + }); +} + +#[test] +fn convert_decimals_should_not_work() { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + + new_test_ext().execute_with(|| { + assert_eq!( + EVM::create( + RuntimeOrigin::signed(alice_account_id.clone()), + vec![], + 1, + 1000000, + 1000000, + vec![] + ), + Ok(PostDispatchInfo { + actual_weight: None, + pays_fee: Pays::Yes + }) + ); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::CreatedFailed { + from: alice(), + contract: H160::default(), + exit_reason: ExitReason::Error(ExitError::Other( + Into::<&str>::into(Error::::InvalidDecimals).into(), + )), + logs: vec![], + used_gas: 1000000, + used_storage: 0, + })); + assert_eq!( + EVM::create2( + RuntimeOrigin::signed(alice_account_id.clone()), + vec![], + H256::default(), + 1, + 1000000, + 1000000, + vec![], + ), + Ok(PostDispatchInfo { + actual_weight: None, + pays_fee: Pays::Yes + }) + ); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::CreatedFailed { + from: alice(), + contract: H160::default(), + exit_reason: ExitReason::Error(ExitError::Other( + Into::<&str>::into(Error::::InvalidDecimals).into(), + )), + logs: vec![], + used_gas: 1000000, + used_storage: 0, + })); + assert_eq!( + EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + H160::default(), + vec![], + 1, + 1000000, + 1000000, + vec![], + ), + Ok(PostDispatchInfo { + actual_weight: None, + pays_fee: Pays::Yes + }) + ); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::ExecutedFailed { + from: alice(), + contract: H160::default(), + exit_reason: ExitReason::Error(ExitError::Other( + Into::<&str>::into(Error::::InvalidDecimals).into(), + )), + output: vec![], + logs: vec![], + used_gas: 1000000, + used_storage: 0, + })); + }); +} + +#[test] +#[should_panic(expected = "removed account while is still linked to contract info")] +fn remove_account_with_provides_should_panic() { + new_test_ext().execute_with(|| { + let address = H160::from([1; 20]); + let code = vec![0x00]; + let code_hash = code_hash(&code); + Codes::::insert(&code_hash, BoundedVec::try_from(code).unwrap()); + CodeInfos::::insert( + &code_hash, + CodeInfo { + code_size: 1, + ref_count: 1, + }, + ); + Accounts::::insert( + &address, + AccountInfo { + nonce: 0, + contract_info: Some(ContractInfo { + code_hash, + maintainer: Default::default(), + published: false, + }), + }, + ); + let _ = Pallet::::remove_account(&address); + }); +} + +#[test] +fn remove_account_works() { + new_test_ext().execute_with(|| { + let address = H160::from([1; 20]); + Accounts::::insert( + &address, + AccountInfo { + nonce: 0, + contract_info: None, + }, + ); + Pallet::::remove_account(&address); + assert_eq!(Accounts::::contains_key(&address), false); + }); +} + +#[test] +fn auto_publish_works() { + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/CreateContractFactory.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + + new_test_ext().execute_with(|| { + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + assert_ok!(EVM::create( + RuntimeOrigin::signed(alice_account_id.clone()), + code, + 0, + 2_100_000, + 10000, + vec![] + )); + + let factory = H160::from_str("0x5f8bd49cd9f0cb2bd5bb9d4320dfe9b61023249d").unwrap(); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Created { + from: alice(), + contract: factory, + logs: vec![], + used_gas: 593369, + used_storage: 2609, + })); + + // call method `createContract()` + assert_ok!(EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + factory, + from_hex("0x412a5a6d").unwrap(), + 0, + 1000000, + 10000, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Executed { + from: alice(), + contract: factory, + logs: vec![ + crate::Log { + address: H160::from_str("0x7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(), + topics: vec![ + H256::from_str("0xb0199510a4d57fac89f9b613861450ae948394f2abe3bf9918eb3c6890243f00").unwrap(), + H256::from_str("0x00000000000000000000000030f612c54706d40f65acaf10b8f6989103c2af58").unwrap(), + ], + data: vec![], + }, + crate::Log { + address: factory, + topics: vec![ + H256::from_str("0x6837ff1e738d95fc8bb5f12ce1513f42866f6c59c226c77342c4f36a1958ea10").unwrap(), + H256::from_str("0x0000000000000000000000007b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(), + ], + data: vec![], + }, + ], + used_gas: 387768, + used_storage: 1530, + })); + + assert_eq!( + EVM::accounts(factory).unwrap().contract_info, + Some(ContractInfo { + code_hash: H256::from_str("0xd007bd109daec7dec73d897c079b67b3d2fd6ad4892a916c5e03e21bb60ff384") + .unwrap(), + maintainer: alice(), + published: false + }) + ); + assert_eq!( + EVM::accounts(H160::from_str("0x7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap()) + .unwrap() + .contract_info, + Some(ContractInfo { + code_hash: H256::from_str("0xe12fa7753d9cd8de1f8b597fef33ab91c2749fe4a1022b648f949ab2566f391f") + .unwrap(), + maintainer: factory, + published: false + }) + ); + assert_eq!( + EVM::accounts(H160::from_str("0x30f612c54706d40f65acaf10b8f6989103c2af58").unwrap()) + .unwrap() + .contract_info, + Some(ContractInfo { + code_hash: H256::from_str("0x46460b564756d0e02bbfdc8fc3d47d1a68c3b3d8301b5de90da83d6d75e0b6c7") + .unwrap(), + maintainer: H160::from_str("0x7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(), + published: false + }) + ); + + // publish the factory + assert_ok!(EVM::publish_free(RuntimeOrigin::signed(CouncilAccount::get()), factory)); + + // call method `createContract()` + assert_ok!(EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + factory, + from_hex("0x412a5a6d").unwrap(), + 0, + 1000000, + 10000, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Executed { + from: alice(), + contract: factory, + logs: vec![ + crate::Log { + address: H160::from_str("0x39b26a36a8a175ce7d498b5ef187d1ab2f381bbd").unwrap(), + topics: vec![ + H256::from_str("0xb0199510a4d57fac89f9b613861450ae948394f2abe3bf9918eb3c6890243f00").unwrap(), + H256::from_str("0x000000000000000000000000769a55efaf4dbdd6f44efce668455522b61abb82").unwrap(), + ], + data: vec![], + }, + crate::Log { + address: factory, + topics: vec![ + H256::from_str("0x6837ff1e738d95fc8bb5f12ce1513f42866f6c59c226c77342c4f36a1958ea10").unwrap(), + H256::from_str("0x00000000000000000000000039b26a36a8a175ce7d498b5ef187d1ab2f381bbd").unwrap(), + ], + data: vec![], + }, + ], + used_gas: 370668, + used_storage: 1466, + })); + + assert_eq!( + EVM::accounts(factory).unwrap().contract_info, + Some(ContractInfo { + code_hash: H256::from_str("0xd007bd109daec7dec73d897c079b67b3d2fd6ad4892a916c5e03e21bb60ff384") + .unwrap(), + maintainer: alice(), + published: true + }) + ); + assert_eq!( + EVM::accounts(H160::from_str("0x39b26a36a8a175ce7d498b5ef187d1ab2f381bbd").unwrap()) + .unwrap() + .contract_info, + Some(ContractInfo { + code_hash: H256::from_str("0xe12fa7753d9cd8de1f8b597fef33ab91c2749fe4a1022b648f949ab2566f391f") + .unwrap(), + maintainer: H160::from_str("0x5f8bd49cd9f0cb2bd5bb9d4320dfe9b61023249d").unwrap(), + published: true + }) + ); + assert_eq!( + EVM::accounts(H160::from_str("0x769a55efaf4dbdd6f44efce668455522b61abb82").unwrap()) + .unwrap() + .contract_info, + Some(ContractInfo { + code_hash: H256::from_str("0x46460b564756d0e02bbfdc8fc3d47d1a68c3b3d8301b5de90da83d6d75e0b6c7") + .unwrap(), + maintainer: H160::from_str("0x39b26a36a8a175ce7d498b5ef187d1ab2f381bbd").unwrap(), + published: true + }) + ); + + // call method `callContract()` + assert_ok!(EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + factory, + from_hex("0x0f24df3a").unwrap(), + 0, + 1000000, + 10000, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Executed { + from: alice(), + contract: factory, + logs: vec![crate::Log { + address: H160::from_str("0x7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(), + topics: vec![ + H256::from_str("0xb0199510a4d57fac89f9b613861450ae948394f2abe3bf9918eb3c6890243f00").unwrap(), + H256::from_str("0x000000000000000000000000d8a09b53762a01c2beb363d5355f4eecf7b48360").unwrap(), + ], + data: vec![], + }], + used_gas: 147228, + used_storage: 407, + })); + + assert_eq!( + EVM::accounts(H160::from_str("d8a09b53762a01c2beb363d5355f4eecf7b48360").unwrap()) + .unwrap() + .contract_info, + Some(ContractInfo { + code_hash: H256::from_str("0x46460b564756d0e02bbfdc8fc3d47d1a68c3b3d8301b5de90da83d6d75e0b6c7") + .unwrap(), + maintainer: H160::from_str("0x7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(), + published: true + }) + ); + }); +} + +#[test] +fn reserve_deposit_makes_user_developer() { + new_test_ext().execute_with(|| { + let addr = H160(hex!("1100000000000000000000000000000000000011")); + let who = ::AddressMapping::get_account_id(&addr); + + assert_eq!(Pallet::::is_developer_or_contract(&addr), false); + + // mock deploy contract, will inc provider for the account of contract address before transfer and + // reserved + System::inc_providers(&who); + + assert_ok!(>::transfer( + GetNativeCurrencyId::get(), + &::AddressMapping::get_account_id(&alice()), + &who, + DEVELOPER_DEPOSIT, + )); + + assert_ok!(::Currency::ensure_reserved_named( + &RESERVE_ID_DEVELOPER_DEPOSIT, + &who, + DEVELOPER_DEPOSIT, + )); + assert_eq!(Pallet::::is_developer_or_contract(&addr), true); + }) +} + +#[test] +fn strict_call_works() { + // pragma solidity ^0.5.0; + // + // contract Test { + // mapping(address => uint256) public values; + // + // constructor() public { + // values[msg.sender] = 42; + // } + // + // function set(uint val) public { + // values[msg.sender] = val; + // } + // } + + let contract = from_hex( + "0x608060405234801561001057600080fd5b50602a6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610154806100646000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806354fe9fd71461003b57806360fe47b114610093575b600080fd5b61007d6004803603602081101561005157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c1565b6040518082815260200191505060405180910390f35b6100bf600480360360208110156100a957600080fd5b81019080803590602001909291905050506100d9565b005b60006020528060005260406000206000915090505481565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505056fea265627a7a723158207ab6991e97c9c12f57d81df0c7f955435418354adeb26116b581d7f2f035ca8f64736f6c63430005110032" + ).unwrap(); + + new_test_ext().execute_with(|| { + // create contract + let result = ::Runner::create( + alice(), + contract, + 0, + 500000, + 100000, + vec![], + ::config(), + ) + .unwrap(); + + let contract_address = result.value; + + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + let bob_account_id = ::AddressMapping::get_account_id(&bob()); + + assert_eq!( + Utility::batch_all( + RuntimeOrigin::signed(bob_account_id.clone()), + vec![ + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: bob_account_id.clone(), + value: 5 + }), + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: bob_account_id.clone(), + value: 6 + }), + // call method `set(123)` + RuntimeCall::EVM(evm_module::Call::strict_call { + target: contract_address, + input: from_hex("0x60fe47b1000000000000000000000000000000000000000000000000000000000000007b") + .unwrap(), + value: 0, + gas_limit: 1000000, + storage_limit: 0, + access_list: vec![], + }) + ] + ), + Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(Weight::from_parts(1464914680, 7186)), + pays_fee: Pays::Yes + }, + error: Error::::NoPermission.into(), + }) + ); + + assert_eq!( + Utility::batch_all( + RuntimeOrigin::signed(alice_account_id.clone()), + vec![ + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: bob_account_id.clone(), + value: 5 + }), + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: bob_account_id.clone(), + value: 6 + }), + // call undefined method + RuntimeCall::EVM(evm_module::Call::strict_call { + target: contract_address, + input: from_hex("0x00000000000000000000000000000000000000000000000000000000000000000000007b") + .unwrap(), + value: 0, + gas_limit: 1000000, + storage_limit: 0, + access_list: vec![], + }) + ] + ), + Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(Weight::from_parts(1463958382, 7186)), + pays_fee: Pays::Yes + }, + error: Error::::StrictCallFailed.into(), + }) + ); + + assert_ok!(Utility::batch_all( + RuntimeOrigin::signed(alice_account_id.clone()), + vec![ + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: bob_account_id.clone(), + value: 5 + }), + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: bob_account_id.clone(), + value: 6 + }), + // call method `set(123)` + RuntimeCall::EVM(evm_module::Call::strict_call { + target: contract_address, + input: from_hex("0x60fe47b1000000000000000000000000000000000000000000000000000000000000007b") + .unwrap(), + value: 0, + gas_limit: 1000000, + storage_limit: 0, + access_list: vec![], + }) + ] + )); + }) +} + +#[test] +// ensure storage reserve/unreserved is done in a single operation +fn aggregated_storage_logs_works() { + // pragma solidity =0.8.9; + // + // contract StorageManager { + // Storage public s; + // + // constructor() { + // s = new Storage(); + // } + // + // function loop_insert_and_remove(uint insert, uint remove) public { + // loop_insert(insert); + // loop_remove(remove); + // } + // + // function loop_insert(uint max) public { + // for (uint i = 0; i < max; i++) { + // s.insert(i, 1); + // } + // } + // + // function loop_remove(uint max) public { + // for (uint i = 0; i < max; i++) { + // s.insert(i, 0); + // } + // } + // } + // + // contract Storage { + // mapping(uint=>uint) table; + // + // function insert(uint index, uint value) public { + // if (value != 0) { + // table[index] = value; + // } else { + // delete table[index]; + // } + // } + // } + let contract = from_hex( + "0x608060405234801561001057600080fd5b5060405161001d9061007e565b604051809103906000f080158015610039573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061008b565b610147806105be83390190565b6105248061009a6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80630be6fe5d146100515780631358f5251461006d57806386b714e214610089578063da1385d5146100a7575b600080fd5b61006b60048036038101906100669190610298565b6100c3565b005b610087600480360381019061008291906102d8565b6100d9565b005b610091610189565b60405161009e9190610384565b60405180910390f35b6100c160048036038101906100bc91906102d8565b6101ad565b005b6100cc826101ad565b6100d5816100d9565b5050565b60005b818110156101855760008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631d834a1b8260006040518363ffffffff1660e01b81526004016101409291906103e9565b600060405180830381600087803b15801561015a57600080fd5b505af115801561016e573d6000803e3d6000fd5b50505050808061017d90610441565b9150506100dc565b5050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60005b818110156102595760008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631d834a1b8260016040518363ffffffff1660e01b81526004016102149291906104c5565b600060405180830381600087803b15801561022e57600080fd5b505af1158015610242573d6000803e3d6000fd5b50505050808061025190610441565b9150506101b0565b5050565b600080fd5b6000819050919050565b61027581610262565b811461028057600080fd5b50565b6000813590506102928161026c565b92915050565b600080604083850312156102af576102ae61025d565b5b60006102bd85828601610283565b92505060206102ce85828601610283565b9150509250929050565b6000602082840312156102ee576102ed61025d565b5b60006102fc84828501610283565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061034a61034561034084610305565b610325565b610305565b9050919050565b600061035c8261032f565b9050919050565b600061036e82610351565b9050919050565b61037e81610363565b82525050565b60006020820190506103996000830184610375565b92915050565b6103a881610262565b82525050565b6000819050919050565b60006103d36103ce6103c9846103ae565b610325565b610262565b9050919050565b6103e3816103b8565b82525050565b60006040820190506103fe600083018561039f565b61040b60208301846103da565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044c82610262565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141561047f5761047e610412565b5b600182019050919050565b6000819050919050565b60006104af6104aa6104a58461048a565b610325565b610262565b9050919050565b6104bf81610494565b82525050565b60006040820190506104da600083018561039f565b6104e760208301846104b6565b939250505056fea2646970667358221220c53549ea0c54d760bc0fd8aa7f8eeebf806e4474546e87e9783e4ad3f55dfa6564736f6c63430008090033608060405234801561001057600080fd5b50610127806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80631d834a1b14602d575b600080fd5b60436004803603810190603f919060b8565b6045565b005b600081146067578060008084815260200190815260200160002081905550607e565b600080838152602001908152602001600020600090555b5050565b600080fd5b6000819050919050565b6098816087565b811460a257600080fd5b50565b60008135905060b2816091565b92915050565b6000806040838503121560cc5760cb6082565b5b600060d88582860160a5565b925050602060e78582860160a5565b915050925092905056fea2646970667358221220941edb58b322ea8088f4f9091a8a48c92e59c2f39db303d8e126a0c3dd434dde64736f6c63430008090033" + ).unwrap(); + + new_test_ext().execute_with(|| { + let config = ::config(); + let cost_per_byte = convert_decimals_from_evm(StorageDepositPerByte::get()).unwrap_or_default(); + + // create contract + let result = ::Runner::create(alice(), contract, 0, 500000, 100000, vec![], config).unwrap(); + + let contract_address = result.value; + let alice_account_id = ::AddressMapping::get_account_id(&alice()); + + let result = ::Runner::call( + alice(), + alice(), + contract_address, + hex! {"86b714e2"}.to_vec(), + 0, + 30000, + 0, + vec![], + config, + ) + .unwrap(); + // get storage address + let contract_acc = + ::AddressMapping::get_account_id(&H160::from(H256::from_slice(&result.value))); + + frame_system::Pallet::::reset_events(); + + assert_ok!(EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + contract_address, + // loop_insert(100) + hex! {" + da1385d5 + 0000000000000000000000000000000000000000000000000000000000000064 + "} + .to_vec(), + 0, + 3000000, + 10000, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Executed { + from: alice(), + contract: contract_address, + logs: vec![], + used_gas: 2407098, + used_storage: 6400, // storage +100 * 64 + })); + + let amount = 10000 * cost_per_byte; + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: alice_account_id.clone(), + amount, + })); + let amount = 6400 * cost_per_byte; + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { + from: alice_account_id.clone(), + to: contract_acc.clone(), + amount, + destination_status: BalanceStatus::Reserved, + })); + // unreserved remaining storage + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::Unreserved { + who: alice_account_id.clone(), + amount: (10000 - 6400) * cost_per_byte, + })); + + frame_system::Pallet::::reset_events(); + + assert_ok!(EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + contract_address, + // loop_remove(100) + hex! {" + 1358f525 + 0000000000000000000000000000000000000000000000000000000000000064 + "} + .to_vec(), + 0, + 3000000, + 0, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Executed { + from: alice(), + contract: contract_address, + logs: vec![], + used_gas: 695554, + used_storage: -6400, // storage -100 * 64 + })); + + let amount = 6400 * cost_per_byte; + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { + from: contract_acc.clone(), + to: alice_account_id.clone(), + amount, + destination_status: BalanceStatus::Reserved, + })); + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::Unreserved { + who: alice_account_id.clone(), + amount, + })); + + frame_system::Pallet::::reset_events(); + + assert_ok!(EVM::call( + RuntimeOrigin::signed(alice_account_id.clone()), + contract_address, + // loop_insert_and_remove(10, 5) + hex! {" + 0be6fe5d + 000000000000000000000000000000000000000000000000000000000000000a + 0000000000000000000000000000000000000000000000000000000000000005 + "} + .to_vec(), + 0, + 3_000_000, + 320, + vec![], + )); + System::assert_last_event(RuntimeEvent::EVM(crate::Event::Executed { + from: alice(), + contract: contract_address, + logs: vec![], + used_gas: 287595, + used_storage: 320, // storage (+10 - 5) * 64 + })); + let amount = 320 * cost_per_byte; + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: alice_account_id.clone(), + amount, + })); + System::assert_has_event(RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { + from: alice_account_id.clone(), + to: contract_acc.clone(), + amount, + destination_status: BalanceStatus::Reserved, + })); + }) +} + +#[allow(deprecated)] +#[test] +fn should_not_allow_contracts_send_tx() { + new_test_ext().execute_with(|| { + let origin = RuntimeOrigin::signed(MockAddressMapping::get_account_id(&contract_a())); + assert_noop!( + EVM::eth_call( + origin.clone(), + TransactionAction::Call(contract_a()), + vec![], + 0, + 1_000_000, + 100, + vec![], + 0 + ), + Error::::NotEOA + ); + assert_noop!( + EVM::eth_call( + origin.clone(), + TransactionAction::Create, + vec![], + 0, + 1_000_000, + 100, + vec![], + 0 + ), + Error::::NotEOA + ); + assert_noop!( + EVM::eth_call_v2( + origin.clone(), + TransactionAction::Call(contract_a()), + vec![], + 0, + 1_000_000, + 100, + vec![] + ), + Error::::NotEOA + ); + assert_noop!( + EVM::eth_call_v2( + origin.clone(), + TransactionAction::Create, + vec![], + 0, + 1_000_000, + 100, + vec![] + ), + Error::::NotEOA + ); + assert_noop!( + EVM::call(origin.clone(), contract_a(), vec![], 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + assert_noop!( + EVM::create(origin.clone(), vec![], 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + assert_noop!( + EVM::create2(origin.clone(), vec![], Default::default(), 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + assert_noop!( + EVM::strict_call(origin, contract_a(), vec![], 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + }); +} + +#[allow(deprecated)] +#[test] +fn should_not_allow_system_contracts_send_tx() { + new_test_ext().execute_with(|| { + let origin = RuntimeOrigin::signed(MockAddressMapping::get_account_id( + &H160::from_str("000000000000000000ffffffffffffffffffffff").unwrap(), + )); + assert_noop!( + EVM::eth_call( + origin.clone(), + TransactionAction::Call(contract_a()), + vec![], + 0, + 1_000_000, + 100, + vec![], + 0 + ), + Error::::NotEOA + ); + assert_noop!( + EVM::eth_call( + origin.clone(), + TransactionAction::Create, + vec![], + 0, + 1_000_000, + 100, + vec![], + 0 + ), + Error::::NotEOA + ); + assert_noop!( + EVM::eth_call_v2( + origin.clone(), + TransactionAction::Call(contract_a()), + vec![], + 0, + 1_000_000, + 100, + vec![] + ), + Error::::NotEOA + ); + assert_noop!( + EVM::eth_call_v2( + origin.clone(), + TransactionAction::Create, + vec![], + 0, + 1_000_000, + 100, + vec![] + ), + Error::::NotEOA + ); + assert_noop!( + EVM::call(origin.clone(), contract_a(), vec![], 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + assert_noop!( + EVM::create(origin.clone(), vec![], 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + assert_noop!( + EVM::create2(origin.clone(), vec![], Default::default(), 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + assert_noop!( + EVM::strict_call(origin, contract_a(), vec![], 0, 1_000_000, 100, vec![]), + Error::::NotEOA + ); + }); +} diff --git a/blockchain/modules/evm/src/weights.rs b/blockchain/modules/evm/src/weights.rs index 71206d55..fe0e560f 100644 --- a/blockchain/modules/evm/src/weights.rs +++ b/blockchain/modules/evm/src/weights.rs @@ -1,139 +1,471 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - - -//! Autogenerated weights for module_evm -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/setheum-node -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module_evm -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./blockchain/modules/evm/src/weights.rs -// --template=.maintain/module-weight-template.hbs - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for module_evm. -pub trait WeightInfo { - fn transfer_maintainer() -> Weight; - fn deploy() -> Weight; - fn deploy_free() -> Weight; - fn enable_contract_development() -> Weight; - fn disable_contract_development() -> Weight; - fn set_code() -> Weight; - fn selfdestruct() -> Weight; -} - -/// Weights for module_evm using the Setheum node and recommended hardware. -pub struct SetheumWeight(PhantomData); -impl WeightInfo for SetheumWeight { - fn transfer_maintainer() -> Weight { - (69_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn deploy() -> Weight { - (101_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - fn deploy_free() -> Weight { - (19_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn enable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn disable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn set_code() -> Weight { - (83_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - fn selfdestruct() -> Weight { - (141_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn transfer_maintainer() -> Weight { - (69_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn deploy() -> Weight { - (101_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - fn deploy_free() -> Weight { - (19_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - } - fn enable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn disable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - } - fn set_code() -> Weight { - (83_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - } - fn selfdestruct() -> Weight { - (141_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for module_evm +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-04-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `ip-172-31-40-247`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/setheum +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_evm +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/evm/src/weights.rs +// --template=.maintain/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_evm. +pub trait WeightInfo { + fn create() -> Weight; + fn create2() -> Weight; + fn create_nft_contract() -> Weight; + fn create_predeploy_contract() -> Weight; + fn call() -> Weight; + fn transfer_maintainer() -> Weight; + fn publish_contract() -> Weight; + fn publish_free() -> Weight; + fn enable_contract_development() -> Weight; + fn disable_contract_development() -> Weight; + fn set_code(c: u32, ) -> Weight; + fn selfdestruct() -> Weight; +} + +/// Weights for module_evm using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create() -> Weight { + // Minimum execution time: 202_922 nanoseconds. + Weight::from_parts(204_527_000, 0) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(9)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create2() -> Weight { + // Minimum execution time: 194_188 nanoseconds. + Weight::from_parts(199_650_000, 0) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(9)) + } + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM NetworkContractIndex (r:1 w:1) + // Proof Skipped: EVM NetworkContractIndex (max_values: Some(1), max_size: None, mode: Measured) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create_nft_contract() -> Weight { + // Minimum execution time: 223_480 nanoseconds. + Weight::from_parts(227_640_000, 0) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(10)) + } + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create_predeploy_contract() -> Weight { + // Minimum execution time: 229_528 nanoseconds. + Weight::from_parts(233_183_000, 0) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(9)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Codes (r:1 w:0) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + fn call() -> Weight { + // Minimum execution time: 185_756 nanoseconds. + Weight::from_parts(189_885_000, 0) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(6)) + } + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + fn transfer_maintainer() -> Weight { + // Minimum execution time: 120_422 nanoseconds. + Weight::from_parts(122_117_000, 0) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + fn publish_contract() -> Weight { + // Minimum execution time: 149_010 nanoseconds. + Weight::from_parts(150_918_000, 0) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + fn publish_free() -> Weight { + // Minimum execution time: 39_214 nanoseconds. + Weight::from_parts(40_271_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Balances Reserves (r:1 w:1) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + fn enable_contract_development() -> Weight { + // Minimum execution time: 126_304 nanoseconds. + Weight::from_parts(127_492_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: Balances Reserves (r:1 w:1) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + fn disable_contract_development() -> Weight { + // Minimum execution time: 128_756 nanoseconds. + Weight::from_parts(129_795_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM CodeInfos (r:2 w:2) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:2) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + /// The range of component `c` is `[0, 61440]`. + fn set_code(c: u32, ) -> Weight { + // Minimum execution time: 221_718 nanoseconds. + Weight::from_parts(218_913_195, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(5_766, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(9)) + } + // Storage: EvmAccounts EvmAddresses (r:2 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM CodeInfos (r:1 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: Balances Reserves (r:1 w:1) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Tokens Accounts (r:1 w:0) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn selfdestruct() -> Weight { + // Minimum execution time: 239_686 nanoseconds. + Weight::from_parts(246_450_000, 0) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(8)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create() -> Weight { + // Minimum execution time: 202_922 nanoseconds. + Weight::from_parts(204_527_000, 0) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(9)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create2() -> Weight { + // Minimum execution time: 194_188 nanoseconds. + Weight::from_parts(199_650_000, 0) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(9)) + } + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM NetworkContractIndex (r:1 w:1) + // Proof Skipped: EVM NetworkContractIndex (max_values: Some(1), max_size: None, mode: Measured) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create_nft_contract() -> Weight { + // Minimum execution time: 223_480 nanoseconds. + Weight::from_parts(227_640_000, 0) + .saturating_add(RocksDbWeight::get().reads(12)) + .saturating_add(RocksDbWeight::get().writes(10)) + } + // Storage: EVM Accounts (r:2 w:2) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM CodeInfos (r:2 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn create_predeploy_contract() -> Weight { + // Minimum execution time: 229_528 nanoseconds. + Weight::from_parts(233_183_000, 0) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(9)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:2 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: System Account (r:2 w:2) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: EVM Codes (r:1 w:0) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + fn call() -> Weight { + // Minimum execution time: 185_756 nanoseconds. + Weight::from_parts(189_885_000, 0) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(6)) + } + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + fn transfer_maintainer() -> Weight { + // Minimum execution time: 120_422 nanoseconds. + Weight::from_parts(122_117_000, 0) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + fn publish_contract() -> Weight { + // Minimum execution time: 149_010 nanoseconds. + Weight::from_parts(150_918_000, 0) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + fn publish_free() -> Weight { + // Minimum execution time: 39_214 nanoseconds. + Weight::from_parts(40_271_000, 0) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Balances Reserves (r:1 w:1) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + fn enable_contract_development() -> Weight { + // Minimum execution time: 126_304 nanoseconds. + Weight::from_parts(127_492_000, 0) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: Balances Reserves (r:1 w:1) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + fn disable_contract_development() -> Weight { + // Minimum execution time: 128_756 nanoseconds. + Weight::from_parts(129_795_000, 0) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM CodeInfos (r:2 w:2) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: Balances Reserves (r:2 w:2) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: EVM Codes (r:0 w:2) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + /// The range of component `c` is `[0, 61440]`. + fn set_code(c: u32, ) -> Weight { + // Minimum execution time: 221_718 nanoseconds. + Weight::from_parts(218_913_195, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(5_766, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(10)) + .saturating_add(RocksDbWeight::get().writes(9)) + } + // Storage: EvmAccounts EvmAddresses (r:2 w:0) + // Proof: EvmAccounts EvmAddresses (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM Accounts (r:1 w:1) + // Proof Skipped: EVM Accounts (max_values: None, max_size: None, mode: Measured) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Proof: EvmAccounts Accounts (max_values: None, max_size: Some(60), added: 2535, mode: MaxEncodedLen) + // Storage: EVM CodeInfos (r:1 w:1) + // Proof Skipped: EVM CodeInfos (max_values: None, max_size: None, mode: Measured) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Proof Skipped: EVM ContractStorageSizes (max_values: None, max_size: None, mode: Measured) + // Storage: Balances Reserves (r:1 w:1) + // Proof: Balances Reserves (max_values: None, max_size: Some(168), added: 2643, mode: MaxEncodedLen) + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: Tokens Accounts (r:1 w:0) + // Proof: Tokens Accounts (max_values: None, max_size: Some(147), added: 2622, mode: MaxEncodedLen) + // Storage: EVM Codes (r:0 w:1) + // Proof Skipped: EVM Codes (max_values: None, max_size: None, mode: Measured) + fn selfdestruct() -> Weight { + // Minimum execution time: 239_686 nanoseconds. + Weight::from_parts(246_450_000, 0) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(8)) + } +} diff --git a/blockchain/modules/prices/src/lib.rs b/blockchain/modules/prices/src/lib.rs index 07b0e873..1279f6a9 100644 --- a/blockchain/modules/prices/src/lib.rs +++ b/blockchain/modules/prices/src/lib.rs @@ -101,13 +101,8 @@ pub mod module { /// Mapping between CurrencyId and ERC20 address so user can use Erc20. type Erc20InfoMapping: Erc20InfoMapping; - /// Block number provider for the relaychain. - type RelayChainBlockNumber: BlockNumberProvider>; - - /// The staking reward rate per relaychain block for StakingCurrency. - /// In fact, the staking reward is not settled according to the block on relaychain. - #[pallet::constant] - type RewardRatePerRelaychainBlock: Get; + /// Block number provider. + type BlockNumber: BlockNumberProvider>; /// If a currency is pegged to another currency in price, price of this currency is /// equal to the price of another. diff --git a/blockchain/modules/prices/src/mock.rs b/blockchain/modules/prices/src/mock.rs index 67abbce0..a5e18777 100644 --- a/blockchain/modules/prices/src/mock.rs +++ b/blockchain/modules/prices/src/mock.rs @@ -206,7 +206,7 @@ impl orml_tokens::Config for Runtime { type DustRemovalWhitelist = Nothing; } -impl BlockNumberProvider for MockRelayBlockNumberProvider { +impl BlockNumberProvider for MockBlockNumberProvider { type BlockNumber = BlockNumber; fn current_block_number() -> Self::BlockNumber { @@ -223,8 +223,7 @@ parameter_types! { pub const GetSEECurrencyId: CurrencyId = SEE; pub const GetLiquidSEECurrencyId: CurrencyId = LSEE; pub USSDFixedPrice: Price = Price::one(); - pub static MockRelayBlockNumberProvider: BlockNumber = 0; - pub RewardRatePerRelaychainBlock: Rate = Rate::saturating_from_rational(1, 1000); + pub static MockBlockNumberProvider: BlockNumber = 0; } impl Config for Runtime { @@ -239,8 +238,7 @@ impl Config for Runtime { type SwapDex = MockSwapDex; type Currency = Tokens; type Erc20InfoMapping = MockErc20InfoMapping; - type RelayChainBlockNumber = MockRelayBlockNumberProvider; - type RewardRatePerRelaychainBlock = RewardRatePerRelaychainBlock; + type BlockNumber = MockBlockNumberProvider; type PricingPegged = PricingPegged; type WeightInfo = (); } diff --git a/blockchain/modules/support/src/lib.rs b/blockchain/modules/support/src/lib.rs index 77f0f2d1..77c2c72e 100644 --- a/blockchain/modules/support/src/lib.rs +++ b/blockchain/modules/support/src/lib.rs @@ -135,12 +135,6 @@ pub trait DispatchableTask { fn dispatch(self, weight: Weight) -> TaskResult; } -/// Idle scheduler trait -pub trait IdleScheduler { - fn schedule(task: Task) -> Result; - fn dispatch(id: Nonce, weight: Weight) -> Weight; -} - #[cfg(feature = "std")] impl DispatchableTask for () { fn dispatch(self, _weight: Weight) -> TaskResult { @@ -148,16 +142,6 @@ impl DispatchableTask for () { } } -#[cfg(feature = "std")] -impl IdleScheduler for () { - fn schedule(_task: Task) -> Result { - unimplemented!() - } - fn dispatch(_id: Nonce, _weight: Weight) -> Weight { - unimplemented!() - } -} - #[impl_trait_for_tuples::impl_for_tuples(30)] pub trait OnNewEra { fn on_new_era(era: EraIndex); diff --git a/blockchain/modules/transaction-payment/src/mock.rs b/blockchain/modules/transaction-payment/src/mock.rs index 6fce076f..1e8bf200 100644 --- a/blockchain/modules/transaction-payment/src/mock.rs +++ b/blockchain/modules/transaction-payment/src/mock.rs @@ -170,14 +170,14 @@ parameter_types! { pub const TradingPathLimit: u32 = 4; } -impl module_dex::Config for Runtime { +impl edfis_swap_module::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; type Erc20InfoMapping = (); - type DEXIncentives = (); + type SwapDexIncentives = (); type WeightInfo = (); type ListingOrigin = EnsureSignedBy; type ExtendedProvisioningBlocks = ConstU64<0>; @@ -272,7 +272,7 @@ impl Config for Runtime { type WeightToFee = WeightToFee; type LengthToFee = TransactionByteFee; type FeeMultiplierUpdate = (); - type Swap = SpecificJointsSwap; + type Swap = SpecificJointsSwap; type MaxSwapSlippageComparedToOracle = MaxSwapSlippageComparedToOracle; type TradingPathLimit = TradingPathLimit; type PriceSource = MockPriceSource; @@ -312,7 +312,7 @@ construct_runtime!( PalletBalances: pallet_balances, Tokens: orml_tokens, Currencies: module_currencies, - DEXModule: module_dex, + EdfisSwapModule: edfis_swap_module, } ); @@ -388,7 +388,7 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - module_dex::GenesisConfig:: { + edfis_swap_module::GenesisConfig:: { initial_listing_trading_pairs: vec![], initial_enabled_trading_pairs: EnabledTradingPairs::get(), initial_added_liquidity_pools: vec![], diff --git a/blockchain/modules/transaction-payment/src/tests.rs b/blockchain/modules/transaction-payment/src/tests.rs index 51ff9f46..3ee3d677 100644 --- a/blockchain/modules/transaction-payment/src/tests.rs +++ b/blockchain/modules/transaction-payment/src/tests.rs @@ -23,13 +23,13 @@ #![cfg(test)] use super::*; -use crate::mock::{AlternativeFeeSurplus, AusdFeeSwapPath, CustomFeeSurplus, DotFeeSwapPath, PalletBalances}; +use crate::mock::{AlternativeFeeSurplus, UssdFeeSwapPath, CustomFeeSurplus, DotFeeSwapPath, PalletBalances}; use frame_support::{ assert_noop, assert_ok, dispatch::{DispatchClass, DispatchInfo, Pays}, }; use mock::{ - AccountId, BlockWeights, Currencies, DEXModule, ExtBuilder, FeePoolSize, MockPriceSource, Runtime, RuntimeCall, + AccountId, BlockWeights, Currencies, EdfisSwapModule, ExtBuilder, FeePoolSize, MockPriceSource, Runtime, RuntimeCall, RuntimeOrigin, System, TransactionPayment, ALICE, BOB, CHARLIE, DAVE, FEE_UNBALANCED_AMOUNT, TIP_UNBALANCED_AMOUNT, SEE, USSD, EDF, LSEE, }; @@ -135,7 +135,7 @@ fn enable_dex_and_tx_fee_pool() { } // enable dex - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), SEE, USSD, @@ -144,7 +144,7 @@ fn enable_dex_and_tx_fee_pool() { 0, false )); - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), EDF, USSD, @@ -153,7 +153,7 @@ fn enable_dex_and_tx_fee_pool() { 0, false )); - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), LSEE, SEE, @@ -162,10 +162,10 @@ fn enable_dex_and_tx_fee_pool() { 0, false )); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); - assert_eq!(DEXModule::get_liquidity_pool(EDF, USSD), (100, 1000)); - assert_eq!(DEXModule::get_liquidity_pool(LSEE, SEE), (100, 1000)); - assert_eq!(DEXModule::get_liquidity_pool(EDF, SEE), (0, 0)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (100, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(LSEE, SEE), (100, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, SEE), (0, 0)); // enable tx fee pool for USSD and EDF token. vec![USSD, EDF].iter().for_each(|token| { @@ -559,7 +559,7 @@ fn pre_post_dispatch_and_refund_with_fee_call_use_dex(with_fee_call: ::from(0).validate(&BOB, &with_fee_call, &INFO2, 50)); - System::assert_has_event(crate::mock::RuntimeEvent::DEXModule(module_dex::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { trader: BOB, path: vec![LSEE, SEE], liquidity_changes: vec![46, 315], @@ -808,7 +808,7 @@ fn charges_fee_when_validate_with_fee_call_use_swap(with_fee_call: ::from(0).validate(&BOB, &with_fee_call, &INFO2, 50)); - System::assert_has_event(crate::mock::RuntimeEvent::DEXModule(module_dex::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { trader: BOB, path: vec![LSEE, SEE], liquidity_changes: vec![114, 300], @@ -823,10 +823,10 @@ fn charges_fee_when_validate_with_fee_call_use_swap(with_fee_call: ::sub_account_id(USSD); + let ussd_acc = Pallet::::sub_account_id(USSD); let edf_acc = Pallet::::sub_account_id(EDF); - let sub_ausd_see = Currencies::free_balance(SEE, &ausd_acc); - let sub_ausd_usd = Currencies::free_balance(USSD, &ausd_acc); + let sub_ussd_see = Currencies::free_balance(SEE, &ussd_acc); + let sub_ussd_usd = Currencies::free_balance(USSD, &ussd_acc); let sub_edf_see = Currencies::free_balance(SEE, &edf_acc); let sub_edf_edf = Currencies::free_balance(EDF, &edf_acc); @@ -850,21 +850,21 @@ fn charges_fee_when_validate_with_fee_currency_call_use_pool() { System::assert_has_event(crate::mock::RuntimeEvent::Tokens(orml_tokens::Event::Transfer { currency_id: USSD, from: BOB, - to: ausd_acc.clone(), + to: ussd_acc.clone(), amount: 2630, })); System::assert_has_event(crate::mock::RuntimeEvent::PalletBalances( pallet_balances::Event::Transfer { - from: ausd_acc.clone(), + from: ussd_acc.clone(), to: BOB, amount: 263, }, )); - assert_eq!(sub_ausd_see - fee_amount, Currencies::free_balance(SEE, &ausd_acc)); + assert_eq!(sub_ussd_see - fee_amount, Currencies::free_balance(SEE, &ussd_acc)); assert_eq!( - sub_ausd_usd + fee_amount * 10, // 1 SEE = 10 USSD - Currencies::free_balance(USSD, &ausd_acc) + sub_ussd_usd + fee_amount * 10, // 1 SEE = 10 USSD + Currencies::free_balance(USSD, &ussd_acc) ); // second tx no need to consider existential deposit. @@ -896,14 +896,14 @@ fn charges_fee_when_validate_and_native_is_not_enough() { builder_with_dex_and_fee_pool(true).execute_with(|| { let sub_account = Pallet::::sub_account_id(USSD); let init_balance = FeePoolSize::get(); - let ausd_ed: Balance = >::minimum_balance(USSD); + let ussd_ed: Balance = >::minimum_balance(USSD); let ed: Balance = >::minimum_balance(SEE); let rate: u128 = 10; // transfer token to Bob, and use Bob as tx sender to test // Bob do not have enough native asset(SEE), but he has USSD assert_ok!(>::transfer(USSD, &ALICE, &BOB, 4000)); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); assert_eq!(Currencies::total_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(USSD, &BOB), 4000); @@ -929,14 +929,14 @@ fn charges_fee_when_validate_and_native_is_not_enough() { // surplus=50SEE/500USSD, balance=4000, swap_in=2600, left=1400 // surplus=0, balance=4000, swap_in=2100, left=1900 assert_eq!(Currencies::free_balance(USSD, &BOB), 1900 - surplus1 * 10); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); assert_eq!( Currencies::free_balance(SEE, &sub_account), init_balance - (fee + ed + surplus1) ); assert_eq!( Currencies::free_balance(USSD, &sub_account), - ausd_ed + (fee + ed + surplus1) * rate + ussd_ed + (fee + ed + surplus1) * rate ); // native balance is eq ED, cannot keep alive after charge, swap with foreign asset @@ -957,7 +957,7 @@ fn charges_fee_when_validate_and_native_is_not_enough() { // two tx, first receive: (fee+ED+surplus)*10, second receive: (fee2+surplus)*10 assert_eq!( Currencies::free_balance(USSD, &sub_account), - ausd_ed + (fee + ed + surplus1 + fee2 + surplus2) * rate + ussd_ed + (fee + ed + surplus1 + fee2 + surplus2) * rate ); // Bob only has ED of native asset, but has not enough USSD, validate failed. @@ -1030,35 +1030,35 @@ fn charges_fee_failed_by_slippage_limit() { builder_with_dex_and_fee_pool(true).execute_with(|| { assert_ok!(>::transfer(USSD, &ALICE, &BOB, 1000)); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); assert_eq!(Currencies::total_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(USSD, &BOB), 1000); assert_eq!( - DEXModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), + EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), Some((252, 2010)) ); assert_eq!( - DEXModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), + EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), Some((1000, 5000)) ); // pool is enough, but slippage limit the swap MockPriceSource::set_relative_price(Some(Price::saturating_from_rational(252, 4020))); assert_eq!( - DEXModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), + EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), Some((252, 2010)) ); assert_eq!( - DEXModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), + EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), Some((1000, 5000)) ); assert_noop!( ChargeTransactionPayment::::from(0).validate(&BOB, &CALL2, &INFO, 500), TransactionValidityError::Invalid(InvalidTransaction::Payment) ); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); }); } @@ -1110,8 +1110,8 @@ fn charge_fee_by_alternative_swap_first_priority() { let alternative_fee_swap_deposit: u128 = <::AlternativeFeeSwapDeposit as frame_support::traits::Get>::get(); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); - assert_eq!(DEXModule::get_liquidity_pool(EDF, USSD), (100, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (100, 1000)); assert_ok!(Currencies::update_balance( RuntimeOrigin::root(), BOB, @@ -1156,7 +1156,7 @@ fn charge_fee_by_alternative_swap_first_priority() { .priority, 1 ); - System::assert_has_event(crate::mock::RuntimeEvent::DEXModule(module_dex::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { trader: BOB, path: vec![EDF, USSD, SEE], liquidity_changes: vec![51, 334, fee_surplus], @@ -1165,8 +1165,8 @@ fn charge_fee_by_alternative_swap_first_priority() { assert_eq!(Currencies::free_balance(SEE, &BOB), ed); assert_eq!(Currencies::free_balance(USSD, &BOB), 0); assert_eq!(Currencies::free_balance(EDF, &BOB), 249); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); - assert_eq!(DEXModule::get_liquidity_pool(EDF, USSD), (151, 666)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (151, 666)); assert_eq!(Currencies::free_balance(SEE, &sub_account), init_balance,); assert_eq!(Currencies::free_balance(EDF, &sub_account), edf_ed); }); @@ -1229,7 +1229,7 @@ fn charge_fee_by_default_fee_tokens_second_priority() { 1 ); // Alternative fee swap directly from dex, not from fee pool. - System::assert_has_event(crate::mock::RuntimeEvent::DEXModule(module_dex::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { trader: BOB, path: vec![EDF, USSD, SEE], liquidity_changes: vec![51, 334, fee_surplus], @@ -1238,8 +1238,8 @@ fn charge_fee_by_default_fee_tokens_second_priority() { assert_eq!(Currencies::free_balance(SEE, &BOB), ed); assert_eq!(Currencies::free_balance(USSD, &BOB), 0); assert_eq!(Currencies::free_balance(EDF, &BOB), 249); - assert_eq!(DEXModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); - assert_eq!(DEXModule::get_liquidity_pool(EDF, USSD), (151, 666)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (151, 666)); // sub-account balance not changed, because not passing through sub-account. assert_eq!(Currencies::free_balance(SEE, &sub_account), init_balance,); assert_eq!(Currencies::free_balance(EDF, &sub_account), edf_ed); @@ -1708,24 +1708,24 @@ fn swap_from_pool_with_enough_balance() { // 1 SEE = 10 USSD, swap 500 SEE with 5000 USSD let balance = 500 as u128; - let ausd_balance = (balance * 11) as u128; // 5500 USSD + let ussd_balance = (balance * 11) as u128; // 5500 USSD assert_ok!(Currencies::update_balance( RuntimeOrigin::root(), BOB, USSD, - ausd_balance.unique_saturated_into(), + ussd_balance.unique_saturated_into(), )); assert_eq!(0, Currencies::free_balance(USSD, &usd_fee_account) - usd_ed); let fee = balance; // 500 SEE - let expect_treasury_ausd = (balance * 10) as u128; // 5000 USSD - let expect_user_ausd = balance; // (balance * 11) - (balance * 10) = balance = 500 USSD + let expect_treasury_ussd = (balance * 10) as u128; // 5000 USSD + let expect_user_ussd = balance; // (balance * 11) - (balance * 10) = balance = 500 USSD let expect_treasury_see = pool_size - fee; // 1000 SEE - 500 SEE let expect_user_see = expect_user_see + fee; // 500 SEE assert_ok!(Pallet::::swap_from_pool_or_dex(&BOB, fee, USSD)); - assert_eq!(expect_user_ausd, Currencies::free_balance(USSD, &BOB)); + assert_eq!(expect_user_ussd, Currencies::free_balance(USSD, &BOB)); assert_eq!( - expect_treasury_ausd, + expect_treasury_ussd, Currencies::free_balance(USSD, &usd_fee_account) - usd_ed ); assert_eq!(expect_user_see, Currencies::free_balance(SEE, &BOB)); @@ -1771,7 +1771,7 @@ fn swap_from_pool_and_dex_with_higher_threshold() { let supply_amount = Currencies::free_balance(EDF, &edf_fee_account) - edf_ed; // here just get swap out amount, the swap not happened let (supply_in_amount, swap_out_native) = - module_dex::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) + edfis_swap_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) .unwrap(); assert_eq!(3074, swap_out_native); assert_eq!(supply_in_amount, supply_amount); @@ -1847,7 +1847,7 @@ fn swap_from_pool_and_dex_with_midd_threshold() { // 500 | 4544 | FixedU128(0.110035211267605633) // 600 | 4614 | FixedU128(0.130039011703511053) <- this case hit here let (supply_in_amount, swap_out_native) = - module_dex::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) + edfis_swap_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) .unwrap(); assert_eq!(600, supply_in_amount); assert_eq!(4614, swap_out_native); @@ -1886,16 +1886,16 @@ fn swap_from_pool_and_dex_with_midd_threshold() { #[test] #[should_panic(expected = "Swap tx fee pool should not fail!")] fn charge_fee_failed_when_disable_dex() { - use module_dex::TradingPairStatus; + use edfis_swap_module::TradingPairStatus; use primitives::TradingPair; ExtBuilder::default().build().execute_with(|| { let fee_account = Pallet::::sub_account_id(USSD); let pool_size = FeePoolSize::get(); let swap_balance_threshold = (pool_size - 200) as u128; - let ausd_ed = >::minimum_balance(USSD); + let ussd_ed = >::minimum_balance(USSD); let ed = >::minimum_balance(SEE); - let trading_path = AusdFeeSwapPath::get(); + let trading_path = UssdFeeSwapPath::get(); assert_ok!(Currencies::update_balance( RuntimeOrigin::root(), @@ -1913,7 +1913,7 @@ fn charge_fee_failed_when_disable_dex() { enable_dex_and_tx_fee_pool(); // after runtime upgrade, tx success because of dex enabled and has enough token balance - // fee=50*2+100=200, ED=10, surplus=200*0.25=50, fee_amount=260, ausd_swap=260*10=2600 + // fee=50*2+100=200, ED=10, surplus=200*0.25=50, fee_amount=260, ussd_swap=260*10=2600 let surplus = AlternativeFeeSurplus::get().mul_ceil(200); assert_ok!(ChargeTransactionPayment::::from(0).validate(&BOB, &CALL2, &INFO2, 50)); assert_eq!(100000 - (210 + surplus) * 10, Currencies::free_balance(USSD, &BOB)); @@ -1924,13 +1924,13 @@ fn charge_fee_failed_when_disable_dex() { // trading pair is enabled let pair = TradingPair::from_currency_ids(USSD, SEE).unwrap(); assert_eq!( - module_dex::Pallet::::trading_pair_statuses(pair), + edfis_swap_module::Pallet::::trading_pair_statuses(pair), TradingPairStatus::Enabled ); // make sure swap is valid - let swap_result = module_dex::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(1, 0)); + let swap_result = edfis_swap_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(1, 0)); assert!(swap_result.is_some()); - assert_ok!(module_dex::Pallet::::swap_with_specific_path( + assert_ok!(edfis_swap_module::Pallet::::swap_with_specific_path( &ALICE, &trading_path, SwapLimit::ExactSupply(100, 0) @@ -1938,16 +1938,16 @@ fn charge_fee_failed_when_disable_dex() { // balance lt threshold, trigger swap from dex assert_eq!( - ausd_ed + (210 + surplus) * 10, + ussd_ed + (210 + surplus) * 10, Currencies::free_balance(USSD, &fee_account) ); assert_eq!(9790 - surplus, Currencies::free_balance(SEE, &fee_account)); assert_ok!(ChargeTransactionPayment::::from(0).validate(&BOB, &CALL2, &INFO2, 50)); // AlternativeFeeSurplus=25%, swap 2600 USSD with 6388 SEE, pool_size=9740+6388=16128 - // fee=50*2+100=200, surplus=200*0.25=50, fee_amount=250, ausd_swap=250*10=2500 + // fee=50*2+100=200, surplus=200*0.25=50, fee_amount=250, ussd_swap=250*10=2500 let fee_see = Currencies::free_balance(SEE, &fee_account); assert_eq!( - ausd_ed + (200 + surplus) * 10, + ussd_ed + (200 + surplus) * 10, Currencies::free_balance(USSD, &fee_account) ); if AlternativeFeeSurplus::get() == Percent::from_percent(25) { @@ -1991,16 +1991,16 @@ fn charge_fee_failed_when_disable_dex() { } // when trading pair disabled, the swap action will failed - assert_ok!(module_dex::Pallet::::disable_trading_pair( + assert_ok!(edfis_swap_module::Pallet::::disable_trading_pair( RuntimeOrigin::signed(AccountId::new([0u8; 32])), USSD, SEE )); assert_eq!( - module_dex::Pallet::::trading_pair_statuses(pair), + edfis_swap_module::Pallet::::trading_pair_statuses(pair), TradingPairStatus::Disabled ); - let res = module_dex::Pallet::::swap_with_specific_path( + let res = edfis_swap_module::Pallet::::swap_with_specific_path( &ALICE, &trading_path, SwapLimit::ExactSupply(100, 0), @@ -2060,7 +2060,7 @@ fn charge_fee_pool_operation_works() { 10000.unique_saturated_into(), )); - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), SEE, USSD, @@ -2121,7 +2121,7 @@ fn charge_fee_pool_operation_works() { Error::::InvalidToken ); - let ausd_amount1 = >::free_balance(USSD, &sub_account); + let ussd_amount1 = >::free_balance(USSD, &sub_account); let see_amount1 = crate::mock::PalletBalances::free_balance(&sub_account); assert_ok!(Pallet::::disable_charge_fee_pool( RuntimeOrigin::signed(ALICE), @@ -2131,14 +2131,14 @@ fn charge_fee_pool_operation_works() { System::assert_has_event(crate::mock::RuntimeEvent::TransactionPayment( crate::Event::ChargeFeePoolDisabled { currency_id: USSD, - foreign_amount: ausd_amount1, + foreign_amount: ussd_amount1, native_amount: see_amount1, }, )); - let ausd_amount2 = >::free_balance(USSD, &sub_account); + let ussd_amount2 = >::free_balance(USSD, &sub_account); let see_amount2 = crate::mock::PalletBalances::free_balance(&sub_account); assert_eq!(see_amount2, 0); - assert_eq!(ausd_amount2, 0); + assert_eq!(ussd_amount2, 0); assert_ok!(Pallet::::enable_charge_fee_pool( RuntimeOrigin::signed(ALICE), diff --git a/blockchain/runtime/common/src/precompile/mock.rs b/blockchain/runtime/common/src/precompile/mock.rs index 83d1eb29..e587291f 100644 --- a/blockchain/runtime/common/src/precompile/mock.rs +++ b/blockchain/runtime/common/src/precompile/mock.rs @@ -482,7 +482,7 @@ parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"edf/swap"); } -impl module_dex::Config for Test { +impl edfis_swap_module::Config for Test { type Event = Event; type Currency = Tokens; type StableCurrencyIds = StableCurrencyIds; @@ -521,7 +521,7 @@ pub type ScheduleCallPrecompile = crate::ScheduleCallPrecompile< OriginCaller, Test, >; -pub type DexPrecompile = crate::DexPrecompile; +pub type DexPrecompile = crate::DexPrecompile; parameter_types! { pub NetworkContractSource: H160 = alice_evm_addr(); @@ -594,7 +594,7 @@ impl module_prices::Config for Test { type SetUSDFixedPrice = SetUSDFixedPrice; type SetterFixedPrice = SetterFixedPrice; type LockOrigin = EnsureSignedBy; - type DEX = DexModule; + type DEX = EdfisSwapModule; type Currency = Currencies; type CurrencyIdMapping = EvmCurrencyIdMapping; type WeightInfo = (); @@ -688,7 +688,7 @@ frame_support::construct_runtime!( Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, Utility: pallet_utility::{Pallet, Call, Event}, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, - DexModule: module_dex::{Pallet, Storage, Call, Event, Config}, + EdfisSwapModule: edfis_swap_module::{Pallet, Storage, Call, Event, Config}, ModuleEVM: module_evm::{Pallet, Config, Call, Storage, Event}, } ); diff --git a/blockchain/runtime/common/src/precompile/tests.rs b/blockchain/runtime/common/src/precompile/tests.rs index bcbcdb39..acdf6af1 100644 --- a/blockchain/runtime/common/src/precompile/tests.rs +++ b/blockchain/runtime/common/src/precompile/tests.rs @@ -1,905 +1,905 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -#![allow(clippy::erasing_op)] -#![cfg(test)] -use super::*; -use crate::precompile::{ - mock::{ - setm_evm_address, alice, alice_evm_addr, setusd_evm_address, bob, bob_evm_addr, erc20_address_not_exists, - get_task_id, lp_setm_setusd_evm_address, new_test_ext, serp_evm_address, run_to_block, Balances, DexModule, - DexPrecompile, Event as TestEvent, MultiCurrencyPrecompile, Oracle, OraclePrecompile, Origin, Price, - ScheduleCallPrecompile, System, Test, ALICE, SETUSD, INITIAL_BALANCE, SERP, - }, - schedule_call::TaskInfo, -}; -use codec::Encode; -use frame_support::{assert_noop, assert_ok}; -use hex_literal::hex; -use module_evm::{Context, ExitError, ExitSucceed, Precompile}; -use module_support::AddressMapping; -use orml_traits::DataFeeder; -use primitives::{Balance, PREDEPLOY_ADDRESS_START}; -use sp_core::{H160, U256}; -use sp_runtime::FixedPointNumber; -use std::str::FromStr; - -pub struct DummyPrecompile; -impl Precompile for DummyPrecompile { - fn execute( - _input: &[u8], - _target_gas: Option, - _context: &Context, - ) -> core::result::Result { - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - cost: 0, - output: vec![], - logs: Default::default(), - }) - } -} - -pub type WithSystemContractFilter = AllPrecompiles< - crate::SystemContractsFilter, - DummyPrecompile, - DummyPrecompile, - DummyPrecompile, - DummyPrecompile, - DummyPrecompile, - DummyPrecompile, ->; - -#[test] -fn precompile_filter_works_on_setheum_precompiles() { - let precompile = H160::from_low_u64_be(PRECOMPILE_ADDRESS_START); - - let mut non_system = [0u8; 20]; - non_system[0] = 1; - - let non_system_caller_context = Context { - address: precompile, - caller: non_system.into(), - apparent_value: 0.into(), - }; - assert_eq!( - WithSystemContractFilter::execute(precompile, &[0u8; 1], None, &non_system_caller_context), - Some(Err(ExitError::Other("no permission".into()))), - ); -} - -#[test] -fn precompile_filter_does_not_work_on_system_contracts() { - let system = H160::from_low_u64_be(PREDEPLOY_ADDRESS_START); - - let mut non_system = [0u8; 20]; - non_system[0] = 1; - - let non_system_caller_context = Context { - address: system, - caller: non_system.into(), - apparent_value: 0.into(), - }; - assert!( - WithSystemContractFilter::execute(non_system.into(), &[0u8; 1], None, &non_system_caller_context).is_none() - ); -} - -#[test] -fn precompile_filter_does_not_work_on_non_system_contracts() { - let mut non_system = [0u8; 20]; - non_system[0] = 1; - let mut another_non_system = [0u8; 20]; - another_non_system[0] = 2; - - let non_system_caller_context = Context { - address: non_system.into(), - caller: another_non_system.into(), - apparent_value: 0.into(), - }; - assert!( - WithSystemContractFilter::execute(non_system.into(), &[0u8; 1], None, &non_system_caller_context).is_none() - ); -} -// TODO - FIXME: Fix Mock Prefix -// #[test] -// fn multicurrency_precompile_should_work() { -// new_test_ext().execute_with(|| { -// let mut context = Context { -// address: Default::default(), -// caller: Default::default(), -// apparent_value: Default::default(), -// }; - -// // call with not exists erc20 -// context.caller = erc20_address_not_exists(); -// let mut input = [0u8; 68]; -// // action -// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QuerySymbol).to_be_bytes()); -// assert_noop!( -// MultiCurrencyPrecompile::execute(&input, None, &context), -// ExitError::Other("invalid currency id".into()) -// ); - -// // 1.QueryName -// let mut input = [0u8; 4]; -// // action -// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryName).to_be_bytes()); - -// // Token -// context.caller = setm_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 96]; -// // skip offset -// expected_output[31] = 32; -// // length -// expected_output[63] = 5; -// expected_output[64..64 + 5].copy_from_slice(&b"Setheum"[..]); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // DexShare -// context.caller = lp_setm_setusd_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 96]; -// // skip offset -// expected_output[31] = 32; -// // length -// expected_output[63] = 23; -// expected_output[64..64 + 23].copy_from_slice(&b"LP Setheum - SetDollar"[..]); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // 2.QuerySymbol -// let mut input = [0u8; 4]; -// // action -// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QuerySymbol).to_be_bytes()); - -// // Token -// context.caller = setm_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 96]; -// // skip offset -// expected_output[31] = 32; -// // length -// expected_output[63] = 3; -// expected_output[64..64 + 3].copy_from_slice(&b"SEE"[..]); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // DexShare -// context.caller = lp_setm_setusd_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 96]; -// // skip offset -// expected_output[31] = 32; -// // length -// expected_output[63] = 11; -// expected_output[64..64 + 11].copy_from_slice(&b"LP_SETM_SETUSD"[..]); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // 3.QueryDecimals -// let mut input = [0u8; 4]; -// // action -// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryDecimals).to_be_bytes()); - -// // Token -// context.caller = setm_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 32]; -// expected_output[31] = 12; -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // DexShare -// context.caller = lp_setm_setusd_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 32]; -// expected_output[31] = 12; -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // 4.QueryTotalIssuance -// let mut input = [0u8; 4]; -// // action -// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryTotalIssuance).to_be_bytes()); - -// // Token -// context.caller = setusd_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 32]; -// expected_output[28..32].copy_from_slice(&1_000_000_000u32.to_be_bytes()[..]); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // DexShare -// context.caller = lp_setm_setusd_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let expected_output = [0u8; 32]; -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // 5.QueryBalance -// let mut input = [0u8; 36]; -// // action -// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryBalance).to_be_bytes()); -// // from -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); - -// // Token -// context.caller = setm_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let mut expected_output = [0u8; 32]; -// expected_output[16..32].copy_from_slice(&INITIAL_BALANCE.to_be_bytes()[..]); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // DexShare -// context.caller = lp_setm_setusd_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let expected_output = [0u8; 32]; -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // 6.Transfer -// let mut input = [0u8; 4 + 3 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::Transfer).to_be_bytes()); -// // from -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); -// // to -// U256::from(bob_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); -// // amount -// U256::from(1).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); -// let from_balance = Balances::free_balance(alice()); -// let to_balance = Balances::free_balance(bob()); - -// // Token -// context.caller = setm_evm_address(); -// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// let expected_output: Vec = vec![]; -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); -// assert_eq!(Balances::free_balance(alice()), from_balance - 1); -// assert_eq!(Balances::free_balance(bob()), to_balance + 1); - -// // DexShare -// context.caller = lp_setm_setusd_evm_address(); -// assert_noop!( -// MultiCurrencyPrecompile::execute(&input, None, &context), -// ExitError::Other("BalanceTooLow".into()) -// ); -// }); -// } - -// #[test] -// fn oracle_precompile_should_work() { -// new_test_ext().execute_with(|| { -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// let price = Price::from(30_000); - -// // action + currency_id -// let mut input = [0u8; 36]; -// // action -// input[0..4].copy_from_slice(&Into::::into(oracle::Action::GetPrice).to_be_bytes()); -// // SERP -// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4..4 + 32]); - -// // no price yet -// let resp = OraclePrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, [0u8; 32]); -// assert_eq!(resp.cost, 0); - -// assert_ok!(Oracle::feed_value(ALICE, SERP, price)); -// assert_eq!( -// Oracle::get_no_op(&SERP), -// Some(orml_oracle::TimestampedValue { -// value: price, -// timestamp: 1 -// }) -// ); - -// // returned price + timestamp -// let mut expected_output = [0u8; 32]; -// U256::from(price.into_inner()).to_big_endian(&mut expected_output[..]); - -// let resp = OraclePrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); -// }); -// } - -// #[test] -// fn oracle_precompile_should_handle_invalid_input() { -// new_test_ext().execute_with(|| { -// assert_noop!( -// OraclePrecompile::execute( -// &[0u8; 0], -// None, -// &Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default() -// } -// ), -// ExitError::Other("invalid input".into()) -// ); - -// assert_noop!( -// OraclePrecompile::execute( -// &[0u8; 3], -// None, -// &Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default() -// } -// ), -// ExitError::Other("invalid input".into()) -// ); - -// assert_noop!( -// OraclePrecompile::execute( -// &[1u8; 32], -// None, -// &Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default() -// } -// ), -// ExitError::Other("invalid action".into()) -// ); -// }); -// } - -// #[test] -// fn schedule_call_precompile_should_work() { -// new_test_ext().execute_with(|| { -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// let mut input = [0u8; 11 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Schedule).to_be_bytes()); -// // from -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); -// // target -// U256::from(setm_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); -// // value -// U256::from(0).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); -// // gas_limit -// U256::from(300000).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); -// // storage_limit -// U256::from(100).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); -// // min_delay -// U256::from(1).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); -// // skip offset -// // input_len -// U256::from(4 + 32 + 32).to_big_endian(&mut input[4 + 7 * 32..4 + 8 * 32]); - -// // input_data -// let mut transfer_to_bob = [0u8; 68]; -// // transfer bytes4(keccak256(signature)) 0xa9059cbb -// transfer_to_bob[0..4].copy_from_slice(&hex!("a9059cbb")); -// // to address -// U256::from(bob_evm_addr().as_bytes()).to_big_endian(&mut transfer_to_bob[4..36]); -// // amount -// U256::from(1000).to_big_endian(&mut transfer_to_bob[36..68]); - -// U256::from(&transfer_to_bob[0..32]).to_big_endian(&mut input[4 + 8 * 32..4 + 9 * 32]); -// U256::from(&transfer_to_bob[32..64]).to_big_endian(&mut input[4 + 9 * 32..4 + 10 * 32]); -// input[4 + 10 * 32..4 + 10 * 32 + 4].copy_from_slice(&transfer_to_bob[64..68]); - -// let resp = ScheduleCallPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.cost, 0); -// let event = TestEvent::Scheduler(pallet_scheduler::Event::::Scheduled(3, 0)); -// assert!(System::events().iter().any(|record| record.event == event)); - -// // cancel schedule -// let task_id = get_task_id(resp.output); -// let mut cancel_input = [0u8; 5 * 32]; -// // action -// cancel_input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Cancel).to_be_bytes()); -// // from -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut cancel_input[4 + 0 * 32..4 + 1 * 32]); -// // skip offset -// // task_id_len -// U256::from(task_id.len()).to_big_endian(&mut cancel_input[4 + 2 * 32..4 + 3 * 32]); -// // task_id -// cancel_input[4 + 3 * 32..4 + 3 * 32 + task_id.len()].copy_from_slice(&task_id[..]); - -// let resp = ScheduleCallPrecompile::execute(&cancel_input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.cost, 0); -// let event = TestEvent::Scheduler(pallet_scheduler::Event::::Canceled(3, 0)); -// assert!(System::events().iter().any(|record| record.event == event)); - -// let resp = ScheduleCallPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.cost, 0); - -// run_to_block(2); - -// // reschedule call -// let task_id = get_task_id(resp.output); -// let mut reschedule_input = [0u8; 6 * 32]; -// // action -// reschedule_input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Reschedule).to_be_bytes()); -// // from -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut reschedule_input[4 + 0 * 32..4 + 1 * 32]); -// // min_delay -// U256::from(2).to_big_endian(&mut reschedule_input[4 + 1 * 32..4 + 2 * 32]); -// // skip offset -// // task_id_len -// U256::from(task_id.len()).to_big_endian(&mut reschedule_input[4 + 3 * 32..4 + 4 * 32]); -// // task_id -// reschedule_input[4 + 4 * 32..4 + 4 * 32 + task_id.len()].copy_from_slice(&task_id[..]); - -// let resp = ScheduleCallPrecompile::execute(&reschedule_input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.cost, 0); -// let event = TestEvent::Scheduler(pallet_scheduler::Event::::Scheduled(5, 0)); -// assert!(System::events().iter().any(|record| record.event == event)); - -// let from_account = ::AddressMapping::get_account_id(&alice_evm_addr()); -// let to_account = ::AddressMapping::get_account_id(&bob_evm_addr()); -// #[cfg(not(feature = "with-ethereum-compatibility"))] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 999999700000); -// assert_eq!(Balances::reserved_balance(from_account.clone()), 300000); -// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); -// } -// #[cfg(feature = "with-ethereum-compatibility")] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 1000000000000); -// assert_eq!(Balances::reserved_balance(from_account.clone()), 0); -// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); -// } - -// run_to_block(5); -// #[cfg(not(feature = "with-ethereum-compatibility"))] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 999999972553); -// assert_eq!(Balances::reserved_balance(from_account), 0); -// assert_eq!(Balances::free_balance(to_account), 1000000001000); -// } -// #[cfg(feature = "with-ethereum-compatibility")] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 999999999000); -// assert_eq!(Balances::reserved_balance(from_account), 0); -// assert_eq!(Balances::free_balance(to_account), 1000000001000); -// } -// }); -// } - -// #[test] -// fn schedule_call_precompile_should_handle_invalid_input() { -// new_test_ext().execute_with(|| { -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// let mut input = [0u8; 10 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Schedule).to_be_bytes()); -// // from -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); -// // target -// U256::from(setm_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); -// // value -// U256::from(0).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); -// // gas_limit -// U256::from(300000).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); -// // storage_limit -// U256::from(100).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); -// // min_delay -// U256::from(1).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); -// // skip offset -// // input_len -// U256::from(1).to_big_endian(&mut input[4 + 7 * 32..4 + 8 * 32]); - -// // input_data = 0x12 -// input[4 + 9 * 32] = hex!("12")[0]; - -// let resp = ScheduleCallPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.cost, 0); - -// let from_account = ::AddressMapping::get_account_id(&alice_evm_addr()); -// let to_account = ::AddressMapping::get_account_id(&bob_evm_addr()); -// #[cfg(not(feature = "with-ethereum-compatibility"))] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 999999700000); -// assert_eq!(Balances::reserved_balance(from_account.clone()), 300000); -// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); -// } -// #[cfg(feature = "with-ethereum-compatibility")] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 1000000000000); -// assert_eq!(Balances::reserved_balance(from_account.clone()), 0); -// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); -// } - -// // cancel schedule -// let task_id = get_task_id(resp.output); -// let mut cancel_input = [0u8; 6 * 32]; -// // action -// cancel_input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Cancel).to_be_bytes()); -// // from -// U256::from(bob_evm_addr().as_bytes()).to_big_endian(&mut cancel_input[4 + 0 * 32..4 + 1 * 32]); -// // skip offset -// // task_id_len -// U256::from(task_id.len()).to_big_endian(&mut cancel_input[4 + 2 * 32..4 + 3 * 32]); -// // task_id -// cancel_input[4 + 3 * 32..4 + 3 * 32 + task_id.len()].copy_from_slice(&task_id[..]); - -// assert_eq!( -// ScheduleCallPrecompile::execute(&cancel_input, None, &context), -// Err(ExitError::Other("NoPermission".into())) -// ); - -// run_to_block(4); -// #[cfg(not(feature = "with-ethereum-compatibility"))] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 999999978926); -// assert_eq!(Balances::reserved_balance(from_account), 0); -// assert_eq!(Balances::free_balance(to_account), 1000000000000); -// } -// #[cfg(feature = "with-ethereum-compatibility")] -// { -// assert_eq!(Balances::free_balance(from_account.clone()), 1000000000000); -// assert_eq!(Balances::reserved_balance(from_account.clone()), 0); -// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); -// } -// }); -// } - -// TODO - FIXME: Fix Mock Prefix -// #[test] -// fn dex_precompile_get_liquidity_should_work() { -// new_test_ext().execute_with(|| { -// // enable SERP/SETUSD -// assert_ok!(DexModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); - -// assert_ok!(DexModule::add_liquidity( -// Origin::signed(ALICE), -// SERP, -// SETUSD, -// 1_000, -// 1_000_000, -// 0 -// )); - -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// // action + currency_id_a + currency_id_b -// let mut input = [0u8; 3 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetLiquidityPool).to_be_bytes()); -// // SERP -// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); -// // SETUSD -// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); - -// let mut expected_output = [0u8; 64]; -// U256::from(1_000).to_big_endian(&mut expected_output[..32]); -// U256::from(1_000_000).to_big_endian(&mut expected_output[32..64]); - -// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); -// }); -// } - -// #[test] -// fn dex_precompile_get_liquidity_token_address_should_work() { -// new_test_ext().execute_with(|| { -// // enable SERP/SETUSD -// assert_ok!(DexModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); - -// assert_ok!(DexModule::add_liquidity( -// Origin::signed(ALICE), -// SERP, -// SETUSD, -// 1_000, -// 1_000_000, -// 0 -// )); - -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// // action + currency_id_a + currency_id_b -// let mut input = [0u8; 4 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetLiquidityTokenAddress).to_be_bytes()); -// // SERP -// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); -// // SETUSD -// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); - -// let mut expected_output = [0u8; 32]; -// let address = H160::from_str("0x0000000000000000000000010000000100000014").unwrap(); -// U256::from(address.as_bytes()).to_big_endian(&mut expected_output[..32]); - -// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); - -// // unkonwn token -// let mut id = [0u8; 32]; -// id[31] = u8::MAX; // not exists -// U256::from_big_endian(&id.to_vec()).to_big_endian(&mut input[2 * 32..3 * 32]); -// assert_noop!( -// DexPrecompile::execute(&input, None, &context), -// ExitError::Other("invalid currency id".into()) -// ); -// }); -// } - -// #[test] -// fn dex_precompile_get_swap_target_amount_should_work() { -// new_test_ext().execute_with(|| { -// // enable SERP/SETUSD -// assert_ok!(DexModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); - -// assert_ok!(DexModule::add_liquidity( -// Origin::signed(ALICE), -// SERP, -// SETUSD, -// 1_000, -// 1_000_000, -// 0 -// )); - -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// // action + path_len + currency_id_a + currency_id_b + -// // supply_amount -// let mut input = [0u8; 6 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetSwapTargetAmount).to_be_bytes()); -// // skip offset -// // supply_amount -// U256::from(1).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); -// // path_len -// U256::from(2).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); -// // SERP -// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); -// // SETUSD -// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); - -// let mut expected_output = [0u8; 32]; -// U256::from(989).to_big_endian(&mut expected_output[..32]); - -// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); -// }); -// } - -// #[test] -// fn dex_precompile_get_swap_supply_amount_should_work() { -// new_test_ext().execute_with(|| { -// // enable SERP/SETUSD -// assert_ok!(DexModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); - -// assert_ok!(DexModule::add_liquidity( -// Origin::signed(ALICE), -// SERP, -// SETUSD, -// 1_000, -// 1_000_000, -// 0 -// )); - -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// // action + path_len + currency_id_a + currency_id_b + -// // target_amount -// let mut input = [0u8; 6 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetSwapSupplyAmount).to_be_bytes()); -// // skip offset -// // target_amount -// U256::from(1).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); -// // path_len -// U256::from(2).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); -// // SERP -// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); -// // SETUSD -// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); - -// let mut expected_output = [0u8; 32]; -// U256::from(1).to_big_endian(&mut expected_output[..32]); - -// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); -// }); -// } - -// #[test] -// fn dex_precompile_swap_with_exact_supply_should_work() { -// new_test_ext().execute_with(|| { -// // enable SERP/SETUSD -// assert_ok!(DexModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); - -// assert_ok!(DexModule::add_liquidity( -// Origin::signed(ALICE), -// SERP, -// SETUSD, -// 1_000, -// 1_000_000, -// 0 -// )); - -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// // action + who + path_len + currency_id_a + currency_id_b + -// // supply_amount + min_target_amount -// let mut input = [0u8; 8 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(dex::Action::SwapWithExactSupply).to_be_bytes()); -// // who -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); -// // skip offset -// // supply_amount -// U256::from(1).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); -// // min_target_amount -// U256::from(0).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); -// // path_len -// U256::from(2).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); -// // SERP -// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); -// // SETUSD -// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 6 * 32..4 + 7 * 32]); - -// let mut expected_output = [0u8; 32]; -// U256::from(989).to_big_endian(&mut expected_output[..32]); - -// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); -// }); -// } - -// #[test] -// fn dex_precompile_swap_with_exact_target_should_work() { -// new_test_ext().execute_with(|| { -// // enable SERP/SETUSD -// assert_ok!(DexModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); - -// assert_ok!(DexModule::add_liquidity( -// Origin::signed(ALICE), -// SERP, -// SETUSD, -// 1_000, -// 1_000_000, -// 0 -// )); - -// let context = Context { -// address: Default::default(), -// caller: alice_evm_addr(), -// apparent_value: Default::default(), -// }; - -// // action + who + path_len + currency_id_a + currency_id_b + -// // target_amount + max_supply_amount -// let mut input = [0u8; 8 * 32]; -// // action -// input[0..4].copy_from_slice(&Into::::into(dex::Action::SwapWithExactTarget).to_be_bytes()); -// // who -// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); -// // skip offset -// // target_amount -// U256::from(1).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); -// // max_supply_amount -// U256::from(1).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); -// // path_len -// U256::from(2).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); -// // SERP -// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); -// // SETUSD -// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 6 * 32..4 + 7 * 32]); - -// let mut expected_output = [0u8; 32]; -// U256::from(1).to_big_endian(&mut expected_output[..32]); - -// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); -// assert_eq!(resp.exit_status, ExitSucceed::Returned); -// assert_eq!(resp.output, expected_output); -// assert_eq!(resp.cost, 0); -// }); -// } - -#[test] -fn task_id_max_and_min() { - let task_id = TaskInfo { - prefix: b"ScheduleCall".to_vec(), - id: u32::MAX, - sender: H160::default(), - fee: Balance::MAX, - } - .encode(); - - assert_eq!(54, task_id.len()); - - let task_id = TaskInfo { - prefix: b"ScheduleCall".to_vec(), - id: u32::MIN, - sender: H160::default(), - fee: Balance::MIN, - } - .encode(); - - assert_eq!(38, task_id.len()); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +#![allow(clippy::erasing_op)] +#![cfg(test)] +use super::*; +use crate::precompile::{ + mock::{ + setm_evm_address, alice, alice_evm_addr, setusd_evm_address, bob, bob_evm_addr, erc20_address_not_exists, + get_task_id, lp_setm_setusd_evm_address, new_test_ext, serp_evm_address, run_to_block, Balances, EdfisSwapModule, + DexPrecompile, Event as TestEvent, MultiCurrencyPrecompile, Oracle, OraclePrecompile, Origin, Price, + ScheduleCallPrecompile, System, Test, ALICE, SETUSD, INITIAL_BALANCE, SERP, + }, + schedule_call::TaskInfo, +}; +use codec::Encode; +use frame_support::{assert_noop, assert_ok}; +use hex_literal::hex; +use module_evm::{Context, ExitError, ExitSucceed, Precompile}; +use module_support::AddressMapping; +use orml_traits::DataFeeder; +use primitives::{Balance, PREDEPLOY_ADDRESS_START}; +use sp_core::{H160, U256}; +use sp_runtime::FixedPointNumber; +use std::str::FromStr; + +pub struct DummyPrecompile; +impl Precompile for DummyPrecompile { + fn execute( + _input: &[u8], + _target_gas: Option, + _context: &Context, + ) -> core::result::Result { + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + cost: 0, + output: vec![], + logs: Default::default(), + }) + } +} + +pub type WithSystemContractFilter = AllPrecompiles< + crate::SystemContractsFilter, + DummyPrecompile, + DummyPrecompile, + DummyPrecompile, + DummyPrecompile, + DummyPrecompile, + DummyPrecompile, +>; + +#[test] +fn precompile_filter_works_on_setheum_precompiles() { + let precompile = H160::from_low_u64_be(PRECOMPILE_ADDRESS_START); + + let mut non_system = [0u8; 20]; + non_system[0] = 1; + + let non_system_caller_context = Context { + address: precompile, + caller: non_system.into(), + apparent_value: 0.into(), + }; + assert_eq!( + WithSystemContractFilter::execute(precompile, &[0u8; 1], None, &non_system_caller_context), + Some(Err(ExitError::Other("no permission".into()))), + ); +} + +#[test] +fn precompile_filter_does_not_work_on_system_contracts() { + let system = H160::from_low_u64_be(PREDEPLOY_ADDRESS_START); + + let mut non_system = [0u8; 20]; + non_system[0] = 1; + + let non_system_caller_context = Context { + address: system, + caller: non_system.into(), + apparent_value: 0.into(), + }; + assert!( + WithSystemContractFilter::execute(non_system.into(), &[0u8; 1], None, &non_system_caller_context).is_none() + ); +} + +#[test] +fn precompile_filter_does_not_work_on_non_system_contracts() { + let mut non_system = [0u8; 20]; + non_system[0] = 1; + let mut another_non_system = [0u8; 20]; + another_non_system[0] = 2; + + let non_system_caller_context = Context { + address: non_system.into(), + caller: another_non_system.into(), + apparent_value: 0.into(), + }; + assert!( + WithSystemContractFilter::execute(non_system.into(), &[0u8; 1], None, &non_system_caller_context).is_none() + ); +} +// TODO - FIXME: Fix Mock Prefix +// #[test] +// fn multicurrency_precompile_should_work() { +// new_test_ext().execute_with(|| { +// let mut context = Context { +// address: Default::default(), +// caller: Default::default(), +// apparent_value: Default::default(), +// }; + +// // call with not exists erc20 +// context.caller = erc20_address_not_exists(); +// let mut input = [0u8; 68]; +// // action +// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QuerySymbol).to_be_bytes()); +// assert_noop!( +// MultiCurrencyPrecompile::execute(&input, None, &context), +// ExitError::Other("invalid currency id".into()) +// ); + +// // 1.QueryName +// let mut input = [0u8; 4]; +// // action +// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryName).to_be_bytes()); + +// // Token +// context.caller = setm_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 96]; +// // skip offset +// expected_output[31] = 32; +// // length +// expected_output[63] = 5; +// expected_output[64..64 + 5].copy_from_slice(&b"Setheum"[..]); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // DexShare +// context.caller = lp_setm_setusd_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 96]; +// // skip offset +// expected_output[31] = 32; +// // length +// expected_output[63] = 23; +// expected_output[64..64 + 23].copy_from_slice(&b"LP Setheum - SetDollar"[..]); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // 2.QuerySymbol +// let mut input = [0u8; 4]; +// // action +// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QuerySymbol).to_be_bytes()); + +// // Token +// context.caller = setm_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 96]; +// // skip offset +// expected_output[31] = 32; +// // length +// expected_output[63] = 3; +// expected_output[64..64 + 3].copy_from_slice(&b"SEE"[..]); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // DexShare +// context.caller = lp_setm_setusd_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 96]; +// // skip offset +// expected_output[31] = 32; +// // length +// expected_output[63] = 11; +// expected_output[64..64 + 11].copy_from_slice(&b"LP_SETM_SETUSD"[..]); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // 3.QueryDecimals +// let mut input = [0u8; 4]; +// // action +// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryDecimals).to_be_bytes()); + +// // Token +// context.caller = setm_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 32]; +// expected_output[31] = 12; +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // DexShare +// context.caller = lp_setm_setusd_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 32]; +// expected_output[31] = 12; +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // 4.QueryTotalIssuance +// let mut input = [0u8; 4]; +// // action +// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryTotalIssuance).to_be_bytes()); + +// // Token +// context.caller = setusd_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 32]; +// expected_output[28..32].copy_from_slice(&1_000_000_000u32.to_be_bytes()[..]); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // DexShare +// context.caller = lp_setm_setusd_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let expected_output = [0u8; 32]; +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // 5.QueryBalance +// let mut input = [0u8; 36]; +// // action +// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::QueryBalance).to_be_bytes()); +// // from +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); + +// // Token +// context.caller = setm_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let mut expected_output = [0u8; 32]; +// expected_output[16..32].copy_from_slice(&INITIAL_BALANCE.to_be_bytes()[..]); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // DexShare +// context.caller = lp_setm_setusd_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let expected_output = [0u8; 32]; +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // 6.Transfer +// let mut input = [0u8; 4 + 3 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(multicurrency::Action::Transfer).to_be_bytes()); +// // from +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); +// // to +// U256::from(bob_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); +// // amount +// U256::from(1).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); +// let from_balance = Balances::free_balance(alice()); +// let to_balance = Balances::free_balance(bob()); + +// // Token +// context.caller = setm_evm_address(); +// let resp = MultiCurrencyPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// let expected_output: Vec = vec![]; +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); +// assert_eq!(Balances::free_balance(alice()), from_balance - 1); +// assert_eq!(Balances::free_balance(bob()), to_balance + 1); + +// // DexShare +// context.caller = lp_setm_setusd_evm_address(); +// assert_noop!( +// MultiCurrencyPrecompile::execute(&input, None, &context), +// ExitError::Other("BalanceTooLow".into()) +// ); +// }); +// } + +// #[test] +// fn oracle_precompile_should_work() { +// new_test_ext().execute_with(|| { +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// let price = Price::from(30_000); + +// // action + currency_id +// let mut input = [0u8; 36]; +// // action +// input[0..4].copy_from_slice(&Into::::into(oracle::Action::GetPrice).to_be_bytes()); +// // SERP +// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4..4 + 32]); + +// // no price yet +// let resp = OraclePrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, [0u8; 32]); +// assert_eq!(resp.cost, 0); + +// assert_ok!(Oracle::feed_value(ALICE, SERP, price)); +// assert_eq!( +// Oracle::get_no_op(&SERP), +// Some(orml_oracle::TimestampedValue { +// value: price, +// timestamp: 1 +// }) +// ); + +// // returned price + timestamp +// let mut expected_output = [0u8; 32]; +// U256::from(price.into_inner()).to_big_endian(&mut expected_output[..]); + +// let resp = OraclePrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); +// }); +// } + +// #[test] +// fn oracle_precompile_should_handle_invalid_input() { +// new_test_ext().execute_with(|| { +// assert_noop!( +// OraclePrecompile::execute( +// &[0u8; 0], +// None, +// &Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default() +// } +// ), +// ExitError::Other("invalid input".into()) +// ); + +// assert_noop!( +// OraclePrecompile::execute( +// &[0u8; 3], +// None, +// &Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default() +// } +// ), +// ExitError::Other("invalid input".into()) +// ); + +// assert_noop!( +// OraclePrecompile::execute( +// &[1u8; 32], +// None, +// &Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default() +// } +// ), +// ExitError::Other("invalid action".into()) +// ); +// }); +// } + +// #[test] +// fn schedule_call_precompile_should_work() { +// new_test_ext().execute_with(|| { +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// let mut input = [0u8; 11 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Schedule).to_be_bytes()); +// // from +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); +// // target +// U256::from(setm_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); +// // value +// U256::from(0).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); +// // gas_limit +// U256::from(300000).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); +// // storage_limit +// U256::from(100).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); +// // min_delay +// U256::from(1).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); +// // skip offset +// // input_len +// U256::from(4 + 32 + 32).to_big_endian(&mut input[4 + 7 * 32..4 + 8 * 32]); + +// // input_data +// let mut transfer_to_bob = [0u8; 68]; +// // transfer bytes4(keccak256(signature)) 0xa9059cbb +// transfer_to_bob[0..4].copy_from_slice(&hex!("a9059cbb")); +// // to address +// U256::from(bob_evm_addr().as_bytes()).to_big_endian(&mut transfer_to_bob[4..36]); +// // amount +// U256::from(1000).to_big_endian(&mut transfer_to_bob[36..68]); + +// U256::from(&transfer_to_bob[0..32]).to_big_endian(&mut input[4 + 8 * 32..4 + 9 * 32]); +// U256::from(&transfer_to_bob[32..64]).to_big_endian(&mut input[4 + 9 * 32..4 + 10 * 32]); +// input[4 + 10 * 32..4 + 10 * 32 + 4].copy_from_slice(&transfer_to_bob[64..68]); + +// let resp = ScheduleCallPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.cost, 0); +// let event = TestEvent::Scheduler(pallet_scheduler::Event::::Scheduled(3, 0)); +// assert!(System::events().iter().any(|record| record.event == event)); + +// // cancel schedule +// let task_id = get_task_id(resp.output); +// let mut cancel_input = [0u8; 5 * 32]; +// // action +// cancel_input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Cancel).to_be_bytes()); +// // from +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut cancel_input[4 + 0 * 32..4 + 1 * 32]); +// // skip offset +// // task_id_len +// U256::from(task_id.len()).to_big_endian(&mut cancel_input[4 + 2 * 32..4 + 3 * 32]); +// // task_id +// cancel_input[4 + 3 * 32..4 + 3 * 32 + task_id.len()].copy_from_slice(&task_id[..]); + +// let resp = ScheduleCallPrecompile::execute(&cancel_input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.cost, 0); +// let event = TestEvent::Scheduler(pallet_scheduler::Event::::Canceled(3, 0)); +// assert!(System::events().iter().any(|record| record.event == event)); + +// let resp = ScheduleCallPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.cost, 0); + +// run_to_block(2); + +// // reschedule call +// let task_id = get_task_id(resp.output); +// let mut reschedule_input = [0u8; 6 * 32]; +// // action +// reschedule_input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Reschedule).to_be_bytes()); +// // from +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut reschedule_input[4 + 0 * 32..4 + 1 * 32]); +// // min_delay +// U256::from(2).to_big_endian(&mut reschedule_input[4 + 1 * 32..4 + 2 * 32]); +// // skip offset +// // task_id_len +// U256::from(task_id.len()).to_big_endian(&mut reschedule_input[4 + 3 * 32..4 + 4 * 32]); +// // task_id +// reschedule_input[4 + 4 * 32..4 + 4 * 32 + task_id.len()].copy_from_slice(&task_id[..]); + +// let resp = ScheduleCallPrecompile::execute(&reschedule_input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.cost, 0); +// let event = TestEvent::Scheduler(pallet_scheduler::Event::::Scheduled(5, 0)); +// assert!(System::events().iter().any(|record| record.event == event)); + +// let from_account = ::AddressMapping::get_account_id(&alice_evm_addr()); +// let to_account = ::AddressMapping::get_account_id(&bob_evm_addr()); +// #[cfg(not(feature = "with-ethereum-compatibility"))] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 999999700000); +// assert_eq!(Balances::reserved_balance(from_account.clone()), 300000); +// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); +// } +// #[cfg(feature = "with-ethereum-compatibility")] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 1000000000000); +// assert_eq!(Balances::reserved_balance(from_account.clone()), 0); +// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); +// } + +// run_to_block(5); +// #[cfg(not(feature = "with-ethereum-compatibility"))] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 999999972553); +// assert_eq!(Balances::reserved_balance(from_account), 0); +// assert_eq!(Balances::free_balance(to_account), 1000000001000); +// } +// #[cfg(feature = "with-ethereum-compatibility")] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 999999999000); +// assert_eq!(Balances::reserved_balance(from_account), 0); +// assert_eq!(Balances::free_balance(to_account), 1000000001000); +// } +// }); +// } + +// #[test] +// fn schedule_call_precompile_should_handle_invalid_input() { +// new_test_ext().execute_with(|| { +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// let mut input = [0u8; 10 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Schedule).to_be_bytes()); +// // from +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); +// // target +// U256::from(setm_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); +// // value +// U256::from(0).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); +// // gas_limit +// U256::from(300000).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); +// // storage_limit +// U256::from(100).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); +// // min_delay +// U256::from(1).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); +// // skip offset +// // input_len +// U256::from(1).to_big_endian(&mut input[4 + 7 * 32..4 + 8 * 32]); + +// // input_data = 0x12 +// input[4 + 9 * 32] = hex!("12")[0]; + +// let resp = ScheduleCallPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.cost, 0); + +// let from_account = ::AddressMapping::get_account_id(&alice_evm_addr()); +// let to_account = ::AddressMapping::get_account_id(&bob_evm_addr()); +// #[cfg(not(feature = "with-ethereum-compatibility"))] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 999999700000); +// assert_eq!(Balances::reserved_balance(from_account.clone()), 300000); +// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); +// } +// #[cfg(feature = "with-ethereum-compatibility")] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 1000000000000); +// assert_eq!(Balances::reserved_balance(from_account.clone()), 0); +// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); +// } + +// // cancel schedule +// let task_id = get_task_id(resp.output); +// let mut cancel_input = [0u8; 6 * 32]; +// // action +// cancel_input[0..4].copy_from_slice(&Into::::into(schedule_call::Action::Cancel).to_be_bytes()); +// // from +// U256::from(bob_evm_addr().as_bytes()).to_big_endian(&mut cancel_input[4 + 0 * 32..4 + 1 * 32]); +// // skip offset +// // task_id_len +// U256::from(task_id.len()).to_big_endian(&mut cancel_input[4 + 2 * 32..4 + 3 * 32]); +// // task_id +// cancel_input[4 + 3 * 32..4 + 3 * 32 + task_id.len()].copy_from_slice(&task_id[..]); + +// assert_eq!( +// ScheduleCallPrecompile::execute(&cancel_input, None, &context), +// Err(ExitError::Other("NoPermission".into())) +// ); + +// run_to_block(4); +// #[cfg(not(feature = "with-ethereum-compatibility"))] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 999999978926); +// assert_eq!(Balances::reserved_balance(from_account), 0); +// assert_eq!(Balances::free_balance(to_account), 1000000000000); +// } +// #[cfg(feature = "with-ethereum-compatibility")] +// { +// assert_eq!(Balances::free_balance(from_account.clone()), 1000000000000); +// assert_eq!(Balances::reserved_balance(from_account.clone()), 0); +// assert_eq!(Balances::free_balance(to_account.clone()), 1000000000000); +// } +// }); +// } + +// TODO - FIXME: Fix Mock Prefix +// #[test] +// fn dex_precompile_get_liquidity_should_work() { +// new_test_ext().execute_with(|| { +// // enable SERP/SETUSD +// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); + +// assert_ok!(EdfisSwapModule::add_liquidity( +// Origin::signed(ALICE), +// SERP, +// SETUSD, +// 1_000, +// 1_000_000, +// 0 +// )); + +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// // action + currency_id_a + currency_id_b +// let mut input = [0u8; 3 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetLiquidityPool).to_be_bytes()); +// // SERP +// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); +// // SETUSD +// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); + +// let mut expected_output = [0u8; 64]; +// U256::from(1_000).to_big_endian(&mut expected_output[..32]); +// U256::from(1_000_000).to_big_endian(&mut expected_output[32..64]); + +// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); +// }); +// } + +// #[test] +// fn dex_precompile_get_liquidity_token_address_should_work() { +// new_test_ext().execute_with(|| { +// // enable SERP/SETUSD +// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); + +// assert_ok!(EdfisSwapModule::add_liquidity( +// Origin::signed(ALICE), +// SERP, +// SETUSD, +// 1_000, +// 1_000_000, +// 0 +// )); + +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// // action + currency_id_a + currency_id_b +// let mut input = [0u8; 4 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetLiquidityTokenAddress).to_be_bytes()); +// // SERP +// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); +// // SETUSD +// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); + +// let mut expected_output = [0u8; 32]; +// let address = H160::from_str("0x0000000000000000000000010000000100000014").unwrap(); +// U256::from(address.as_bytes()).to_big_endian(&mut expected_output[..32]); + +// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); + +// // unkonwn token +// let mut id = [0u8; 32]; +// id[31] = u8::MAX; // not exists +// U256::from_big_endian(&id.to_vec()).to_big_endian(&mut input[2 * 32..3 * 32]); +// assert_noop!( +// DexPrecompile::execute(&input, None, &context), +// ExitError::Other("invalid currency id".into()) +// ); +// }); +// } + +// #[test] +// fn dex_precompile_get_swap_target_amount_should_work() { +// new_test_ext().execute_with(|| { +// // enable SERP/SETUSD +// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); + +// assert_ok!(EdfisSwapModule::add_liquidity( +// Origin::signed(ALICE), +// SERP, +// SETUSD, +// 1_000, +// 1_000_000, +// 0 +// )); + +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// // action + path_len + currency_id_a + currency_id_b + +// // supply_amount +// let mut input = [0u8; 6 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetSwapTargetAmount).to_be_bytes()); +// // skip offset +// // supply_amount +// U256::from(1).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); +// // path_len +// U256::from(2).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); +// // SERP +// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); +// // SETUSD +// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); + +// let mut expected_output = [0u8; 32]; +// U256::from(989).to_big_endian(&mut expected_output[..32]); + +// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); +// }); +// } + +// #[test] +// fn dex_precompile_get_swap_supply_amount_should_work() { +// new_test_ext().execute_with(|| { +// // enable SERP/SETUSD +// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); + +// assert_ok!(EdfisSwapModule::add_liquidity( +// Origin::signed(ALICE), +// SERP, +// SETUSD, +// 1_000, +// 1_000_000, +// 0 +// )); + +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// // action + path_len + currency_id_a + currency_id_b + +// // target_amount +// let mut input = [0u8; 6 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(dex::Action::GetSwapSupplyAmount).to_be_bytes()); +// // skip offset +// // target_amount +// U256::from(1).to_big_endian(&mut input[4 + 1 * 32..4 + 2 * 32]); +// // path_len +// U256::from(2).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); +// // SERP +// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); +// // SETUSD +// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); + +// let mut expected_output = [0u8; 32]; +// U256::from(1).to_big_endian(&mut expected_output[..32]); + +// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); +// }); +// } + +// #[test] +// fn dex_precompile_swap_with_exact_supply_should_work() { +// new_test_ext().execute_with(|| { +// // enable SERP/SETUSD +// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); + +// assert_ok!(EdfisSwapModule::add_liquidity( +// Origin::signed(ALICE), +// SERP, +// SETUSD, +// 1_000, +// 1_000_000, +// 0 +// )); + +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// // action + who + path_len + currency_id_a + currency_id_b + +// // supply_amount + min_target_amount +// let mut input = [0u8; 8 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(dex::Action::SwapWithExactSupply).to_be_bytes()); +// // who +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); +// // skip offset +// // supply_amount +// U256::from(1).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); +// // min_target_amount +// U256::from(0).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); +// // path_len +// U256::from(2).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); +// // SERP +// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); +// // SETUSD +// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 6 * 32..4 + 7 * 32]); + +// let mut expected_output = [0u8; 32]; +// U256::from(989).to_big_endian(&mut expected_output[..32]); + +// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); +// }); +// } + +// #[test] +// fn dex_precompile_swap_with_exact_target_should_work() { +// new_test_ext().execute_with(|| { +// // enable SERP/SETUSD +// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); + +// assert_ok!(EdfisSwapModule::add_liquidity( +// Origin::signed(ALICE), +// SERP, +// SETUSD, +// 1_000, +// 1_000_000, +// 0 +// )); + +// let context = Context { +// address: Default::default(), +// caller: alice_evm_addr(), +// apparent_value: Default::default(), +// }; + +// // action + who + path_len + currency_id_a + currency_id_b + +// // target_amount + max_supply_amount +// let mut input = [0u8; 8 * 32]; +// // action +// input[0..4].copy_from_slice(&Into::::into(dex::Action::SwapWithExactTarget).to_be_bytes()); +// // who +// U256::from(alice_evm_addr().as_bytes()).to_big_endian(&mut input[4 + 0 * 32..4 + 1 * 32]); +// // skip offset +// // target_amount +// U256::from(1).to_big_endian(&mut input[4 + 2 * 32..4 + 3 * 32]); +// // max_supply_amount +// U256::from(1).to_big_endian(&mut input[4 + 3 * 32..4 + 4 * 32]); +// // path_len +// U256::from(2).to_big_endian(&mut input[4 + 4 * 32..4 + 5 * 32]); +// // SERP +// U256::from_big_endian(serp_evm_address().as_bytes()).to_big_endian(&mut input[4 + 5 * 32..4 + 6 * 32]); +// // SETUSD +// U256::from_big_endian(setusd_evm_address().as_bytes()).to_big_endian(&mut input[4 + 6 * 32..4 + 7 * 32]); + +// let mut expected_output = [0u8; 32]; +// U256::from(1).to_big_endian(&mut expected_output[..32]); + +// let resp = DexPrecompile::execute(&input, None, &context).unwrap(); +// assert_eq!(resp.exit_status, ExitSucceed::Returned); +// assert_eq!(resp.output, expected_output); +// assert_eq!(resp.cost, 0); +// }); +// } + +#[test] +fn task_id_max_and_min() { + let task_id = TaskInfo { + prefix: b"ScheduleCall".to_vec(), + id: u32::MAX, + sender: H160::default(), + fee: Balance::MAX, + } + .encode(); + + assert_eq!(54, task_id.len()); + + let task_id = TaskInfo { + prefix: b"ScheduleCall".to_vec(), + id: u32::MIN, + sender: H160::default(), + fee: Balance::MIN, + } + .encode(); + + assert_eq!(38, task_id.len()); +} diff --git a/blockchain/runtime/src/benchmarking/dex.rs b/blockchain/runtime/src/benchmarking/dex.rs index 6da10b2c..9b59d0b1 100644 --- a/blockchain/runtime/src/benchmarking/dex.rs +++ b/blockchain/runtime/src/benchmarking/dex.rs @@ -1,335 +1,335 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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::{ - dollar, AccountId, Balance, Currencies, CurrencyId, Dex, Event, GetNativeCurrencyId, GetSetUSDId, Runtime, - System, TradingPathLimit, -}; - -use frame_benchmarking::{account, whitelisted_caller}; -use frame_system::RawOrigin; -use module_dex::TradingPairStatus; -use orml_benchmarking::runtime_benchmarks; -use orml_traits::{MultiCurrency, MultiCurrencyExtended}; -use primitives::TradingPair; -use sp_runtime::traits::UniqueSaturatedInto; -use sp_std::prelude::*; - -const SEED: u32 = 0; - -const NATIVE: CurrencyId = GetNativeCurrencyId::get(); -const STABLECOIN: CurrencyId = GetSetUSDId::get(); -const CURRENCY_LIST: [CurrencyId; 2] = [NATIVE, STABLECOIN]; - -fn assert_last_event(generic_event: Event) { - System::assert_last_event(generic_event.into()); -} - -fn inject_liquidity( - maker: AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - max_amount_a: Balance, - max_amount_b: Balance, -) -> Result<(), &'static str> { - // set balance - >::update_balance( - currency_id_a, - &maker, - max_amount_a.unique_saturated_into(), - )?; - >::update_balance( - currency_id_b, - &maker, - max_amount_b.unique_saturated_into(), - )?; - - let _ = Dex::enable_trading_pair(RawOrigin::Root.into(), currency_id_a, currency_id_b); - - Dex::add_liquidity( - RawOrigin::Signed(maker.clone()).into(), - currency_id_a, - currency_id_b, - max_amount_a, - max_amount_b, - Default::default(), - )?; - - Ok(()) -} - -runtime_benchmarks! { - { Runtime, module_dex } - - // enable a Disabled trading pair - enable_trading_pair { - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { - Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; - } - }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) - verify { - assert_last_event(module_dex::Event::EnableTradingPair{trading_pair: trading_pair}.into()); - } - - // disable a Enabled trading pair - disable_trading_pair { - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - if let TradingPairStatus::Disabled = Dex::trading_pair_statuses(trading_pair) { - Dex::enable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; - } - }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) - verify { - assert_last_event(module_dex::Event::DisableTradingPair{trading_pair}.into()); - } - - // list a Provisioning trading pair - list_provisioning { - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { - Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; - } - }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second()), dollar(trading_pair.first()), dollar(trading_pair.second()), 10) - verify { - assert_last_event(module_dex::Event::ListProvisioning{trading_pair: trading_pair}.into()); - } - - // update parameters of a Provisioning trading pair - update_provisioning_parameters { - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { - Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; - } - Dex::list_provisioning( - RawOrigin::Root.into(), - trading_pair.first(), - trading_pair.second(), - dollar(trading_pair.first()), - dollar(trading_pair.second()), - 100 * dollar(trading_pair.first()), - 1000 * dollar(trading_pair.second()), - 100 - )?; - }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second(), 2 * dollar(trading_pair.first()), 2 * dollar(trading_pair.second()), 10 * dollar(trading_pair.first()), 100 * dollar(trading_pair.second()), 200) - - // end a Provisioning trading pair - end_provisioning { - let founder: AccountId = whitelisted_caller(); - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { - Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; - } - Dex::list_provisioning( - RawOrigin::Root.into(), - trading_pair.first(), - trading_pair.second(), - dollar(trading_pair.first()), - dollar(trading_pair.second()), - 100 * dollar(trading_pair.first()), - 100 * dollar(trading_pair.second()), - 0 - )?; - - // set balance - >::update_balance(trading_pair.first(), &founder, (100 * dollar(trading_pair.first())).unique_saturated_into())?; - >::update_balance(trading_pair.second(), &founder, (100 * dollar(trading_pair.second())).unique_saturated_into())?; - - // add enough provision - Dex::add_provision( - RawOrigin::Signed(founder.clone()).into(), - trading_pair.first(), - trading_pair.second(), - 100 * dollar(trading_pair.first()), - 100 * dollar(trading_pair.second()), - )?; - }: _(RawOrigin::Signed(founder), trading_pair.first(), trading_pair.second()) - verify { - assert_last_event(module_dex::Event::ProvisioningToEnabled{trading_pair, pool_0: 100 * dollar(trading_pair.first()), pool_1: 100 * dollar(trading_pair.second()), share_amount: 200 * dollar(trading_pair.first())}.into()) - } - - add_provision { - let founder: AccountId = whitelisted_caller(); - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { - Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; - } - Dex::list_provisioning( - RawOrigin::Root.into(), - trading_pair.first(), - trading_pair.second(), - dollar(trading_pair.first()), - dollar(trading_pair.second()), - 100 * dollar(trading_pair.first()), - 1000 * dollar(trading_pair.second()), - 0 - )?; - - // set balance - >::update_balance(trading_pair.first(), &founder, (10 * dollar(trading_pair.first())).unique_saturated_into())?; - >::update_balance(trading_pair.second(), &founder, (10 * dollar(trading_pair.second())).unique_saturated_into())?; - }: _(RawOrigin::Signed(founder.clone()), trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second())) - verify{ - assert_last_event(module_dex::Event::AddProvision{who: founder, currency_0: trading_pair.first(), contribution_0: dollar(trading_pair.first()), currency_1: trading_pair.second(), contribution_1: dollar(trading_pair.second())}.into()); - } - - claim_dex_share { - let founder: AccountId = whitelisted_caller(); - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { - Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; - } - Dex::list_provisioning( - RawOrigin::Root.into(), - trading_pair.first(), - trading_pair.second(), - dollar(trading_pair.first()), - dollar(trading_pair.second()), - 10 * dollar(trading_pair.first()), - 10 * dollar(trading_pair.second()), - 0 - )?; - - // set balance - >::update_balance(trading_pair.first(), &founder, (100 * dollar(trading_pair.first())).unique_saturated_into())?; - >::update_balance(trading_pair.second(), &founder, (100 * dollar(trading_pair.second())).unique_saturated_into())?; - - Dex::add_provision( - RawOrigin::Signed(founder.clone()).into(), - trading_pair.first(), - trading_pair.second(), - dollar(trading_pair.first()), - 20 * dollar(trading_pair.second()) - )?; - Dex::end_provisioning( - RawOrigin::Signed(founder.clone()).into(), - trading_pair.first(), - trading_pair.second(), - )?; - }: _(RawOrigin::Signed(whitelisted_caller()), founder.clone(), trading_pair.first(), trading_pair.second()) - verify { - assert_eq!(Currencies::free_balance(trading_pair.dex_share_currency_id(), &founder), 2_000_000_000_000_000_000); - } - - // add liquidity - add_liquidity { - let first_maker: AccountId = account("first_maker", 0, SEED); - let second_maker: AccountId = whitelisted_caller(); - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - let amount_a = 100 * dollar(trading_pair.first()); - let amount_b = 10_000 * dollar(trading_pair.second()); - - // set balance - >::update_balance(trading_pair.first(), &second_maker, amount_a.unique_saturated_into())?; - >::update_balance(trading_pair.second(), &second_maker, amount_b.unique_saturated_into())?; - - // first maker inject liquidity - inject_liquidity(first_maker.clone(), trading_pair.first(), trading_pair.second(), amount_a, amount_b)?; - }: add_liquidity(RawOrigin::Signed(second_maker), trading_pair.first(), trading_pair.second(), amount_a, amount_b, Default::default()) - - // worst: add liquidity - add_liquidity_and_stake { - let first_maker: AccountId = account("first_maker", 0, SEED); - let second_maker: AccountId = whitelisted_caller(); - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - let amount_a = 100 * dollar(trading_pair.first()); - let amount_b = 10_000 * dollar(trading_pair.second()); - - // set balance - >::update_balance(trading_pair.first(), &second_maker, amount_a.unique_saturated_into())?; - >::update_balance(trading_pair.second(), &second_maker, amount_b.unique_saturated_into())?; - - // first maker inject liquidity - inject_liquidity(first_maker.clone(), trading_pair.first(), trading_pair.second(), amount_a, amount_b)?; - }: add_liquidity(RawOrigin::Signed(second_maker), trading_pair.first(), trading_pair.second(), amount_a, amount_b, Default::default()) - - // remove liquidity by liquid lp share - remove_liquidity { - let maker: AccountId = whitelisted_caller(); - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - inject_liquidity(maker.clone(), trading_pair.first(), trading_pair.second(), 100 * dollar(trading_pair.first()), 10_000 * dollar(trading_pair.second()))?; - }: remove_liquidity(RawOrigin::Signed(maker), trading_pair.first(), trading_pair.second(), 50 * dollar(trading_pair.first()), Default::default(), Default::default()) - - swap_with_exact_supply { - let u in 2 .. TradingPathLimit::get() as u32; - - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - let mut path: Vec = vec![]; - for i in 1 .. u { - if i == 1 { - path.push(trading_pair.first()); - path.push(trading_pair.second()); - } else { - if i % 2 == 0 { - path.push(trading_pair.first()); - } else { - path.push(trading_pair.second()); - } - } - } - - let maker: AccountId = account("maker", 0, SEED); - let taker: AccountId = whitelisted_caller(); - inject_liquidity(maker, trading_pair.first(), trading_pair.second(), 10_000 * dollar(trading_pair.first()), 10_000 * dollar(trading_pair.second()))?; - - >::update_balance(path[0], &taker, (10_000 * dollar(path[0])).unique_saturated_into())?; - }: swap_with_exact_supply(RawOrigin::Signed(taker), path.clone(), 100 * dollar(path[0]), 0) - verify { - // would panic the benchmark anyways, must add new currencies to CURRENCY_LIST for benchmarking to work - assert!(TradingPathLimit::get() < CURRENCY_LIST.len() as u32); - } - - swap_with_exact_target { - let u in 2 .. TradingPathLimit::get() as u32; - - let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); - let mut path: Vec = vec![]; - for i in 1 .. u { - if i == 1 { - path.push(trading_pair.first()); - path.push(trading_pair.second()); - } else { - if i % 2 == 0 { - path.push(trading_pair.first()); - } else { - path.push(trading_pair.second()); - } - } - } - - let maker: AccountId = account("maker", 0, SEED); - let taker: AccountId = whitelisted_caller(); - inject_liquidity(maker, trading_pair.first(), trading_pair.second(), 10_000 * dollar(trading_pair.first()), 10_000 * dollar(trading_pair.second()))?; - - >::update_balance(path[0], &taker, (10_000 * dollar(path[0])).unique_saturated_into())?; - }: swap_with_exact_target(RawOrigin::Signed(taker), path.clone(), 10 * dollar(path[path.len() - 1]), 100 * dollar(path[0])) - verify { - // would panic the benchmark anyways, must add new currencies to CURRENCY_LIST for benchmarking to work - assert!(TradingPathLimit::get() < CURRENCY_LIST.len() as u32); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::benchmarking::utils::tests::new_test_ext; - use orml_benchmarking::impl_benchmark_test_suite; - - impl_benchmark_test_suite!(new_test_ext(),); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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::{ + dollar, AccountId, Balance, Currencies, CurrencyId, Dex, Event, GetNativeCurrencyId, GetSetUSDId, Runtime, + System, TradingPathLimit, +}; + +use frame_benchmarking::{account, whitelisted_caller}; +use frame_system::RawOrigin; +use edfis_swap_module::TradingPairStatus; +use orml_benchmarking::runtime_benchmarks; +use orml_traits::{MultiCurrency, MultiCurrencyExtended}; +use primitives::TradingPair; +use sp_runtime::traits::UniqueSaturatedInto; +use sp_std::prelude::*; + +const SEED: u32 = 0; + +const NATIVE: CurrencyId = GetNativeCurrencyId::get(); +const STABLECOIN: CurrencyId = GetSetUSDId::get(); +const CURRENCY_LIST: [CurrencyId; 2] = [NATIVE, STABLECOIN]; + +fn assert_last_event(generic_event: Event) { + System::assert_last_event(generic_event.into()); +} + +fn inject_liquidity( + maker: AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, +) -> Result<(), &'static str> { + // set balance + >::update_balance( + currency_id_a, + &maker, + max_amount_a.unique_saturated_into(), + )?; + >::update_balance( + currency_id_b, + &maker, + max_amount_b.unique_saturated_into(), + )?; + + let _ = Dex::enable_trading_pair(RawOrigin::Root.into(), currency_id_a, currency_id_b); + + Dex::add_liquidity( + RawOrigin::Signed(maker.clone()).into(), + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + Default::default(), + )?; + + Ok(()) +} + +runtime_benchmarks! { + { Runtime, edfis_swap_module } + + // enable a Disabled trading pair + enable_trading_pair { + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { + Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; + } + }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) + verify { + assert_last_event(edfis_swap_module::Event::EnableTradingPair{trading_pair: trading_pair}.into()); + } + + // disable a Enabled trading pair + disable_trading_pair { + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + if let TradingPairStatus::Disabled = Dex::trading_pair_statuses(trading_pair) { + Dex::enable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; + } + }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) + verify { + assert_last_event(edfis_swap_module::Event::DisableTradingPair{trading_pair}.into()); + } + + // list a Provisioning trading pair + list_provisioning { + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { + Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; + } + }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second()), dollar(trading_pair.first()), dollar(trading_pair.second()), 10) + verify { + assert_last_event(edfis_swap_module::Event::ListProvisioning{trading_pair: trading_pair}.into()); + } + + // update parameters of a Provisioning trading pair + update_provisioning_parameters { + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { + Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; + } + Dex::list_provisioning( + RawOrigin::Root.into(), + trading_pair.first(), + trading_pair.second(), + dollar(trading_pair.first()), + dollar(trading_pair.second()), + 100 * dollar(trading_pair.first()), + 1000 * dollar(trading_pair.second()), + 100 + )?; + }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second(), 2 * dollar(trading_pair.first()), 2 * dollar(trading_pair.second()), 10 * dollar(trading_pair.first()), 100 * dollar(trading_pair.second()), 200) + + // end a Provisioning trading pair + end_provisioning { + let founder: AccountId = whitelisted_caller(); + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { + Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; + } + Dex::list_provisioning( + RawOrigin::Root.into(), + trading_pair.first(), + trading_pair.second(), + dollar(trading_pair.first()), + dollar(trading_pair.second()), + 100 * dollar(trading_pair.first()), + 100 * dollar(trading_pair.second()), + 0 + )?; + + // set balance + >::update_balance(trading_pair.first(), &founder, (100 * dollar(trading_pair.first())).unique_saturated_into())?; + >::update_balance(trading_pair.second(), &founder, (100 * dollar(trading_pair.second())).unique_saturated_into())?; + + // add enough provision + Dex::add_provision( + RawOrigin::Signed(founder.clone()).into(), + trading_pair.first(), + trading_pair.second(), + 100 * dollar(trading_pair.first()), + 100 * dollar(trading_pair.second()), + )?; + }: _(RawOrigin::Signed(founder), trading_pair.first(), trading_pair.second()) + verify { + assert_last_event(edfis_swap_module::Event::ProvisioningToEnabled{trading_pair, pool_0: 100 * dollar(trading_pair.first()), pool_1: 100 * dollar(trading_pair.second()), share_amount: 200 * dollar(trading_pair.first())}.into()) + } + + add_provision { + let founder: AccountId = whitelisted_caller(); + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { + Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; + } + Dex::list_provisioning( + RawOrigin::Root.into(), + trading_pair.first(), + trading_pair.second(), + dollar(trading_pair.first()), + dollar(trading_pair.second()), + 100 * dollar(trading_pair.first()), + 1000 * dollar(trading_pair.second()), + 0 + )?; + + // set balance + >::update_balance(trading_pair.first(), &founder, (10 * dollar(trading_pair.first())).unique_saturated_into())?; + >::update_balance(trading_pair.second(), &founder, (10 * dollar(trading_pair.second())).unique_saturated_into())?; + }: _(RawOrigin::Signed(founder.clone()), trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second())) + verify{ + assert_last_event(edfis_swap_module::Event::AddProvision{who: founder, currency_0: trading_pair.first(), contribution_0: dollar(trading_pair.first()), currency_1: trading_pair.second(), contribution_1: dollar(trading_pair.second())}.into()); + } + + claim_dex_share { + let founder: AccountId = whitelisted_caller(); + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + if let TradingPairStatus::Enabled = Dex::trading_pair_statuses(trading_pair) { + Dex::disable_trading_pair(RawOrigin::Root.into(), trading_pair.first(), trading_pair.second())?; + } + Dex::list_provisioning( + RawOrigin::Root.into(), + trading_pair.first(), + trading_pair.second(), + dollar(trading_pair.first()), + dollar(trading_pair.second()), + 10 * dollar(trading_pair.first()), + 10 * dollar(trading_pair.second()), + 0 + )?; + + // set balance + >::update_balance(trading_pair.first(), &founder, (100 * dollar(trading_pair.first())).unique_saturated_into())?; + >::update_balance(trading_pair.second(), &founder, (100 * dollar(trading_pair.second())).unique_saturated_into())?; + + Dex::add_provision( + RawOrigin::Signed(founder.clone()).into(), + trading_pair.first(), + trading_pair.second(), + dollar(trading_pair.first()), + 20 * dollar(trading_pair.second()) + )?; + Dex::end_provisioning( + RawOrigin::Signed(founder.clone()).into(), + trading_pair.first(), + trading_pair.second(), + )?; + }: _(RawOrigin::Signed(whitelisted_caller()), founder.clone(), trading_pair.first(), trading_pair.second()) + verify { + assert_eq!(Currencies::free_balance(trading_pair.dex_share_currency_id(), &founder), 2_000_000_000_000_000_000); + } + + // add liquidity + add_liquidity { + let first_maker: AccountId = account("first_maker", 0, SEED); + let second_maker: AccountId = whitelisted_caller(); + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + let amount_a = 100 * dollar(trading_pair.first()); + let amount_b = 10_000 * dollar(trading_pair.second()); + + // set balance + >::update_balance(trading_pair.first(), &second_maker, amount_a.unique_saturated_into())?; + >::update_balance(trading_pair.second(), &second_maker, amount_b.unique_saturated_into())?; + + // first maker inject liquidity + inject_liquidity(first_maker.clone(), trading_pair.first(), trading_pair.second(), amount_a, amount_b)?; + }: add_liquidity(RawOrigin::Signed(second_maker), trading_pair.first(), trading_pair.second(), amount_a, amount_b, Default::default()) + + // worst: add liquidity + add_liquidity_and_stake { + let first_maker: AccountId = account("first_maker", 0, SEED); + let second_maker: AccountId = whitelisted_caller(); + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + let amount_a = 100 * dollar(trading_pair.first()); + let amount_b = 10_000 * dollar(trading_pair.second()); + + // set balance + >::update_balance(trading_pair.first(), &second_maker, amount_a.unique_saturated_into())?; + >::update_balance(trading_pair.second(), &second_maker, amount_b.unique_saturated_into())?; + + // first maker inject liquidity + inject_liquidity(first_maker.clone(), trading_pair.first(), trading_pair.second(), amount_a, amount_b)?; + }: add_liquidity(RawOrigin::Signed(second_maker), trading_pair.first(), trading_pair.second(), amount_a, amount_b, Default::default()) + + // remove liquidity by liquid lp share + remove_liquidity { + let maker: AccountId = whitelisted_caller(); + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + inject_liquidity(maker.clone(), trading_pair.first(), trading_pair.second(), 100 * dollar(trading_pair.first()), 10_000 * dollar(trading_pair.second()))?; + }: remove_liquidity(RawOrigin::Signed(maker), trading_pair.first(), trading_pair.second(), 50 * dollar(trading_pair.first()), Default::default(), Default::default()) + + swap_with_exact_supply { + let u in 2 .. TradingPathLimit::get() as u32; + + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + let mut path: Vec = vec![]; + for i in 1 .. u { + if i == 1 { + path.push(trading_pair.first()); + path.push(trading_pair.second()); + } else { + if i % 2 == 0 { + path.push(trading_pair.first()); + } else { + path.push(trading_pair.second()); + } + } + } + + let maker: AccountId = account("maker", 0, SEED); + let taker: AccountId = whitelisted_caller(); + inject_liquidity(maker, trading_pair.first(), trading_pair.second(), 10_000 * dollar(trading_pair.first()), 10_000 * dollar(trading_pair.second()))?; + + >::update_balance(path[0], &taker, (10_000 * dollar(path[0])).unique_saturated_into())?; + }: swap_with_exact_supply(RawOrigin::Signed(taker), path.clone(), 100 * dollar(path[0]), 0) + verify { + // would panic the benchmark anyways, must add new currencies to CURRENCY_LIST for benchmarking to work + assert!(TradingPathLimit::get() < CURRENCY_LIST.len() as u32); + } + + swap_with_exact_target { + let u in 2 .. TradingPathLimit::get() as u32; + + let trading_pair = TradingPair::from_currency_ids(STABLECOIN, NATIVE).unwrap(); + let mut path: Vec = vec![]; + for i in 1 .. u { + if i == 1 { + path.push(trading_pair.first()); + path.push(trading_pair.second()); + } else { + if i % 2 == 0 { + path.push(trading_pair.first()); + } else { + path.push(trading_pair.second()); + } + } + } + + let maker: AccountId = account("maker", 0, SEED); + let taker: AccountId = whitelisted_caller(); + inject_liquidity(maker, trading_pair.first(), trading_pair.second(), 10_000 * dollar(trading_pair.first()), 10_000 * dollar(trading_pair.second()))?; + + >::update_balance(path[0], &taker, (10_000 * dollar(path[0])).unique_saturated_into())?; + }: swap_with_exact_target(RawOrigin::Signed(taker), path.clone(), 10 * dollar(path[path.len() - 1]), 100 * dollar(path[0])) + verify { + // would panic the benchmark anyways, must add new currencies to CURRENCY_LIST for benchmarking to work + assert!(TradingPathLimit::get() < CURRENCY_LIST.len() as u32); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::benchmarking::utils::tests::new_test_ext; + use orml_benchmarking::impl_benchmark_test_suite; + + impl_benchmark_test_suite!(new_test_ext(),); +} diff --git a/blockchain/runtime/src/benchmarking/serp_setmint.rs b/blockchain/runtime/src/benchmarking/serp_setmint.rs index 03ad096c..ab870498 100644 --- a/blockchain/runtime/src/benchmarking/serp_setmint.rs +++ b/blockchain/runtime/src/benchmarking/serp_setmint.rs @@ -1,317 +1,317 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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::{ - dollar, AccountId, Amount, Balance, CdpEngine, CollateralCurrencyIds, CurrencyId, Currencies, DepositPerAuthorization, Dex, - ExistentialDeposits, GetNativeCurrencyId, GetSetUSDId, GetDinarCurrencyId, Setmint, Price, Rate, Ratio, - Runtime, TradingPathLimit, -}; - -use super::utils::{feed_price, set_balance}; -use core::convert::TryInto; -use frame_benchmarking::{account, whitelisted_caller}; -use frame_system::RawOrigin; -use module_dex::TradingPairStatus; -use orml_benchmarking::runtime_benchmarks; -use orml_traits::{Change, GetByKey, MultiCurrencyExtended}; -use sp_runtime::{ - traits::{AccountIdLookup, One, StaticLookup, UniqueSaturatedInto}, - FixedPointNumber, -}; -use sp_std::prelude::*; - -const SEED: u32 = 0; - -const NATIVE: CurrencyId = GetNativeCurrencyId::get(); -const STABLECOIN: CurrencyId = GetSetUSDId::get(); -const DINARID: CurrencyId = GetDinarCurrencyId::get(); -const SERP: CurrencyId = GetDinarCurrencyId::get(); - -fn inject_liquidity( - maker: AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - max_amount_a: Balance, - max_amount_b: Balance, -) -> Result<(), &'static str> { - // set balance - >::update_balance( - currency_id_a, - &maker, - max_amount_a.unique_saturated_into(), - )?; - >::update_balance( - currency_id_b, - &maker, - max_amount_b.unique_saturated_into(), - )?; - - let _ = Dex::enable_trading_pair(RawOrigin::Root.into(), currency_id_a, currency_id_b); - - Dex::add_liquidity( - RawOrigin::Signed(maker.clone()).into(), - currency_id_a, - currency_id_b, - max_amount_a, - max_amount_b, - Default::default(), - )?; - - Ok(()) -} - -runtime_benchmarks! { - { Runtime, serp_setmint } - - authorize { - let caller: AccountId = whitelisted_caller(); - let to: AccountId = account("to", 0, SEED); - let to_lookup = AccountIdLookup::unlookup(to); - - // set balance - set_balance(NATIVE, &caller, DepositPerAuthorization::get()); - }: _(RawOrigin::Signed(caller), DINARID, to_lookup) - - unauthorize { - let caller: AccountId = whitelisted_caller(); - let to: AccountId = account("to", 0, SEED); - let to_lookup = AccountIdLookup::unlookup(to); - - // set balance - set_balance(NATIVE, &caller, DepositPerAuthorization::get()); - Setmint::authorize( - RawOrigin::Signed(caller.clone()).into(), - DINARID, - to_lookup.clone() - )?; - }: _(RawOrigin::Signed(caller), DINARID, to_lookup) - - unauthorize_all { - let c in 0 .. CollateralCurrencyIds::get().len().saturating_sub(1) as u32; - - let caller: AccountId = whitelisted_caller(); - let currency_ids = CollateralCurrencyIds::get(); - let to: AccountId = account("to", 0, SEED); - let to_lookup = AccountIdLookup::unlookup(to); - - // set balance - set_balance(NATIVE, &caller, DepositPerAuthorization::get().saturating_mul(c.into())); - for i in 0 .. c { - Setmint::authorize( - RawOrigin::Signed(caller.clone()).into(), - currency_ids[i as usize], - to_lookup.clone(), - )?; - } - }: _(RawOrigin::Signed(caller)) - - // `adjust_loan`, best case: - // adjust both collateral and debit - adjust_loan { - let caller: AccountId = whitelisted_caller(); - let currency_id: CurrencyId = CollateralCurrencyIds::get()[0]; - let collateral_price = Price::one(); // 1 USD - let debit_value = 100 * dollar(STABLECOIN); - let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); - let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); - let debit_amount: Amount = debit_amount.unique_saturated_into(); - let collateral_value = 10 * debit_value; - let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); - - // set balance - set_balance(currency_id, &caller, collateral_amount + ExistentialDeposits::get(¤cy_id)); - - // feed price - feed_price(vec![(currency_id, collateral_price)])?; - - // set risk params - CdpEngine::set_collateral_params( - RawOrigin::Root.into(), - currency_id, - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(debit_value * 100), - )?; - }: _(RawOrigin::Signed(caller), currency_id, collateral_amount.try_into().unwrap(), debit_amount) - - transfer_loan_from { - let currency_id: CurrencyId = CollateralCurrencyIds::get()[0]; - let sender: AccountId = account("sender", 0, SEED); - let sender_lookup = AccountIdLookup::unlookup(sender.clone()); - let receiver: AccountId = whitelisted_caller(); - let receiver_lookup = AccountIdLookup::unlookup(receiver.clone()); - - let debit_value = 100 * dollar(STABLECOIN); - let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); - let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); - let debit_amount: Amount = debit_amount.unique_saturated_into(); - let collateral_value = 10 * debit_value; - let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); - - // set balance - set_balance(currency_id, &sender, collateral_amount + ExistentialDeposits::get(¤cy_id)); - set_balance(NATIVE, &sender, DepositPerAuthorization::get()); - - // feed price - feed_price(vec![(currency_id, Price::one())])?; - - // set risk params - CdpEngine::set_collateral_params( - RawOrigin::Root.into(), - currency_id, - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(debit_value * 100), - )?; - - // initialize sender's loan - Setmint::adjust_loan( - RawOrigin::Signed(sender.clone()).into(), - currency_id, - collateral_amount.try_into().unwrap(), - debit_amount, - )?; - - // authorize receiver - Setmint::authorize( - RawOrigin::Signed(sender.clone()).into(), - currency_id, - receiver_lookup, - )?; - }: _(RawOrigin::Signed(receiver), currency_id, sender_lookup) - - close_loan_has_debit_by_dex { - let currency_id: CurrencyId = CollateralCurrencyIds::get()[0]; - let sender: AccountId = whitelisted_caller(); - let maker: AccountId = account("maker", 0, SEED); - let debit_value = 100 * dollar(STABLECOIN); - let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); - let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); - let debit_amount: Amount = debit_amount.unique_saturated_into(); - let collateral_value = 10 * debit_value; - let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); - - // set balance - set_balance(currency_id, &sender, collateral_amount + ExistentialDeposits::get(¤cy_id)); - inject_liquidity(maker.clone(), currency_id, DINARID, 10_000 * dollar(SERP), 10_000 * dollar(DINARID))?; - inject_liquidity(maker, DINARID, STABLECOIN, 10_000 * dollar(DINARID), 10_000 * dollar(STABLECOIN))?; - - feed_price(vec![(DINARID, Price::one())])?; - - // set risk params - CdpEngine::set_collateral_params( - RawOrigin::Root.into(), - currency_id, - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(debit_value * 100), - )?; - - // initialize sender's loan - Setmint::adjust_loan( - RawOrigin::Signed(sender.clone()).into(), - currency_id, - (10 * collateral_amount).try_into().unwrap(), - debit_amount, - )?; - }: _(RawOrigin::Signed(sender), currency_id, collateral_amount) - - expand_position_collateral { - let currency_id: CurrencyId = DINARID; - let sender: AccountId = whitelisted_caller(); - let maker: AccountId = account("maker", 0, SEED); - let debit_value = 100 * dollar(STABLECOIN); - let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); - let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); - let collateral_value = 10 * debit_value; - let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); - - // set balance and inject liquidity - set_balance(currency_id, &sender, (10 * collateral_amount) + ExistentialDeposits::get(¤cy_id)); - inject_liquidity(maker, currency_id, STABLECOIN, 10_000 * dollar(currency_id), 10_000 * dollar(STABLECOIN))?; - - feed_price(vec![(currency_id, Price::one())])?; - - // set risk params - CdpEngine::set_collateral_params( - RawOrigin::Root.into(), - currency_id, - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(debit_value * 100), - )?; - - // initialize sender's loan - Setmint::adjust_loan( - RawOrigin::Signed(sender.clone()).into(), - currency_id, - collateral_amount.try_into().unwrap(), - debit_amount.try_into().unwrap(), - )?; - }: _(RawOrigin::Signed(sender), currency_id, debit_value, 0) - - shrink_position_debit { - let currency_id: CurrencyId = DINARID; - let sender: AccountId = whitelisted_caller(); - let maker: AccountId = account("maker", 0, SEED); - let debit_value = 100 * dollar(STABLECOIN); - let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); - let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); - let collateral_value = 10 * debit_value; - let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); - - // set balance and inject liquidity - set_balance(currency_id, &sender, (10 * collateral_amount) + ExistentialDeposits::get(¤cy_id)); - inject_liquidity(maker, currency_id, STABLECOIN, 10_000 * dollar(currency_id), 10_000 * dollar(STABLECOIN))?; - - feed_price(vec![(currency_id, Price::one())])?; - - // set risk params - CdpEngine::set_collateral_params( - RawOrigin::Root.into(), - currency_id, - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(debit_value * 100), - )?; - - // initialize sender's loan - Setmint::adjust_loan( - RawOrigin::Signed(sender.clone()).into(), - currency_id, - collateral_amount.try_into().unwrap(), - debit_amount.try_into().unwrap(), - )?; - }: _(RawOrigin::Signed(sender), currency_id, collateral_amount / 5, 0) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::benchmarking::utils::tests::new_test_ext; - use orml_benchmarking::impl_benchmark_test_suite; - - impl_benchmark_test_suite!(new_test_ext(),); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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::{ + dollar, AccountId, Amount, Balance, CdpEngine, CollateralCurrencyIds, CurrencyId, Currencies, DepositPerAuthorization, Dex, + ExistentialDeposits, GetNativeCurrencyId, GetSetUSDId, GetDinarCurrencyId, Setmint, Price, Rate, Ratio, + Runtime, TradingPathLimit, +}; + +use super::utils::{feed_price, set_balance}; +use core::convert::TryInto; +use frame_benchmarking::{account, whitelisted_caller}; +use frame_system::RawOrigin; +use edfis_swap_module::TradingPairStatus; +use orml_benchmarking::runtime_benchmarks; +use orml_traits::{Change, GetByKey, MultiCurrencyExtended}; +use sp_runtime::{ + traits::{AccountIdLookup, One, StaticLookup, UniqueSaturatedInto}, + FixedPointNumber, +}; +use sp_std::prelude::*; + +const SEED: u32 = 0; + +const NATIVE: CurrencyId = GetNativeCurrencyId::get(); +const STABLECOIN: CurrencyId = GetSetUSDId::get(); +const DINARID: CurrencyId = GetDinarCurrencyId::get(); +const SERP: CurrencyId = GetDinarCurrencyId::get(); + +fn inject_liquidity( + maker: AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, +) -> Result<(), &'static str> { + // set balance + >::update_balance( + currency_id_a, + &maker, + max_amount_a.unique_saturated_into(), + )?; + >::update_balance( + currency_id_b, + &maker, + max_amount_b.unique_saturated_into(), + )?; + + let _ = Dex::enable_trading_pair(RawOrigin::Root.into(), currency_id_a, currency_id_b); + + Dex::add_liquidity( + RawOrigin::Signed(maker.clone()).into(), + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + Default::default(), + )?; + + Ok(()) +} + +runtime_benchmarks! { + { Runtime, serp_setmint } + + authorize { + let caller: AccountId = whitelisted_caller(); + let to: AccountId = account("to", 0, SEED); + let to_lookup = AccountIdLookup::unlookup(to); + + // set balance + set_balance(NATIVE, &caller, DepositPerAuthorization::get()); + }: _(RawOrigin::Signed(caller), DINARID, to_lookup) + + unauthorize { + let caller: AccountId = whitelisted_caller(); + let to: AccountId = account("to", 0, SEED); + let to_lookup = AccountIdLookup::unlookup(to); + + // set balance + set_balance(NATIVE, &caller, DepositPerAuthorization::get()); + Setmint::authorize( + RawOrigin::Signed(caller.clone()).into(), + DINARID, + to_lookup.clone() + )?; + }: _(RawOrigin::Signed(caller), DINARID, to_lookup) + + unauthorize_all { + let c in 0 .. CollateralCurrencyIds::get().len().saturating_sub(1) as u32; + + let caller: AccountId = whitelisted_caller(); + let currency_ids = CollateralCurrencyIds::get(); + let to: AccountId = account("to", 0, SEED); + let to_lookup = AccountIdLookup::unlookup(to); + + // set balance + set_balance(NATIVE, &caller, DepositPerAuthorization::get().saturating_mul(c.into())); + for i in 0 .. c { + Setmint::authorize( + RawOrigin::Signed(caller.clone()).into(), + currency_ids[i as usize], + to_lookup.clone(), + )?; + } + }: _(RawOrigin::Signed(caller)) + + // `adjust_loan`, best case: + // adjust both collateral and debit + adjust_loan { + let caller: AccountId = whitelisted_caller(); + let currency_id: CurrencyId = CollateralCurrencyIds::get()[0]; + let collateral_price = Price::one(); // 1 USD + let debit_value = 100 * dollar(STABLECOIN); + let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); + let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); + let debit_amount: Amount = debit_amount.unique_saturated_into(); + let collateral_value = 10 * debit_value; + let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); + + // set balance + set_balance(currency_id, &caller, collateral_amount + ExistentialDeposits::get(¤cy_id)); + + // feed price + feed_price(vec![(currency_id, collateral_price)])?; + + // set risk params + CdpEngine::set_collateral_params( + RawOrigin::Root.into(), + currency_id, + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(debit_value * 100), + )?; + }: _(RawOrigin::Signed(caller), currency_id, collateral_amount.try_into().unwrap(), debit_amount) + + transfer_loan_from { + let currency_id: CurrencyId = CollateralCurrencyIds::get()[0]; + let sender: AccountId = account("sender", 0, SEED); + let sender_lookup = AccountIdLookup::unlookup(sender.clone()); + let receiver: AccountId = whitelisted_caller(); + let receiver_lookup = AccountIdLookup::unlookup(receiver.clone()); + + let debit_value = 100 * dollar(STABLECOIN); + let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); + let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); + let debit_amount: Amount = debit_amount.unique_saturated_into(); + let collateral_value = 10 * debit_value; + let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); + + // set balance + set_balance(currency_id, &sender, collateral_amount + ExistentialDeposits::get(¤cy_id)); + set_balance(NATIVE, &sender, DepositPerAuthorization::get()); + + // feed price + feed_price(vec![(currency_id, Price::one())])?; + + // set risk params + CdpEngine::set_collateral_params( + RawOrigin::Root.into(), + currency_id, + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(debit_value * 100), + )?; + + // initialize sender's loan + Setmint::adjust_loan( + RawOrigin::Signed(sender.clone()).into(), + currency_id, + collateral_amount.try_into().unwrap(), + debit_amount, + )?; + + // authorize receiver + Setmint::authorize( + RawOrigin::Signed(sender.clone()).into(), + currency_id, + receiver_lookup, + )?; + }: _(RawOrigin::Signed(receiver), currency_id, sender_lookup) + + close_loan_has_debit_by_dex { + let currency_id: CurrencyId = CollateralCurrencyIds::get()[0]; + let sender: AccountId = whitelisted_caller(); + let maker: AccountId = account("maker", 0, SEED); + let debit_value = 100 * dollar(STABLECOIN); + let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); + let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); + let debit_amount: Amount = debit_amount.unique_saturated_into(); + let collateral_value = 10 * debit_value; + let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); + + // set balance + set_balance(currency_id, &sender, collateral_amount + ExistentialDeposits::get(¤cy_id)); + inject_liquidity(maker.clone(), currency_id, DINARID, 10_000 * dollar(SERP), 10_000 * dollar(DINARID))?; + inject_liquidity(maker, DINARID, STABLECOIN, 10_000 * dollar(DINARID), 10_000 * dollar(STABLECOIN))?; + + feed_price(vec![(DINARID, Price::one())])?; + + // set risk params + CdpEngine::set_collateral_params( + RawOrigin::Root.into(), + currency_id, + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(debit_value * 100), + )?; + + // initialize sender's loan + Setmint::adjust_loan( + RawOrigin::Signed(sender.clone()).into(), + currency_id, + (10 * collateral_amount).try_into().unwrap(), + debit_amount, + )?; + }: _(RawOrigin::Signed(sender), currency_id, collateral_amount) + + expand_position_collateral { + let currency_id: CurrencyId = DINARID; + let sender: AccountId = whitelisted_caller(); + let maker: AccountId = account("maker", 0, SEED); + let debit_value = 100 * dollar(STABLECOIN); + let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); + let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); + let collateral_value = 10 * debit_value; + let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); + + // set balance and inject liquidity + set_balance(currency_id, &sender, (10 * collateral_amount) + ExistentialDeposits::get(¤cy_id)); + inject_liquidity(maker, currency_id, STABLECOIN, 10_000 * dollar(currency_id), 10_000 * dollar(STABLECOIN))?; + + feed_price(vec![(currency_id, Price::one())])?; + + // set risk params + CdpEngine::set_collateral_params( + RawOrigin::Root.into(), + currency_id, + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(debit_value * 100), + )?; + + // initialize sender's loan + Setmint::adjust_loan( + RawOrigin::Signed(sender.clone()).into(), + currency_id, + collateral_amount.try_into().unwrap(), + debit_amount.try_into().unwrap(), + )?; + }: _(RawOrigin::Signed(sender), currency_id, debit_value, 0) + + shrink_position_debit { + let currency_id: CurrencyId = DINARID; + let sender: AccountId = whitelisted_caller(); + let maker: AccountId = account("maker", 0, SEED); + let debit_value = 100 * dollar(STABLECOIN); + let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); + let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); + let collateral_value = 10 * debit_value; + let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); + + // set balance and inject liquidity + set_balance(currency_id, &sender, (10 * collateral_amount) + ExistentialDeposits::get(¤cy_id)); + inject_liquidity(maker, currency_id, STABLECOIN, 10_000 * dollar(currency_id), 10_000 * dollar(STABLECOIN))?; + + feed_price(vec![(currency_id, Price::one())])?; + + // set risk params + CdpEngine::set_collateral_params( + RawOrigin::Root.into(), + currency_id, + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), + Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), + Change::NewValue(debit_value * 100), + )?; + + // initialize sender's loan + Setmint::adjust_loan( + RawOrigin::Signed(sender.clone()).into(), + currency_id, + collateral_amount.try_into().unwrap(), + debit_amount.try_into().unwrap(), + )?; + }: _(RawOrigin::Signed(sender), currency_id, collateral_amount / 5, 0) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::benchmarking::utils::tests::new_test_ext; + use orml_benchmarking::impl_benchmark_test_suite; + + impl_benchmark_test_suite!(new_test_ext(),); +} diff --git a/blockchain/runtime/src/lib.rs b/blockchain/runtime/src/lib.rs index 48caabc8..1948d105 100644 --- a/blockchain/runtime/src/lib.rs +++ b/blockchain/runtime/src/lib.rs @@ -842,7 +842,7 @@ parameter_types! { ]; } -// impl module_dex::Config for Runtime { +// impl edfis_swap_module::Config for Runtime { // type Event = Event; // type Currency = Currencies; // type StableCurrencyIds = StableCurrencyIds; @@ -851,7 +851,7 @@ parameter_types! { // type TradingPathLimit = TradingPathLimit; // type PalletId = DEXPalletId; // type CurrencyIdMapping = EvmCurrencyIdMapping; -// type WeightInfo = weights::module_dex::WeightInfo; +// type WeightInfo = weights::edfis_swap_module::WeightInfo; // type ListingOrigin = EnsureRootOrHalfFinancialCouncil; // } @@ -1141,8 +1141,8 @@ impl InstanceFilter for ProxyType { ProxyType::Swap => { matches!( c, - Call::Dex(module_dex::Call::swap_with_exact_supply(..)) - | Call::Dex(module_dex::Call::swap_with_exact_target(..)) + Call::Dex(edfis_swap_module::Call::swap_with_exact_supply(..)) + | Call::Dex(edfis_swap_module::Call::swap_with_exact_target(..)) ) } ProxyType::Loan => { @@ -1524,7 +1524,7 @@ construct_runtime!( Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event} = 3, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 4, Prices: module_prices::{Pallet, Storage, Call, Event} = 5, - // Dex: module_dex::{Pallet, Storage, Call, Event, Config} = 6, + // Dex: edfis_swap_module::{Pallet, Storage, Call, Event, Config} = 6, Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 7, Recovery: pallet_recovery::{Pallet, Call, Storage, Event} = 8, @@ -2002,7 +2002,7 @@ impl_runtime_apis! { list_benchmark!(list, extra, module_nft, NftBench::); - // orml_list_benchmark!(list, extra, module_dex, benchmarking::dex); + // orml_list_benchmark!(list, extra, edfis_swap_module, benchmarking::dex); // orml_list_benchmark!(list, extra, auction_manager, benchmarking::auction_manager); // orml_list_benchmark!(list, extra, cdp_engine, benchmarking::cdp_engine); // orml_list_benchmark!(list, extra, emergency_shutdown, benchmarking::emergency_shutdown); @@ -2060,7 +2060,7 @@ impl_runtime_apis! { let params = (&config, &whitelist); add_benchmark!(params, batches, module_nft, NftBench::); - // orml_add_benchmark!(params, batches, module_dex, benchmarking::dex); + // orml_add_benchmark!(params, batches, edfis_swap_module, benchmarking::dex); // orml_add_benchmark!(params, batches, auction_manager, benchmarking::auction_manager); // orml_add_benchmark!(params, batches, cdp_engine, benchmarking::cdp_engine); // orml_add_benchmark!(params, batches, emergency_shutdown, benchmarking::emergency_shutdown); diff --git a/blockchain/runtime/src/weights/mod.rs b/blockchain/runtime/src/weights/mod.rs index 286ec713..a5d6b99b 100644 --- a/blockchain/runtime/src/weights/mod.rs +++ b/blockchain/runtime/src/weights/mod.rs @@ -26,7 +26,7 @@ pub mod module_auction_manager; pub mod module_cdp_engine; pub mod module_cdp_treasury; pub mod module_currencies; -pub mod module_dex; +pub mod edfis_swap_module; pub mod emergency_shutdown; pub mod module_evm; pub mod module_unified_accounts; diff --git a/blockchain/runtime/src/weights/module_dex.rs b/blockchain/runtime/src/weights/module_dex.rs index a6bc0b4a..b4317390 100644 --- a/blockchain/runtime/src/weights/module_dex.rs +++ b/blockchain/runtime/src/weights/module_dex.rs @@ -18,7 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Autogenerated weights for module_dex +//! Autogenerated weights for edfis_swap_module //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 //! DATE: 2021-07-19, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` @@ -46,9 +46,9 @@ use frame_support::{traits::Get, weights::Weight}; use sp_std::marker::PhantomData; -/// Weight functions for module_dex. +/// Weight functions for edfis_swap_module. pub struct WeightInfo(PhantomData); -impl module_dex::WeightInfo for WeightInfo { +impl edfis_swap_module::WeightInfo for WeightInfo { fn enable_trading_pair() -> Weight { (25_878_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) From 796fd2b31262cf834669ce77a9e016ff13646206 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sat, 2 Mar 2024 03:14:58 +0800 Subject: [PATCH 04/21] Update EVMBridge Module --- blockchain/modules/evm-bridge/Cargo.toml | 111 ++-- .../evm-bridge/src/erc20_demo_contract | 6 - blockchain/modules/evm-bridge/src/lib.rs | 628 +++++++++++------- blockchain/modules/evm-bridge/src/mock.rs | 513 +++++++------- blockchain/modules/evm-bridge/src/tests.rs | 560 ++++++++++------ 5 files changed, 1082 insertions(+), 736 deletions(-) delete mode 100644 blockchain/modules/evm-bridge/src/erc20_demo_contract diff --git a/blockchain/modules/evm-bridge/Cargo.toml b/blockchain/modules/evm-bridge/Cargo.toml index 7e5fc40a..6c91db32 100644 --- a/blockchain/modules/evm-bridge/Cargo.toml +++ b/blockchain/modules/evm-bridge/Cargo.toml @@ -1,50 +1,61 @@ -[package] -name = "module-evm-bridge" -version = "1.0.0" -authors = ["Setheum Labs"] -edition = "2018" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -serde = { version = "1.0.124", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10", default-features = false } -primitive-types = { version = "0.10.0", default-features = false, features = ["rlp", "byteorder"] } -impl-trait-for-tuples = "0.2.1" -ethereum-types = { version = "0.12.0", default-features = false } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -primitives-proc-macro = { path = "../primitives/proc-macro" } -support = { package = "module-support", path = "../support", default-features = false } -module-evm = { path = "../evm", default-features = false } -num_enum = { version = "0.5.1", default-features = false } - -[dev-dependencies] -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } -pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.10" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "num_enum/std", - "sp-core/std", - "sp-runtime/std", - "frame-support/std", - "frame-system/std", - "sp-io/std", - "sp-std/std", - "ethereum-types/std", - "primitives/std", - "primitive-types/std", - "support/std", - "module-evm/std", -] +[package] +name = "module-evm-bridge" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +impl-trait-for-tuples = { workspace = true } +ethereum-types = { workspace = true } +num_enum = { workspace = true } + +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } + +module-evm = { workspace = true } +module-evm-utility-macro = { workspace = true } +module-support = { workspace = true } +primitives = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } +hex = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "ethereum-types/std", + "frame-support/std", + "frame-system/std", + "module-evm/std", + "num_enum/std", + "primitives/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "module-evm/try-runtime", +] diff --git a/blockchain/modules/evm-bridge/src/erc20_demo_contract b/blockchain/modules/evm-bridge/src/erc20_demo_contract deleted file mode 100644 index 5ef7efd3..00000000 --- a/blockchain/modules/evm-bridge/src/erc20_demo_contract +++ /dev/null @@ -1,6 +0,0 @@ -// Generated by https://github.com/SetheumNetwork/predeploy-contracts/blob/master/contracts/test/Erc20DemoContract.sol -// The `deployedBytecode` does not contain the constructor. -// The constructor has no input parameters, so it can be deployed directly. -// Copy `bytecode` from predeploy-contracts/build/contracts/Erc20DemoContract.json - -"0x60806040523480156200001157600080fd5b506040518060800160405280605881526020016200152c605891396040518060400160405280600981526020017f54657374546f6b656e000000000000000000000000000000000000000000000081525081600390805190602001906200007a9291906200037c565b508060049080519060200190620000939291906200037c565b506012600560006101000a81548160ff021916908360ff1602179055505050620000da731000000000000000000000000000000000000001612710620000f260201b60201c565b620000ec6011620002d060201b60201c565b6200042b565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141562000196576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b620001aa60008383620002ee60201b60201c565b620001c681600254620002f360201b62000f2d1790919060201c565b60028190555062000224816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002f360201b62000f2d1790919060201c565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b80600560006101000a81548160ff021916908360ff16021790555050565b505050565b60008082840190508381101562000372576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620003bf57805160ff1916838001178555620003f0565b82800160010185558215620003f0579182015b82811115620003ef578251825591602001919060010190620003d2565b5b509050620003ff919062000403565b5090565b6200042891905b80821115620004245760008160009055506001016200040a565b5090565b90565b6110f1806200043b6000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461025f57806370a08231146102c557806395d89b411461031d578063a457c2d7146103a0578063a9059cbb14610406578063dd62ed3e1461046c576100a9565b806306fdde03146100ae578063095ea7b31461013157806318160ddd1461019757806323b872dd146101b5578063313ce5671461023b575b600080fd5b6100b66104e4565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f65780820151818401526020810190506100db565b50505050905090810190601f1680156101235780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d6004803603604081101561014757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610586565b604051808215151515815260200191505060405180910390f35b61019f6105a4565b6040518082815260200191505060405180910390f35b610221600480360360608110156101cb57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105ae565b604051808215151515815260200191505060405180910390f35b610243610687565b604051808260ff1660ff16815260200191505060405180910390f35b6102ab6004803603604081101561027557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061069e565b604051808215151515815260200191505060405180910390f35b610307600480360360208110156102db57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610751565b6040518082815260200191505060405180910390f35b610325610799565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561036557808201518184015260208101905061034a565b50505050905090810190601f1680156103925780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103ec600480360360408110156103b657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061083b565b604051808215151515815260200191505060405180910390f35b6104526004803603604081101561041c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610908565b604051808215151515815260200191505060405180910390f35b6104ce6004803603604081101561048257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610926565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561057c5780601f106105515761010080835404028352916020019161057c565b820191906000526020600020905b81548152906001019060200180831161055f57829003601f168201915b5050505050905090565b600061059a6105936109ad565b84846109b5565b6001905092915050565b6000600254905090565b60006105bb848484610bac565b61067c846105c76109ad565b6106778560405180606001604052806028815260200161102660289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600061062d6109ad565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e6d9092919063ffffffff16565b6109b5565b600190509392505050565b6000600560009054906101000a900460ff16905090565b60006107476106ab6109ad565b8461074285600160006106bc6109ad565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f2d90919063ffffffff16565b6109b5565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156108315780601f1061080657610100808354040283529160200191610831565b820191906000526020600020905b81548152906001019060200180831161081457829003601f168201915b5050505050905090565b60006108fe6108486109ad565b846108f98560405180606001604052806025815260200161109760259139600160006108726109ad565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e6d9092919063ffffffff16565b6109b5565b6001905092915050565b600061091c6109156109ad565b8484610bac565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610a3b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806110736024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610ac1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180610fde6022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610c32576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061104e6025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610cb8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180610fbb6023913960400191505060405180910390fd5b610cc3838383610fb5565b610d2e81604051806060016040528060268152602001611000602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e6d9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610dc1816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f2d90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610f1a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610edf578082015181840152602081019050610ec4565b50505050905090810190601f168015610f0c5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610fab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220cd01ee5a91a881794c7b04ba72b192caf5c93474b9dbe4b16207f9ef5ccc7e2364736f6c634300060200336c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d65" diff --git a/blockchain/modules/evm-bridge/src/lib.rs b/blockchain/modules/evm-bridge/src/lib.rs index 7fe6b02b..d47e185e 100644 --- a/blockchain/modules/evm-bridge/src/lib.rs +++ b/blockchain/modules/evm-bridge/src/lib.rs @@ -1,242 +1,386 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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_attr(not(feature = "std"), no_std)] -#![allow(clippy::unused_unit)] - -use ethereum_types::BigEndianHash; -use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - pallet_prelude::*, -}; -use module_evm::{ExitReason, ExitSucceed}; -use num_enum::{IntoPrimitive, TryFromPrimitive}; -use primitive_types::H256; -use sp_core::{H160, U256}; -use sp_runtime::SaturatedConversion; -use sp_std::vec::Vec; -use support::{EVMBridge as EVMBridgeTrait, ExecutionMode, InvokeContext, EVM}; - -type AccountIdOf = ::AccountId; -type BalanceOf = <::EVM as EVM>>::Balance; - -#[primitives_proc_macro::generate_function_selector] -#[derive(RuntimeDebug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] -#[repr(u32)] -pub enum Action { - Name = "name()", - Symbol = "symbol()", - Decimals = "decimals()", - TotalSupply = "totalSupply()", - BalanceOf = "balanceOf(address)", - Transfer = "transfer(address,uint256)", -} - -mod mock; -mod tests; - -pub use module::*; - -#[frame_support::pallet] -pub mod module { - use super::*; - - /// EvmBridge module trait - #[pallet::config] - pub trait Config: frame_system::Config { - type EVM: EVM>; - } - - #[pallet::error] - pub enum Error { - /// Execution failed - ExecutionFail, - /// Execution reverted - ExecutionRevert, - /// Execution fatal - ExecutionFatal, - /// Execution error - ExecutionError, - /// Invalid return value - InvalidReturnValue, - } - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet {} - - #[pallet::call] - impl Pallet {} -} - -impl EVMBridgeTrait, BalanceOf> for Pallet { - // Calls the name method on an ERC20 contract using the given context - // and returns the token name. - fn name(context: InvokeContext) -> Result, DispatchError> { - // ERC20.name method hash - let input = Into::::into(Action::Name).to_be_bytes().to_vec(); - - let info = T::EVM::execute(context, input, Default::default(), 2_100_000, 0, ExecutionMode::View)?; - - Self::handle_exit_reason(info.exit_reason)?; - Self::decode_string(info.value.as_slice().to_vec()) - } - - // Calls the symbol method on an ERC20 contract using the given context - // and returns the token symbol. - fn symbol(context: InvokeContext) -> Result, DispatchError> { - // ERC20.symbol method hash - let input = Into::::into(Action::Symbol).to_be_bytes().to_vec(); - - let info = T::EVM::execute(context, input, Default::default(), 2_100_000, 0, ExecutionMode::View)?; - - Self::handle_exit_reason(info.exit_reason)?; - Self::decode_string(info.value.as_slice().to_vec()) - } - - // Calls the decimals method on an ERC20 contract using the given context - // and returns the decimals. - fn decimals(context: InvokeContext) -> Result { - // ERC20.decimals method hash - let input = Into::::into(Action::Decimals).to_be_bytes().to_vec(); - - let info = T::EVM::execute(context, input, Default::default(), 2_100_000, 0, ExecutionMode::View)?; - - Self::handle_exit_reason(info.exit_reason)?; - - ensure!(info.value.len() == 32, Error::::InvalidReturnValue); - let value = U256::from(info.value.as_slice()).saturated_into::(); - Ok(value) - } - - // Calls the totalSupply method on an ERC20 contract using the given context - // and returns the total supply. - fn total_supply(context: InvokeContext) -> Result, DispatchError> { - // ERC20.totalSupply method hash - let input = Into::::into(Action::TotalSupply).to_be_bytes().to_vec(); - - let info = T::EVM::execute(context, input, Default::default(), 2_100_000, 0, ExecutionMode::View)?; - - Self::handle_exit_reason(info.exit_reason)?; - - ensure!(info.value.len() == 32, Error::::InvalidReturnValue); - let value = U256::from(info.value.as_slice()).saturated_into::(); - Ok(value.saturated_into::>()) - } - - // Calls the balanceOf method on an ERC20 contract using the given context - // and returns the address's balance. - fn balance_of(context: InvokeContext, address: H160) -> Result, DispatchError> { - // ERC20.balanceOf method hash - let mut input = Into::::into(Action::BalanceOf).to_be_bytes().to_vec(); - // append address - input.extend_from_slice(H256::from(address).as_bytes()); - - let info = T::EVM::execute(context, input, Default::default(), 2_100_000, 0, ExecutionMode::View)?; - - Self::handle_exit_reason(info.exit_reason)?; - - Ok(U256::from(info.value.as_slice()) - .saturated_into::() - .saturated_into::>()) - } - - // Calls the transfer method on an ERC20 contract using the given context. - fn transfer(context: InvokeContext, to: H160, value: BalanceOf) -> DispatchResult { - // ERC20.transfer method hash - let mut input = Into::::into(Action::Transfer).to_be_bytes().to_vec(); - // append receiver address - input.extend_from_slice(H256::from(to).as_bytes()); - // append amount to be transferred - input.extend_from_slice(H256::from_uint(&U256::from(value.saturated_into::())).as_bytes()); - - let storage_limit = if context.origin == Default::default() { 0 } else { 1_000 }; - - let info = T::EVM::execute( - context, - input, - Default::default(), - 2_100_000, - storage_limit, - ExecutionMode::Execute, - )?; - - Self::handle_exit_reason(info.exit_reason)?; - - // return value is true. - let mut bytes = [0u8; 32]; - U256::from(1).to_big_endian(&mut bytes); - - // Check return value to make sure not calling on empty contracts. - ensure!( - !info.value.is_empty() && info.value == bytes, - Error::::InvalidReturnValue - ); - Ok(()) - } - - fn get_origin() -> Option> { - T::EVM::get_origin() - } - - fn set_origin(origin: AccountIdOf) { - T::EVM::set_origin(origin); - } -} - -impl Pallet { - fn handle_exit_reason(exit_reason: ExitReason) -> Result<(), DispatchError> { - match exit_reason { - ExitReason::Succeed(ExitSucceed::Returned) => Ok(()), - ExitReason::Succeed(ExitSucceed::Stopped) => Ok(()), - ExitReason::Succeed(_) => Err(Error::::ExecutionFail.into()), - ExitReason::Revert(_) => Err(Error::::ExecutionRevert.into()), - ExitReason::Fatal(_) => Err(Error::::ExecutionFatal.into()), - ExitReason::Error(_) => Err(Error::::ExecutionError.into()), - } - } - - fn decode_string(output: Vec) -> Result, DispatchError> { - // output is 32-byte aligned and consists of 3 parts: - // - part 1: 32 byte, the offset of its description is passed in the position of - // the corresponding parameter or return value. - // - part 2: 32 byte, string length - // - part 3: string data - ensure!( - output.len() >= 64 && output.len() % 32 == 0, - Error::::InvalidReturnValue - ); - - let offset = U256::from_big_endian(&output[0..32]); - let length = U256::from_big_endian(&output[offset.as_usize()..offset.as_usize() + 32]); - ensure!( - // output is 32-byte aligned. ensure total_length >= offset + string length + string data length. - output.len() >= offset.as_usize() + 32 + length.as_usize(), - Error::::InvalidReturnValue - ); - - let mut data = Vec::new(); - data.extend_from_slice(&output[offset.as_usize() + 32..offset.as_usize() + 32 + length.as_usize()]); - - Ok(data.to_vec()) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use ethereum_types::BigEndianHash; +use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; +use frame_system::pallet_prelude::*; +use module_evm::{ExitReason, ExitSucceed}; +use module_support::{ + evm::limits::{erc20, liquidation}, + EVMBridge as EVMBridgeTrait, ExecutionMode, InvokeContext, LiquidationEvmBridge as LiquidationEvmBridgeT, EVM, +}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use primitives::{evm::EvmAddress, Balance}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{ArithmeticError, DispatchError, SaturatedConversion}; +use sp_std::vec::Vec; + +type AccountIdOf = ::AccountId; +type BalanceOf = <::EVM as EVM>>::Balance; + +#[module_evm_utility_macro::generate_function_selector] +#[derive(RuntimeDebug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] +#[repr(u32)] +pub enum Action { + Name = "name()", + Symbol = "symbol()", + Decimals = "decimals()", + TotalSupply = "totalSupply()", + BalanceOf = "balanceOf(address)", + Transfer = "transfer(address,uint256)", + Liquidate = "liquidate(address,address,uint256,uint256)", + OnCollateralTransfer = "onCollateralTransfer(address,uint256)", + OnRepaymentRefund = "onRepaymentRefund(address,uint256)", +} + +mod mock; +mod tests; + +pub use module::*; + +#[frame_support::pallet] +pub mod module { + use super::*; + + /// EvmBridge module trait + #[pallet::config] + pub trait Config: frame_system::Config { + type EVM: EVM>; + } + + #[pallet::error] + pub enum Error { + /// Execution failed + ExecutionFail, + /// Execution reverted + ExecutionRevert, + /// Execution fatal + ExecutionFatal, + /// Execution error + ExecutionError, + /// Invalid return value + InvalidReturnValue, + } + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +pub struct EVMBridge(sp_std::marker::PhantomData); + +impl EVMBridgeTrait, BalanceOf> for EVMBridge { + // Calls the name method on an ERC20 contract using the given context + // and returns the token name. + fn name(context: InvokeContext) -> Result, DispatchError> { + // ERC20.name method hash + let input = Into::::into(Action::Name).to_be_bytes().to_vec(); + + let info = T::EVM::execute( + context, + input, + Default::default(), + erc20::NAME.gas, + erc20::NAME.storage, + ExecutionMode::View, + )?; + + Pallet::::handle_exit_reason(info.exit_reason)?; + Pallet::::decode_string(info.value.as_slice().to_vec()) + } + + // Calls the symbol method on an ERC20 contract using the given context + // and returns the token symbol. + fn symbol(context: InvokeContext) -> Result, DispatchError> { + // ERC20.symbol method hash + let input = Into::::into(Action::Symbol).to_be_bytes().to_vec(); + + let info = T::EVM::execute( + context, + input, + Default::default(), + erc20::SYMBOL.gas, + erc20::SYMBOL.storage, + ExecutionMode::View, + )?; + + Pallet::::handle_exit_reason(info.exit_reason)?; + Pallet::::decode_string(info.value.as_slice().to_vec()) + } + + // Calls the decimals method on an ERC20 contract using the given context + // and returns the decimals. + fn decimals(context: InvokeContext) -> Result { + // ERC20.decimals method hash + let input = Into::::into(Action::Decimals).to_be_bytes().to_vec(); + + let info = T::EVM::execute( + context, + input, + Default::default(), + erc20::DECIMALS.gas, + erc20::DECIMALS.storage, + ExecutionMode::View, + )?; + + Pallet::::handle_exit_reason(info.exit_reason)?; + + ensure!(info.value.len() == 32, Error::::InvalidReturnValue); + let value: u8 = U256::from(info.value.as_slice()) + .try_into() + .map_err(|_| ArithmeticError::Overflow)?; + Ok(value) + } + + // Calls the totalSupply method on an ERC20 contract using the given context + // and returns the total supply. + fn total_supply(context: InvokeContext) -> Result, DispatchError> { + // ERC20.totalSupply method hash + let input = Into::::into(Action::TotalSupply).to_be_bytes().to_vec(); + + let info = T::EVM::execute( + context, + input, + Default::default(), + erc20::TOTAL_SUPPLY.gas, + erc20::TOTAL_SUPPLY.storage, + ExecutionMode::View, + )?; + + Pallet::::handle_exit_reason(info.exit_reason)?; + + ensure!(info.value.len() == 32, Error::::InvalidReturnValue); + let value: u128 = U256::from(info.value.as_slice()) + .try_into() + .map_err(|_| ArithmeticError::Overflow)?; + let supply = value.try_into().map_err(|_| ArithmeticError::Overflow)?; + Ok(supply) + } + + // Calls the balanceOf method on an ERC20 contract using the given context + // and returns the address's balance. + fn balance_of(context: InvokeContext, address: H160) -> Result, DispatchError> { + // ERC20.balanceOf method hash + let mut input = Into::::into(Action::BalanceOf).to_be_bytes().to_vec(); + // append address + input.extend_from_slice(H256::from(address).as_bytes()); + + let info = T::EVM::execute( + context, + input, + Default::default(), + erc20::BALANCE_OF.gas, + erc20::BALANCE_OF.storage, + ExecutionMode::View, + )?; + + Pallet::::handle_exit_reason(info.exit_reason)?; + + let value: u128 = U256::from(info.value.as_slice()) + .try_into() + .map_err(|_| ArithmeticError::Overflow)?; + let balance = value.try_into().map_err(|_| ArithmeticError::Overflow)?; + Ok(balance) + } + + // Calls the transfer method on an ERC20 contract using the given context. + fn transfer(context: InvokeContext, to: H160, value: BalanceOf) -> DispatchResult { + // ERC20.transfer method hash + let mut input = Into::::into(Action::Transfer).to_be_bytes().to_vec(); + // append receiver address + input.extend_from_slice(H256::from(to).as_bytes()); + // append amount to be transferred + input.extend_from_slice(H256::from_uint(&U256::from(value.saturated_into::())).as_bytes()); + + let storage_limit = if context.origin == Default::default() { + 0 + } else { + erc20::TRANSFER.storage + }; + + let info = T::EVM::execute( + context, + input, + Default::default(), + erc20::TRANSFER.gas, + storage_limit, + ExecutionMode::Execute, + )?; + + Pallet::::handle_exit_reason(info.exit_reason)?; + + // return value is true. + let mut bytes = [0u8; 32]; + U256::from(1).to_big_endian(&mut bytes); + + // Check return value to make sure not calling on empty contracts. + ensure!( + !info.value.is_empty() && info.value == bytes, + Error::::InvalidReturnValue + ); + Ok(()) + } + + fn get_origin() -> Option> { + T::EVM::get_origin() + } + + fn set_origin(origin: AccountIdOf) { + T::EVM::set_origin(origin); + } + + fn kill_origin() { + T::EVM::kill_origin(); + } + + fn push_xcm_origin(origin: AccountIdOf) { + T::EVM::push_xcm_origin(origin); + } + + fn pop_xcm_origin() { + T::EVM::pop_xcm_origin(); + } + + fn kill_xcm_origin() { + T::EVM::kill_xcm_origin(); + } + + fn get_real_or_xcm_origin() -> Option> { + T::EVM::get_real_or_xcm_origin() + } +} + +pub struct LiquidationEvmBridge(sp_std::marker::PhantomData); + +impl LiquidationEvmBridgeT for LiquidationEvmBridge { + fn liquidate( + context: InvokeContext, + collateral: EvmAddress, + repay_dest: EvmAddress, + amount: Balance, + min_repayment: Balance, + ) -> DispatchResult { + // liquidation contract method hash + let mut input = Into::::into(Action::Liquidate).to_be_bytes().to_vec(); + + // append collateral ERC20 address + input.extend_from_slice(H256::from(collateral).as_bytes()); + // append repay dest address + input.extend_from_slice(H256::from(repay_dest).as_bytes()); + // append collateral amount + input.extend_from_slice(H256::from_uint(&U256::from(amount)).as_bytes()); + // append minimum repayment amount + input.extend_from_slice(H256::from_uint(&U256::from(min_repayment)).as_bytes()); + + let info = T::EVM::execute( + context, + input, + Default::default(), + liquidation::LIQUIDATE.gas, + liquidation::LIQUIDATE.storage, + ExecutionMode::Execute, + )?; + + Pallet::::handle_exit_reason(info.exit_reason) + } + + fn on_collateral_transfer(context: InvokeContext, collateral: EvmAddress, amount: Balance) { + // liquidation contract method hash + let mut input = Into::::into(Action::OnCollateralTransfer).to_be_bytes().to_vec(); + // append collateral ERC20 address + input.extend_from_slice(H256::from(collateral).as_bytes()); + // append collateral amount + input.extend_from_slice(H256::from_uint(&U256::from(amount)).as_bytes()); + + let _ = T::EVM::execute( + context, + input, + Default::default(), + liquidation::ON_COLLATERAL_TRANSFER.gas, + liquidation::ON_COLLATERAL_TRANSFER.storage, + ExecutionMode::Execute, + ); + } + + fn on_repayment_refund(context: InvokeContext, collateral: EvmAddress, repayment: Balance) { + // liquidation contract method hash + let mut input = Into::::into(Action::OnRepaymentRefund).to_be_bytes().to_vec(); + // append collateral ERC20 address + input.extend_from_slice(H256::from(collateral).as_bytes()); + // append repayment amount + input.extend_from_slice(H256::from_uint(&U256::from(repayment)).as_bytes()); + + let _ = T::EVM::execute( + context, + input, + Default::default(), + liquidation::ON_REPAYMENT_REFUND.gas, + liquidation::ON_REPAYMENT_REFUND.storage, + ExecutionMode::Execute, + ); + } +} + +impl Pallet { + fn handle_exit_reason(exit_reason: ExitReason) -> Result<(), DispatchError> { + match exit_reason { + ExitReason::Succeed(ExitSucceed::Returned) => Ok(()), + ExitReason::Succeed(ExitSucceed::Stopped) => Ok(()), + ExitReason::Succeed(_) => Err(Error::::ExecutionFail.into()), + ExitReason::Revert(_) => Err(Error::::ExecutionRevert.into()), + ExitReason::Fatal(_) => Err(Error::::ExecutionFatal.into()), + ExitReason::Error(_) => Err(Error::::ExecutionError.into()), + } + } + + fn decode_string(output: Vec) -> Result, DispatchError> { + // output is 32-byte aligned and consists of 3 parts: + // - part 1: 32 byte, the offset of its description is passed in the position of + // the corresponding parameter or return value. + // - part 2: 32 byte, string length + // - part 3: string data + ensure!( + output.len() >= 64 && output.len() % 32 == 0, + Error::::InvalidReturnValue + ); + + let offset = U256::from_big_endian(&output[0..32]); + let length = U256::from_big_endian(&output[offset.as_usize()..offset.as_usize() + 32]); + ensure!( + // output is 32-byte aligned. ensure total_length >= offset + string length + string data length. + output.len() >= offset.as_usize() + 32 + length.as_usize(), + Error::::InvalidReturnValue + ); + + let mut data = Vec::new(); + data.extend_from_slice(&output[offset.as_usize() + 32..offset.as_usize() + 32 + length.as_usize()]); + + Ok(data.to_vec()) + } +} diff --git a/blockchain/modules/evm-bridge/src/mock.rs b/blockchain/modules/evm-bridge/src/mock.rs index f399b3e1..31f3aafc 100644 --- a/blockchain/modules/evm-bridge/src/mock.rs +++ b/blockchain/modules/evm-bridge/src/mock.rs @@ -1,235 +1,278 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! Mocks for the evm-bridge module. - -#![cfg(test)] - -use super::*; -use frame_support::{assert_ok, construct_runtime, ord_parameter_types, parameter_types}; -use frame_system::EnsureSignedBy; -use primitives::{evm::EvmAddress, ReserveIdentifier}; -use sp_core::{bytes::from_hex, crypto::AccountId32, H256}; -use sp_runtime::{testing::Header, traits::IdentityLookup}; -use sp_std::str::FromStr; -use support::{mocks::MockAddressMapping, AddressMapping}; - -pub type AccountId = AccountId32; -pub type BlockNumber = u64; -pub type Balance = u128; - -mod evm_bridge { - pub use super::super::*; -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -impl frame_system::Config for Runtime { - type BaseCallFilter = (); - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = BlockNumber; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxReserves: u32 = 50; -} - -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type MaxLocks = (); - type MaxReserves = MaxReserves; - type ReserveIdentifier = ReserveIdentifier; - type WeightInfo = (); -} - -parameter_types! { - pub const MinimumPeriod: u64 = 1000; -} - -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_types! { - pub const NewContractExtraBytes: u32 = 1; - pub NetworkContractSource: EvmAddress = alice_evm_addr(); -} - -ord_parameter_types! { - pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); - pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); - pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); - pub const StorageDepositPerByte: u128 = 10; - pub const DeveloperDeposit: u64 = 1000; - pub const DeploymentFee: u64 = 200; -} - -impl module_evm::Config for Runtime { - type AddressMapping = MockAddressMapping; - type Currency = Balances; - type TransferAll = (); - type NewContractExtraBytes = NewContractExtraBytes; - type StorageDepositPerByte = StorageDepositPerByte; - type Event = Event; - type Precompiles = (); - type ChainId = (); - type GasToWeight = (); - type ChargeTransactionPayment = (); - type NetworkContractOrigin = EnsureSignedBy; - type NetworkContractSource = NetworkContractSource; - - type DeveloperDeposit = DeveloperDeposit; - type DeploymentFee = DeploymentFee; - type TreasuryAccount = TreasuryAccount; - type FreeDeploymentOrigin = EnsureSignedBy; - - type Runner = module_evm::runner::stack::Runner; - type FindAuthor = (); - type WeightInfo = (); -} - -impl Config for Runtime { - type EVM = EVM; -} -pub type EvmBridgeModule = Pallet; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - EVMBridge: evm_bridge::{Pallet}, - EVM: module_evm::{Pallet, Config, Call, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - } -); - -pub struct ExtBuilder { - balances: Vec<(AccountId, Balance)>, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { balances: vec![] } - } -} - -pub fn erc20_address() -> EvmAddress { - EvmAddress::from_str("5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() -} - -pub fn alice() -> AccountId { - ::AddressMapping::get_account_id(&alice_evm_addr()) -} - -pub fn alice_evm_addr() -> EvmAddress { - EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap() -} - -pub fn bob() -> AccountId { - ::AddressMapping::get_account_id(&bob_evm_addr()) -} - -pub fn bob_evm_addr() -> EvmAddress { - EvmAddress::from_str("1000000000000000000000000000000000000002").unwrap() -} - -pub fn deploy_contracts() { - let code = from_hex(include!("./erc20_demo_contract")).unwrap(); - assert_ok!(EVM::create(Origin::signed(alice()), code, 0, 2_100_000, 10000)); - - System::assert_last_event(Event::EVM(module_evm::Event::Created( - alice_evm_addr(), - erc20_address(), - vec![module_evm::Log { - address: erc20_address(), - topics: vec![ - H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), - H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), - ], - data: H256::from_low_u64_be(10000).as_bytes().to_vec(), - }], - ))); - - assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), erc20_address())); -} - -impl ExtBuilder { - pub fn balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { - self.balances = balances; - self - } - - pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: self.balances.into_iter().collect::>(), - } - .assimilate_storage(&mut t) - .unwrap(); - - module_evm::GenesisConfig::::default() - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the evm-bridge module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + assert_ok, construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64}, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockAddressMapping, AddressMapping}; +use primitives::{evm::convert_decimals_to_evm, evm::EvmAddress, ReserveIdentifier}; +use sp_core::{crypto::AccountId32, H256}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; +pub use sp_std::str::FromStr; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +mod evm_bridge { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = module_support::SystemAccountStore; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1000>; + type WeightInfo = (); +} + +parameter_types! { + pub NetworkContractSource: EvmAddress = alice_evm_addr(); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); + pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); + pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); + pub const StorageDepositPerByte: u128 = convert_decimals_to_evm(10); +} + +impl module_evm::Config for Runtime { + type AddressMapping = MockAddressMapping; + type Currency = Balances; + type TransferAll = (); + type NewContractExtraBytes = ConstU32<1>; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = ConstU128<10>; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = (); + type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + + type DeveloperDeposit = ConstU128<1000>; + type PublicationFee = ConstU128<200>; + type TreasuryAccount = TreasuryAccount; + type FreePublicationOrigin = EnsureSignedBy; + + type Runner = module_evm::runner::stack::Runner; + type FindAuthor = (); + type WeightInfo = (); +} + +impl Config for Runtime { + type EVM = EVM; +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + EVMBridgeModule: evm_bridge, + EVM: module_evm, + Balances: pallet_balances, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![] } + } +} + +pub fn erc20_address() -> EvmAddress { + EvmAddress::from_str("5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() +} + +pub fn alice() -> AccountId { + ::AddressMapping::get_account_id(&alice_evm_addr()) +} + +pub fn alice_evm_addr() -> EvmAddress { + EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap() +} + +pub fn bob() -> AccountId { + ::AddressMapping::get_account_id(&bob_evm_addr()) +} + +pub fn bob_evm_addr() -> EvmAddress { + EvmAddress::from_str("1000000000000000000000000000000000000002").unwrap() +} + +pub const ALICE_BALANCE: u128 = 100_000_000_000_000_000_000_000u128; + +pub fn deploy_contracts() { + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/Erc20DemoContract2.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + assert_ok!(EVM::create( + RuntimeOrigin::signed(alice()), + code, + 0, + 2_100_000, + 10000, + vec![] + )); + + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![module_evm::Log { + address: erc20_address(), + topics: vec![ + H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), + ], + data: { + let mut buf = [0u8; 32]; + U256::from(ALICE_BALANCE).to_big_endian(&mut buf); + H256::from_slice(&buf).as_bytes().to_vec() + }, + }], + used_gas: 1235455, + used_storage: 5131, + })); + + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address() + )); +} + +pub fn deploy_liquidation_ok_contracts() { + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/LiquidationOk.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + assert_ok!(EVM::create( + RuntimeOrigin::signed(alice()), + code, + 0, + 2_100_000, + 10000, + vec![] + )); + + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![], + used_gas: 235330, + used_storage: 844, + })); + + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address() + )); +} + +// TODO: Add ts-tests directory +pub fn deploy_liquidation_err_contracts() { + let json: serde_json::Value = + serde_json::from_str(include_str!("../../../ts-tests/build/LiquidationErr.json")).unwrap(); + let code = hex::decode(json.get("bytecode").unwrap().as_str().unwrap()).unwrap(); + assert_ok!(EVM::create( + RuntimeOrigin::signed(alice()), + code, + 0, + 2_100_000, + 10000, + vec![] + )); + + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![], + used_gas: 228338, + used_storage: 818, + })); + + assert_ok!(EVM::publish_free( + RuntimeOrigin::signed(CouncilAccount::get()), + erc20_address() + )); +} + +impl ExtBuilder { + pub fn balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self.balances.into_iter().collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + module_evm::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/blockchain/modules/evm-bridge/src/tests.rs b/blockchain/modules/evm-bridge/src/tests.rs index c3aad1e4..3ab89c44 100644 --- a/blockchain/modules/evm-bridge/src/tests.rs +++ b/blockchain/modules/evm-bridge/src/tests.rs @@ -1,203 +1,357 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 . - -//! Unit tests for the evm-bridge module. - -#![cfg(test)] - -use super::*; -use frame_support::{assert_err, assert_ok}; -use mock::{ - alice, alice_evm_addr, bob, bob_evm_addr, deploy_contracts, erc20_address, EvmBridgeModule, ExtBuilder, Runtime, -}; - -#[test] -fn should_read_name() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_eq!( - >::name(InvokeContext { - contract: erc20_address(), - sender: Default::default(), - origin: Default::default(), - }), - Ok( - b"long string name, long string name, long string name, long string name, long string name" - .to_vec() - ) - ); - }); -} - -#[test] -fn should_read_symbol() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_eq!( - EvmBridgeModule::symbol(InvokeContext { - contract: erc20_address(), - sender: Default::default(), - origin: Default::default(), - }), - Ok(b"TestToken".to_vec()) - ); - }); -} - -#[test] -fn should_read_decimals() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_eq!( - EvmBridgeModule::decimals(InvokeContext { - contract: erc20_address(), - sender: Default::default(), - origin: Default::default(), - }), - Ok(17) - ); - }); -} - -#[test] -fn should_read_total_supply() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_eq!( - EvmBridgeModule::total_supply(InvokeContext { - contract: erc20_address(), - sender: Default::default(), - origin: Default::default(), - }), - Ok(10000) - ); - }); -} - -#[test] -fn should_read_balance_of() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - let context = InvokeContext { - contract: erc20_address(), - sender: Default::default(), - origin: Default::default(), - }; - - assert_eq!(EvmBridgeModule::balance_of(context, bob_evm_addr()), Ok(0)); - - assert_eq!(EvmBridgeModule::balance_of(context, alice_evm_addr()), Ok(10000)); - - assert_eq!(EvmBridgeModule::balance_of(context, bob_evm_addr()), Ok(0)); - }); -} - -#[test] -fn should_transfer() { - ExtBuilder::default() - .balances(vec![(alice(), 1_000_000_000_000_000_000), (bob(), 1_000_000_000_000_000_000)]) - .build() - .execute_with(|| { - deploy_contracts(); - assert_err!( - EvmBridgeModule::transfer( - InvokeContext { - contract: erc20_address(), - sender: bob_evm_addr(), - origin: bob_evm_addr(), - }, - alice_evm_addr(), - 10 - ), - Error::::ExecutionRevert - ); - - assert_ok!(EvmBridgeModule::transfer( - InvokeContext { - contract: erc20_address(), - sender: alice_evm_addr(), - origin: alice_evm_addr(), - }, - bob_evm_addr(), - 100 - )); - assert_eq!( - EvmBridgeModule::balance_of( - InvokeContext { - contract: erc20_address(), - sender: alice_evm_addr(), - origin: alice_evm_addr(), - }, - bob_evm_addr() - ), - Ok(100) - ); - - assert_ok!(EvmBridgeModule::transfer( - InvokeContext { - contract: erc20_address(), - sender: bob_evm_addr(), - origin: bob_evm_addr(), - }, - alice_evm_addr(), - 10 - )); - - assert_eq!( - EvmBridgeModule::balance_of( - InvokeContext { - contract: erc20_address(), - sender: alice_evm_addr(), - origin: bob_evm_addr(), - }, - bob_evm_addr() - ), - Ok(90) - ); - - assert_err!( - EvmBridgeModule::transfer( - InvokeContext { - contract: erc20_address(), - sender: bob_evm_addr(), - origin: bob_evm_addr(), - }, - alice_evm_addr(), - 100 - ), - Error::::ExecutionRevert - ); - }); -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the evm-bridge module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_err, assert_noop, assert_ok}; +use mock::*; + +#[test] +fn should_read_name() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_eq!( + EVMBridge::::name(InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: Default::default(), + }), + Ok( + b"long string name, long string name, long string name, long string name, long string name" + .to_vec() + ) + ); + }); +} + +#[test] +fn should_read_symbol() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_eq!( + EVMBridge::::symbol(InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: Default::default(), + }), + Ok(b"TestToken".to_vec()) + ); + }); +} + +#[test] +fn should_read_decimals() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_eq!( + EVMBridge::::decimals(InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: Default::default(), + }), + Ok(17) + ); + }); +} + +#[test] +fn should_read_total_supply() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_eq!( + EVMBridge::::total_supply(InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: Default::default(), + }), + Ok(ALICE_BALANCE) + ); + }); +} + +#[test] +fn should_read_balance_of() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + let context = InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: Default::default(), + }; + + assert_eq!(EVMBridge::::balance_of(context, bob_evm_addr()), Ok(0)); + + assert_eq!( + EVMBridge::::balance_of(context, alice_evm_addr()), + Ok(ALICE_BALANCE) + ); + + assert_eq!(EVMBridge::::balance_of(context, bob_evm_addr()), Ok(0)); + }); +} + +#[test] +fn should_transfer() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000), (bob(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + assert_err!( + EVMBridge::::transfer( + InvokeContext { + contract: erc20_address(), + sender: bob_evm_addr(), + origin: bob_evm_addr(), + }, + alice_evm_addr(), + 10 + ), + Error::::ExecutionRevert + ); + + assert_ok!(EVMBridge::::transfer( + InvokeContext { + contract: erc20_address(), + sender: alice_evm_addr(), + origin: alice_evm_addr(), + }, + bob_evm_addr(), + 100 + )); + assert_eq!( + EVMBridge::::balance_of( + InvokeContext { + contract: erc20_address(), + sender: alice_evm_addr(), + origin: alice_evm_addr(), + }, + bob_evm_addr() + ), + Ok(100) + ); + + assert_ok!(EVMBridge::::transfer( + InvokeContext { + contract: erc20_address(), + sender: bob_evm_addr(), + origin: bob_evm_addr(), + }, + alice_evm_addr(), + 10 + )); + + assert_eq!( + EVMBridge::::balance_of( + InvokeContext { + contract: erc20_address(), + sender: alice_evm_addr(), + origin: bob_evm_addr(), + }, + bob_evm_addr() + ), + Ok(90) + ); + + assert_err!( + EVMBridge::::transfer( + InvokeContext { + contract: erc20_address(), + sender: bob_evm_addr(), + origin: bob_evm_addr(), + }, + alice_evm_addr(), + 100 + ), + Error::::ExecutionRevert + ); + }); +} + +#[test] +fn liquidation_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_liquidation_ok_contracts(); + let collateral = EvmAddress::from_str("1000000000000000000000000000000000000111").unwrap(); + let repay_dest = EvmAddress::from_str("1000000000000000000000000000000000000112").unwrap(); + + assert_ok!(LiquidationEvmBridge::::liquidate( + InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: alice_evm_addr(), + }, + collateral, + repay_dest, + 100, + 100, + )); + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Executed { + from: Default::default(), + contract: erc20_address(), + logs: vec![module_evm::Log { + address: erc20_address(), + topics: vec![ + H256::from_str("0xf3fa0eaee8f258c23b013654df25d1527f98a5c7ccd5e951dd77caca400ef972").unwrap(), + ], + data: { + let mut buf = [0u8; 128]; + buf[12..32].copy_from_slice(collateral.as_bytes()); + buf[44..64].copy_from_slice(repay_dest.as_bytes()); + let mut amount_data = [0u8; 32]; + U256::from(100).to_big_endian(&mut amount_data); + buf[64..96].copy_from_slice(&amount_data); + buf[96..128].copy_from_slice(&amount_data); + buf.to_vec() + }, + }], + used_gas: 25083, + used_storage: 0, + })); + }); +} + +#[test] +fn on_collateral_transfer_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_liquidation_ok_contracts(); + let collateral = EvmAddress::from_str("1000000000000000000000000000000000000111").unwrap(); + LiquidationEvmBridge::::on_collateral_transfer( + InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: alice_evm_addr(), + }, + collateral, + 100, + ); + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Executed { + from: Default::default(), + contract: erc20_address(), + logs: vec![module_evm::Log { + address: erc20_address(), + topics: vec![ + H256::from_str("0xa5625c5568ddba471a5e1190863744239495ca35883ce7f3e7d3beea2e89be74").unwrap(), + ], + data: { + let mut buf = [0u8; 64]; + buf[12..32].copy_from_slice(collateral.as_bytes()); + let mut amount_data = [0u8; 32]; + U256::from(100).to_big_endian(&mut amount_data); + buf[32..64].copy_from_slice(&amount_data); + buf.to_vec() + }, + }], + used_gas: 23573, + used_storage: 0, + })); + }); +} + +#[test] +fn on_repayment_refund_works() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_liquidation_ok_contracts(); + let collateral = EvmAddress::from_str("1000000000000000000000000000000000000111").unwrap(); + LiquidationEvmBridge::::on_repayment_refund( + InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: alice_evm_addr(), + }, + collateral, + 100, + ); + System::assert_last_event(RuntimeEvent::EVM(module_evm::Event::Executed { + from: Default::default(), + contract: erc20_address(), + logs: vec![module_evm::Log { + address: erc20_address(), + topics: vec![ + H256::from_str("0x003d5a25faf4a774379f05de4f94d8967080f7e731902eb8f542b957a0712e18").unwrap(), + ], + data: { + let mut buf = [0u8; 64]; + buf[12..32].copy_from_slice(collateral.as_bytes()); + let mut amount_data = [0u8; 32]; + U256::from(100).to_big_endian(&mut amount_data); + buf[32..64].copy_from_slice(&amount_data); + buf.to_vec() + }, + }], + used_gas: 23595, + used_storage: 0, + })); + }); +} + +#[test] +fn liquidation_err_fails_as_expected() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_liquidation_err_contracts(); + let collateral = EvmAddress::from_str("1000000000000000000000000000000000000111").unwrap(); + let repay_dest = EvmAddress::from_str("1000000000000000000000000000000000000112").unwrap(); + + assert_noop!( + LiquidationEvmBridge::::liquidate( + InvokeContext { + contract: erc20_address(), + sender: Default::default(), + origin: alice_evm_addr(), + }, + collateral, + repay_dest, + 100, + 100, + ), + Error::::ExecutionRevert, + ); + }); +} From 5ba2cec45521fb38363c47e3268d486f40265d0b Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 00:21:01 +0800 Subject: [PATCH 05/21] Rename `Edfis Liquid` to `Moya Liquid` and separate LSEE and LEDF --- blockchain/modules/edfis-liquid/README.md | 5 -- .../Cargo.toml | 3 +- .../modules/moya-liquid-edf-earn/README.md | 5 ++ .../moya-liquid-edf-validators/Cargo.toml | 50 +++++++++++++++++++ .../moya-liquid-edf-validators/README.md | 5 ++ blockchain/modules/moya-liquid-edf/Cargo.toml | 50 +++++++++++++++++++ blockchain/modules/moya-liquid-edf/README.md | 5 ++ .../modules/moya-liquid-see-earn/Cargo.toml | 50 +++++++++++++++++++ .../modules/moya-liquid-see-earn/README.md | 5 ++ .../moya-liquid-see-validators/Cargo.toml | 50 +++++++++++++++++++ .../moya-liquid-see-validators/README.md | 5 ++ blockchain/modules/moya-liquid-see/Cargo.toml | 50 +++++++++++++++++++ blockchain/modules/moya-liquid-see/README.md | 5 ++ 13 files changed, 281 insertions(+), 7 deletions(-) delete mode 100644 blockchain/modules/edfis-liquid/README.md rename blockchain/modules/{edfis-liquid => moya-liquid-edf-earn}/Cargo.toml (89%) create mode 100644 blockchain/modules/moya-liquid-edf-earn/README.md create mode 100644 blockchain/modules/moya-liquid-edf-validators/Cargo.toml create mode 100644 blockchain/modules/moya-liquid-edf-validators/README.md create mode 100644 blockchain/modules/moya-liquid-edf/Cargo.toml create mode 100644 blockchain/modules/moya-liquid-edf/README.md create mode 100644 blockchain/modules/moya-liquid-see-earn/Cargo.toml create mode 100644 blockchain/modules/moya-liquid-see-earn/README.md create mode 100644 blockchain/modules/moya-liquid-see-validators/Cargo.toml create mode 100644 blockchain/modules/moya-liquid-see-validators/README.md create mode 100644 blockchain/modules/moya-liquid-see/Cargo.toml create mode 100644 blockchain/modules/moya-liquid-see/README.md diff --git a/blockchain/modules/edfis-liquid/README.md b/blockchain/modules/edfis-liquid/README.md deleted file mode 100644 index 1b3c09ff..00000000 --- a/blockchain/modules/edfis-liquid/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Edfis Liquid Staking Module - -## Overview - -Provides a liquid staking platform on Edfis. diff --git a/blockchain/modules/edfis-liquid/Cargo.toml b/blockchain/modules/moya-liquid-edf-earn/Cargo.toml similarity index 89% rename from blockchain/modules/edfis-liquid/Cargo.toml rename to blockchain/modules/moya-liquid-edf-earn/Cargo.toml index 0722ef59..25275881 100644 --- a/blockchain/modules/edfis-liquid/Cargo.toml +++ b/blockchain/modules/moya-liquid-edf-earn/Cargo.toml @@ -1,6 +1,5 @@ [package] -name = "module-edfis-liquid-staking" -description = "Provides a liquid staking platform on Edfis." +name = "module-moya-liquid-edf-earn" version = "0.9.81-dev" authors = ["Setheum Labs"] edition = "2021" diff --git a/blockchain/modules/moya-liquid-edf-earn/README.md b/blockchain/modules/moya-liquid-edf-earn/README.md new file mode 100644 index 00000000..0752fdce --- /dev/null +++ b/blockchain/modules/moya-liquid-edf-earn/README.md @@ -0,0 +1,5 @@ +# Moya Liquid EDF Earn Module + +## Overview + +Provides a liquid staking platform on Ethical DeFi for `EDF` tokens. diff --git a/blockchain/modules/moya-liquid-edf-validators/Cargo.toml b/blockchain/modules/moya-liquid-edf-validators/Cargo.toml new file mode 100644 index 00000000..97dcf189 --- /dev/null +++ b/blockchain/modules/moya-liquid-edf-validators/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-moya-liquid-edf-validators" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/moya-liquid-edf-validators/README.md b/blockchain/modules/moya-liquid-edf-validators/README.md new file mode 100644 index 00000000..777cf460 --- /dev/null +++ b/blockchain/modules/moya-liquid-edf-validators/README.md @@ -0,0 +1,5 @@ +# Moya Liquid EDF Validators Module + +## Overview + +Provides a liquid staking platform on Ethical DeFi for `EDF` tokens. diff --git a/blockchain/modules/moya-liquid-edf/Cargo.toml b/blockchain/modules/moya-liquid-edf/Cargo.toml new file mode 100644 index 00000000..88f3743a --- /dev/null +++ b/blockchain/modules/moya-liquid-edf/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-moya-liquid-edf" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/moya-liquid-edf/README.md b/blockchain/modules/moya-liquid-edf/README.md new file mode 100644 index 00000000..b62821b2 --- /dev/null +++ b/blockchain/modules/moya-liquid-edf/README.md @@ -0,0 +1,5 @@ +# Moya Liquid EDF Module + +## Overview + +Provides a liquid staking platform on Ethical DeFi for `EDF` tokens. diff --git a/blockchain/modules/moya-liquid-see-earn/Cargo.toml b/blockchain/modules/moya-liquid-see-earn/Cargo.toml new file mode 100644 index 00000000..3e56c976 --- /dev/null +++ b/blockchain/modules/moya-liquid-see-earn/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-moya-liquid-see-earn" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/moya-liquid-see-earn/README.md b/blockchain/modules/moya-liquid-see-earn/README.md new file mode 100644 index 00000000..0752fdce --- /dev/null +++ b/blockchain/modules/moya-liquid-see-earn/README.md @@ -0,0 +1,5 @@ +# Moya Liquid EDF Earn Module + +## Overview + +Provides a liquid staking platform on Ethical DeFi for `EDF` tokens. diff --git a/blockchain/modules/moya-liquid-see-validators/Cargo.toml b/blockchain/modules/moya-liquid-see-validators/Cargo.toml new file mode 100644 index 00000000..d89fb546 --- /dev/null +++ b/blockchain/modules/moya-liquid-see-validators/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-moya-liquid-see-validators" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/moya-liquid-see-validators/README.md b/blockchain/modules/moya-liquid-see-validators/README.md new file mode 100644 index 00000000..a8fe17ec --- /dev/null +++ b/blockchain/modules/moya-liquid-see-validators/README.md @@ -0,0 +1,5 @@ +# Moya Liquid SEE Validators Module + +## Overview + +Provides a liquid staking platform on Ethical DeFi for `SEE` tokens. diff --git a/blockchain/modules/moya-liquid-see/Cargo.toml b/blockchain/modules/moya-liquid-see/Cargo.toml new file mode 100644 index 00000000..1b4f3254 --- /dev/null +++ b/blockchain/modules/moya-liquid-see/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-moya-liquid-see" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/moya-liquid-see/README.md b/blockchain/modules/moya-liquid-see/README.md new file mode 100644 index 00000000..c11d84ec --- /dev/null +++ b/blockchain/modules/moya-liquid-see/README.md @@ -0,0 +1,5 @@ +# Edfis Liquid SEE Module + +## Overview + +Provides a liquid staking platform on Ethical DeFi for `SEE` tokens. From b9c78a84ed7138b8968a2a680c46f85a313b410f Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 00:22:27 +0800 Subject: [PATCH 06/21] ECDP Slick USD Modules --- .../modules/ecdp-slick-usd-engine/README.md | 5 -- .../Cargo.toml | 3 +- blockchain/modules/ecdp-ussd-engine/README.md | 5 ++ blockchain/modules/ecdp-ussd-loans/Cargo.toml | 50 +++++++++++++++++++ blockchain/modules/ecdp-ussd-loans/README.md | 5 ++ .../modules/ecdp-ussd-treasury/Cargo.toml | 50 +++++++++++++++++++ .../modules/ecdp-ussd-treasury/README.md | 5 ++ 7 files changed, 116 insertions(+), 7 deletions(-) delete mode 100644 blockchain/modules/ecdp-slick-usd-engine/README.md rename blockchain/modules/{ecdp-slick-usd-engine => ecdp-ussd-engine}/Cargo.toml (90%) create mode 100644 blockchain/modules/ecdp-ussd-engine/README.md create mode 100644 blockchain/modules/ecdp-ussd-loans/Cargo.toml create mode 100644 blockchain/modules/ecdp-ussd-loans/README.md create mode 100644 blockchain/modules/ecdp-ussd-treasury/Cargo.toml create mode 100644 blockchain/modules/ecdp-ussd-treasury/README.md diff --git a/blockchain/modules/ecdp-slick-usd-engine/README.md b/blockchain/modules/ecdp-slick-usd-engine/README.md deleted file mode 100644 index a28db9b5..00000000 --- a/blockchain/modules/ecdp-slick-usd-engine/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Edfis Liquid Staking Module - -## Overview - -Provides USD-Pegged ECDP Slick USD Stablecoin. diff --git a/blockchain/modules/ecdp-slick-usd-engine/Cargo.toml b/blockchain/modules/ecdp-ussd-engine/Cargo.toml similarity index 90% rename from blockchain/modules/ecdp-slick-usd-engine/Cargo.toml rename to blockchain/modules/ecdp-ussd-engine/Cargo.toml index 47a1948a..e7a05660 100644 --- a/blockchain/modules/ecdp-slick-usd-engine/Cargo.toml +++ b/blockchain/modules/ecdp-ussd-engine/Cargo.toml @@ -1,6 +1,5 @@ [package] -name = "ecdp-slick-usd-engine" -description = "Provides USD-Pegged ECDP Slick USD Stablecoin." +name = "module-ecdp-ussd-engine" version = "0.9.81-dev" authors = ["Setheum Labs"] edition = "2021" diff --git a/blockchain/modules/ecdp-ussd-engine/README.md b/blockchain/modules/ecdp-ussd-engine/README.md new file mode 100644 index 00000000..2e1c8a5d --- /dev/null +++ b/blockchain/modules/ecdp-ussd-engine/README.md @@ -0,0 +1,5 @@ +# ECDP Slick USD Engine Module + +## Overview + +Provides USD-Pegged ECDP `Slick USD (USSD)` Stablecoin on Ethical DeFi. diff --git a/blockchain/modules/ecdp-ussd-loans/Cargo.toml b/blockchain/modules/ecdp-ussd-loans/Cargo.toml new file mode 100644 index 00000000..b3edf82e --- /dev/null +++ b/blockchain/modules/ecdp-ussd-loans/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-ecdp-ussd-loans" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/ecdp-ussd-loans/README.md b/blockchain/modules/ecdp-ussd-loans/README.md new file mode 100644 index 00000000..b8bf3d7d --- /dev/null +++ b/blockchain/modules/ecdp-ussd-loans/README.md @@ -0,0 +1,5 @@ +# ECDP Slick USD Loans Module + +## Overview + +Provides USD-Pegged ECDP `Slick USD (USSD)` Stablecoin on Ethical DeFi. diff --git a/blockchain/modules/ecdp-ussd-treasury/Cargo.toml b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml new file mode 100644 index 00000000..82caa87a --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-ecdp-ussd-treasury" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/ecdp-ussd-treasury/README.md b/blockchain/modules/ecdp-ussd-treasury/README.md new file mode 100644 index 00000000..b08f666c --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/README.md @@ -0,0 +1,5 @@ +# ECDP Slick USD Treasury Module + +## Overview + +Provides USD-Pegged ECDP `Slick USD (USSD)` Stablecoin on Ethical DeFi. From 742a86ac8c23f5c3130eedcc64d7845e9d55f110 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 00:22:52 +0800 Subject: [PATCH 07/21] ECDP Setter Modules --- .../Cargo.toml | 3 +- blockchain/modules/ecdp-setr-engine/README.md | 5 ++ blockchain/modules/ecdp-setr-loans/Cargo.toml | 50 +++++++++++++++++++ blockchain/modules/ecdp-setr-loans/README.md | 5 ++ .../modules/ecdp-setr-treasury/Cargo.toml | 50 +++++++++++++++++++ .../modules/ecdp-setr-treasury/README.md | 5 ++ .../modules/ecdp-setter-engine/README.md | 5 -- 7 files changed, 116 insertions(+), 7 deletions(-) rename blockchain/modules/{ecdp-setter-engine => ecdp-setr-engine}/Cargo.toml (90%) create mode 100644 blockchain/modules/ecdp-setr-engine/README.md create mode 100644 blockchain/modules/ecdp-setr-loans/Cargo.toml create mode 100644 blockchain/modules/ecdp-setr-loans/README.md create mode 100644 blockchain/modules/ecdp-setr-treasury/Cargo.toml create mode 100644 blockchain/modules/ecdp-setr-treasury/README.md delete mode 100644 blockchain/modules/ecdp-setter-engine/README.md diff --git a/blockchain/modules/ecdp-setter-engine/Cargo.toml b/blockchain/modules/ecdp-setr-engine/Cargo.toml similarity index 90% rename from blockchain/modules/ecdp-setter-engine/Cargo.toml rename to blockchain/modules/ecdp-setr-engine/Cargo.toml index 4d80d66f..2b200a9d 100644 --- a/blockchain/modules/ecdp-setter-engine/Cargo.toml +++ b/blockchain/modules/ecdp-setr-engine/Cargo.toml @@ -1,6 +1,5 @@ [package] -name = "module-ecdp-setter-engine" -description = "Provides Unpegged ECDP Setter Stablecoin." +name = "module-ecdp-setr-engine" version = "0.9.81-dev" authors = ["Setheum Labs"] edition = "2021" diff --git a/blockchain/modules/ecdp-setr-engine/README.md b/blockchain/modules/ecdp-setr-engine/README.md new file mode 100644 index 00000000..51e4cc75 --- /dev/null +++ b/blockchain/modules/ecdp-setr-engine/README.md @@ -0,0 +1,5 @@ +# ECDP Setter Engine Module + +## Overview + +Provides Unegged ECDP `Setter (SETR)` Stablecoin on Ethical DeFi. diff --git a/blockchain/modules/ecdp-setr-loans/Cargo.toml b/blockchain/modules/ecdp-setr-loans/Cargo.toml new file mode 100644 index 00000000..60c4db9a --- /dev/null +++ b/blockchain/modules/ecdp-setr-loans/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-ecdp-setr-loans" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/ecdp-setr-loans/README.md b/blockchain/modules/ecdp-setr-loans/README.md new file mode 100644 index 00000000..44942a6c --- /dev/null +++ b/blockchain/modules/ecdp-setr-loans/README.md @@ -0,0 +1,5 @@ +# ECDP SetterLoans Module + +## Overview + +Provides Unegged ECDP `Setter (SETR)` Stablecoin on Ethical DeFi. diff --git a/blockchain/modules/ecdp-setr-treasury/Cargo.toml b/blockchain/modules/ecdp-setr-treasury/Cargo.toml new file mode 100644 index 00000000..ec3046da --- /dev/null +++ b/blockchain/modules/ecdp-setr-treasury/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-ecdp-setr-treasury" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/ecdp-setr-treasury/README.md b/blockchain/modules/ecdp-setr-treasury/README.md new file mode 100644 index 00000000..7968ee1e --- /dev/null +++ b/blockchain/modules/ecdp-setr-treasury/README.md @@ -0,0 +1,5 @@ +# ECDP SetterTreasury Module + +## Overview + +Provides Unegged ECDP `Setter (SETR)` Stablecoin on Ethical DeFi. diff --git a/blockchain/modules/ecdp-setter-engine/README.md b/blockchain/modules/ecdp-setter-engine/README.md deleted file mode 100644 index 1b3c09ff..00000000 --- a/blockchain/modules/ecdp-setter-engine/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Edfis Liquid Staking Module - -## Overview - -Provides a liquid staking platform on Edfis. From 01fab79e1cf8202a474ede6ab2caf8450ccc131c Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 00:23:15 +0800 Subject: [PATCH 08/21] ECDP Auctions Module --- blockchain/modules/ecdp-auctions/Cargo.toml | 50 +++++++++++++++++++++ blockchain/modules/ecdp-auctions/README.md | 5 +++ 2 files changed, 55 insertions(+) create mode 100644 blockchain/modules/ecdp-auctions/Cargo.toml create mode 100644 blockchain/modules/ecdp-auctions/README.md diff --git a/blockchain/modules/ecdp-auctions/Cargo.toml b/blockchain/modules/ecdp-auctions/Cargo.toml new file mode 100644 index 00000000..ab1c301a --- /dev/null +++ b/blockchain/modules/ecdp-auctions/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-ecdp-auctions" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/ecdp-auctions/README.md b/blockchain/modules/ecdp-auctions/README.md new file mode 100644 index 00000000..db9ef649 --- /dev/null +++ b/blockchain/modules/ecdp-auctions/README.md @@ -0,0 +1,5 @@ +# ECDP Auctions Module + +## Overview + +Provides an Auctions protocol for ECDP Stablecoin on Ethical DeFi. From e81d0f6fca1a9db62b305c481d678b0f7be99c1c Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 00:46:20 +0800 Subject: [PATCH 09/21] Add ECDP Emergency Shutdown Module # ECDP Emergency Shutdown Module ## Overview Provides an Emergency Shutdown protocol for ECDP Stablecoin on Ethical DeFi. When a black swan occurs such as price plunge or fatal bug, the highest priority is to minimize user losses as much as possible. The Emergency Shutdown Module enables three types of Emergency Shutdown's which are: - Setter Emergency Shutdown: To Shutdown the Setter (SETR) Stablecoin ECDP System. - Slick USD Emergency Shutdown: To Shutdown the Slick USD (USSD) Stablecoin ECDP System. When the decision to shutdown the system is made, emergency shutdown module needs to trigger all related modules to halt, and start a series of operations including close some user entry, freeze feed prices, run offchain worker to settle CDPs that have debit, cancel all activities in the auctions module, when debits and gaps are settled, the stable currency holders are allowed to refund the remaining collateral assets. --- .../ecdp-emergency-shutdown/Cargo.toml | 50 +++++++++++++++++++ .../modules/ecdp-emergency-shutdown/README.md | 11 ++++ 2 files changed, 61 insertions(+) create mode 100644 blockchain/modules/ecdp-emergency-shutdown/Cargo.toml create mode 100644 blockchain/modules/ecdp-emergency-shutdown/README.md diff --git a/blockchain/modules/ecdp-emergency-shutdown/Cargo.toml b/blockchain/modules/ecdp-emergency-shutdown/Cargo.toml new file mode 100644 index 00000000..5482f4cc --- /dev/null +++ b/blockchain/modules/ecdp-emergency-shutdown/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "module-ecdp-emergency-shutdown" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +scale-info = { workspace = true } +serde = { workspace = true, optional = true } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "serde", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/ecdp-emergency-shutdown/README.md b/blockchain/modules/ecdp-emergency-shutdown/README.md new file mode 100644 index 00000000..e4773b1b --- /dev/null +++ b/blockchain/modules/ecdp-emergency-shutdown/README.md @@ -0,0 +1,11 @@ +# ECDP Emergency Shutdown Module + +## Overview + +Provides an Emergency Shutdown protocol for ECDP Stablecoin on Ethical DeFi. When a black swan occurs such as price plunge or fatal bug, the highest priority is to minimize user losses as much as possible. + +The Emergency Shutdown Module enables three types of Emergency Shutdown's which are: +- Setter Emergency Shutdown: To Shutdown the Setter (SETR) Stablecoin ECDP System. +- Slick USD Emergency Shutdown: To Shutdown the Slick USD (USSD) Stablecoin ECDP System. + +When the decision to shutdown the system is made, emergency shutdown module needs to trigger all related modules to halt, and start a series of operations including close some user entry, freeze feed prices, run offchain worker to settle CDPs that have debit, cancel all activities in the auctions module, when debits and gaps are settled, the stable currency holders are allowed to refund the remaining collateral assets. From 5e2b654c1e9912eeea67b7736f63277027b1c80c Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 01:09:21 +0800 Subject: [PATCH 10/21] Init new `EdfisSwap` module, rename the old one to `EdfisSwapLegacy`. --- .../modules/edfis-swap-legacy/Cargo.toml | 43 + .../modules/edfis-swap-legacy/README.md | 7 + .../modules/edfis-swap-legacy/src/lib.rs | 1546 +++++++++++++ .../modules/edfis-swap-legacy/src/mock.rs | 222 ++ .../modules/edfis-swap-legacy/src/tests.rs | 1997 +++++++++++++++++ .../modules/edfis-swap-legacy/src/weights.rs | 241 ++ blockchain/modules/edfis-swap/README.md | 2 +- blockchain/modules/edfis-swap/src/lib.rs | 7 +- blockchain/modules/evm/src/bench/mock.rs | 4 +- .../modules/transaction-payment/src/mock.rs | 8 +- .../modules/transaction-payment/src/tests.rs | 74 +- .../runtime/common/src/precompile/mock.rs | 8 +- .../runtime/common/src/precompile/tests.rs | 26 +- blockchain/runtime/src/benchmarking/dex.rs | 14 +- .../runtime/src/benchmarking/serp_setmint.rs | 2 +- blockchain/runtime/src/lib.rs | 14 +- blockchain/runtime/src/weights/mod.rs | 2 +- blockchain/runtime/src/weights/module_dex.rs | 6 +- 18 files changed, 4139 insertions(+), 84 deletions(-) create mode 100644 blockchain/modules/edfis-swap-legacy/Cargo.toml create mode 100644 blockchain/modules/edfis-swap-legacy/README.md create mode 100644 blockchain/modules/edfis-swap-legacy/src/lib.rs create mode 100644 blockchain/modules/edfis-swap-legacy/src/mock.rs create mode 100644 blockchain/modules/edfis-swap-legacy/src/tests.rs create mode 100644 blockchain/modules/edfis-swap-legacy/src/weights.rs diff --git a/blockchain/modules/edfis-swap-legacy/Cargo.toml b/blockchain/modules/edfis-swap-legacy/Cargo.toml new file mode 100644 index 00000000..13f8a2ea --- /dev/null +++ b/blockchain/modules/edfis-swap-legacy/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "module-edfis-swap-legacy" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-std = { workspace = true } +orml-traits = { workspace = true } + +module-support = { workspace = true } +primitives = { workspace = true } + +[dev-dependencies] +orml-tokens = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "primitives/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/blockchain/modules/edfis-swap-legacy/README.md b/blockchain/modules/edfis-swap-legacy/README.md new file mode 100644 index 00000000..6d92dfe7 --- /dev/null +++ b/blockchain/modules/edfis-swap-legacy/README.md @@ -0,0 +1,7 @@ +بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +# Edfis Swap Legacy Module + +## Overview + +Ethical DeFi's Built-in decentralized exchange `SwapDex` (Swap Exchange) module, the swap mechanism refers to the design of `Uniswap V2` with additional features and functionalities. In addition to being used for trading, DEX also participates in `ECDP liquidation`, which is faster than Liquidation By Auction when the liquidity is sufficient. diff --git a/blockchain/modules/edfis-swap-legacy/src/lib.rs b/blockchain/modules/edfis-swap-legacy/src/lib.rs new file mode 100644 index 00000000..6f4cd675 --- /dev/null +++ b/blockchain/modules/edfis-swap-legacy/src/lib.rs @@ -0,0 +1,1546 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! # DEX Module +//! +//! ## Overview +//! +//! Ethical DeFi's Built-in decentralized exchange `SwapDex` (Swap Exchange) module, the swap +//! mechanism refers to the design of `Uniswap V2` with additional features and functionalities. +//! In addition to being used for trading, DEX also participates in `ECDP liquidation`, +//! which is faster than Liquidation By Auction when the liquidity is sufficient. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] +#![allow(clippy::unused_unit)] +#![allow(clippy::collapsible_if)] + +use frame_support::{pallet_prelude::*, transactional, PalletId}; +use frame_system::pallet_prelude::*; +use module_support::{SwapDexIncentives, SwapManager, Erc20InfoMapping, ExchangeRate, Ratio, SwapLimit}; +use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; +use parity_scale_codec::MaxEncodedLen; +use primitives::{Balance, CurrencyId, TradingPair}; +use scale_info::TypeInfo; +use sp_core::{H160, U256}; +use sp_runtime::{ + traits::{AccountIdConversion, One, Saturating, Zero}, + ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, SaturatedConversion, +}; +use sp_std::{prelude::*, vec}; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +/// Parameters of TradingPair in Provisioning status +#[derive(Encode, Decode, Clone, Copy, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct ProvisioningParameters { + /// limit contribution per time. + min_contribution: (Balance, Balance), + /// target provision that trading pair could to be Enabled. + target_provision: (Balance, Balance), + /// accumulated provision amount for this Provisioning trading pair. + accumulated_provision: (Balance, Balance), + /// The number of block that status can be converted to Enabled. + not_before: BlockNumber, +} + +/// Status for TradingPair +#[derive(Clone, Copy, Encode, Decode, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub enum TradingPairStatus { + /// Default status, + /// can withdraw liquidity, re-enable and list this trading pair. + Disabled, + /// TradingPair is Provisioning, + /// can add provision and disable this trading pair. + Provisioning(ProvisioningParameters), + /// TradingPair is Enabled, + /// can add/remove liquidity, trading and disable this trading pair. + Enabled, +} + +impl Default for TradingPairStatus { + fn default() -> Self { + Self::Disabled + } +} + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency for transfer currencies + type Currency: MultiCurrencyExtended; + + /// Trading fee rate + /// The first item of the tuple is the numerator of the fee rate, second + /// item is the denominator, fee_rate = numerator / denominator, + /// use (u32, u32) over `Rate` type to minimize internal division + /// operation. + #[pallet::constant] + type GetExchangeFee: Get<(u32, u32)>; + + /// The limit for length of trading path + #[pallet::constant] + type TradingPathLimit: Get; + + /// The DEX's module id, keep all assets in DEX. + #[pallet::constant] + type PalletId: Get; + + /// Mapping between CurrencyId and ERC20 address so user can use Erc20 + /// address as LP token. + type Erc20InfoMapping: Erc20InfoMapping; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + + /// DEX incentives + type SwapDexIncentives: SwapDexIncentives; + + /// The origin which may list, enable or disable trading pairs. + type ListingOrigin: EnsureOrigin; + + /// The extended provisioning blocks since the `not_before` of provisioning. + #[pallet::constant] + type ExtendedProvisioningBlocks: Get>; + + /// Event handler which calls when update liquidity pool. + type OnLiquidityPoolUpdated: Happened<(TradingPair, Balance, Balance)>; + } + + #[pallet::error] + pub enum Error { + /// Trading pair is already Enabled + AlreadyEnabled, + /// Trading pair must be in Enabled status + MustBeEnabled, + /// Trading pair must be in Provisioning status + MustBeProvisioning, + /// Trading pair must be in Disabled status + MustBeDisabled, + /// This trading pair is not allowed to be listed + NotAllowedList, + /// The increment of provision is invalid + InvalidContributionIncrement, + /// The increment of liquidity is invalid + InvalidLiquidityIncrement, + /// Invalid currency id + InvalidCurrencyId, + /// Invalid trading path length + InvalidTradingPathLength, + /// Target amount is less to min_target_amount + InsufficientTargetAmount, + /// Supply amount is more than max_supply_amount + ExcessiveSupplyAmount, + /// Liquidity is not enough + InsufficientLiquidity, + /// The supply amount is zero + ZeroSupplyAmount, + /// The target amount is zero + ZeroTargetAmount, + /// The share increment is unacceptable + UnacceptableShareIncrement, + /// The liquidity withdrawn is unacceptable + UnacceptableLiquidityWithdrawn, + /// The swap dosen't meet the invariant check + InvariantCheckFailed, + /// The Provision is unqualified to be converted to `Enabled` + UnqualifiedProvision, + /// Trading pair is still provisioning + StillProvisioning, + /// The Asset unregistered. + AssetUnregistered, + /// The trading path is invalid + InvalidTradingPath, + /// Not allowed to refund provision + NotAllowedRefund, + /// Cannot swap + CannotSwap, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// add provision success + AddProvision { + who: T::AccountId, + currency_0: CurrencyId, + contribution_0: Balance, + currency_1: CurrencyId, + contribution_1: Balance, + }, + /// Add liquidity success. + AddLiquidity { + who: T::AccountId, + currency_0: CurrencyId, + pool_0: Balance, + currency_1: CurrencyId, + pool_1: Balance, + share_increment: Balance, + }, + /// Remove liquidity from the trading pool success. + RemoveLiquidity { + who: T::AccountId, + currency_0: CurrencyId, + pool_0: Balance, + currency_1: CurrencyId, + pool_1: Balance, + share_decrement: Balance, + }, + /// Use supply currency to swap target currency. + Swap { + trader: T::AccountId, + path: Vec, + liquidity_changes: Vec, + }, + /// Enable trading pair. + EnableTradingPair { trading_pair: TradingPair }, + /// List provisioning trading pair. + ListProvisioning { trading_pair: TradingPair }, + /// Disable trading pair. + DisableTradingPair { trading_pair: TradingPair }, + /// Provisioning trading pair convert to Enabled. + ProvisioningToEnabled { + trading_pair: TradingPair, + pool_0: Balance, + pool_1: Balance, + share_amount: Balance, + }, + /// refund provision success + RefundProvision { + who: T::AccountId, + currency_0: CurrencyId, + contribution_0: Balance, + currency_1: CurrencyId, + contribution_1: Balance, + }, + /// Provisioning trading pair aborted. + ProvisioningAborted { + trading_pair: TradingPair, + accumulated_provision_0: Balance, + accumulated_provision_1: Balance, + }, + } + + /// Liquidity pool for TradingPair. + /// + /// LiquidityPool: map TradingPair => (Balance, Balance) + #[pallet::storage] + #[pallet::getter(fn liquidity_pool)] + pub type LiquidityPool = StorageMap<_, Twox64Concat, TradingPair, (Balance, Balance), ValueQuery>; + + /// Status for TradingPair. + /// + /// TradingPairStatuses: map TradingPair => TradingPairStatus + #[pallet::storage] + #[pallet::getter(fn trading_pair_statuses)] + pub type TradingPairStatuses = + StorageMap<_, Twox64Concat, TradingPair, TradingPairStatus>, ValueQuery>; + + /// Provision of TradingPair by AccountId. + /// + /// ProvisioningPool: double_map TradingPair, AccountId => (Balance, + /// Balance) + #[pallet::storage] + #[pallet::getter(fn provisioning_pool)] + pub type ProvisioningPool = + StorageDoubleMap<_, Twox64Concat, TradingPair, Twox64Concat, T::AccountId, (Balance, Balance), ValueQuery>; + + /// Initial exchange rate, used to calculate the dex share amount for founders of provisioning + /// + /// InitialShareExchangeRates: map TradingPair => (ExchangeRate, ExchangeRate) + #[pallet::storage] + #[pallet::getter(fn initial_share_exchange_rates)] + pub type InitialShareExchangeRates = + StorageMap<_, Twox64Concat, TradingPair, (ExchangeRate, ExchangeRate), ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub initial_listing_trading_pairs: + Vec<(TradingPair, (Balance, Balance), (Balance, Balance), BlockNumberFor)>, + pub initial_enabled_trading_pairs: Vec, + pub initial_added_liquidity_pools: Vec<(T::AccountId, Vec<(TradingPair, (Balance, Balance))>)>, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.initial_listing_trading_pairs.iter().for_each( + |(trading_pair, min_contribution, target_provision, not_before)| { + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::Provisioning(ProvisioningParameters { + min_contribution: *min_contribution, + target_provision: *target_provision, + accumulated_provision: Default::default(), + not_before: *not_before, + }), + ); + }, + ); + + self.initial_enabled_trading_pairs.iter().for_each(|trading_pair| { + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::<_, _>::Enabled); + }); + + self.initial_added_liquidity_pools + .iter() + .for_each(|(who, trading_pairs_data)| { + trading_pairs_data + .iter() + .for_each(|(trading_pair, (deposit_amount_0, deposit_amount_1))| { + let result = match >::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Enabled => >::do_add_liquidity( + who, + trading_pair.first(), + trading_pair.second(), + *deposit_amount_0, + *deposit_amount_1, + Default::default(), + false, + ), + _ => Err(Error::::MustBeEnabled.into()), + }; + + assert!(result.is_ok(), "genesis add lidquidity pool failed."); + }); + }); + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Trading with DEX, swap with exact supply amount + /// + /// - `path`: trading path. + /// - `supply_amount`: exact supply amount. + /// - `min_target_amount`: acceptable minimum target amount. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::swap_with_exact_supply(path.len() as u32))] + pub fn swap_with_exact_supply( + origin: OriginFor, + path: Vec, + #[pallet::compact] supply_amount: Balance, + #[pallet::compact] min_target_amount: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_swap_with_exact_supply(&who, &path, supply_amount, min_target_amount)?; + Ok(()) + } + + /// Trading with DEX, swap with exact target amount + /// + /// - `path`: trading path. + /// - `target_amount`: exact target amount. + /// - `max_supply_amount`: acceptable maximum supply amount. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::swap_with_exact_target(path.len() as u32))] + pub fn swap_with_exact_target( + origin: OriginFor, + path: Vec, + #[pallet::compact] target_amount: Balance, + #[pallet::compact] max_supply_amount: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_swap_with_exact_target(&who, &path, target_amount, max_supply_amount)?; + Ok(()) + } + + /// Add liquidity to Enabled trading pair. + /// - Add provision success will record the provision, issue shares to caller in the initial + /// exchange rate when trading pair convert to Enabled. + /// + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + /// - `max_amount_a`: maximum amount of currency_id_a is allowed to inject to liquidity + /// pool. + /// - `max_amount_b`: maximum amount of currency_id_b is allowed to inject to liquidity + /// pool. + /// - `min_share_increment`: minimum acceptable share amount. + /// - `stake_increment_share`: indicates whether to stake increased dex share to earn + /// incentives + #[pallet::call_index(2)] + #[pallet::weight(if *stake_increment_share { + ::WeightInfo::add_liquidity_and_stake() + } else { + ::WeightInfo::add_liquidity() + })] + pub fn add_liquidity( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] max_amount_a: Balance, + #[pallet::compact] max_amount_b: Balance, + #[pallet::compact] min_share_increment: Balance, + stake_increment_share: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_add_liquidity( + &who, + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + min_share_increment, + stake_increment_share, + )?; + Ok(()) + } + + /// Add provision to Provisioning trading pair. + /// If succeed, will record the provision, but shares issuing will happen after the + /// trading pair convert to Enabled status. + /// + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + /// - `amount_a`: provision amount for currency_id_a. + /// - `amount_b`: provision amount for currency_id_b. + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::add_provision())] + pub fn add_provision( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] amount_a: Balance, + #[pallet::compact] amount_b: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_add_provision(&who, currency_id_a, currency_id_b, amount_a, amount_b)?; + Ok(()) + } + + /// Claim dex share for founders who have participated in trading pair provision. + /// + /// - `owner`: founder account. + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::claim_dex_share())] + pub fn claim_dex_share( + origin: OriginFor, + owner: T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_claim_dex_share(&owner, currency_id_a, currency_id_b)?; + Ok(()) + } + + /// Remove liquidity from specific liquidity pool in the form of burning + /// shares, and withdrawing currencies in trading pairs from liquidity + /// pool in proportion, and withdraw liquidity incentive interest. + /// + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + /// - `remove_share`: liquidity amount to remove. + /// - `min_withdrawn_a`: minimum acceptable withrawn for currency_id_a. + /// - `min_withdrawn_b`: minimum acceptable withrawn for currency_id_b. + /// - `by_unstake`: this flag indicates whether to withdraw share which is on incentives. + #[pallet::call_index(5)] + #[pallet::weight(if *by_unstake { + ::WeightInfo::remove_liquidity_by_unstake() + } else { + ::WeightInfo::remove_liquidity() + })] + pub fn remove_liquidity( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] remove_share: Balance, + #[pallet::compact] min_withdrawn_a: Balance, + #[pallet::compact] min_withdrawn_b: Balance, + by_unstake: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_remove_liquidity( + &who, + currency_id_a, + currency_id_b, + remove_share, + min_withdrawn_a, + min_withdrawn_b, + by_unstake, + )?; + Ok(()) + } + + /// List a new provisioning trading pair. + #[pallet::call_index(6)] + #[pallet::weight((::WeightInfo::list_provisioning(), DispatchClass::Operational))] + pub fn list_provisioning( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] min_contribution_a: Balance, + #[pallet::compact] min_contribution_b: Balance, + #[pallet::compact] target_provision_a: Balance, + #[pallet::compact] target_provision_b: Balance, + #[pallet::compact] not_before: BlockNumberFor, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Disabled + ), + Error::::MustBeDisabled + ); + ensure!( + T::Currency::total_issuance(trading_pair.dex_share_currency_id()).is_zero() + && ProvisioningPool::::iter_prefix(trading_pair).next().is_none(), + Error::::NotAllowedList + ); + + let check_asset_registry = |currency_id: CurrencyId| match currency_id { + CurrencyId::Erc20(_) | CurrencyId::ForeignAsset(_) => { + T::Erc20InfoMapping::name(currency_id) + .map(|_| ()) + .ok_or(Error::::AssetUnregistered) + } + CurrencyId::Token(_) | CurrencyId::DexShare(_, _) => Ok(()), /* No registration required */ + }; + check_asset_registry(currency_id_a)?; + check_asset_registry(currency_id_b)?; + + let (min_contribution, target_provision) = if currency_id_a == trading_pair.first() { + ( + (min_contribution_a, min_contribution_b), + (target_provision_a, target_provision_b), + ) + } else { + ( + (min_contribution_b, min_contribution_a), + (target_provision_b, target_provision_a), + ) + }; + + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::Provisioning(ProvisioningParameters { + min_contribution, + target_provision, + accumulated_provision: Default::default(), + not_before, + }), + ); + Self::deposit_event(Event::ListProvisioning { trading_pair }); + Ok(()) + } + + /// List a new trading pair, trading pair will become Enabled status + /// after provision process. + #[pallet::call_index(7)] + #[pallet::weight((::WeightInfo::update_provisioning_parameters(), DispatchClass::Operational))] + pub fn update_provisioning_parameters( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + #[pallet::compact] min_contribution_a: Balance, + #[pallet::compact] min_contribution_b: Balance, + #[pallet::compact] target_provision_a: Balance, + #[pallet::compact] target_provision_b: Balance, + #[pallet::compact] not_before: BlockNumberFor, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::Provisioning(provisioning_parameters) => { + let (min_contribution, target_provision) = if currency_id_a == trading_pair.first() { + ( + (min_contribution_a, min_contribution_b), + (target_provision_a, target_provision_b), + ) + } else { + ( + (min_contribution_b, min_contribution_a), + (target_provision_b, target_provision_a), + ) + }; + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::Provisioning(ProvisioningParameters { + min_contribution, + target_provision, + accumulated_provision: provisioning_parameters.accumulated_provision, + not_before, + }), + ); + } + _ => return Err(Error::::MustBeProvisioning.into()), + } + + Ok(()) + } + + /// Enable a Provisioning trading pair if meet the condition. + #[pallet::call_index(8)] + #[pallet::weight((::WeightInfo::end_provisioning(), DispatchClass::Operational))] + pub fn end_provisioning( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Provisioning(provisioning_parameters) => { + let (total_provision_0, total_provision_1) = provisioning_parameters.accumulated_provision; + ensure!( + frame_system::Pallet::::block_number() >= provisioning_parameters.not_before + && !total_provision_0.is_zero() + && !total_provision_1.is_zero() + && (total_provision_0 >= provisioning_parameters.target_provision.0 + || total_provision_1 >= provisioning_parameters.target_provision.1), + Error::::UnqualifiedProvision + ); + + // directly use token_0 as base to calculate initial dex share amount. + let (share_exchange_rate_0, share_exchange_rate_1) = ( + ExchangeRate::one(), + ExchangeRate::checked_from_rational(total_provision_0, total_provision_1) + .ok_or(ArithmeticError::Overflow)?, + ); + let shares_from_provision_0 = share_exchange_rate_0 + .checked_mul_int(total_provision_0) + .ok_or(ArithmeticError::Overflow)?; + let shares_from_provision_1 = share_exchange_rate_1 + .checked_mul_int(total_provision_1) + .ok_or(ArithmeticError::Overflow)?; + let total_shares_to_issue = shares_from_provision_0 + .checked_add(shares_from_provision_1) + .ok_or(ArithmeticError::Overflow)?; + + // issue total shares to module account + T::Currency::deposit( + trading_pair.dex_share_currency_id(), + &Self::account_id(), + total_shares_to_issue, + )?; + + // inject provision to liquidity pool + Self::try_mutate_liquidity_pool(&trading_pair, |(pool_0, pool_1)| -> DispatchResult { + *pool_0 = pool_0.checked_add(total_provision_0).ok_or(ArithmeticError::Overflow)?; + *pool_1 = pool_1.checked_add(total_provision_1).ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + + // update trading_pair to Enabled status + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::<_, _>::Enabled); + + // record initial exchange rate so that founders can use it to calculate their own shares + InitialShareExchangeRates::::insert( + trading_pair, + (share_exchange_rate_0, share_exchange_rate_1), + ); + + Self::deposit_event(Event::ProvisioningToEnabled { + trading_pair, + pool_0: total_provision_0, + pool_1: total_provision_1, + share_amount: total_shares_to_issue, + }); + } + _ => return Err(Error::::MustBeProvisioning.into()), + } + + Ok(()) + } + + /// Enable a trading pair + /// if the status of trading pair is `Disabled`, or `Provisioning` without any accumulated + /// provision, enable it directly. + #[pallet::call_index(9)] + #[pallet::weight((::WeightInfo::enable_trading_pair(), DispatchClass::Operational))] + pub fn enable_trading_pair( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Disabled => {} + TradingPairStatus::<_, _>::Provisioning(provisioning_parameters) => { + ensure!( + provisioning_parameters.accumulated_provision.0.is_zero() + && provisioning_parameters.accumulated_provision.1.is_zero(), + Error::::StillProvisioning + ); + } + TradingPairStatus::<_, _>::Enabled => return Err(Error::::AlreadyEnabled.into()), + } + + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::Enabled); + Self::deposit_event(Event::EnableTradingPair { trading_pair }); + Ok(()) + } + + /// Disable an `Enabled` trading pair. + #[pallet::call_index(10)] + #[pallet::weight((::WeightInfo::disable_trading_pair(), DispatchClass::Operational))] + pub fn disable_trading_pair( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled + ); + + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::Disabled); + Self::deposit_event(Event::DisableTradingPair { trading_pair }); + Ok(()) + } + + /// Refund provision if the provision has already aborted. + /// + /// - `owner`: founder account. + /// - `currency_id_a`: currency id A. + /// - `currency_id_b`: currency id B. + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::refund_provision())] + pub fn refund_provision( + origin: OriginFor, + owner: T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Disabled + ), + Error::::MustBeDisabled + ); + + // Make sure the trading pair has not been successfully ended provisioning. + ensure!( + InitialShareExchangeRates::::get(trading_pair) == Default::default(), + Error::::NotAllowedRefund + ); + + ProvisioningPool::::try_mutate_exists(trading_pair, &owner, |maybe_contribution| -> DispatchResult { + if let Some((contribution_0, contribution_1)) = maybe_contribution.take() { + T::Currency::transfer(trading_pair.first(), &Self::account_id(), &owner, contribution_0)?; + T::Currency::transfer(trading_pair.second(), &Self::account_id(), &owner, contribution_1)?; + + // decrease ref count + frame_system::Pallet::::dec_consumers(&owner); + + Self::deposit_event(Event::RefundProvision { + who: owner.clone(), + currency_0: trading_pair.first(), + contribution_0, + currency_1: trading_pair.second(), + contribution_1, + }); + } + Ok(()) + }) + } + + /// Abort provision when it doesn't meet the target and expires. + #[pallet::call_index(12)] + #[pallet::weight((::WeightInfo::abort_provisioning(), DispatchClass::Operational))] + pub fn abort_provisioning( + origin: OriginFor, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Provisioning(provisioning_parameters) => { + let (total_provision_0, total_provision_1) = provisioning_parameters.accumulated_provision; + let met_target = !total_provision_0.is_zero() + && !total_provision_1.is_zero() + && (total_provision_0 >= provisioning_parameters.target_provision.0 + || total_provision_1 >= provisioning_parameters.target_provision.1); + let expired = frame_system::Pallet::::block_number() + > provisioning_parameters + .not_before + .saturating_add(T::ExtendedProvisioningBlocks::get()); + + if !met_target && expired { + // update trading_pair to disabled status + TradingPairStatuses::::insert(trading_pair, TradingPairStatus::<_, _>::Disabled); + + Self::deposit_event(Event::ProvisioningAborted { + trading_pair, + accumulated_provision_0: total_provision_0, + accumulated_provision_1: total_provision_1, + }); + } + } + _ => return Err(Error::::MustBeProvisioning.into()), + } + + Ok(()) + } + } +} + +impl Pallet { + fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + fn try_mutate_liquidity_pool( + trading_pair: &TradingPair, + f: impl FnOnce((&mut Balance, &mut Balance)) -> sp_std::result::Result, + ) -> sp_std::result::Result { + LiquidityPool::::try_mutate(trading_pair, |(pool_0, pool_1)| -> sp_std::result::Result { + let old_pool_0 = *pool_0; + let old_pool_1 = *pool_1; + f((pool_0, pool_1)).map(move |result| { + if *pool_0 != old_pool_0 || *pool_1 != old_pool_1 { + T::OnLiquidityPoolUpdated::happened(&(*trading_pair, *pool_0, *pool_1)); + } + + result + }) + }) + } + + fn do_claim_dex_share(who: &T::AccountId, currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> DispatchResult { + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + !matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Provisioning(_) + ), + Error::::StillProvisioning + ); + + ProvisioningPool::::try_mutate_exists(trading_pair, who, |maybe_contribution| -> DispatchResult { + if let Some((contribution_0, contribution_1)) = maybe_contribution.take() { + let (exchange_rate_0, exchange_rate_1) = Self::initial_share_exchange_rates(trading_pair); + let shares_from_provision_0 = exchange_rate_0 + .checked_mul_int(contribution_0) + .ok_or(ArithmeticError::Overflow)?; + let shares_from_provision_1 = exchange_rate_1 + .checked_mul_int(contribution_1) + .ok_or(ArithmeticError::Overflow)?; + let shares_to_claim = shares_from_provision_0 + .checked_add(shares_from_provision_1) + .ok_or(ArithmeticError::Overflow)?; + + T::Currency::transfer( + trading_pair.dex_share_currency_id(), + &Self::account_id(), + who, + shares_to_claim, + )?; + + // decrease ref count + frame_system::Pallet::::dec_consumers(who); + } + Ok(()) + })?; + + // clear InitialShareExchangeRates once it is all claimed + if ProvisioningPool::::iter_prefix(trading_pair).next().is_none() { + InitialShareExchangeRates::::remove(trading_pair); + } + + Ok(()) + } + + fn do_add_provision( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + contribution_a: Balance, + contribution_b: Balance, + ) -> DispatchResult { + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + let mut provision_parameters = match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Provisioning(provision_parameters) => provision_parameters, + _ => return Err(Error::::MustBeProvisioning.into()), + }; + let (contribution_0, contribution_1) = if currency_id_a == trading_pair.first() { + (contribution_a, contribution_b) + } else { + (contribution_b, contribution_a) + }; + + ensure!( + contribution_0 >= provision_parameters.min_contribution.0 + || contribution_1 >= provision_parameters.min_contribution.1, + Error::::InvalidContributionIncrement + ); + + ProvisioningPool::::try_mutate_exists(trading_pair, who, |maybe_pool| -> DispatchResult { + let existed = maybe_pool.is_some(); + let mut pool = maybe_pool.unwrap_or_default(); + pool.0 = pool.0.checked_add(contribution_0).ok_or(ArithmeticError::Overflow)?; + pool.1 = pool.1.checked_add(contribution_1).ok_or(ArithmeticError::Overflow)?; + + let module_account_id = Self::account_id(); + T::Currency::transfer(trading_pair.first(), who, &module_account_id, contribution_0)?; + T::Currency::transfer(trading_pair.second(), who, &module_account_id, contribution_1)?; + + *maybe_pool = Some(pool); + + if !existed && maybe_pool.is_some() { + if frame_system::Pallet::::inc_consumers(who).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + "Warning: Attempt to introduce lock consumer reference, yet no providers. \ + This is unexpected but should be safe." + ); + } + } + + provision_parameters.accumulated_provision.0 = provision_parameters + .accumulated_provision + .0 + .checked_add(contribution_0) + .ok_or(ArithmeticError::Overflow)?; + provision_parameters.accumulated_provision.1 = provision_parameters + .accumulated_provision + .1 + .checked_add(contribution_1) + .ok_or(ArithmeticError::Overflow)?; + + TradingPairStatuses::::insert( + trading_pair, + TradingPairStatus::<_, _>::Provisioning(provision_parameters), + ); + + Self::deposit_event(Event::AddProvision { + who: who.clone(), + currency_0: trading_pair.first(), + contribution_0, + currency_1: trading_pair.second(), + contribution_1, + }); + Ok(()) + }) + } + + fn do_add_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + stake_increment_share: bool, + ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled, + ); + + ensure!( + !max_amount_a.is_zero() && !max_amount_b.is_zero(), + Error::::InvalidLiquidityIncrement + ); + + Self::try_mutate_liquidity_pool( + &trading_pair, + |(pool_0, pool_1)| -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { + let dex_share_currency_id = trading_pair.dex_share_currency_id(); + let total_shares = T::Currency::total_issuance(dex_share_currency_id); + let (max_amount_0, max_amount_1) = if currency_id_a == trading_pair.first() { + (max_amount_a, max_amount_b) + } else { + (max_amount_b, max_amount_a) + }; + let (pool_0_increment, pool_1_increment, share_increment): (Balance, Balance, Balance) = + if total_shares.is_zero() { + // directly use token_0 as base to calculate initial dex share amount. + let (exchange_rate_0, exchange_rate_1) = ( + ExchangeRate::one(), + ExchangeRate::checked_from_rational(max_amount_0, max_amount_1) + .ok_or(ArithmeticError::Overflow)?, + ); + + let shares_from_token_0 = exchange_rate_0 + .checked_mul_int(max_amount_0) + .ok_or(ArithmeticError::Overflow)?; + let shares_from_token_1 = exchange_rate_1 + .checked_mul_int(max_amount_1) + .ok_or(ArithmeticError::Overflow)?; + let initial_shares = shares_from_token_0 + .checked_add(shares_from_token_1) + .ok_or(ArithmeticError::Overflow)?; + + (max_amount_0, max_amount_1, initial_shares) + } else { + let exchange_rate_0_1 = + ExchangeRate::checked_from_rational(*pool_1, *pool_0).ok_or(ArithmeticError::Overflow)?; + let input_exchange_rate_0_1 = ExchangeRate::checked_from_rational(max_amount_1, max_amount_0) + .ok_or(ArithmeticError::Overflow)?; + + if input_exchange_rate_0_1 <= exchange_rate_0_1 { + // max_amount_0 may be too much, calculate the actual amount_0 + let exchange_rate_1_0 = ExchangeRate::checked_from_rational(*pool_0, *pool_1) + .ok_or(ArithmeticError::Overflow)?; + let amount_0 = exchange_rate_1_0 + .checked_mul_int(max_amount_1) + .ok_or(ArithmeticError::Overflow)?; + let share_increment = Ratio::checked_from_rational(amount_0, *pool_0) + .and_then(|n| n.checked_mul_int(total_shares)) + .ok_or(ArithmeticError::Overflow)?; + (amount_0, max_amount_1, share_increment) + } else { + // max_amount_1 is too much, calculate the actual amount_1 + let amount_1 = exchange_rate_0_1 + .checked_mul_int(max_amount_0) + .ok_or(ArithmeticError::Overflow)?; + let share_increment = Ratio::checked_from_rational(amount_1, *pool_1) + .and_then(|n| n.checked_mul_int(total_shares)) + .ok_or(ArithmeticError::Overflow)?; + (max_amount_0, amount_1, share_increment) + } + }; + + ensure!( + !share_increment.is_zero() && !pool_0_increment.is_zero() && !pool_1_increment.is_zero(), + Error::::InvalidLiquidityIncrement, + ); + ensure!( + share_increment >= min_share_increment, + Error::::UnacceptableShareIncrement + ); + + let module_account_id = Self::account_id(); + T::Currency::transfer(trading_pair.first(), who, &module_account_id, pool_0_increment)?; + T::Currency::transfer(trading_pair.second(), who, &module_account_id, pool_1_increment)?; + T::Currency::deposit(dex_share_currency_id, who, share_increment)?; + + *pool_0 = pool_0.checked_add(pool_0_increment).ok_or(ArithmeticError::Overflow)?; + *pool_1 = pool_1.checked_add(pool_1_increment).ok_or(ArithmeticError::Overflow)?; + + if stake_increment_share { + T::SwapDexIncentives::do_deposit_dex_share(who, dex_share_currency_id, share_increment)?; + } + + Self::deposit_event(Event::AddLiquidity { + who: who.clone(), + currency_0: trading_pair.first(), + pool_0: pool_0_increment, + currency_1: trading_pair.second(), + pool_1: pool_1_increment, + share_increment, + }); + + if currency_id_a == trading_pair.first() { + Ok((pool_0_increment, pool_1_increment, share_increment)) + } else { + Ok((pool_1_increment, pool_0_increment, share_increment)) + } + }, + ) + } + + #[transactional] + fn do_remove_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + if remove_share.is_zero() { + return Ok((Zero::zero(), Zero::zero())); + } + let trading_pair = + TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; + let dex_share_currency_id = trading_pair.dex_share_currency_id(); + + Self::try_mutate_liquidity_pool( + &trading_pair, + |(pool_0, pool_1)| -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let (min_withdrawn_0, min_withdrawn_1) = if currency_id_a == trading_pair.first() { + (min_withdrawn_a, min_withdrawn_b) + } else { + (min_withdrawn_b, min_withdrawn_a) + }; + let total_shares = T::Currency::total_issuance(dex_share_currency_id); + let proportion = + Ratio::checked_from_rational(remove_share, total_shares).ok_or(ArithmeticError::Overflow)?; + let pool_0_decrement = proportion.checked_mul_int(*pool_0).ok_or(ArithmeticError::Overflow)?; + let pool_1_decrement = proportion.checked_mul_int(*pool_1).ok_or(ArithmeticError::Overflow)?; + let module_account_id = Self::account_id(); + + ensure!( + pool_0_decrement >= min_withdrawn_0 && pool_1_decrement >= min_withdrawn_1, + Error::::UnacceptableLiquidityWithdrawn, + ); + + if by_unstake { + T::SwapDexIncentives::do_withdraw_dex_share(who, dex_share_currency_id, remove_share)?; + } + T::Currency::withdraw(dex_share_currency_id, who, remove_share)?; + T::Currency::transfer(trading_pair.first(), &module_account_id, who, pool_0_decrement)?; + T::Currency::transfer(trading_pair.second(), &module_account_id, who, pool_1_decrement)?; + + *pool_0 = pool_0.checked_sub(pool_0_decrement).ok_or(ArithmeticError::Underflow)?; + *pool_1 = pool_1.checked_sub(pool_1_decrement).ok_or(ArithmeticError::Underflow)?; + + Self::deposit_event(Event::RemoveLiquidity { + who: who.clone(), + currency_0: trading_pair.first(), + pool_0: pool_0_decrement, + currency_1: trading_pair.second(), + pool_1: pool_1_decrement, + share_decrement: remove_share, + }); + + if currency_id_a == trading_pair.first() { + Ok((pool_0_decrement, pool_1_decrement)) + } else { + Ok((pool_1_decrement, pool_0_decrement)) + } + }, + ) + } + + fn get_liquidity(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance) { + if let Some(trading_pair) = TradingPair::from_currency_ids(currency_id_a, currency_id_b) { + let (pool_0, pool_1) = Self::liquidity_pool(trading_pair); + if currency_id_a == trading_pair.first() { + (pool_0, pool_1) + } else { + (pool_1, pool_0) + } + } else { + (Zero::zero(), Zero::zero()) + } + } + + /// Get how much target amount will be got for specific supply amount. + fn get_target_amount(supply_pool: Balance, target_pool: Balance, supply_amount: Balance) -> Balance { + if supply_amount.is_zero() || supply_pool.is_zero() || target_pool.is_zero() { + Zero::zero() + } else { + let (fee_numerator, fee_denominator) = T::GetExchangeFee::get(); + let supply_amount_with_fee: U256 = + U256::from(supply_amount).saturating_mul(U256::from(fee_denominator.saturating_sub(fee_numerator))); + let numerator: U256 = supply_amount_with_fee.saturating_mul(U256::from(target_pool)); + let denominator: U256 = U256::from(supply_pool) + .saturating_mul(U256::from(fee_denominator)) + .saturating_add(supply_amount_with_fee); + + numerator + .checked_div(denominator) + .and_then(|n| TryInto::::try_into(n).ok()) + .unwrap_or_else(Zero::zero) + } + } + + /// Get how much supply amount will be paid for specific target amount. + fn get_supply_amount(supply_pool: Balance, target_pool: Balance, target_amount: Balance) -> Balance { + if target_amount.is_zero() || supply_pool.is_zero() || target_pool.is_zero() { + Zero::zero() + } else { + let (fee_numerator, fee_denominator) = T::GetExchangeFee::get(); + let numerator: U256 = U256::from(supply_pool) + .saturating_mul(U256::from(target_amount)) + .saturating_mul(U256::from(fee_denominator)); + let denominator: U256 = U256::from(target_pool) + .saturating_sub(U256::from(target_amount)) + .saturating_mul(U256::from(fee_denominator.saturating_sub(fee_numerator))); + + numerator + .checked_div(denominator) + .and_then(|r| r.checked_add(U256::one())) // add 1 to result so that correct the possible losses caused by remainder discarding in + .and_then(|n| TryInto::::try_into(n).ok()) + .unwrap_or_else(Zero::zero) + } + } + + fn get_target_amounts( + path: &[CurrencyId], + supply_amount: Balance, + ) -> sp_std::result::Result, DispatchError> { + Self::validate_path(path)?; + + let path_length = path.len(); + let mut target_amounts: Vec = vec![Zero::zero(); path_length]; + target_amounts[0] = supply_amount; + + let mut i: usize = 0; + while i + 1 < path_length { + let trading_pair = + TradingPair::from_currency_ids(path[i], path[i + 1]).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled + ); + let (supply_pool, target_pool) = Self::get_liquidity(path[i], path[i + 1]); + ensure!( + !supply_pool.is_zero() && !target_pool.is_zero(), + Error::::InsufficientLiquidity + ); + let target_amount = Self::get_target_amount(supply_pool, target_pool, target_amounts[i]); + ensure!(!target_amount.is_zero(), Error::::ZeroTargetAmount); + + target_amounts[i + 1] = target_amount; + i += 1; + } + + Ok(target_amounts) + } + + fn get_supply_amounts( + path: &[CurrencyId], + target_amount: Balance, + ) -> sp_std::result::Result, DispatchError> { + Self::validate_path(path)?; + + let path_length = path.len(); + let mut supply_amounts: Vec = vec![Zero::zero(); path_length]; + supply_amounts[path_length - 1] = target_amount; + + let mut i: usize = path_length - 1; + while i > 0 { + let trading_pair = + TradingPair::from_currency_ids(path[i - 1], path[i]).ok_or(Error::::InvalidCurrencyId)?; + ensure!( + matches!( + Self::trading_pair_statuses(trading_pair), + TradingPairStatus::<_, _>::Enabled + ), + Error::::MustBeEnabled + ); + let (supply_pool, target_pool) = Self::get_liquidity(path[i - 1], path[i]); + ensure!( + !supply_pool.is_zero() && !target_pool.is_zero(), + Error::::InsufficientLiquidity + ); + let supply_amount = Self::get_supply_amount(supply_pool, target_pool, supply_amounts[i]); + ensure!(!supply_amount.is_zero(), Error::::ZeroSupplyAmount); + + supply_amounts[i - 1] = supply_amount; + i -= 1; + } + + Ok(supply_amounts) + } + + fn validate_path(path: &[CurrencyId]) -> DispatchResult { + let path_length = path.len(); + ensure!( + path_length >= 2 && path_length <= T::TradingPathLimit::get().saturated_into(), + Error::::InvalidTradingPathLength + ); + ensure!(path.get(0) != path.get(path_length - 1), Error::::InvalidTradingPath); + + Ok(()) + } + + fn _swap( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + supply_increment: Balance, + target_decrement: Balance, + ) -> DispatchResult { + if let Some(trading_pair) = TradingPair::from_currency_ids(supply_currency_id, target_currency_id) { + Self::try_mutate_liquidity_pool(&trading_pair, |(pool_0, pool_1)| -> DispatchResult { + let invariant_before_swap: U256 = U256::from(*pool_0).saturating_mul(U256::from(*pool_1)); + + if supply_currency_id == trading_pair.first() { + *pool_0 = pool_0.checked_add(supply_increment).ok_or(ArithmeticError::Overflow)?; + *pool_1 = pool_1.checked_sub(target_decrement).ok_or(ArithmeticError::Underflow)?; + } else { + *pool_0 = pool_0.checked_sub(target_decrement).ok_or(ArithmeticError::Underflow)?; + *pool_1 = pool_1.checked_add(supply_increment).ok_or(ArithmeticError::Overflow)?; + } + + // invariant check to ensure the constant product formulas (k = x * y) + let invariant_after_swap: U256 = U256::from(*pool_0).saturating_mul(U256::from(*pool_1)); + ensure!( + invariant_after_swap >= invariant_before_swap, + Error::::InvariantCheckFailed, + ); + Ok(()) + })?; + } + Ok(()) + } + + fn _swap_by_path(path: &[CurrencyId], amounts: &[Balance]) -> DispatchResult { + let mut i: usize = 0; + while i + 1 < path.len() { + let (supply_currency_id, target_currency_id) = (path[i], path[i + 1]); + let (supply_increment, target_decrement) = (amounts[i], amounts[i + 1]); + Self::_swap( + supply_currency_id, + target_currency_id, + supply_increment, + target_decrement, + )?; + i += 1; + } + Ok(()) + } + + #[transactional] + fn do_swap_with_exact_supply( + who: &T::AccountId, + path: &[CurrencyId], + supply_amount: Balance, + min_target_amount: Balance, + ) -> sp_std::result::Result { + let amounts = Self::get_target_amounts(path, supply_amount)?; + ensure!( + amounts[amounts.len() - 1] >= min_target_amount, + Error::::InsufficientTargetAmount + ); + let module_account_id = Self::account_id(); + let actual_target_amount = amounts[amounts.len() - 1]; + + T::Currency::transfer(path[0], who, &module_account_id, supply_amount)?; + Self::_swap_by_path(path, &amounts)?; + T::Currency::transfer(path[path.len() - 1], &module_account_id, who, actual_target_amount)?; + + Self::deposit_event(Event::Swap { + trader: who.clone(), + path: path.to_vec(), + liquidity_changes: amounts, + }); + Ok(actual_target_amount) + } + + #[transactional] + fn do_swap_with_exact_target( + who: &T::AccountId, + path: &[CurrencyId], + target_amount: Balance, + max_supply_amount: Balance, + ) -> sp_std::result::Result { + let amounts = Self::get_supply_amounts(path, target_amount)?; + ensure!(amounts[0] <= max_supply_amount, Error::::ExcessiveSupplyAmount); + let module_account_id = Self::account_id(); + let actual_supply_amount = amounts[0]; + + T::Currency::transfer(path[0], who, &module_account_id, actual_supply_amount)?; + Self::_swap_by_path(path, &amounts)?; + T::Currency::transfer(path[path.len() - 1], &module_account_id, who, target_amount)?; + + Self::deposit_event(Event::Swap { + trader: who.clone(), + path: path.to_vec(), + liquidity_changes: amounts, + }); + Ok(actual_supply_amount) + } +} + +impl SwapManager for Pallet { + fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance) { + Self::get_liquidity(currency_id_a, currency_id_b) + } + + fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option { + let trading_pair = TradingPair::from_currency_ids(currency_id_a, currency_id_b)?; + match Self::trading_pair_statuses(trading_pair) { + TradingPairStatus::<_, _>::Disabled => None, + TradingPairStatus::<_, _>::Provisioning(_) | TradingPairStatus::<_, _>::Enabled => { + T::Erc20InfoMapping::encode_evm_address(trading_pair.dex_share_currency_id()) + } + } + } + + fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)> { + match limit { + SwapLimit::ExactSupply(exact_supply_amount, minimum_target_amount) => { + Self::get_target_amounts(path, exact_supply_amount) + .ok() + .and_then(|amounts| { + if amounts[amounts.len() - 1] >= minimum_target_amount { + Some((exact_supply_amount, amounts[amounts.len() - 1])) + } else { + None + } + }) + } + SwapLimit::ExactTarget(maximum_supply_amount, exact_target_amount) => { + Self::get_supply_amounts(path, exact_target_amount) + .ok() + .and_then(|amounts| { + if amounts[0] <= maximum_supply_amount { + Some((amounts[0], exact_target_amount)) + } else { + None + } + }) + } + } + } + + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + let default_swap_path = vec![supply_currency_id, target_currency_id]; + let mut maybe_best = Self::get_swap_amount(&default_swap_path, limit) + .map(|(supply_amout, target_amount)| (default_swap_path, supply_amout, target_amount)); + + for path_joint in alternative_path_joint_list { + if !path_joint.is_empty() { + let mut swap_path = vec![]; + + if supply_currency_id != path_joint[0] { + swap_path.push(supply_currency_id); + } + + swap_path.extend(path_joint.clone()); + + if target_currency_id != path_joint[path_joint.len() - 1] { + swap_path.push(target_currency_id); + } + + if let Some((supply_amount, target_amount)) = Self::get_swap_amount(&swap_path, limit) { + if let Some((_, previous_supply, previous_target)) = maybe_best { + if supply_amount > previous_supply || target_amount < previous_target { + continue; + } + } + + maybe_best = Some((swap_path, supply_amount, target_amount)); + } + } + } + + maybe_best + } + + fn swap_with_specific_path( + who: &T::AccountId, + path: &[CurrencyId], + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + match limit { + SwapLimit::ExactSupply(exact_supply_amount, minimum_target_amount) => { + Self::do_swap_with_exact_supply(who, path, exact_supply_amount, minimum_target_amount) + .map(|actual_target_amount| (exact_supply_amount, actual_target_amount)) + } + SwapLimit::ExactTarget(maximum_supply_amount, exact_target_amount) => { + Self::do_swap_with_exact_target(who, path, exact_target_amount, maximum_supply_amount) + .map(|actual_supply_amount| (actual_supply_amount, exact_target_amount)) + } + } + } + + // `do_add_liquidity` is used in genesis_build, + // but transactions are not supported by BasicExternalities, + // put `transactional` here + #[transactional] + fn add_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + stake_increment_share: bool, + ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { + Self::do_add_liquidity( + who, + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + min_share_increment, + stake_increment_share, + ) + } + + fn remove_liquidity( + who: &T::AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + Self::do_remove_liquidity( + who, + currency_id_a, + currency_id_b, + remove_share, + min_withdrawn_a, + min_withdrawn_b, + by_unstake, + ) + } +} diff --git a/blockchain/modules/edfis-swap-legacy/src/mock.rs b/blockchain/modules/edfis-swap-legacy/src/mock.rs new file mode 100644 index 00000000..0498bc8e --- /dev/null +++ b/blockchain/modules/edfis-swap-legacy/src/mock.rs @@ -0,0 +1,222 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the edfis_swap_legacy module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, Nothing}, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockErc20InfoMapping, SpecificJointsSwap}; +use orml_traits::{parameter_type_with_key, MultiReservableCurrency}; +use primitives::{Amount, TokenSymbol}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; +use sp_std::cell::RefCell; + +pub type BlockNumber = u64; +pub type AccountId = u128; + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; +pub const CAROL: AccountId = 3; +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const WBTC: CurrencyId = CurrencyId::Token(TokenSymbol::FA_WBTC); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); + +parameter_types! { + pub static USSDWBTCPair: TradingPair = TradingPair::from_currency_ids(USSD, WBTC).unwrap(); + pub static USSDEDFPair: TradingPair = TradingPair::from_currency_ids(USSD, EDF).unwrap(); + pub static EDFWBTCPair: TradingPair = TradingPair::from_currency_ids(EDF, WBTC).unwrap(); +} + +mod edfis_swap_legacy { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +pub struct MockSwapDexIncentives; +impl SwapDexIncentives for MockSwapDexIncentives { + fn do_deposit_dex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult { + Tokens::reserve(lp_currency_id, who, amount) + } + + fn do_withdraw_dex_share(who: &AccountId, lp_currency_id: CurrencyId, amount: Balance) -> DispatchResult { + let _ = Tokens::unreserve(lp_currency_id, who, amount); + Ok(()) + } +} + +ord_parameter_types! { + pub const ListingOrigin: AccountId = 3; +} + +parameter_types! { + pub const GetExchangeFee: (u32, u32) = (1, 100); + pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![EDF], + ]; +} + +thread_local! { + pub static USSD_EDF_POOL_RECORD: RefCell<(Balance, Balance)> = RefCell::new((0, 0)); +} + +pub struct MockOnLiquidityPoolUpdated; +impl Happened<(TradingPair, Balance, Balance)> for MockOnLiquidityPoolUpdated { + fn happened(info: &(TradingPair, Balance, Balance)) { + let (trading_pair, new_pool_0, new_pool_1) = info; + if *trading_pair == USSDEDFPair::get() { + USSD_EDF_POOL_RECORD.with(|v| *v.borrow_mut() = (*new_pool_0, *new_pool_1)); + } + } +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Tokens; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = ConstU32<3>; + type PalletId = DEXPalletId; + type Erc20InfoMapping = MockErc20InfoMapping; + type WeightInfo = (); + type SwapDexIncentives = MockSwapDexIncentives; + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU64<2000>; + type OnLiquidityPoolUpdated = MockOnLiquidityPoolUpdated; +} + +parameter_types! { + pub USSDJoint: Vec> = vec![vec![USSD]]; + pub SEEJoint: Vec> = vec![vec![SEE]]; +} + +pub type USSDJointSwap = SpecificJointsSwap; +pub type SEEJointSwap = SpecificJointsSwap; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + EdfisSwapLegacyModule: edfis_swap_legacy, + Tokens: orml_tokens, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, + initial_listing_trading_pairs: Vec<(TradingPair, (Balance, Balance), (Balance, Balance), BlockNumber)>, + initial_enabled_trading_pairs: Vec, + initial_added_liquidity_pools: Vec<(AccountId, Vec<(TradingPair, (Balance, Balance))>)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, SEE, 1_000_000_000_000_000_000u128), + (BOB, SEE, 1_000_000_000_000_000_000u128), + (ALICE, USSD, 1_000_000_000_000_000_000u128), + (BOB, USSD, 1_000_000_000_000_000_000u128), + (ALICE, WBTC, 1_000_000_000_000_000_000u128), + (BOB, WBTC, 1_000_000_000_000_000_000u128), + (ALICE, EDF, 1_000_000_000_000_000_000u128), + (BOB, EDF, 1_000_000_000_000_000_000u128), + ], + initial_listing_trading_pairs: vec![], + initial_enabled_trading_pairs: vec![], + initial_added_liquidity_pools: vec![], + } + } +} + +impl ExtBuilder { + pub fn initialize_enabled_trading_pairs(mut self) -> Self { + self.initial_enabled_trading_pairs = vec![USSDEDFPair::get(), USSDWBTCPair::get(), EDFWBTCPair::get()]; + self + } + + pub fn initialize_added_liquidity_pools(mut self, who: AccountId) -> Self { + self.initial_added_liquidity_pools = vec![( + who, + vec![ + (USSDEDFPair::get(), (1_000_000u128, 2_000_000u128)), + (USSDWBTCPair::get(), (1_000_000u128, 2_000_000u128)), + (EDFWBTCPair::get(), (1_000_000u128, 2_000_000u128)), + ], + )]; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + edfis_swap_legacy::GenesisConfig:: { + initial_listing_trading_pairs: self.initial_listing_trading_pairs, + initial_enabled_trading_pairs: self.initial_enabled_trading_pairs, + initial_added_liquidity_pools: self.initial_added_liquidity_pools, + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/blockchain/modules/edfis-swap-legacy/src/tests.rs b/blockchain/modules/edfis-swap-legacy/src/tests.rs new file mode 100644 index 00000000..71d79618 --- /dev/null +++ b/blockchain/modules/edfis-swap-legacy/src/tests.rs @@ -0,0 +1,1997 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the dex module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{ + SEEJointSwap, USSDWBTCPair, USSDEDFPair, USSDJointSwap, EDFWBTCPair, EdfisSwapLegacyModule, ExtBuilder, ListingOrigin, Runtime, + RuntimeEvent, RuntimeOrigin, System, Tokens, SEE, ALICE, USSD, USSD_EDF_POOL_RECORD, BOB, WBTC, CAROL, EDF, +}; +use module_support::{Swap, SwapError}; +use orml_traits::MultiReservableCurrency; +use sp_core::H160; +use sp_runtime::traits::BadOrigin; +use std::str::FromStr; + +#[test] +fn list_provisioning_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + BadOrigin + ); + + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::ListProvisioning { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + USSD, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::InvalidCurrencyId + ); + + assert_noop!( + EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::MustBeDisabled + ); + + assert_noop!( + EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + CurrencyId::ForeignAsset(0), + USSD, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::AssetUnregistered + ); + assert_noop!( + EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + CurrencyId::ForeignAsset(0), + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::AssetUnregistered + ); + }); +} + +#[test] +fn update_provisioning_parameters_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + EdfisSwapLegacyModule::update_provisioning_parameters( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + BadOrigin + ); + + assert_noop!( + EdfisSwapLegacyModule::update_provisioning_parameters( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::MustBeProvisioning + ); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + + assert_ok!(EdfisSwapLegacyModule::update_provisioning_parameters( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 2_000_000_000_000u128, + 0, + 3_000_000_000_000u128, + 2_000_000_000_000u128, + 50, + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (2_000_000_000_000u128, 0), + target_provision: (3_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 50, + }) + ); + }); +} + +#[test] +fn enable_diabled_trading_pair_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + EdfisSwapLegacyModule::enable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), + BadOrigin + ); + + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_ok!(EdfisSwapLegacyModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::EnableTradingPair { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + EdfisSwapLegacyModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), EDF, USSD), + Error::::AlreadyEnabled + ); + }); +} + +#[test] +fn enable_provisioning_without_provision_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128 + )); + + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + assert_ok!(EdfisSwapLegacyModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::EnableTradingPair { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + EdfisSwapLegacyModule::enable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + Error::::StillProvisioning + ); + }); +} + +#[test] +fn end_provisioning_trading_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 1_000_000_000_000u128, + 2_000_000_000_000u128 + )); + + assert_noop!( + EdfisSwapLegacyModule::end_provisioning(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + Error::::UnqualifiedProvision + ); + System::set_block_number(10); + + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (1_000_000_000_000u128, 2_000_000_000_000u128), + not_before: 10, + }) + ); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDWBTCPair::get()), + Default::default() + ); + assert_eq!(EdfisSwapLegacyModule::liquidity_pool(USSDWBTCPair::get()), (0, 0)); + assert_eq!(Tokens::total_issuance(USSDWBTCPair::get().dex_share_currency_id()), 0); + assert_eq!( + Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &EdfisSwapLegacyModule::account_id()), + 0 + ); + + assert_ok!(EdfisSwapLegacyModule::end_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::ProvisioningToEnabled { + trading_pair: USSDWBTCPair::get(), + pool_0: 1_000_000_000_000u128, + pool_1: 2_000_000_000_000u128, + share_amount: 2_000_000_000_000u128, + })); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDWBTCPair::get()), + (ExchangeRate::one(), ExchangeRate::checked_from_rational(1, 2).unwrap()) + ); + assert_eq!( + EdfisSwapLegacyModule::liquidity_pool(USSDWBTCPair::get()), + (1_000_000_000_000u128, 2_000_000_000_000u128) + ); + assert_eq!( + Tokens::total_issuance(USSDWBTCPair::get().dex_share_currency_id()), + 2_000_000_000_000u128 + ); + assert_eq!( + Tokens::free_balance(USSDWBTCPair::get().dex_share_currency_id(), &EdfisSwapLegacyModule::account_id()), + 2_000_000_000_000u128 + ); + }); +} + +#[test] +fn abort_provisioning_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + EdfisSwapLegacyModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF), + Error::::MustBeProvisioning + ); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 1000, + )); + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 1000, + )); + + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128 + )); + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + WBTC, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + )); + + // not expired, nothing happened. + System::set_block_number(2000); + assert_ok!(EdfisSwapLegacyModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + assert_ok!(EdfisSwapLegacyModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (1_000_000_000_000u128, 1_000_000_000_000u128), + not_before: 1000, + }) + ); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDEDFPair::get()), + Default::default() + ); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + not_before: 1000, + }) + ); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDWBTCPair::get()), + Default::default() + ); + + // both expired, the provision for USSD-EDF could be aborted, the provision for USSD-WBTC + // couldn't be aborted because it's already met the target. + System::set_block_number(3001); + assert_ok!(EdfisSwapLegacyModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::ProvisioningAborted { + trading_pair: USSDEDFPair::get(), + accumulated_provision_0: 1_000_000_000_000u128, + accumulated_provision_1: 1_000_000_000_000u128, + })); + + assert_ok!(EdfisSwapLegacyModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDEDFPair::get()), + Default::default() + ); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + not_before: 1000, + }) + ); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDWBTCPair::get()), + Default::default() + ); + }); +} + +#[test] +fn refund_provision_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 5_000_000_000_000_000_000u128, + 4_000_000_000_000_000_000u128, + 1000, + )); + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 100_000_000_000_000_000u128, + 100_000_000_000_000_000u128, + 1000, + )); + + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000_000_000u128, + 1_000_000_000_000_000_000u128 + )); + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 0, + 600_000_000_000_000_000u128, + )); + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + WBTC, + 100_000_000_000_000_000u128, + 100_000_000_000_000_000u128, + )); + + assert_noop!( + EdfisSwapLegacyModule::refund_provision(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), + Error::::MustBeDisabled + ); + + // abort provisioning of USSD-EDF + System::set_block_number(3001); + assert_ok!(EdfisSwapLegacyModule::abort_provisioning(RuntimeOrigin::signed(ALICE), USSD, EDF)); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDEDFPair::get()), + Default::default() + ); + + assert_eq!( + EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), ALICE), + (1_000_000_000_000_000_000u128, 1_000_000_000_000_000_000u128) + ); + assert_eq!( + EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), BOB), + (0, 600_000_000_000_000_000u128) + ); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 1_100_000_000_000_000_000u128 + ); + assert_eq!( + Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), + 1_600_000_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 0); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 900_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &BOB), 400_000_000_000_000_000u128); + + let alice_ref_count_0 = System::consumers(&ALICE); + let bob_ref_count_0 = System::consumers(&BOB); + + assert_ok!(EdfisSwapLegacyModule::refund_provision( + RuntimeOrigin::signed(ALICE), + ALICE, + USSD, + EDF + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::RefundProvision { + who: ALICE, + currency_0: USSD, + contribution_0: 1_000_000_000_000_000_000u128, + currency_1: EDF, + contribution_1: 1_000_000_000_000_000_000u128, + })); + + assert_eq!(EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 100_000_000_000_000_000u128 + ); + assert_eq!( + Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), + 600_000_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(System::consumers(&ALICE), alice_ref_count_0 - 1); + + assert_ok!(EdfisSwapLegacyModule::refund_provision( + RuntimeOrigin::signed(ALICE), + BOB, + USSD, + EDF + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::RefundProvision { + who: BOB, + currency_0: USSD, + contribution_0: 0, + currency_1: EDF, + contribution_1: 600_000_000_000_000_000u128, + })); + + assert_eq!(EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 100_000_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 900_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000u128); + assert_eq!(System::consumers(&BOB), bob_ref_count_0 - 1); + + // not allow refund if the provisioning has been ended before. + assert_ok!(EdfisSwapLegacyModule::end_provisioning(RuntimeOrigin::signed(ALICE), USSD, WBTC)); + assert_ok!(EdfisSwapLegacyModule::disable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDWBTCPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!( + EdfisSwapLegacyModule::provisioning_pool(USSDWBTCPair::get(), BOB), + (100_000_000_000_000_000u128, 100_000_000_000_000_000u128) + ); + assert_noop!( + EdfisSwapLegacyModule::refund_provision(RuntimeOrigin::signed(BOB), BOB, USSD, WBTC), + Error::::NotAllowedRefund + ); + }); +} + +#[test] +fn disable_trading_pair_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + + assert_noop!( + EdfisSwapLegacyModule::disable_trading_pair(RuntimeOrigin::signed(ALICE), USSD, EDF), + BadOrigin + ); + + assert_ok!(EdfisSwapLegacyModule::disable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::DisableTradingPair { + trading_pair: USSDEDFPair::get(), + })); + + assert_noop!( + EdfisSwapLegacyModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, EDF), + Error::::MustBeEnabled + ); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + WBTC, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_noop!( + EdfisSwapLegacyModule::disable_trading_pair(RuntimeOrigin::signed(ListingOrigin::get()), USSD, WBTC), + Error::::MustBeEnabled + ); + }); +} + +#[test] +fn on_liquidity_pool_updated_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + assert_eq!(USSD_EDF_POOL_RECORD.with(|v| *v.borrow()), (0, 0)); + + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + assert_eq!( + USSD_EDF_POOL_RECORD.with(|v| *v.borrow()), + (5000000000000, 1000000000000) + ); + }); +} + +#[test] +fn add_provision_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_noop!( + EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000u128, + 1_000_000_000_000u128, + ), + Error::::MustBeProvisioning + ); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 5_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 10, + )); + + assert_noop!( + EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 4_999_999_999_999u128, + 999_999_999_999u128, + ), + Error::::InvalidContributionIncrement + ); + + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (5_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000_000u128, 1_000_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + assert_eq!(EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 0); + let alice_ref_count_0 = System::consumers(&ALICE); + + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000u128, + 0, + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (5_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000_000u128, 1_000_000_000_000_000u128), + accumulated_provision: (5_000_000_000_000u128, 0), + not_before: 10, + }) + ); + assert_eq!( + EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), ALICE), + (5_000_000_000_000u128, 0) + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_995_000_000_000_000u128); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000u128); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 5_000_000_000_000u128 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 0); + let alice_ref_count_1 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_1, alice_ref_count_0 + 1); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::AddProvision { + who: ALICE, + currency_0: USSD, + contribution_0: 5_000_000_000_000u128, + currency_1: EDF, + contribution_1: 0, + })); + }); +} + +#[test] +fn claim_dex_share_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 5_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000_000u128, + 1_000_000_000_000_000u128, + 0, + )); + + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 1_000_000_000_000_000u128, + 200_000_000_000_000u128, + )); + assert_ok!(EdfisSwapLegacyModule::add_provision( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 4_000_000_000_000_000u128, + 800_000_000_000_000u128, + )); + + assert_noop!( + EdfisSwapLegacyModule::claim_dex_share(RuntimeOrigin::signed(ALICE), ALICE, USSD, EDF), + Error::::StillProvisioning + ); + + assert_ok!(EdfisSwapLegacyModule::end_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + + let lp_currency_id = USSDEDFPair::get().dex_share_currency_id(); + + assert!(InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); + assert_eq!( + EdfisSwapLegacyModule::initial_share_exchange_rates(USSDEDFPair::get()), + (ExchangeRate::one(), ExchangeRate::saturating_from_rational(5, 1)) + ); + assert_eq!( + Tokens::free_balance(lp_currency_id, &EdfisSwapLegacyModule::account_id()), + 10_000_000_000_000_000u128 + ); + assert_eq!( + EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), ALICE), + (1_000_000_000_000_000u128, 200_000_000_000_000u128) + ); + assert_eq!( + EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), BOB), + (4_000_000_000_000_000u128, 800_000_000_000_000u128) + ); + assert_eq!(Tokens::free_balance(lp_currency_id, &ALICE), 0); + assert_eq!(Tokens::free_balance(lp_currency_id, &BOB), 0); + + let alice_ref_count_0 = System::consumers(&ALICE); + let bob_ref_count_0 = System::consumers(&BOB); + + assert_ok!(EdfisSwapLegacyModule::claim_dex_share( + RuntimeOrigin::signed(ALICE), + ALICE, + USSD, + EDF + )); + assert_eq!( + Tokens::free_balance(lp_currency_id, &EdfisSwapLegacyModule::account_id()), + 8_000_000_000_000_000u128 + ); + assert_eq!(EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), ALICE), (0, 0)); + assert_eq!(Tokens::free_balance(lp_currency_id, &ALICE), 2_000_000_000_000_000u128); + assert_eq!(System::consumers(&ALICE), alice_ref_count_0 - 1); + assert!(InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); + + assert_ok!(EdfisSwapLegacyModule::disable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_ok!(EdfisSwapLegacyModule::claim_dex_share(RuntimeOrigin::signed(BOB), BOB, USSD, EDF)); + assert_eq!(Tokens::free_balance(lp_currency_id, &EdfisSwapLegacyModule::account_id()), 0); + assert_eq!(EdfisSwapLegacyModule::provisioning_pool(USSDEDFPair::get(), BOB), (0, 0)); + assert_eq!(Tokens::free_balance(lp_currency_id, &BOB), 8_000_000_000_000_000u128); + assert_eq!(System::consumers(&BOB), bob_ref_count_0 - 1); + assert!(!InitialShareExchangeRates::::contains_key(USSDEDFPair::get()),); + }); +} + +#[test] +fn get_liquidity_work() { + ExtBuilder::default().build().execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (1000, 20)); + assert_eq!(EdfisSwapLegacyModule::liquidity_pool(USSDEDFPair::get()), (1000, 20)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (1000, 20)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(EDF, USSD), (20, 1000)); + }); +} + +#[test] +fn get_target_amount_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(EdfisSwapLegacyModule::get_target_amount(10000, 0, 1000), 0); + assert_eq!(EdfisSwapLegacyModule::get_target_amount(0, 20000, 1000), 0); + assert_eq!(EdfisSwapLegacyModule::get_target_amount(10000, 20000, 0), 0); + assert_eq!(EdfisSwapLegacyModule::get_target_amount(10000, 1, 1000000), 0); + assert_eq!(EdfisSwapLegacyModule::get_target_amount(10000, 20000, 10000), 9949); + assert_eq!(EdfisSwapLegacyModule::get_target_amount(10000, 20000, 1000), 1801); + }); +} + +#[test] +fn get_supply_amount_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(EdfisSwapLegacyModule::get_supply_amount(10000, 0, 1000), 0); + assert_eq!(EdfisSwapLegacyModule::get_supply_amount(0, 20000, 1000), 0); + assert_eq!(EdfisSwapLegacyModule::get_supply_amount(10000, 20000, 0), 0); + assert_eq!(EdfisSwapLegacyModule::get_supply_amount(10000, 1, 1), 0); + assert_eq!(EdfisSwapLegacyModule::get_supply_amount(10000, 20000, 9949), 9999); + assert_eq!(EdfisSwapLegacyModule::get_target_amount(10000, 20000, 9999), 9949); + assert_eq!(EdfisSwapLegacyModule::get_supply_amount(10000, 20000, 1801), 1000); + assert_eq!(EdfisSwapLegacyModule::get_target_amount(10000, 20000, 1000), 1801); + }); +} + +#[test] +fn get_target_amounts_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); + assert_noop!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, USSD, WBTC, EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, USSD, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, USSD, SEE], 10000), + Error::::MustBeEnabled, + ); + assert_eq!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, USSD], 10000), + Ok(vec![10000, 24874]) + ); + assert_eq!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, USSD, WBTC], 10000), + Ok(vec![10000, 24874, 1]) + ); + assert_noop!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, USSD, WBTC], 100), + Error::::ZeroTargetAmount, + ); + assert_noop!( + EdfisSwapLegacyModule::get_target_amounts(&[EDF, WBTC], 100), + Error::::InsufficientLiquidity, + ); + }); +} + +#[test] +fn calculate_amount_for_big_number_work() { + ExtBuilder::default().build().execute_with(|| { + LiquidityPool::::insert( + USSDEDFPair::get(), + (171_000_000_000_000_000_000_000, 56_000_000_000_000_000_000_000), + ); + assert_eq!( + EdfisSwapLegacyModule::get_supply_amount( + 171_000_000_000_000_000_000_000, + 56_000_000_000_000_000_000_000, + 1_000_000_000_000_000_000_000 + ), + 3_140_495_867_768_595_041_323 + ); + assert_eq!( + EdfisSwapLegacyModule::get_target_amount( + 171_000_000_000_000_000_000_000, + 56_000_000_000_000_000_000_000, + 3_140_495_867_768_595_041_323 + ), + 1_000_000_000_000_000_000_000 + ); + }); +} + +#[test] +fn get_supply_amounts_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); + assert_noop!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, USSD, WBTC, EDF], 10000), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, USSD, EDF], 10000), + Error::::InvalidTradingPath, + ); + assert_noop!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, USSD, SEE], 10000), + Error::::MustBeEnabled, + ); + assert_eq!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, USSD], 24874), + Ok(vec![10000, 24874]) + ); + assert_eq!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, USSD], 25000), + Ok(vec![10102, 25000]) + ); + assert_noop!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, USSD, WBTC], 10000), + Error::::ZeroSupplyAmount, + ); + assert_noop!( + EdfisSwapLegacyModule::get_supply_amounts(&[EDF, WBTC], 10000), + Error::::InsufficientLiquidity, + ); + }); +} + +#[test] +fn _swap_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (50000, 10000)); + assert_noop!( + EdfisSwapLegacyModule::_swap(USSD, EDF, 50000, 5001), + Error::::InvariantCheckFailed + ); + assert_ok!(EdfisSwapLegacyModule::_swap(USSD, EDF, 50000, 5000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (100000, 5000)); + assert_ok!(EdfisSwapLegacyModule::_swap(EDF, USSD, 100, 800)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (99200, 5100)); + }); +} + +#[test] +fn _swap_by_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (100000, 10)); + + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (50000, 10000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), (100000, 10)); + assert_ok!(EdfisSwapLegacyModule::_swap_by_path(&[EDF, USSD], &[10000, 25000])); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (25000, 20000)); + assert_ok!(EdfisSwapLegacyModule::_swap_by_path(&[EDF, USSD, WBTC], &[100000, 20000, 1])); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (5000, 120000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), (120000, 9)); + }); +} + +#[test] +fn add_liquidity_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_noop!( + EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + SEE, + USSD, + 100_000_000, + 100_000_000, + 0, + false + ), + Error::::MustBeEnabled + ); + assert_noop!( + EdfisSwapLegacyModule::add_liquidity(RuntimeOrigin::signed(ALICE), USSD, EDF, 0, 100_000_000, 0, false), + Error::::InvalidLiquidityIncrement + ); + + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 0); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000); + + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::AddLiquidity { + who: ALICE, + currency_0: USSD, + pool_0: 5_000_000_000_000, + currency_1: EDF, + pool_1: 1_000_000_000_000, + share_increment: 10_000_000_000_000, + })); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (5_000_000_000_000, 1_000_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 5_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 1_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 10_000_000_000_000 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_995_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); + + assert_noop!( + EdfisSwapLegacyModule::add_liquidity(RuntimeOrigin::signed(BOB), USSD, EDF, 4, 1, 0, true,), + Error::::InvalidLiquidityIncrement, + ); + + assert_noop!( + EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 50_000_000_000_000, + 8_000_000_000_000, + 80_000_000_000_001, + true, + ), + Error::::UnacceptableShareIncrement + ); + + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 50_000_000_000_000, + 8_000_000_000_000, + 80_000_000_000_000, + true, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::AddLiquidity { + who: BOB, + currency_0: USSD, + pool_0: 40_000_000_000_000, + currency_1: EDF, + pool_1: 8_000_000_000_000, + share_increment: 80_000_000_000_000, + })); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (45_000_000_000_000, 9_000_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 45_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 9_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 80_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(USSD, &BOB), 999_960_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_992_000_000_000_000); + }); +} + +#[test] +fn remove_liquidity_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false + )); + assert_noop!( + EdfisSwapLegacyModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSDEDFPair::get().dex_share_currency_id(), + EDF, + 100_000_000, + 0, + 0, + false, + ), + Error::::InvalidCurrencyId + ); + + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (5_000_000_000_000, 1_000_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 5_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 1_000_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 10_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_995_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_000_000_000_000); + + assert_noop!( + EdfisSwapLegacyModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 8_000_000_000_000, + 4_000_000_000_001, + 800_000_000_000, + false, + ), + Error::::UnacceptableLiquidityWithdrawn + ); + assert_noop!( + EdfisSwapLegacyModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 8_000_000_000_000, + 4_000_000_000_000, + 800_000_000_001, + false, + ), + Error::::UnacceptableLiquidityWithdrawn + ); + assert_ok!(EdfisSwapLegacyModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 8_000_000_000_000, + 4_000_000_000_000, + 800_000_000_000, + false, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::RemoveLiquidity { + who: ALICE, + currency_0: USSD, + pool_0: 4_000_000_000_000, + currency_1: EDF, + pool_1: 800_000_000_000, + share_decrement: 8_000_000_000_000, + })); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (1_000_000_000_000, 200_000_000_000) + ); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 1_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 200_000_000_000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 2_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 999_999_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 999_999_800_000_000_000); + + assert_ok!(EdfisSwapLegacyModule::remove_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 2_000_000_000_000, + 0, + 0, + false, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::RemoveLiquidity { + who: ALICE, + currency_0: USSD, + pool_0: 1_000_000_000_000, + currency_1: EDF, + pool_1: 200_000_000_000, + share_decrement: 2_000_000_000_000, + })); + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (0, 0)); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 0); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 0); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 0 + ); + assert_eq!(Tokens::free_balance(USSD, &ALICE), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &ALICE), 1_000_000_000_000_000_000); + + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + true + )); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 10_000_000_000_000 + ); + assert_ok!(EdfisSwapLegacyModule::remove_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 2_000_000_000_000, + 0, + 0, + true, + )); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 0 + ); + assert_eq!( + Tokens::reserved_balance(USSDEDFPair::get().dex_share_currency_id(), &BOB), + 8_000_000_000_000 + ); + }); +} + +#[test] +fn do_swap_with_exact_supply_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 500_000_000_000_000, + 100_000_000_000_000, + 0, + false, + )); + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 100_000_000_000_000, + 10_000_000_000, + 0, + false, + )); + + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (500_000_000_000_000, 100_000_000_000_000) + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 600_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 100_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapLegacyModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD], 100_000_000_000_000, 250_000_000_000_000,), + Error::::InsufficientTargetAmount + ); + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, WBTC, EDF], 100_000_000_000_000, 0), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_supply(&BOB, &[EDF, USSD, EDF], 100_000_000_000_000, 0), + Error::::InvalidTradingPath, + ); + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_supply(&BOB, &[EDF, SEE], 100_000_000_000_000, 0), + Error::::MustBeEnabled, + ); + + assert_ok!(EdfisSwapLegacyModule::do_swap_with_exact_supply( + &BOB, + &[EDF, USSD], + 100_000_000_000_000, + 200_000_000_000_000, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD], + liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], + })); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (251_256_281_407_036, 200_000_000_000_000) + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 351_256_281_407_036 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 200_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapLegacyModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_248_743_718_592_964); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_900_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_ok!(EdfisSwapLegacyModule::do_swap_with_exact_supply( + &BOB, + &[EDF, USSD, WBTC], + 200_000_000_000_000, + 1, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD, WBTC], + liquidity_changes: vec![200_000_000_000_000, 124_996_843_514_053, 5_530_663_837], + })); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (126_259_437_892_983, 400_000_000_000_000) + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), + (224_996_843_514_053, 4_469_336_163) + ); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 351_256_281_407_036 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 400_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapLegacyModule::account_id()), 4_469_336_163); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_248_743_718_592_964); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_700_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_005_530_663_837); + }); +} + +#[test] +fn do_swap_with_exact_target_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 500_000_000_000_000, + 100_000_000_000_000, + 0, + false, + )); + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 100_000_000_000_000, + 10_000_000_000, + 0, + false, + )); + + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (500_000_000_000_000, 100_000_000_000_000) + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 600_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 100_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapLegacyModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 1_000_000_000_000_000_000); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_target(&BOB, &[EDF, USSD], 250_000_000_000_000, 100_000_000_000_000,), + Error::::ExcessiveSupplyAmount + ); + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_target( + &BOB, + &[EDF, USSD, WBTC, EDF], + 250_000_000_000_000, + 200_000_000_000_000, + ), + Error::::InvalidTradingPathLength, + ); + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_target(&BOB, &[EDF, USSD, EDF], 250_000_000_000_000, 200_000_000_000_000,), + Error::::InvalidTradingPath, + ); + assert_noop!( + EdfisSwapLegacyModule::do_swap_with_exact_target(&BOB, &[EDF, SEE], 250_000_000_000_000, 200_000_000_000_000), + Error::::MustBeEnabled, + ); + + assert_ok!(EdfisSwapLegacyModule::do_swap_with_exact_target( + &BOB, + &[EDF, USSD], + 250_000_000_000_000, + 200_000_000_000_000, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD], + liquidity_changes: vec![101_010_101_010_102, 250_000_000_000_000], + })); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (250_000_000_000_000, 201_010_101_010_102) + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), + (100_000_000_000_000, 10_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 350_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 201_010_101_010_102); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapLegacyModule::account_id()), 10_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_250_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_898_989_898_989_898); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_000_000_000_000); + + assert_ok!(EdfisSwapLegacyModule::do_swap_with_exact_target( + &BOB, + &[EDF, USSD, WBTC], + 5_000_000_000, + 2_000_000_000_000_000, + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD, WBTC], + liquidity_changes: vec![137_654_580_386_993, 101_010_101_010_102, 5_000_000_000], + })); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, EDF), + (148_989_898_989_898, 338_664_681_397_095) + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity(USSD, WBTC), + (201_010_101_010_102, 5_000_000_000) + ); + assert_eq!( + Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), + 350_000_000_000_000 + ); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 338_664_681_397_095); + assert_eq!(Tokens::free_balance(WBTC, &EdfisSwapLegacyModule::account_id()), 5_000_000_000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1_000_250_000_000_000_000); + assert_eq!(Tokens::free_balance(EDF, &BOB), 999_761_335_318_602_905); + assert_eq!(Tokens::free_balance(WBTC, &BOB), 1_000_000_005_000_000_000); + }); +} + +#[test] +fn initialize_added_liquidity_pools_genesis_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .initialize_added_liquidity_pools(ALICE) + .build() + .execute_with(|| { + System::set_block_number(1); + + assert_eq!(EdfisSwapLegacyModule::get_liquidity(USSD, EDF), (1000000, 2000000)); + assert_eq!(Tokens::free_balance(USSD, &EdfisSwapLegacyModule::account_id()), 2000000); + assert_eq!(Tokens::free_balance(EDF, &EdfisSwapLegacyModule::account_id()), 3000000); + assert_eq!( + Tokens::free_balance(USSDEDFPair::get().dex_share_currency_id(), &ALICE), + 2000000 + ); + }); +} + +#[test] +fn get_swap_amount_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (50000, 10000)); + assert_eq!( + EdfisSwapLegacyModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 0)), + Some((10000, 24874)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactSupply(10000, 24875)), + None + ); + assert_eq!( + EdfisSwapLegacyModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(Balance::max_value(), 24874)), + Some((10000, 24874)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_swap_amount(&[EDF, USSD], SwapLimit::ExactTarget(9999, 24874)), + None + ); + }); +} + +#[test] +fn get_best_price_swap_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(USSDEDFPair::get(), (300000, 100000)); + LiquidityPool::::insert(USSDWBTCPair::get(), (50000, 10000)); + LiquidityPool::::insert(EDFWBTCPair::get(), (10000, 10000)); + + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 30), vec![]), + None + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(0, 0), vec![]), + None + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![SEE]]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![EDF]]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![USSD]]), + Some((vec![EDF, USSD], 10, 29)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10, 0), vec![vec![WBTC]]), + Some((vec![EDF, WBTC, USSD], 10, 44)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactSupply(10000, 0), vec![vec![WBTC]]), + Some((vec![EDF, USSD], 10000, 27024)) + ); + + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(10, 30), vec![]), + None + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(0, 0), vec![]), + None + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![SEE]]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![EDF]]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![USSD]]), + Some((vec![EDF, USSD], 11, 30)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(20, 30), vec![vec![WBTC]]), + Some((vec![EDF, WBTC, USSD], 8, 30)) + ); + assert_eq!( + EdfisSwapLegacyModule::get_best_price_swap_path(EDF, USSD, SwapLimit::ExactTarget(100000, 20000), vec![vec![WBTC]]), + Some((vec![EDF, USSD], 7216, 20000)) + ); + }); +} + +#[test] +fn swap_with_specific_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 500_000_000_000_000, + 100_000_000_000_000, + 0, + false, + )); + + assert_noop!( + EdfisSwapLegacyModule::swap_with_specific_path( + &BOB, + &[EDF, USSD], + SwapLimit::ExactSupply(100_000_000_000_000, 248_743_718_592_965) + ), + Error::::InsufficientTargetAmount + ); + + assert_ok!(EdfisSwapLegacyModule::swap_with_specific_path( + &BOB, + &[EDF, USSD], + SwapLimit::ExactSupply(100_000_000_000_000, 200_000_000_000_000) + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::Swap { + trader: BOB, + path: vec![EDF, USSD], + liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], + })); + + assert_noop!( + EdfisSwapLegacyModule::swap_with_specific_path( + &BOB, + &[USSD, EDF], + SwapLimit::ExactTarget(253_794_223_643_470, 100_000_000_000_000) + ), + Error::::ExcessiveSupplyAmount + ); + + assert_ok!(EdfisSwapLegacyModule::swap_with_specific_path( + &BOB, + &[USSD, EDF], + SwapLimit::ExactTarget(300_000_000_000_000, 100_000_000_000_000) + )); + System::assert_last_event(RuntimeEvent::EdfisSwapLegacyModule(crate::Event::Swap { + trader: BOB, + path: vec![USSD, EDF], + liquidity_changes: vec![253_794_223_643_471, 100_000_000_000_000], + })); + }); +} + +#[test] +fn get_liquidity_token_address_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Disabled + ); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_token_address(USSD, EDF), None); + + assert_ok!(EdfisSwapLegacyModule::list_provisioning( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Provisioning(ProvisioningParameters { + min_contribution: (1_000_000_000_000u128, 1_000_000_000_000u128), + target_provision: (5_000_000_000_000u128, 2_000_000_000_000u128), + accumulated_provision: (0, 0), + not_before: 10, + }) + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity_token_address(USSD, EDF), + Some(H160::from_str("0x0000000000000000000200000000010000000002").unwrap()) + ); + + assert_ok!(EdfisSwapLegacyModule::enable_trading_pair( + RuntimeOrigin::signed(ListingOrigin::get()), + USSD, + EDF + )); + assert_eq!( + EdfisSwapLegacyModule::trading_pair_statuses(USSDEDFPair::get()), + TradingPairStatus::<_, _>::Enabled + ); + assert_eq!( + EdfisSwapLegacyModule::get_liquidity_token_address(USSD, EDF), + Some(H160::from_str("0x0000000000000000000200000000010000000002").unwrap()) + ); + }); +} + +#[test] +fn specific_joint_swap_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + EDF, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + assert_ok!(EdfisSwapLegacyModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + USSD, + WBTC, + 5_000_000_000_000, + 1_000_000_000_000, + 0, + false, + )); + + assert_eq!( + USSDJointSwap::get_swap_amount(WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + Some((10000, 9800)) + ); + assert_eq!( + SEEJointSwap::get_swap_amount(WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + None + ); + + assert_noop!( + USSDJointSwap::swap(&CAROL, WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + orml_tokens::Error::::BalanceTooLow, + ); + assert_noop!( + USSDJointSwap::swap(&BOB, WBTC, EDF, SwapLimit::ExactSupply(10000, 9801)), + SwapError::CannotSwap, + ); + assert_noop!( + SEEJointSwap::swap(&BOB, WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + SwapError::CannotSwap, + ); + + assert_eq!( + USSDJointSwap::swap(&BOB, WBTC, EDF, SwapLimit::ExactSupply(10000, 0)), + Ok((10000, 9800)), + ); + + assert_eq!( + USSDJointSwap::swap(&BOB, EDF, WBTC, SwapLimit::ExactTarget(20000, 10000)), + Ok((10204, 10000)), + ); + }); +} diff --git a/blockchain/modules/edfis-swap-legacy/src/weights.rs b/blockchain/modules/edfis-swap-legacy/src/weights.rs new file mode 100644 index 00000000..75b8bb18 --- /dev/null +++ b/blockchain/modules/edfis-swap-legacy/src/weights.rs @@ -0,0 +1,241 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for edfis_swap_legacy_module +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=edfis_swap_legacy_module +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/dex/src/weights.rs +// --template=.maintain/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for edfis_swap_legacy_module. +pub trait WeightInfo { + fn enable_trading_pair() -> Weight; + fn disable_trading_pair() -> Weight; + fn list_provisioning() -> Weight; + fn update_provisioning_parameters() -> Weight; + fn end_provisioning() -> Weight; + fn add_provision() -> Weight; + fn claim_dex_share() -> Weight; + fn add_liquidity() -> Weight; + fn add_liquidity_and_stake() -> Weight; + fn remove_liquidity() -> Weight; + fn remove_liquidity_by_unstake() -> Weight; + fn swap_with_exact_supply(u: u32, ) -> Weight; + fn swap_with_exact_target(u: u32, ) -> Weight; + fn refund_provision() -> Weight; + fn abort_provisioning() -> Weight; +} + +/// Weights for edfis_swap_legacy_module using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn enable_trading_pair() -> Weight { + Weight::from_parts(24_728_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn disable_trading_pair() -> Weight { + Weight::from_parts(24_891_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn list_provisioning() -> Weight { + Weight::from_parts(37_619_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn update_provisioning_parameters() -> Weight { + Weight::from_parts(11_808_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn end_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + fn add_provision() -> Weight { + Weight::from_parts(127_543_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn claim_dex_share() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn add_liquidity() -> Weight { + Weight::from_parts(184_975_000, 0) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + fn add_liquidity_and_stake() -> Weight { + Weight::from_parts(258_276_000, 0) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + fn remove_liquidity() -> Weight { + Weight::from_parts(158_440_000, 0) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + fn remove_liquidity_by_unstake() -> Weight { + Weight::from_parts(277_297_000, 0) + .saturating_add(T::DbWeight::get().reads(12 as u64)) + .saturating_add(T::DbWeight::get().writes(10 as u64)) + } + fn swap_with_exact_supply(u: u32, ) -> Weight { + Weight::from_parts(93_799_000, 0) + // Standard Error: 117_000 + .saturating_add(Weight::from_parts(16_008_000, 0).saturating_mul(u as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn swap_with_exact_target(u: u32, ) -> Weight { + Weight::from_parts(93_966_000, 0) + // Standard Error: 226_000 + .saturating_add(Weight::from_parts(16_058_000, 0).saturating_mul(u as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn refund_provision() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + fn abort_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn enable_trading_pair() -> Weight { + Weight::from_parts(24_728_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn disable_trading_pair() -> Weight { + Weight::from_parts(24_891_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn list_provisioning() -> Weight { + Weight::from_parts(37_619_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn update_provisioning_parameters() -> Weight { + Weight::from_parts(11_808_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn end_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + fn add_provision() -> Weight { + Weight::from_parts(127_543_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn claim_dex_share() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn add_liquidity() -> Weight { + Weight::from_parts(184_975_000, 0) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + fn add_liquidity_and_stake() -> Weight { + Weight::from_parts(258_276_000, 0) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + fn remove_liquidity() -> Weight { + Weight::from_parts(158_440_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + fn remove_liquidity_by_unstake() -> Weight { + Weight::from_parts(277_297_000, 0) + .saturating_add(RocksDbWeight::get().reads(12 as u64)) + .saturating_add(RocksDbWeight::get().writes(10 as u64)) + } + fn swap_with_exact_supply(u: u32, ) -> Weight { + Weight::from_parts(93_799_000, 0) + // Standard Error: 117_000 + .saturating_add(Weight::from_parts(16_008_000, 0).saturating_mul(u as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn swap_with_exact_target(u: u32, ) -> Weight { + Weight::from_parts(93_966_000, 0) + // Standard Error: 226_000 + .saturating_add(Weight::from_parts(16_058_000, 0).saturating_mul(u as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(u as u64))) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(u as u64))) + } + fn refund_provision() -> Weight { + Weight::from_parts(105_716_000, 0) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + fn abort_provisioning() -> Weight { + Weight::from_parts(78_617_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } +} diff --git a/blockchain/modules/edfis-swap/README.md b/blockchain/modules/edfis-swap/README.md index 538e238c..e04f3f8f 100644 --- a/blockchain/modules/edfis-swap/README.md +++ b/blockchain/modules/edfis-swap/README.md @@ -4,4 +4,4 @@ ## Overview -Ethical DeFi's Built-in decentralized exchange `SwapDex` (Swap Exchange) module, the swap mechanism refers to the design of `Uniswap V2`. In addition to being used for trading, DEX also participates in `ECDP liquidation`, which is faster than Liquidation By Auction when the liquidity is sufficient. The `Swap Exchange` is one of the DEXs on `Edfis` (Ethical DeFi's Exchange) providing market making liquidity for Edfis Exchange. +Ethical DeFi's Built-in decentralized exchange `SwapDex` (Swap Exchange) module, the swap mechanism refers to the design of `Uniswap V3` with additional features and functionalities that makes Edfis unique. In addition to being used for trading, DEX also participates in `ECDP liquidation`, which is faster than Liquidation By Auction when the liquidity is sufficient. diff --git a/blockchain/modules/edfis-swap/src/lib.rs b/blockchain/modules/edfis-swap/src/lib.rs index f7193109..d7fa574b 100644 --- a/blockchain/modules/edfis-swap/src/lib.rs +++ b/blockchain/modules/edfis-swap/src/lib.rs @@ -23,10 +23,9 @@ //! ## Overview //! //! Ethical DeFi's Built-in decentralized exchange `SwapDex` (Swap Exchange) module, the swap -//! mechanism refers to the design of `Uniswap V2`. In addition to being used for trading, -//! DEX also participates in `ECDP liquidation`, which is faster than Liquidation By Auction -//! when the liquidity is sufficient. The `Swap Exchange` is one of the DEXs on -//! `Edfis` (Ethical DeFi's Exchange) providing market making liquidity for `Edfis Exchange`. +//! mechanism refers to the design of `Uniswap V3` with additional features and functionalities +//! that makes Edfis unique. In addition to being used for trading, DEX also participates +//! in `ECDP liquidation`, which is faster than Liquidation By Auction when the liquidity is sufficient. #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::too_many_arguments)] diff --git a/blockchain/modules/evm/src/bench/mock.rs b/blockchain/modules/evm/src/bench/mock.rs index 438da8b7..3216c762 100644 --- a/blockchain/modules/evm/src/bench/mock.rs +++ b/blockchain/modules/evm/src/bench/mock.rs @@ -242,7 +242,7 @@ parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); } -impl edfis_swap_module::Config for Runtime { +impl edfis_swap_legacy_module::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Tokens; type GetExchangeFee = GetExchangeFee; @@ -263,7 +263,7 @@ pub type Block = generic::Block; construct_runtime!( pub enum Runtime { System: frame_system, - Dex: edfis_swap_module, + Dex: edfis_swap_legacy_module, EVM: evm_module, Tokens: orml_tokens, Balances: pallet_balances, diff --git a/blockchain/modules/transaction-payment/src/mock.rs b/blockchain/modules/transaction-payment/src/mock.rs index 1e8bf200..d8c4cd69 100644 --- a/blockchain/modules/transaction-payment/src/mock.rs +++ b/blockchain/modules/transaction-payment/src/mock.rs @@ -170,7 +170,7 @@ parameter_types! { pub const TradingPathLimit: u32 = 4; } -impl edfis_swap_module::Config for Runtime { +impl edfis_swap_legacy_module::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Currencies; type GetExchangeFee = GetExchangeFee; @@ -272,7 +272,7 @@ impl Config for Runtime { type WeightToFee = WeightToFee; type LengthToFee = TransactionByteFee; type FeeMultiplierUpdate = (); - type Swap = SpecificJointsSwap; + type Swap = SpecificJointsSwap; type MaxSwapSlippageComparedToOracle = MaxSwapSlippageComparedToOracle; type TradingPathLimit = TradingPathLimit; type PriceSource = MockPriceSource; @@ -312,7 +312,7 @@ construct_runtime!( PalletBalances: pallet_balances, Tokens: orml_tokens, Currencies: module_currencies, - EdfisSwapModule: edfis_swap_module, + EdfisSwapLegacyModule: edfis_swap_legacy_module, } ); @@ -388,7 +388,7 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - edfis_swap_module::GenesisConfig:: { + edfis_swap_legacy_module::GenesisConfig:: { initial_listing_trading_pairs: vec![], initial_enabled_trading_pairs: EnabledTradingPairs::get(), initial_added_liquidity_pools: vec![], diff --git a/blockchain/modules/transaction-payment/src/tests.rs b/blockchain/modules/transaction-payment/src/tests.rs index 3ee3d677..b181488b 100644 --- a/blockchain/modules/transaction-payment/src/tests.rs +++ b/blockchain/modules/transaction-payment/src/tests.rs @@ -29,7 +29,7 @@ use frame_support::{ dispatch::{DispatchClass, DispatchInfo, Pays}, }; use mock::{ - AccountId, BlockWeights, Currencies, EdfisSwapModule, ExtBuilder, FeePoolSize, MockPriceSource, Runtime, RuntimeCall, + AccountId, BlockWeights, Currencies, EdfisSwapLegacyModule, ExtBuilder, FeePoolSize, MockPriceSource, Runtime, RuntimeCall, RuntimeOrigin, System, TransactionPayment, ALICE, BOB, CHARLIE, DAVE, FEE_UNBALANCED_AMOUNT, TIP_UNBALANCED_AMOUNT, SEE, USSD, EDF, LSEE, }; @@ -135,7 +135,7 @@ fn enable_dex_and_tx_fee_pool() { } // enable dex - assert_ok!(EdfisSwapModule::add_liquidity( + assert_ok!(EdfisSwapLegacyModule::add_liquidity( RuntimeOrigin::signed(ALICE), SEE, USSD, @@ -144,7 +144,7 @@ fn enable_dex_and_tx_fee_pool() { 0, false )); - assert_ok!(EdfisSwapModule::add_liquidity( + assert_ok!(EdfisSwapLegacyModule::add_liquidity( RuntimeOrigin::signed(ALICE), EDF, USSD, @@ -153,7 +153,7 @@ fn enable_dex_and_tx_fee_pool() { 0, false )); - assert_ok!(EdfisSwapModule::add_liquidity( + assert_ok!(EdfisSwapLegacyModule::add_liquidity( RuntimeOrigin::signed(ALICE), LSEE, SEE, @@ -162,10 +162,10 @@ fn enable_dex_and_tx_fee_pool() { 0, false )); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (100, 1000)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(LSEE, SEE), (100, 1000)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, SEE), (0, 0)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(EDF, USSD), (100, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(LSEE, SEE), (100, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(EDF, SEE), (0, 0)); // enable tx fee pool for USSD and EDF token. vec![USSD, EDF].iter().for_each(|token| { @@ -559,7 +559,7 @@ fn pre_post_dispatch_and_refund_with_fee_call_use_dex(with_fee_call: ::from(0).validate(&BOB, &with_fee_call, &INFO2, 50)); - System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapLegacyModule(edfis_swap_legacy_module::Event::Swap { trader: BOB, path: vec![LSEE, SEE], liquidity_changes: vec![46, 315], @@ -808,7 +808,7 @@ fn charges_fee_when_validate_with_fee_call_use_swap(with_fee_call: ::from(0).validate(&BOB, &with_fee_call, &INFO2, 50)); - System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapLegacyModule(edfis_swap_legacy_module::Event::Swap { trader: BOB, path: vec![LSEE, SEE], liquidity_changes: vec![114, 300], @@ -903,7 +903,7 @@ fn charges_fee_when_validate_and_native_is_not_enough() { // transfer token to Bob, and use Bob as tx sender to test // Bob do not have enough native asset(SEE), but he has USSD assert_ok!(>::transfer(USSD, &ALICE, &BOB, 4000)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); assert_eq!(Currencies::total_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(USSD, &BOB), 4000); @@ -929,7 +929,7 @@ fn charges_fee_when_validate_and_native_is_not_enough() { // surplus=50SEE/500USSD, balance=4000, swap_in=2600, left=1400 // surplus=0, balance=4000, swap_in=2100, left=1900 assert_eq!(Currencies::free_balance(USSD, &BOB), 1900 - surplus1 * 10); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); assert_eq!( Currencies::free_balance(SEE, &sub_account), init_balance - (fee + ed + surplus1) @@ -1030,35 +1030,35 @@ fn charges_fee_failed_by_slippage_limit() { builder_with_dex_and_fee_pool(true).execute_with(|| { assert_ok!(>::transfer(USSD, &ALICE, &BOB, 1000)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); assert_eq!(Currencies::total_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(SEE, &BOB), 0); assert_eq!(>::free_balance(USSD, &BOB), 1000); assert_eq!( - EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), + EdfisSwapLegacyModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), Some((252, 2010)) ); assert_eq!( - EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), + EdfisSwapLegacyModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), Some((1000, 5000)) ); // pool is enough, but slippage limit the swap MockPriceSource::set_relative_price(Some(Price::saturating_from_rational(252, 4020))); assert_eq!( - EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), + EdfisSwapLegacyModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactTarget(Balance::MAX, 2010)), Some((252, 2010)) ); assert_eq!( - EdfisSwapModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), + EdfisSwapLegacyModule::get_swap_amount(&vec![USSD, SEE], SwapLimit::ExactSupply(1000, 0)), Some((1000, 5000)) ); assert_noop!( ChargeTransactionPayment::::from(0).validate(&BOB, &CALL2, &INFO, 500), TransactionValidityError::Invalid(InvalidTransaction::Payment) ); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); }); } @@ -1110,8 +1110,8 @@ fn charge_fee_by_alternative_swap_first_priority() { let alternative_fee_swap_deposit: u128 = <::AlternativeFeeSwapDeposit as frame_support::traits::Get>::get(); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (100, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (10000, 1000)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(EDF, USSD), (100, 1000)); assert_ok!(Currencies::update_balance( RuntimeOrigin::root(), BOB, @@ -1156,7 +1156,7 @@ fn charge_fee_by_alternative_swap_first_priority() { .priority, 1 ); - System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapLegacyModule(edfis_swap_legacy_module::Event::Swap { trader: BOB, path: vec![EDF, USSD, SEE], liquidity_changes: vec![51, 334, fee_surplus], @@ -1165,8 +1165,8 @@ fn charge_fee_by_alternative_swap_first_priority() { assert_eq!(Currencies::free_balance(SEE, &BOB), ed); assert_eq!(Currencies::free_balance(USSD, &BOB), 0); assert_eq!(Currencies::free_balance(EDF, &BOB), 249); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (151, 666)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(EDF, USSD), (151, 666)); assert_eq!(Currencies::free_balance(SEE, &sub_account), init_balance,); assert_eq!(Currencies::free_balance(EDF, &sub_account), edf_ed); }); @@ -1229,7 +1229,7 @@ fn charge_fee_by_default_fee_tokens_second_priority() { 1 ); // Alternative fee swap directly from dex, not from fee pool. - System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapModule(edfis_swap_module::Event::Swap { + System::assert_has_event(crate::mock::RuntimeEvent::EdfisSwapLegacyModule(edfis_swap_legacy_module::Event::Swap { trader: BOB, path: vec![EDF, USSD, SEE], liquidity_changes: vec![51, 334, fee_surplus], @@ -1238,8 +1238,8 @@ fn charge_fee_by_default_fee_tokens_second_priority() { assert_eq!(Currencies::free_balance(SEE, &BOB), ed); assert_eq!(Currencies::free_balance(USSD, &BOB), 0); assert_eq!(Currencies::free_balance(EDF, &BOB), 249); - assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); - assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (151, 666)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(SEE, USSD), (7500, 1334)); + assert_eq!(EdfisSwapLegacyModule::get_liquidity_pool(EDF, USSD), (151, 666)); // sub-account balance not changed, because not passing through sub-account. assert_eq!(Currencies::free_balance(SEE, &sub_account), init_balance,); assert_eq!(Currencies::free_balance(EDF, &sub_account), edf_ed); @@ -1771,7 +1771,7 @@ fn swap_from_pool_and_dex_with_higher_threshold() { let supply_amount = Currencies::free_balance(EDF, &edf_fee_account) - edf_ed; // here just get swap out amount, the swap not happened let (supply_in_amount, swap_out_native) = - edfis_swap_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) + edfis_swap_legacy_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) .unwrap(); assert_eq!(3074, swap_out_native); assert_eq!(supply_in_amount, supply_amount); @@ -1847,7 +1847,7 @@ fn swap_from_pool_and_dex_with_midd_threshold() { // 500 | 4544 | FixedU128(0.110035211267605633) // 600 | 4614 | FixedU128(0.130039011703511053) <- this case hit here let (supply_in_amount, swap_out_native) = - edfis_swap_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) + edfis_swap_legacy_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) .unwrap(); assert_eq!(600, supply_in_amount); assert_eq!(4614, swap_out_native); @@ -1886,7 +1886,7 @@ fn swap_from_pool_and_dex_with_midd_threshold() { #[test] #[should_panic(expected = "Swap tx fee pool should not fail!")] fn charge_fee_failed_when_disable_dex() { - use edfis_swap_module::TradingPairStatus; + use edfis_swap_legacy_module::TradingPairStatus; use primitives::TradingPair; ExtBuilder::default().build().execute_with(|| { @@ -1924,13 +1924,13 @@ fn charge_fee_failed_when_disable_dex() { // trading pair is enabled let pair = TradingPair::from_currency_ids(USSD, SEE).unwrap(); assert_eq!( - edfis_swap_module::Pallet::::trading_pair_statuses(pair), + edfis_swap_legacy_module::Pallet::::trading_pair_statuses(pair), TradingPairStatus::Enabled ); // make sure swap is valid - let swap_result = edfis_swap_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(1, 0)); + let swap_result = edfis_swap_legacy_module::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(1, 0)); assert!(swap_result.is_some()); - assert_ok!(edfis_swap_module::Pallet::::swap_with_specific_path( + assert_ok!(edfis_swap_legacy_module::Pallet::::swap_with_specific_path( &ALICE, &trading_path, SwapLimit::ExactSupply(100, 0) @@ -1991,16 +1991,16 @@ fn charge_fee_failed_when_disable_dex() { } // when trading pair disabled, the swap action will failed - assert_ok!(edfis_swap_module::Pallet::::disable_trading_pair( + assert_ok!(edfis_swap_legacy_module::Pallet::::disable_trading_pair( RuntimeOrigin::signed(AccountId::new([0u8; 32])), USSD, SEE )); assert_eq!( - edfis_swap_module::Pallet::::trading_pair_statuses(pair), + edfis_swap_legacy_module::Pallet::::trading_pair_statuses(pair), TradingPairStatus::Disabled ); - let res = edfis_swap_module::Pallet::::swap_with_specific_path( + let res = edfis_swap_legacy_module::Pallet::::swap_with_specific_path( &ALICE, &trading_path, SwapLimit::ExactSupply(100, 0), @@ -2060,7 +2060,7 @@ fn charge_fee_pool_operation_works() { 10000.unique_saturated_into(), )); - assert_ok!(EdfisSwapModule::add_liquidity( + assert_ok!(EdfisSwapLegacyModule::add_liquidity( RuntimeOrigin::signed(ALICE), SEE, USSD, diff --git a/blockchain/runtime/common/src/precompile/mock.rs b/blockchain/runtime/common/src/precompile/mock.rs index e587291f..6b4de0e1 100644 --- a/blockchain/runtime/common/src/precompile/mock.rs +++ b/blockchain/runtime/common/src/precompile/mock.rs @@ -482,7 +482,7 @@ parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"edf/swap"); } -impl edfis_swap_module::Config for Test { +impl edfis_swap_legacy_module::Config for Test { type Event = Event; type Currency = Tokens; type StableCurrencyIds = StableCurrencyIds; @@ -521,7 +521,7 @@ pub type ScheduleCallPrecompile = crate::ScheduleCallPrecompile< OriginCaller, Test, >; -pub type DexPrecompile = crate::DexPrecompile; +pub type DexPrecompile = crate::DexPrecompile; parameter_types! { pub NetworkContractSource: H160 = alice_evm_addr(); @@ -594,7 +594,7 @@ impl module_prices::Config for Test { type SetUSDFixedPrice = SetUSDFixedPrice; type SetterFixedPrice = SetterFixedPrice; type LockOrigin = EnsureSignedBy; - type DEX = EdfisSwapModule; + type DEX = EdfisSwapLegacyModule; type Currency = Currencies; type CurrencyIdMapping = EvmCurrencyIdMapping; type WeightInfo = (); @@ -688,7 +688,7 @@ frame_support::construct_runtime!( Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, Utility: pallet_utility::{Pallet, Call, Event}, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, - EdfisSwapModule: edfis_swap_module::{Pallet, Storage, Call, Event, Config}, + EdfisSwapLegacyModule: edfis_swap_legacy_module::{Pallet, Storage, Call, Event, Config}, ModuleEVM: module_evm::{Pallet, Config, Call, Storage, Event}, } ); diff --git a/blockchain/runtime/common/src/precompile/tests.rs b/blockchain/runtime/common/src/precompile/tests.rs index acdf6af1..d855107a 100644 --- a/blockchain/runtime/common/src/precompile/tests.rs +++ b/blockchain/runtime/common/src/precompile/tests.rs @@ -24,7 +24,7 @@ use super::*; use crate::precompile::{ mock::{ setm_evm_address, alice, alice_evm_addr, setusd_evm_address, bob, bob_evm_addr, erc20_address_not_exists, - get_task_id, lp_setm_setusd_evm_address, new_test_ext, serp_evm_address, run_to_block, Balances, EdfisSwapModule, + get_task_id, lp_setm_setusd_evm_address, new_test_ext, serp_evm_address, run_to_block, Balances, EdfisSwapLegacyModule, DexPrecompile, Event as TestEvent, MultiCurrencyPrecompile, Oracle, OraclePrecompile, Origin, Price, ScheduleCallPrecompile, System, Test, ALICE, SETUSD, INITIAL_BALANCE, SERP, }, @@ -602,9 +602,9 @@ fn precompile_filter_does_not_work_on_non_system_contracts() { // fn dex_precompile_get_liquidity_should_work() { // new_test_ext().execute_with(|| { // // enable SERP/SETUSD -// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); +// assert_ok!(EdfisSwapLegacyModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); -// assert_ok!(EdfisSwapModule::add_liquidity( +// assert_ok!(EdfisSwapLegacyModule::add_liquidity( // Origin::signed(ALICE), // SERP, // SETUSD, @@ -643,9 +643,9 @@ fn precompile_filter_does_not_work_on_non_system_contracts() { // fn dex_precompile_get_liquidity_token_address_should_work() { // new_test_ext().execute_with(|| { // // enable SERP/SETUSD -// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); +// assert_ok!(EdfisSwapLegacyModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); -// assert_ok!(EdfisSwapModule::add_liquidity( +// assert_ok!(EdfisSwapLegacyModule::add_liquidity( // Origin::signed(ALICE), // SERP, // SETUSD, @@ -693,9 +693,9 @@ fn precompile_filter_does_not_work_on_non_system_contracts() { // fn dex_precompile_get_swap_target_amount_should_work() { // new_test_ext().execute_with(|| { // // enable SERP/SETUSD -// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); +// assert_ok!(EdfisSwapLegacyModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); -// assert_ok!(EdfisSwapModule::add_liquidity( +// assert_ok!(EdfisSwapLegacyModule::add_liquidity( // Origin::signed(ALICE), // SERP, // SETUSD, @@ -739,9 +739,9 @@ fn precompile_filter_does_not_work_on_non_system_contracts() { // fn dex_precompile_get_swap_supply_amount_should_work() { // new_test_ext().execute_with(|| { // // enable SERP/SETUSD -// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); +// assert_ok!(EdfisSwapLegacyModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); -// assert_ok!(EdfisSwapModule::add_liquidity( +// assert_ok!(EdfisSwapLegacyModule::add_liquidity( // Origin::signed(ALICE), // SERP, // SETUSD, @@ -785,9 +785,9 @@ fn precompile_filter_does_not_work_on_non_system_contracts() { // fn dex_precompile_swap_with_exact_supply_should_work() { // new_test_ext().execute_with(|| { // // enable SERP/SETUSD -// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); +// assert_ok!(EdfisSwapLegacyModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); -// assert_ok!(EdfisSwapModule::add_liquidity( +// assert_ok!(EdfisSwapLegacyModule::add_liquidity( // Origin::signed(ALICE), // SERP, // SETUSD, @@ -835,9 +835,9 @@ fn precompile_filter_does_not_work_on_non_system_contracts() { // fn dex_precompile_swap_with_exact_target_should_work() { // new_test_ext().execute_with(|| { // // enable SERP/SETUSD -// assert_ok!(EdfisSwapModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); +// assert_ok!(EdfisSwapLegacyModule::enable_trading_pair(Origin::signed(ALICE), SERP, SETUSD,)); -// assert_ok!(EdfisSwapModule::add_liquidity( +// assert_ok!(EdfisSwapLegacyModule::add_liquidity( // Origin::signed(ALICE), // SERP, // SETUSD, diff --git a/blockchain/runtime/src/benchmarking/dex.rs b/blockchain/runtime/src/benchmarking/dex.rs index 9b59d0b1..5718aaa0 100644 --- a/blockchain/runtime/src/benchmarking/dex.rs +++ b/blockchain/runtime/src/benchmarking/dex.rs @@ -25,7 +25,7 @@ use crate::{ use frame_benchmarking::{account, whitelisted_caller}; use frame_system::RawOrigin; -use edfis_swap_module::TradingPairStatus; +use edfis_swap_legacy_module::TradingPairStatus; use orml_benchmarking::runtime_benchmarks; use orml_traits::{MultiCurrency, MultiCurrencyExtended}; use primitives::TradingPair; @@ -76,7 +76,7 @@ fn inject_liquidity( } runtime_benchmarks! { - { Runtime, edfis_swap_module } + { Runtime, edfis_swap_legacy_module } // enable a Disabled trading pair enable_trading_pair { @@ -86,7 +86,7 @@ runtime_benchmarks! { } }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) verify { - assert_last_event(edfis_swap_module::Event::EnableTradingPair{trading_pair: trading_pair}.into()); + assert_last_event(edfis_swap_legacy_module::Event::EnableTradingPair{trading_pair: trading_pair}.into()); } // disable a Enabled trading pair @@ -97,7 +97,7 @@ runtime_benchmarks! { } }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) verify { - assert_last_event(edfis_swap_module::Event::DisableTradingPair{trading_pair}.into()); + assert_last_event(edfis_swap_legacy_module::Event::DisableTradingPair{trading_pair}.into()); } // list a Provisioning trading pair @@ -108,7 +108,7 @@ runtime_benchmarks! { } }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second()), dollar(trading_pair.first()), dollar(trading_pair.second()), 10) verify { - assert_last_event(edfis_swap_module::Event::ListProvisioning{trading_pair: trading_pair}.into()); + assert_last_event(edfis_swap_legacy_module::Event::ListProvisioning{trading_pair: trading_pair}.into()); } // update parameters of a Provisioning trading pair @@ -161,7 +161,7 @@ runtime_benchmarks! { )?; }: _(RawOrigin::Signed(founder), trading_pair.first(), trading_pair.second()) verify { - assert_last_event(edfis_swap_module::Event::ProvisioningToEnabled{trading_pair, pool_0: 100 * dollar(trading_pair.first()), pool_1: 100 * dollar(trading_pair.second()), share_amount: 200 * dollar(trading_pair.first())}.into()) + assert_last_event(edfis_swap_legacy_module::Event::ProvisioningToEnabled{trading_pair, pool_0: 100 * dollar(trading_pair.first()), pool_1: 100 * dollar(trading_pair.second()), share_amount: 200 * dollar(trading_pair.first())}.into()) } add_provision { @@ -186,7 +186,7 @@ runtime_benchmarks! { >::update_balance(trading_pair.second(), &founder, (10 * dollar(trading_pair.second())).unique_saturated_into())?; }: _(RawOrigin::Signed(founder.clone()), trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second())) verify{ - assert_last_event(edfis_swap_module::Event::AddProvision{who: founder, currency_0: trading_pair.first(), contribution_0: dollar(trading_pair.first()), currency_1: trading_pair.second(), contribution_1: dollar(trading_pair.second())}.into()); + assert_last_event(edfis_swap_legacy_module::Event::AddProvision{who: founder, currency_0: trading_pair.first(), contribution_0: dollar(trading_pair.first()), currency_1: trading_pair.second(), contribution_1: dollar(trading_pair.second())}.into()); } claim_dex_share { diff --git a/blockchain/runtime/src/benchmarking/serp_setmint.rs b/blockchain/runtime/src/benchmarking/serp_setmint.rs index ab870498..0102bf14 100644 --- a/blockchain/runtime/src/benchmarking/serp_setmint.rs +++ b/blockchain/runtime/src/benchmarking/serp_setmint.rs @@ -28,7 +28,7 @@ use super::utils::{feed_price, set_balance}; use core::convert::TryInto; use frame_benchmarking::{account, whitelisted_caller}; use frame_system::RawOrigin; -use edfis_swap_module::TradingPairStatus; +use edfis_swap_legacy_module::TradingPairStatus; use orml_benchmarking::runtime_benchmarks; use orml_traits::{Change, GetByKey, MultiCurrencyExtended}; use sp_runtime::{ diff --git a/blockchain/runtime/src/lib.rs b/blockchain/runtime/src/lib.rs index 1948d105..56806ebc 100644 --- a/blockchain/runtime/src/lib.rs +++ b/blockchain/runtime/src/lib.rs @@ -842,7 +842,7 @@ parameter_types! { ]; } -// impl edfis_swap_module::Config for Runtime { +// impl edfis_swap_legacy_module::Config for Runtime { // type Event = Event; // type Currency = Currencies; // type StableCurrencyIds = StableCurrencyIds; @@ -851,7 +851,7 @@ parameter_types! { // type TradingPathLimit = TradingPathLimit; // type PalletId = DEXPalletId; // type CurrencyIdMapping = EvmCurrencyIdMapping; -// type WeightInfo = weights::edfis_swap_module::WeightInfo; +// type WeightInfo = weights::edfis_swap_legacy_module::WeightInfo; // type ListingOrigin = EnsureRootOrHalfFinancialCouncil; // } @@ -1141,8 +1141,8 @@ impl InstanceFilter for ProxyType { ProxyType::Swap => { matches!( c, - Call::Dex(edfis_swap_module::Call::swap_with_exact_supply(..)) - | Call::Dex(edfis_swap_module::Call::swap_with_exact_target(..)) + Call::Dex(edfis_swap_legacy_module::Call::swap_with_exact_supply(..)) + | Call::Dex(edfis_swap_legacy_module::Call::swap_with_exact_target(..)) ) } ProxyType::Loan => { @@ -1524,7 +1524,7 @@ construct_runtime!( Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event} = 3, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 4, Prices: module_prices::{Pallet, Storage, Call, Event} = 5, - // Dex: edfis_swap_module::{Pallet, Storage, Call, Event, Config} = 6, + // Dex: edfis_swap_legacy_module::{Pallet, Storage, Call, Event, Config} = 6, Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 7, Recovery: pallet_recovery::{Pallet, Call, Storage, Event} = 8, @@ -2002,7 +2002,7 @@ impl_runtime_apis! { list_benchmark!(list, extra, module_nft, NftBench::); - // orml_list_benchmark!(list, extra, edfis_swap_module, benchmarking::dex); + // orml_list_benchmark!(list, extra, edfis_swap_legacy_module, benchmarking::dex); // orml_list_benchmark!(list, extra, auction_manager, benchmarking::auction_manager); // orml_list_benchmark!(list, extra, cdp_engine, benchmarking::cdp_engine); // orml_list_benchmark!(list, extra, emergency_shutdown, benchmarking::emergency_shutdown); @@ -2060,7 +2060,7 @@ impl_runtime_apis! { let params = (&config, &whitelist); add_benchmark!(params, batches, module_nft, NftBench::); - // orml_add_benchmark!(params, batches, edfis_swap_module, benchmarking::dex); + // orml_add_benchmark!(params, batches, edfis_swap_legacy_module, benchmarking::dex); // orml_add_benchmark!(params, batches, auction_manager, benchmarking::auction_manager); // orml_add_benchmark!(params, batches, cdp_engine, benchmarking::cdp_engine); // orml_add_benchmark!(params, batches, emergency_shutdown, benchmarking::emergency_shutdown); diff --git a/blockchain/runtime/src/weights/mod.rs b/blockchain/runtime/src/weights/mod.rs index a5d6b99b..ffc39470 100644 --- a/blockchain/runtime/src/weights/mod.rs +++ b/blockchain/runtime/src/weights/mod.rs @@ -26,7 +26,7 @@ pub mod module_auction_manager; pub mod module_cdp_engine; pub mod module_cdp_treasury; pub mod module_currencies; -pub mod edfis_swap_module; +pub mod edfis_swap_legacy_module; pub mod emergency_shutdown; pub mod module_evm; pub mod module_unified_accounts; diff --git a/blockchain/runtime/src/weights/module_dex.rs b/blockchain/runtime/src/weights/module_dex.rs index b4317390..0e0b40f4 100644 --- a/blockchain/runtime/src/weights/module_dex.rs +++ b/blockchain/runtime/src/weights/module_dex.rs @@ -18,7 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Autogenerated weights for edfis_swap_module +//! Autogenerated weights for edfis_swap_legacy_module //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 //! DATE: 2021-07-19, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` @@ -46,9 +46,9 @@ use frame_support::{traits::Get, weights::Weight}; use sp_std::marker::PhantomData; -/// Weight functions for edfis_swap_module. +/// Weight functions for edfis_swap_legacy_module. pub struct WeightInfo(PhantomData); -impl edfis_swap_module::WeightInfo for WeightInfo { +impl edfis_swap_legacy_module::WeightInfo for WeightInfo { fn enable_trading_pair() -> Weight { (25_878_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) From b69b08faeb0293d803a5f431b78c93a08d67b5b7 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 17:59:55 +0800 Subject: [PATCH 11/21] Remove `stake_increment_share`, add `Fees` type for Edfis NEW, rename `Position` to `ECDPPosition`. Remove `stake_increment_share`, add `Fees` type for Edfis NEW, rename `Position` to `ECDPPosition`. --- blockchain/modules/edfis-swap/src/lib.rs | 21 +- blockchain/modules/support/src/ecdp.rs | 4 +- blockchain/modules/support/src/edfis.rs | 230 +++++++++ .../support/src/{dex.rs => edfis_legacy.rs} | 446 +++++++++--------- blockchain/modules/support/src/lib.rs | 8 +- blockchain/primitives/src/lib.rs | 5 +- blockchain/resources/types.json | 2 +- 7 files changed, 469 insertions(+), 247 deletions(-) create mode 100644 blockchain/modules/support/src/edfis.rs rename blockchain/modules/support/src/{dex.rs => edfis_legacy.rs} (96%) diff --git a/blockchain/modules/edfis-swap/src/lib.rs b/blockchain/modules/edfis-swap/src/lib.rs index d7fa574b..6c9850e7 100644 --- a/blockchain/modules/edfis-swap/src/lib.rs +++ b/blockchain/modules/edfis-swap/src/lib.rs @@ -38,7 +38,7 @@ use frame_system::pallet_prelude::*; use module_support::{SwapDexIncentives, SwapManager, Erc20InfoMapping, ExchangeRate, Ratio, SwapLimit}; use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; use parity_scale_codec::MaxEncodedLen; -use primitives::{Balance, CurrencyId, TradingPair}; +use primitives::{Balance, CurrencyId, Fees, TradingPair}; use scale_info::TypeInfo; use sp_core::{H160, U256}; use sp_runtime::{ @@ -63,6 +63,8 @@ pub struct ProvisioningParameters { target_provision: (Balance, Balance), /// accumulated provision amount for this Provisioning trading pair. accumulated_provision: (Balance, Balance), + /// accumulated provision amount for this Provisioning trading pair. + trading_fee: (u32, u32), /// The number of block that status can be converted to Enabled. not_before: BlockNumber, } @@ -391,14 +393,8 @@ pub mod module { /// - `max_amount_b`: maximum amount of currency_id_b is allowed to inject to liquidity /// pool. /// - `min_share_increment`: minimum acceptable share amount. - /// - `stake_increment_share`: indicates whether to stake increased dex share to earn - /// incentives #[pallet::call_index(2)] - #[pallet::weight(if *stake_increment_share { - ::WeightInfo::add_liquidity_and_stake() - } else { - ::WeightInfo::add_liquidity() - })] + #[pallet::weight(::WeightInfo::add_liquidity())] pub fn add_liquidity( origin: OriginFor, currency_id_a: CurrencyId, @@ -406,7 +402,6 @@ pub mod module { #[pallet::compact] max_amount_a: Balance, #[pallet::compact] max_amount_b: Balance, #[pallet::compact] min_share_increment: Balance, - stake_increment_share: bool, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_add_liquidity( @@ -416,7 +411,6 @@ pub mod module { max_amount_a, max_amount_b, min_share_increment, - stake_increment_share, )?; Ok(()) } @@ -987,7 +981,6 @@ impl Pallet { max_amount_a: Balance, max_amount_b: Balance, min_share_increment: Balance, - stake_increment_share: bool, ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { let trading_pair = TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; @@ -1080,10 +1073,6 @@ impl Pallet { *pool_0 = pool_0.checked_add(pool_0_increment).ok_or(ArithmeticError::Overflow)?; *pool_1 = pool_1.checked_add(pool_1_increment).ok_or(ArithmeticError::Overflow)?; - if stake_increment_share { - T::SwapDexIncentives::do_deposit_dex_share(who, dex_share_currency_id, share_increment)?; - } - Self::deposit_event(Event::AddLiquidity { who: who.clone(), currency_0: trading_pair.first(), @@ -1511,7 +1500,6 @@ impl SwapManager for Pallet { max_amount_a: Balance, max_amount_b: Balance, min_share_increment: Balance, - stake_increment_share: bool, ) -> sp_std::result::Result<(Balance, Balance, Balance), DispatchError> { Self::do_add_liquidity( who, @@ -1520,7 +1508,6 @@ impl SwapManager for Pallet { max_amount_a, max_amount_b, min_share_increment, - stake_increment_share, ) } diff --git a/blockchain/modules/support/src/ecdp.rs b/blockchain/modules/support/src/ecdp.rs index b54a6b1c..7967456e 100644 --- a/blockchain/modules/support/src/ecdp.rs +++ b/blockchain/modules/support/src/ecdp.rs @@ -19,7 +19,7 @@ // along with this program. If not, see . use parity_scale_codec::FullCodec; -use primitives::Position; +use primitives::ECDPPosition; use sp_core::U256; use sp_runtime::{DispatchError, DispatchResult}; use sp_std::{ @@ -199,7 +199,7 @@ pub trait SlickUsdEcdpManager { /// Close ECDP loan using DEX fn close_loan_by_dex(who: AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult; /// Get open ECDP corresponding to an account and collateral `CurrencyId` - fn get_position(who: &AccountId, currency_id: CurrencyId) -> Position; + fn get_position(who: &AccountId, currency_id: CurrencyId) -> ECDPPosition; /// Get liquidation ratio for collateral `CurrencyId` fn get_collateral_parameters(currency_id: CurrencyId) -> Vec; /// Get current ratio of collateral to debit of open ECDP diff --git a/blockchain/modules/support/src/edfis.rs b/blockchain/modules/support/src/edfis.rs new file mode 100644 index 00000000..fc9206e8 --- /dev/null +++ b/blockchain/modules/support/src/edfis.rs @@ -0,0 +1,230 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 frame_support::{ensure, traits::Get}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_core::H160; +use sp_runtime::{DispatchError, DispatchResult, RuntimeDebug}; +use sp_std::{cmp::PartialEq, prelude::*, result::Result}; + +#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] +pub enum SwapLimit { + /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) + ExactSupply(Balance, Balance), + /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) + ExactTarget(Balance, Balance), +} + +pub trait SwapManager { + fn get_liquidity_pool( + currency_id_a: CurrencyId, + currency_id_b: CurrencyId + ) -> (Balance, Balance); + + fn get_liquidity_token_address( + currency_id_a: CurrencyId, + currency_id_b: CurrencyId + ) -> Option; + + fn get_swap_amount( + path: &[CurrencyId], + limit: SwapLimit + ) -> Option<(Balance, Balance)>; + + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)>; + + fn swap_with_specific_path( + who: &AccountId, + path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn add_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + ) -> Result<(Balance, Balance, Balance), DispatchError>; + + fn remove_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError>; +} + +pub trait Swap +where + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)>; + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; +} + +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum SwapError { + CannotSwap, +} + +impl Into for SwapError { + fn into(self) -> DispatchError { + DispatchError::Other("Cannot swap") + } +} + +// Dex wrapper of Swap implementation +pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); + +impl Swap + for SpecificJointsSwap +where + Dex: SwapManager, + Joints: Get>>, + Balance: Clone, + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)> { + >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit, + Joints::get(), + ) + .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) + } + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let path = >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit.clone(), + Joints::get(), + ) + .ok_or_else(|| Into::::into(SwapError::CannotSwap))? + .0; + + >::swap_with_specific_path(who, &path, limit) + } + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + >::swap_with_specific_path(who, swap_path, limit) + } +} + +#[cfg(feature = "std")] +impl SwapManager for () +where + Balance: Default, +{ + fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { + Default::default() + } + + fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { + Some(Default::default()) + } + + fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { + Some(Default::default()) + } + + fn get_best_price_swap_path( + _supply_currency_id: CurrencyId, + _target_currency_id: CurrencyId, + _limit: SwapLimit, + _alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + Some(Default::default()) + } + + fn swap_with_specific_path( + _who: &AccountId, + _path: &[CurrencyId], + _limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn add_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _max_amount_a: Balance, + _max_amount_b: Balance, + _min_share_increment: Balance, + ) -> Result<(Balance, Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn remove_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _remove_share: Balance, + _min_withdrawn_a: Balance, + _min_withdrawn_b: Balance, + _by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } +} diff --git a/blockchain/modules/support/src/dex.rs b/blockchain/modules/support/src/edfis_legacy.rs similarity index 96% rename from blockchain/modules/support/src/dex.rs rename to blockchain/modules/support/src/edfis_legacy.rs index aea21420..e9848cdc 100644 --- a/blockchain/modules/support/src/dex.rs +++ b/blockchain/modules/support/src/edfis_legacy.rs @@ -1,223 +1,223 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 frame_support::{ensure, traits::Get}; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_core::H160; -use sp_runtime::{DispatchError, RuntimeDebug}; -use sp_std::{cmp::PartialEq, prelude::*, result::Result}; - -#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] -pub enum SwapLimit { - /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) - ExactSupply(Balance, Balance), - /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) - ExactTarget(Balance, Balance), -} - -pub trait SwapManager { - fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance); - - fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option; - - fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)>; - - fn get_best_price_swap_path( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - alternative_path_joint_list: Vec>, - ) -> Option<(Vec, Balance, Balance)>; - - fn swap_with_specific_path( - who: &AccountId, - path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; - - fn add_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - max_amount_a: Balance, - max_amount_b: Balance, - min_share_increment: Balance, - stake_increment_share: bool, - ) -> Result<(Balance, Balance, Balance), DispatchError>; - - fn remove_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - remove_share: Balance, - min_withdrawn_a: Balance, - min_withdrawn_b: Balance, - by_unstake: bool, - ) -> Result<(Balance, Balance), DispatchError>; -} - -pub trait Swap -where - CurrencyId: Clone, -{ - fn get_swap_amount( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Option<(Balance, Balance)>; - - fn swap( - who: &AccountId, - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; - - fn swap_by_path( - who: &AccountId, - swap_path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; -} - -#[derive(Eq, PartialEq, RuntimeDebug)] -pub enum SwapError { - CannotSwap, -} - -impl Into for SwapError { - fn into(self) -> DispatchError { - DispatchError::Other("Cannot swap") - } -} - -// Dex wrapper of Swap implementation -pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); - -impl Swap - for SpecificJointsSwap -where - Dex: SwapManager, - Joints: Get>>, - Balance: Clone, - CurrencyId: Clone, -{ - fn get_swap_amount( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Option<(Balance, Balance)> { - >::get_best_price_swap_path( - supply_currency_id, - target_currency_id, - limit, - Joints::get(), - ) - .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) - } - - fn swap( - who: &AccountId, - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - let path = >::get_best_price_swap_path( - supply_currency_id, - target_currency_id, - limit.clone(), - Joints::get(), - ) - .ok_or_else(|| Into::::into(SwapError::CannotSwap))? - .0; - - >::swap_with_specific_path(who, &path, limit) - } - - fn swap_by_path( - who: &AccountId, - swap_path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError> { - >::swap_with_specific_path(who, swap_path, limit) - } -} - -#[cfg(feature = "std")] -impl SwapManager for () -where - Balance: Default, -{ - fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { - Default::default() - } - - fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { - Some(Default::default()) - } - - fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { - Some(Default::default()) - } - - fn get_best_price_swap_path( - _supply_currency_id: CurrencyId, - _target_currency_id: CurrencyId, - _limit: SwapLimit, - _alternative_path_joint_list: Vec>, - ) -> Option<(Vec, Balance, Balance)> { - Some(Default::default()) - } - - fn swap_with_specific_path( - _who: &AccountId, - _path: &[CurrencyId], - _limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn add_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _max_amount_a: Balance, - _max_amount_b: Balance, - _min_share_increment: Balance, - _stake_increment_share: bool, - ) -> Result<(Balance, Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn remove_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _remove_share: Balance, - _min_withdrawn_a: Balance, - _min_withdrawn_b: Balance, - _by_unstake: bool, - ) -> Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 frame_support::{ensure, traits::Get}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_core::H160; +use sp_runtime::{DispatchError, RuntimeDebug}; +use sp_std::{cmp::PartialEq, prelude::*, result::Result}; + +#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] +pub enum SwapLimit { + /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) + ExactSupply(Balance, Balance), + /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) + ExactTarget(Balance, Balance), +} + +pub trait SwapManager { + fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance); + + fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option; + + fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)>; + + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)>; + + fn swap_with_specific_path( + who: &AccountId, + path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn add_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + stake_increment_share: bool, + ) -> Result<(Balance, Balance, Balance), DispatchError>; + + fn remove_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError>; +} + +pub trait Swap +where + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)>; + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; +} + +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum SwapError { + CannotSwap, +} + +impl Into for SwapError { + fn into(self) -> DispatchError { + DispatchError::Other("Cannot swap") + } +} + +// Dex wrapper of Swap implementation +pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); + +impl Swap + for SpecificJointsSwap +where + Dex: SwapManager, + Joints: Get>>, + Balance: Clone, + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)> { + >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit, + Joints::get(), + ) + .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) + } + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let path = >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit.clone(), + Joints::get(), + ) + .ok_or_else(|| Into::::into(SwapError::CannotSwap))? + .0; + + >::swap_with_specific_path(who, &path, limit) + } + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + >::swap_with_specific_path(who, swap_path, limit) + } +} + +#[cfg(feature = "std")] +impl SwapManager for () +where + Balance: Default, +{ + fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { + Default::default() + } + + fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { + Some(Default::default()) + } + + fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { + Some(Default::default()) + } + + fn get_best_price_swap_path( + _supply_currency_id: CurrencyId, + _target_currency_id: CurrencyId, + _limit: SwapLimit, + _alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + Some(Default::default()) + } + + fn swap_with_specific_path( + _who: &AccountId, + _path: &[CurrencyId], + _limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn add_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _max_amount_a: Balance, + _max_amount_b: Balance, + _min_share_increment: Balance, + _stake_increment_share: bool, + ) -> Result<(Balance, Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn remove_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _remove_share: Balance, + _min_withdrawn_a: Balance, + _min_withdrawn_b: Balance, + _by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } +} diff --git a/blockchain/modules/support/src/lib.rs b/blockchain/modules/support/src/lib.rs index 77c2c72e..4bff5e59 100644 --- a/blockchain/modules/support/src/lib.rs +++ b/blockchain/modules/support/src/lib.rs @@ -24,7 +24,7 @@ #![allow(clippy::type_complexity)] use frame_support::pallet_prelude::{DispatchClass, Pays, Weight}; -use primitives::{task::TaskResult, Balance, CurrencyId, Multiplier, Nonce, ReserveIdentifier}; +use primitives::{task::TaskResult, Balance, CurrencyId, Fees, Multiplier, Nonce, ReserveIdentifier}; use sp_runtime::{ traits::CheckedDiv, transaction_validity::TransactionValidityError, DispatchError, DispatchResult, FixedU128, }; @@ -32,7 +32,8 @@ use sp_std::{prelude::*, result::Result}; use xcm::prelude::*; pub mod bounded; -pub mod dex; +pub mod edfis; +pub mod edfis_legacy; pub mod evm; pub mod liquid_staking; pub mod ecdp; @@ -40,7 +41,8 @@ pub mod incentives; pub mod mocks; pub use crate::bounded::*; -pub use crate::dex::*; +pub use crate::edfis::*; +pub use crate::edfis_legacy::*; pub use crate::evm::*; pub use crate::liquid_staking::*; pub use crate::ecdp::*; diff --git a/blockchain/primitives/src/lib.rs b/blockchain/primitives/src/lib.rs index ed211209..60b87042 100644 --- a/blockchain/primitives/src/lib.rs +++ b/blockchain/primitives/src/lib.rs @@ -91,6 +91,9 @@ pub type Balance = u128; /// Signed version of Balance pub type Amount = i128; +/// Fees type primarily for Edfis `ExchangeFee` and `TradingFee`. +pub type Fees = u128; + /// Auction ID pub type AuctionId = u32; @@ -180,7 +183,7 @@ impl Decode for TradingPair { } #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)] -pub struct Position { +pub struct ECDPPosition { /// The amount of collateral. pub collateral: Balance, /// The amount of debit. diff --git a/blockchain/resources/types.json b/blockchain/resources/types.json index a430a99e..e109ca78 100644 --- a/blockchain/resources/types.json +++ b/blockchain/resources/types.json @@ -211,7 +211,7 @@ "Dex": "CurrencyId" } }, - "Position": { + "ECDPPosition": { "collateral": "Balance", "debit": "Balance" }, From d9c0004ba3e7f3da43b39985575976a57429b789 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Sun, 3 Mar 2024 23:09:04 +0800 Subject: [PATCH 12/21] Populate `EcdpUssdTreasury` --- .../modules/asset-registry/src/weights.rs | 25 - .../modules/ecdp-ussd-treasury/Cargo.toml | 40 +- .../modules/ecdp-ussd-treasury/src/lib.rs | 539 +++++++++++++++++ .../modules/ecdp-ussd-treasury/src/mock.rs | 259 ++++++++ .../modules/ecdp-ussd-treasury/src/tests.rs | 571 ++++++++++++++++++ .../modules/ecdp-ussd-treasury/src/weights.rs | 108 ++++ blockchain/modules/edfis-swap/src/mock.rs | 3 +- blockchain/modules/evm/src/bench/mock.rs | 1 - blockchain/modules/prices/README.md | 2 +- blockchain/modules/prices/src/lib.rs | 2 +- blockchain/modules/prices/src/tests.rs | 2 +- blockchain/modules/support/src/ecdp.rs | 383 ++++++------ blockchain/modules/support/src/lib.rs | 6 +- 13 files changed, 1676 insertions(+), 265 deletions(-) create mode 100644 blockchain/modules/ecdp-ussd-treasury/src/lib.rs create mode 100644 blockchain/modules/ecdp-ussd-treasury/src/mock.rs create mode 100644 blockchain/modules/ecdp-ussd-treasury/src/tests.rs create mode 100644 blockchain/modules/ecdp-ussd-treasury/src/weights.rs diff --git a/blockchain/modules/asset-registry/src/weights.rs b/blockchain/modules/asset-registry/src/weights.rs index db3f04be..db889bdb 100644 --- a/blockchain/modules/asset-registry/src/weights.rs +++ b/blockchain/modules/asset-registry/src/weights.rs @@ -50,8 +50,6 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn register_foreign_asset() -> Weight; fn update_foreign_asset() -> Weight; - fn register_stable_asset() -> Weight; - fn update_stable_asset() -> Weight; fn register_erc20_asset() -> Weight; fn update_erc20_asset() -> Weight; fn register_native_asset() -> Weight; @@ -77,19 +75,6 @@ impl WeightInfo for SetheumWeight { .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: AssetRegistry NextStableAssetId (r:1 w:1) - // Storage: AssetRegistry AssetMetadatas (r:1 w:1) - fn register_stable_asset() -> Weight { - Weight::from_parts(15_830_000, 0) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: AssetRegistry AssetMetadatas (r:1 w:1) - fn update_stable_asset() -> Weight { - Weight::from_parts(14_342_000, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } // Storage: EVM Accounts (r:2 w:0) // Storage: EVM Codes (r:1 w:0) // Storage: EVM AccountStorages (r:5 w:0) @@ -132,16 +117,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - fn register_stable_asset() -> Weight { - Weight::from_parts(15_830_000, 0) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - fn update_stable_asset() -> Weight { - Weight::from_parts(14_342_000, 0) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } fn register_erc20_asset() -> Weight { Weight::from_parts(187_828_000, 0) .saturating_add(RocksDbWeight::get().reads(10 as u64)) diff --git a/blockchain/modules/ecdp-ussd-treasury/Cargo.toml b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml index 82caa87a..4e2afaac 100644 --- a/blockchain/modules/ecdp-ussd-treasury/Cargo.toml +++ b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml @@ -5,46 +5,40 @@ authors = ["Setheum Labs"] edition = "2021" [dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true } scale-info = { workspace = true } -serde = { workspace = true, optional = true } -parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } sp-runtime = { workspace = true } -sp-io = { workspace = true } -sp-std = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } +sp-std = { workspace = true } +orml-traits = { workspace = true } +module-support ={ workspace = true } +primitives = { workspace = true } [dev-dependencies] sp-core = { workspace = true, features = ["std"] } -pallet-balances = { workspace = true } -orml-tokens = { workspace = true } +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +orml-currencies = { workspace = true, features = ["std"] } +orml-auction = { workspace = true, features = ["std"] } +module-dex = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ - "scale-info/std", - "serde", "parity-scale-codec/std", - "sp-runtime/std", - "sp-std/std", - "sp-io/std", "frame-support/std", "frame-system/std", - "primitives/std", - "support/std", "orml-traits/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "primitives/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/blockchain/modules/ecdp-ussd-treasury/src/lib.rs b/blockchain/modules/ecdp-ussd-treasury/src/lib.rs new file mode 100644 index 00000000..4571c1bb --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/lib.rs @@ -0,0 +1,539 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! # ECDP USSD Treasury Module +//! +//! ## Overview +//! +//! The ECDP USSD Treasury manages the accumulated interest and bad debts generated by ECDPs, +//! and handle excessive surplus or debits timely in order to keep the system healthy with low risk. +//! It's the only entry for issuing/burning USSD. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] +#![allow(clippy::needless_range_loop)] + +use frame_support::{pallet_prelude::*, transactional, PalletId}; +use frame_system::pallet_prelude::*; +use module_support::{AuctionsManager, SlickUsdEcdpTreasury, SlickUsdEcdpTreasuryExtended, SwapManager, Ratio, Swap, SwapLimit}; +use orml_traits::{MultiCurrency, MultiCurrencyExtended}; +use primitives::{Balance, CurrencyId}; +use sp_runtime::{ + traits::{AccountIdConversion, One, Zero}, + ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, +}; +use sp_std::prelude::*; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The origin which may update parameters and handle + /// surplus/collateral. + type UpdateOrigin: EnsureOrigin; + + /// The Currency for managing assets related to ECDP + type Currency: MultiCurrencyExtended; + + /// Stablecoin currency id + #[pallet::constant] + type GetUSSDCurrencyId: Get; + + /// Auction manager creates auction to handle system surplus and debit + type AuctionsManagerHandler: AuctionsManager; + + /// Dex manager + type DEX: SwapManager; + + /// Swap + type Swap: Swap; + + /// The cap of lots number when create collateral auction on a + /// liquidation or to create debit/surplus auction on block end. + /// If set to 0, does not work. + #[pallet::constant] + type MaxAuctionsCount: Get; + + #[pallet::constant] + type TreasuryAccount: Get; + + /// The ECDP USSD Treasury's module id, stores the surplus and collateral assets. + #[pallet::constant] + type PalletId: Get; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The collateral amount of ECDP USSD Treasury is not enough + CollateralNotEnough, + /// The surplus pool of ECDP USSD Treasury is not enough + SurplusPoolNotEnough, + /// The debit pool of ECDP USSD Treasury is not enough + DebitPoolNotEnough, + /// Cannot use collateral to swap USSD + CannotSwap, + /// The currency id is not DexShare type + NotDexShare, + } + + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + /// The expected amount size for per lot collateral auction of specific collateral type + /// updated. + ExpectedCollateralAuctionSizeUpdated { + collateral_type: CurrencyId, + new_size: Balance, + }, + /// The buffer amount of debit pool that will not be offset by suplus pool updated. + DebitOffsetBufferUpdated { amount: Balance }, + } + + /// The expected amount size for per lot collateral auction of specific + /// collateral type. + /// + /// ExpectedCollateralAuctionSize: map CurrencyId => Balance + #[pallet::storage] + #[pallet::getter(fn expected_collateral_auction_size)] + pub type ExpectedCollateralAuctionSize = StorageMap<_, Twox64Concat, CurrencyId, Balance, ValueQuery>; + + /// Current total debit value of system. It's not same as debit in ECDP Egine, + /// it is the bad debt of the system. + /// + /// DebitPool: Balance + #[pallet::storage] + #[pallet::getter(fn debit_pool)] + pub type DebitPool = StorageValue<_, Balance, ValueQuery>; + + /// The buffer amount of debit pool that will not be offset by surplus pool. + /// + /// DebitOffsetBuffer: Balance + #[pallet::storage] + #[pallet::getter(fn debit_offset_buffer)] + pub type DebitOffsetBuffer = StorageValue<_, Balance, ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub expected_collateral_auction_size: Vec<(CurrencyId, Balance)>, + pub _phantom: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.expected_collateral_auction_size + .iter() + .for_each(|(currency_id, size)| { + ExpectedCollateralAuctionSize::::insert(currency_id, size); + }); + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet { + /// Handle excessive surplus or debits of system when block end + fn on_finalize(_now: BlockNumberFor) { + // offset the same amount between debit pool and surplus pool + Self::offset_surplus_and_debit(); + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::extract_surplus_to_treasury())] + pub fn extract_surplus_to_treasury(origin: OriginFor, #[pallet::compact] amount: Balance) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + T::Currency::transfer( + T::GetUSSDCurrencyId::get(), + &Self::account_id(), + &T::TreasuryAccount::get(), + amount, + )?; + Ok(()) + } + + /// Auction the collateral not occupied by the auction. + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `currency_id`: collateral type + /// - `amount`: collateral amount + /// - `target`: target amount + /// - `splited`: split collateral to multiple auction according to the config size + #[pallet::call_index(1)] + #[pallet::weight( + if *splited { + T::WeightInfo::auction_collateral(T::MaxAuctionsCount::get()) + } else { + T::WeightInfo::auction_collateral(1) + } + )] + pub fn auction_collateral( + origin: OriginFor, + currency_id: CurrencyId, + #[pallet::compact] amount: Balance, + #[pallet::compact] target: Balance, + splited: bool, + ) -> DispatchResultWithPostInfo { + T::UpdateOrigin::ensure_origin(origin)?; + let created_auctions = >::create_collateral_auctions( + currency_id, + amount, + target, + Self::account_id(), + splited, + )?; + Ok(Some(T::WeightInfo::auction_collateral(created_auctions)).into()) + } + + /// Swap the collateral not occupied by the auction to USSD. + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `currency_id`: collateral type + /// - `swap_limit`: target amount + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::exchange_collateral_to_ussd())] + pub fn exchange_collateral_to_ussd( + origin: OriginFor, + currency_id: CurrencyId, + swap_limit: SwapLimit, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + // the supply collateral must not be occupied by the auction. + Self::swap_collateral_to_ussd(currency_id, swap_limit, false)?; + Ok(()) + } + + /// Update parameters related to collateral auction under specific + /// collateral type + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `currency_id`: collateral type + /// - `amount`: expected size of per lot collateral auction + #[pallet::call_index(3)] + #[pallet::weight((T::WeightInfo::set_expected_collateral_auction_size(), DispatchClass::Operational))] + pub fn set_expected_collateral_auction_size( + origin: OriginFor, + currency_id: CurrencyId, + #[pallet::compact] size: Balance, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + ExpectedCollateralAuctionSize::::insert(currency_id, size); + Self::deposit_event(Event::ExpectedCollateralAuctionSizeUpdated { + collateral_type: currency_id, + new_size: size, + }); + Ok(()) + } + + /// Update the debit offset buffer + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `amount`: the buffer amount of debit pool + #[pallet::call_index(4)] + #[pallet::weight((T::WeightInfo::set_expected_collateral_auction_size(), DispatchClass::Operational))] + pub fn set_debit_offset_buffer(origin: OriginFor, #[pallet::compact] amount: Balance) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + DebitOffsetBuffer::::mutate(|v| { + if *v != amount { + *v = amount; + Self::deposit_event(Event::DebitOffsetBufferUpdated { amount }); + } + }); + Ok(()) + } + } +} + +impl Pallet { + /// Get account of cdp treasury module. + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + /// Get current total surplus of system. + pub fn surplus_pool() -> Balance { + T::Currency::free_balance(T::GetUSSDCurrencyId::get(), &Self::account_id()) + } + + /// Get total collateral amount of cdp treasury module. + pub fn total_collaterals(currency_id: CurrencyId) -> Balance { + T::Currency::free_balance(currency_id, &Self::account_id()) + } + + /// Get collateral amount not in auction + pub fn total_collaterals_not_in_auction(currency_id: CurrencyId) -> Balance { + T::Currency::free_balance(currency_id, &Self::account_id()) + .saturating_sub(T::AuctionsManagerHandler::get_total_collateral_in_auction(currency_id)) + } + + fn offset_surplus_and_debit() { + // The part of the debit pool that exceeds the debit offset buffer can be offset by the surplus + let offset_amount = sp_std::cmp::min( + Self::debit_pool().saturating_sub(Self::debit_offset_buffer()), + Self::surplus_pool(), + ); + + // Burn the amount that is equal to offset amount of USSD + if !offset_amount.is_zero() { + let res = Self::burn_debit(&Self::account_id(), offset_amount); + match res { + Ok(_) => { + DebitPool::::mutate(|debit| { + *debit = debit + .checked_sub(offset_amount) + .expect("offset = min(debit, surplus); qed") + }); + } + Err(e) => { + log::warn!( + target: "cdp-treasury", + "get_swap_supply_amount: Attempt to burn surplus {:?} failed: {:?}, this is unexpected but should be safe", + offset_amount, e + ); + } + } + } + } +} + +impl SlickUsdEcdpTreasury for Pallet { + type Balance = Balance; + type CurrencyId = CurrencyId; + + fn get_surplus_pool() -> Self::Balance { + Self::surplus_pool() + } + + fn get_debit_pool() -> Self::Balance { + Self::debit_pool() + } + + fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance { + Self::total_collaterals(id) + } + + fn get_debit_proportion(amount: Self::Balance) -> Ratio { + let ussd_total_supply = T::Currency::total_issuance(T::GetUSSDCurrencyId::get()); + Ratio::checked_from_rational(amount, ussd_total_supply).unwrap_or_default() + } + + fn on_system_debit(amount: Self::Balance) -> DispatchResult { + DebitPool::::try_mutate(|debit_pool| -> DispatchResult { + *debit_pool = debit_pool.checked_add(amount).ok_or(ArithmeticError::Overflow)?; + Ok(()) + }) + } + + fn on_system_surplus(amount: Self::Balance) -> DispatchResult { + Self::issue_debit(&Self::account_id(), amount, true) + } + + /// This should be the only function in the system that issues USSD + fn issue_debit(who: &T::AccountId, debit: Self::Balance, backed: bool) -> DispatchResult { + // increase system debit if the debit is unbacked + if !backed { + Self::on_system_debit(debit)?; + } + T::Currency::deposit(T::GetUSSDCurrencyId::get(), who, debit)?; + + Ok(()) + } + + /// This should be the only function in the system that burns USSD + fn burn_debit(who: &T::AccountId, debit: Self::Balance) -> DispatchResult { + T::Currency::withdraw(T::GetUSSDCurrencyId::get(), who, debit) + } + + fn deposit_surplus(from: &T::AccountId, surplus: Self::Balance) -> DispatchResult { + T::Currency::transfer(T::GetUSSDCurrencyId::get(), from, &Self::account_id(), surplus) + } + + fn withdraw_surplus(to: &T::AccountId, surplus: Self::Balance) -> DispatchResult { + T::Currency::transfer(T::GetUSSDCurrencyId::get(), &Self::account_id(), to, surplus) + } + + fn deposit_collateral(from: &T::AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult { + T::Currency::transfer(currency_id, from, &Self::account_id(), amount) + } + + fn withdraw_collateral(to: &T::AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult { + T::Currency::transfer(currency_id, &Self::account_id(), to, amount) + } +} + +impl SlickUsdEcdpTreasuryExtended for Pallet { + #[transactional] + fn swap_collateral_to_ussd( + currency_id: CurrencyId, + limit: SwapLimit, + collateral_in_auction: bool, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let supply_limit = match limit { + SwapLimit::ExactSupply(supply_amount, _) => supply_amount, + SwapLimit::ExactTarget(max_supply_amount, _) => max_supply_amount, + }; + let target_limit = match limit { + SwapLimit::ExactSupply(_, minimum_target_amount) => minimum_target_amount, + SwapLimit::ExactTarget(_, exact_target_amount) => exact_target_amount, + }; + + if collateral_in_auction { + ensure!( + Self::total_collaterals(currency_id) >= supply_limit + && T::AuctionsManagerHandler::get_total_collateral_in_auction(currency_id) >= supply_limit, + Error::::CollateralNotEnough, + ); + } else { + ensure!( + Self::total_collaterals_not_in_auction(currency_id) >= supply_limit, + Error::::CollateralNotEnough, + ); + } + + T::Swap::swap(&Self::account_id(), currency_id, T::GetUSSDCurrencyId::get(), limit)?; + } + + fn create_collateral_auctions( + currency_id: CurrencyId, + amount: Balance, + target: Balance, + refund_receiver: T::AccountId, + splited: bool, + ) -> Result { + ensure!( + Self::total_collaterals_not_in_auction(currency_id) >= amount, + Error::::CollateralNotEnough, + ); + + let mut unhandled_collateral_amount = amount; + let mut unhandled_target = target; + let expected_collateral_auction_size = Self::expected_collateral_auction_size(currency_id); + let max_auctions_count: Balance = T::MaxAuctionsCount::get().into(); + let lots_count = if !splited + || max_auctions_count.is_zero() + || expected_collateral_auction_size.is_zero() + || amount <= expected_collateral_auction_size + { + One::one() + } else { + let mut count = amount + .checked_div(expected_collateral_auction_size) + .expect("collateral auction maximum size is not zero; qed"); + + let remainder = amount + .checked_rem(expected_collateral_auction_size) + .expect("collateral auction maximum size is not zero; qed"); + if !remainder.is_zero() { + count = count.saturating_add(One::one()); + } + sp_std::cmp::min(count, max_auctions_count) + }; + let average_amount_per_lot = amount.checked_div(lots_count).expect("lots count is at least 1; qed"); + let average_target_per_lot = target.checked_div(lots_count).expect("lots count is at least 1; qed"); + let mut created_lots: Balance = Zero::zero(); + + while !unhandled_collateral_amount.is_zero() { + created_lots = created_lots.saturating_add(One::one()); + let (lot_collateral_amount, lot_target) = if created_lots == lots_count { + // the last lot may be have some remnant than average + (unhandled_collateral_amount, unhandled_target) + } else { + (average_amount_per_lot, average_target_per_lot) + }; + + T::AuctionsManagerHandler::new_collateral_auction( + &refund_receiver, + currency_id, + lot_collateral_amount, + lot_target, + )?; + + unhandled_collateral_amount = unhandled_collateral_amount.saturating_sub(lot_collateral_amount); + unhandled_target = unhandled_target.saturating_sub(lot_target); + } + let created_auctions: u32 = created_lots.try_into().map_err(|_| ArithmeticError::Overflow)?; + Ok(created_auctions) + } + + fn remove_liquidity_for_lp_collateral( + lp_currency_id: CurrencyId, + amount: Balance, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let (currency_id_0, currency_id_1) = lp_currency_id + .split_dex_share_currency_id() + .ok_or(Error::::NotDexShare)?; + T::DEX::remove_liquidity( + &Self::account_id(), + currency_id_0, + currency_id_1, + amount, + Zero::zero(), + Zero::zero(), + false, + ) + } + + fn max_auction() -> u32 { + T::MaxAuctionsCount::get() + } +} + +pub struct InitializeDebitOffsetBuffer( + sp_std::marker::PhantomData, + sp_std::marker::PhantomData, +); +impl> frame_support::traits::OnRuntimeUpgrade + for InitializeDebitOffsetBuffer +{ + fn on_runtime_upgrade() -> Weight { + let amount = GetBufferSize::get(); + DebitOffsetBuffer::::mutate(|v| { + if *v != amount { + *v = amount; + Pallet::::deposit_event(Event::DebitOffsetBufferUpdated { amount }); + } + }); + + Weight::from_parts(0, 0) + } +} diff --git a/blockchain/modules/ecdp-ussd-treasury/src/mock.rs b/blockchain/modules/ecdp-ussd-treasury/src/mock.rs new file mode 100644 index 00000000..e365ba3f --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/mock.rs @@ -0,0 +1,259 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the cdp treasury module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, EitherOfDiverse, Nothing}, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use module_support::SpecificJointsSwap; +use orml_traits::parameter_type_with_key; +use primitives::{DexShare, TokenSymbol, TradingPair}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; +use sp_std::cell::RefCell; + +pub type AccountId = u128; +pub type BlockNumber = u64; +pub type Amount = i64; +pub type AuctionId = u32; + +pub const ALICE: AccountId = 0; +pub const BOB: AccountId = 1; +pub const CHARLIE: AccountId = 2; +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const BTC: CurrencyId = CurrencyId::ForeignAsset(255); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); +pub const LP_USSD_EDF: CurrencyId = + CurrencyId::DexShare(DexShare::Token(TokenSymbol::USSD), DexShare::Token(TokenSymbol::EDF)); + +mod cdp_treasury { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = SEE; +} + +impl orml_currencies::Config for Runtime { + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} + +parameter_types! { + pub const GetUSSDCurrencyId: CurrencyId = USSD; + pub const GetExchangeFee: (u32, u32) = (0, 100); + pub EnabledTradingPairs: Vec = vec![ + TradingPair::from_currency_ids(USSD, BTC).unwrap(), + TradingPair::from_currency_ids(USSD, EDF).unwrap(), + TradingPair::from_currency_ids(BTC, EDF).unwrap(), + ]; + pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); +} + +impl module_dex::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = ConstU32<4>; + type PalletId = DEXPalletId; + type Erc20InfoMapping = (); + type DEXIncentives = (); + type WeightInfo = (); + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU64<0>; + type OnLiquidityPoolUpdated = (); +} + +thread_local! { + pub static TOTAL_COLLATERAL_AUCTION: RefCell = RefCell::new(0); + pub static TOTAL_COLLATERAL_IN_AUCTION: RefCell = RefCell::new(0); +} + +pub struct MockAuctionsManager; +impl AuctionsManager for MockAuctionsManager { + type CurrencyId = CurrencyId; + type Balance = Balance; + type AuctionId = AuctionId; + + fn new_collateral_auction( + _refund_recipient: &AccountId, + _currency_id: Self::CurrencyId, + amount: Self::Balance, + _target: Self::Balance, + ) -> DispatchResult { + TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut() += 1); + TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut() += amount); + Ok(()) + } + + fn cancel_auction(_id: Self::AuctionId) -> DispatchResult { + unimplemented!() + } + + fn get_total_collateral_in_auction(_id: Self::CurrencyId) -> Self::Balance { + TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()) + } + + fn get_total_target_in_auction() -> Self::Balance { + unimplemented!() + } +} + +ord_parameter_types! { + pub const One: AccountId = 1; +} + +parameter_types! { + pub const SlickUsdEcdpTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub const TreasuryAccount: AccountId = 10; + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![EDF], + ]; +} + +thread_local! { + static IS_SHUTDOWN: RefCell = RefCell::new(false); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type AuctionsManagerHandler = MockAuctionsManager; + type UpdateOrigin = EitherOfDiverse, EnsureSignedBy>; + type DEX = DEXModule; + type Swap = SpecificJointsSwap; + type MaxAuctionsCount = ConstU32<5>; + type PalletId = SlickUsdEcdpTreasuryPalletId; + type TreasuryAccount = TreasuryAccount; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + SlickUsdEcdpTreasuryModule: cdp_treasury, + Currencies: orml_currencies, + Tokens: orml_tokens, + PalletBalances: pallet_balances, + DEXModule: module_dex, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, EDF, 1000), + (ALICE, USSD, 1000), + (ALICE, BTC, 1000), + (BOB, EDF, 1000), + (BOB, USSD, 1000), + (BOB, BTC, 1000), + (CHARLIE, EDF, 1000), + (CHARLIE, BTC, 1000), + ], + } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + module_dex::GenesisConfig:: { + initial_listing_trading_pairs: vec![], + initial_enabled_trading_pairs: EnabledTradingPairs::get(), + initial_added_liquidity_pools: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/blockchain/modules/ecdp-ussd-treasury/src/tests.rs b/blockchain/modules/ecdp-ussd-treasury/src/tests.rs new file mode 100644 index 00000000..8f70d9bc --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/tests.rs @@ -0,0 +1,571 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the cdp treasury module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{RuntimeEvent, *}; +use module_support::SwapError; +use sp_runtime::traits::BadOrigin; + +#[test] +fn surplus_pool_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(Currencies::deposit( + GetUSSDCurrencyId::get(), + &SlickUsdEcdpTreasuryModule::account_id(), + 500 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 500); + }); +} + +#[test] +fn total_collaterals_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 0); + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10); + }); +} + +#[test] +fn on_system_debit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(1000)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + assert_noop!( + SlickUsdEcdpTreasuryModule::on_system_debit(Balance::max_value()), + ArithmeticError::Overflow, + ); + }); +} + +#[test] +fn on_system_surplus_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + }); +} + +#[test] +fn offset_surplus_and_debit_on_finalize_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + SlickUsdEcdpTreasuryModule::on_finalize(1); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(300)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 300); + SlickUsdEcdpTreasuryModule::on_finalize(2); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 700); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 700); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(800)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 800); + SlickUsdEcdpTreasuryModule::on_finalize(3); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); + }); +} + +#[test] +fn issue_debit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + + assert_ok!(SlickUsdEcdpTreasuryModule::issue_debit(&ALICE, 1000, true)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 2000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + + assert_ok!(SlickUsdEcdpTreasuryModule::issue_debit(&ALICE, 1000, false)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 3000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + }); +} + +#[test] +fn burn_debit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::burn_debit(&ALICE, 300)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + }); +} + +#[test] +fn deposit_surplus_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_surplus(&ALICE, 300)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 300); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 300); + }); +} + +#[test] +fn withdraw_surplus_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_surplus(&ALICE, 300)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 300); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 300); + + assert_ok!(SlickUsdEcdpTreasuryModule::withdraw_surplus(&ALICE, 200)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 100); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 100); + }); +} + +#[test] +fn deposit_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + assert!(!SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 10000).is_ok()); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 500); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 500); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 500); + }); +} + +#[test] +fn withdraw_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 500); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 500); + assert_eq!(Currencies::free_balance(BTC, &BOB), 1000); + assert!(!SlickUsdEcdpTreasuryModule::withdraw_collateral(&BOB, BTC, 501).is_ok()); + assert_ok!(SlickUsdEcdpTreasuryModule::withdraw_collateral(&BOB, BTC, 400)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 100); + assert_eq!(Currencies::free_balance(BTC, &BOB), 1400); + }); +} + +#[test] +fn get_total_collaterals_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(SlickUsdEcdpTreasuryModule::get_total_collaterals(BTC), 500); + }); +} + +#[test] +fn get_debit_proportion_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!( + SlickUsdEcdpTreasuryModule::get_debit_proportion(100), + Ratio::saturating_from_rational(100, Currencies::total_issuance(USSD)) + ); + }); +} + +#[test] +fn swap_collateral_to_ussd_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&BOB, BTC, 200)); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&CHARLIE, EDF, 1000)); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 200); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(EDF), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(BOB), + EDF, + USSD, + 1000, + 1000, + 0, + false + )); + + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(201, 200), false), + Error::::CollateralNotEnough, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1001, 0), false), + Error::::CollateralNotEnough, + ); + + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false), + SwapError::CannotSwap + ); + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(ALICE), + BTC, + EDF, + 100, + 1000, + 0, + false + )); + + assert_eq!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false).unwrap(), + (198, 399) + ); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 399); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 2); + + assert_noop!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 1000), false), + SwapError::CannotSwap + ); + + assert_eq!( + SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 0), false).unwrap(), + (1000, 225) + ); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 624); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(EDF), 0); + }); +} + +#[test] +fn create_collateral_auctions_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10000)); + assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_noop!( + SlickUsdEcdpTreasuryModule::create_collateral_auctions(BTC, 10001, 1000, ALICE, true), + Error::::CollateralNotEnough, + ); + + // without collateral auction maximum size + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 1000, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 1); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1000); + + // set collateral auction maximum size + assert_ok!(SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size( + RuntimeOrigin::signed(1), + BTC, + 300 + )); + + // amount < collateral auction maximum size + // auction + 1 + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 200, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 2); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1200); + + // not exceed lots count cap + // auction + 4 + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 1000, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 6); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 2200); + + // exceed lots count cap + // auction + 5 + assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + BTC, 2000, 1000, ALICE, true + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 11); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 4200); + }); +} + +#[test] +fn remove_liquidity_for_lp_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(BOB), + USSD, + EDF, + 1000, + 100, + 0, + false + )); + assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&BOB, LP_USSD_EDF, 200)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 2000); + assert_eq!(DEXModule::get_liquidity_pool(USSD, EDF), (1000, 100)); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &SlickUsdEcdpTreasuryModule::account_id()), + 200 + ); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(EDF, &SlickUsdEcdpTreasuryModule::account_id()), 0); + + assert_noop!( + SlickUsdEcdpTreasuryModule::remove_liquidity_for_lp_collateral(EDF, 200), + Error::::NotDexShare + ); + + assert_eq!( + SlickUsdEcdpTreasuryModule::remove_liquidity_for_lp_collateral(LP_USSD_EDF, 120), + Ok((60, 6)) + ); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 1880); + assert_eq!(DEXModule::get_liquidity_pool(USSD, EDF), (940, 94)); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &SlickUsdEcdpTreasuryModule::account_id()), + 80 + ); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 60); + assert_eq!(Currencies::free_balance(EDF, &SlickUsdEcdpTreasuryModule::account_id()), 6); + }); +} + +#[test] +fn set_expected_collateral_auction_size_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_noop!( + SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size(RuntimeOrigin::signed(5), BTC, 200), + BadOrigin + ); + assert_ok!(SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size( + RuntimeOrigin::signed(1), + BTC, + 200 + )); + System::assert_last_event(RuntimeEvent::SlickUsdEcdpTreasuryModule( + crate::Event::ExpectedCollateralAuctionSizeUpdated { + collateral_type: BTC, + new_size: 200, + }, + )); + }); +} + +#[test] +fn extract_surplus_to_treasury_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_eq!(Currencies::free_balance(USSD, &TreasuryAccount::get()), 0); + + assert_noop!( + SlickUsdEcdpTreasuryModule::extract_surplus_to_treasury(RuntimeOrigin::signed(5), 200), + BadOrigin + ); + assert_ok!(SlickUsdEcdpTreasuryModule::extract_surplus_to_treasury( + RuntimeOrigin::signed(1), + 200 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 800); + assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 800); + assert_eq!(Currencies::free_balance(USSD, &TreasuryAccount::get()), 200); + }); +} + +#[test] +fn auction_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10000)); + assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10000); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 10000); + assert_noop!( + SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(5), BTC, 10000, 1000, false), + BadOrigin, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 10001, 1000, false), + Error::::CollateralNotEnough, + ); + + assert_ok!(SlickUsdEcdpTreasuryModule::auction_collateral( + RuntimeOrigin::signed(1), + BTC, + 1000, + 1000, + false + )); + assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 1); + assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1000); + + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10000); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 9000); + assert_noop!( + SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 9001, 1000, false), + Error::::CollateralNotEnough, + ); + }); +} + +#[test] +fn exchange_collateral_to_ussd_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(DEXModule::add_liquidity( + RuntimeOrigin::signed(BOB), + BTC, + USSD, + 200, + 1000, + 0, + false + )); + + assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 1000)); + assert_ok!(SlickUsdEcdpTreasuryModule::auction_collateral( + RuntimeOrigin::signed(1), + BTC, + 800, + 1000, + false + )); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 200); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(5), + BTC, + SwapLimit::ExactTarget(200, 200) + ), + BadOrigin, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactTarget(201, 200) + ), + Error::::CollateralNotEnough, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactSupply(201, 0) + ), + Error::::CollateralNotEnough, + ); + assert_noop!( + SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactTarget(200, 1000) + ), + SwapError::CannotSwap + ); + + assert_ok!(SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + RuntimeOrigin::signed(1), + BTC, + SwapLimit::ExactTarget(200, 399) + )); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 399); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 867); + assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 67); + }); +} + +#[test] +fn set_debit_offset_buffer_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + assert_noop!( + SlickUsdEcdpTreasuryModule::set_debit_offset_buffer(RuntimeOrigin::signed(5), 200), + BadOrigin + ); + assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + RuntimeOrigin::signed(1), + 200 + )); + System::assert_last_event(RuntimeEvent::SlickUsdEcdpTreasuryModule( + crate::Event::DebitOffsetBufferUpdated { amount: 200 }, + )); + }); +} + +#[test] +fn offset_surplus_and_debit_limited_by_debit_offset_buffer() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(2000)); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 2000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + + // offset all debit pool when surplus is enough + SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + + assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + RuntimeOrigin::signed(1), + 100 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 100); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(2000)); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 2000); + + // keep the buffer for debit pool when surplus is enough + SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1100); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 100); + + assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + RuntimeOrigin::signed(1), + 200 + )); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 200); + assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(1400)); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1500); + + SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); + assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 400); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 200); + }); +} diff --git a/blockchain/modules/ecdp-ussd-treasury/src/weights.rs b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs new file mode 100644 index 00000000..b1884981 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs @@ -0,0 +1,108 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for module_ecdp_ussd_treasury +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-01-27, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_ecdp_ussd_treasury +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./modules/cdp-treasury/src/weights.rs +// --template=./templates/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_ecdp_ussd_treasury. +pub trait WeightInfo { + fn extract_surplus_to_treasury() -> Weight; + fn auction_collateral(b: u32) -> Weight; + fn exchange_collateral_to_ussd() -> Weight; + fn set_expected_collateral_auction_size() -> Weight; +} + +/// Weights for module_ecdp_ussd_treasury using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn auction_collateral(b: u32, ) -> Weight { + Weight::from_parts(2_672_000, 0) + // Standard Error: 326_000 + .saturating_add(Weight::from_parts(32_334_000, 0).saturating_mul(b as u64)) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(b as u64))) + } + fn exchange_collateral_to_ussd() -> Weight { + Weight::from_parts(176_000_000, 0) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(6 as u64)) + } + fn set_expected_collateral_auction_size() -> Weight { + Weight::from_parts(25_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn extract_surplus_to_treasury() -> Weight { + Weight::from_parts(75_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn auction_collateral(b: u32, ) -> Weight { + Weight::from_parts(2_672_000, 0) + .saturating_add(Weight::from_parts(32_334_000, 0).saturating_mul(b as u64)) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(b as u64))) + } + fn exchange_collateral_to_ussd() -> Weight { + Weight::from_parts(176_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(6 as u64)) + } + fn set_expected_collateral_auction_size() -> Weight { + Weight::from_parts(25_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn extract_surplus_to_treasury() -> Weight { + Weight::from_parts(75_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/blockchain/modules/edfis-swap/src/mock.rs b/blockchain/modules/edfis-swap/src/mock.rs index 1be114ba..eaf475ac 100644 --- a/blockchain/modules/edfis-swap/src/mock.rs +++ b/blockchain/modules/edfis-swap/src/mock.rs @@ -41,7 +41,8 @@ pub const ALICE: AccountId = 1; pub const BOB: AccountId = 2; pub const CAROL: AccountId = 3; pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); -pub const WBTC: CurrencyId = CurrencyId::Token(TokenSymbol::FA_WBTC); +pub const WBTC: CurrencyId = CurrencyId::ForeignAsset(255); + pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); diff --git a/blockchain/modules/evm/src/bench/mock.rs b/blockchain/modules/evm/src/bench/mock.rs index 3216c762..5ff7aa77 100644 --- a/blockchain/modules/evm/src/bench/mock.rs +++ b/blockchain/modules/evm/src/bench/mock.rs @@ -172,7 +172,6 @@ impl Config for Runtime { } parameter_types! { - pub const GetStableCurrencyId: CurrencyId = USSD; pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::one(); pub const TreasuryPalletId: PalletId = PalletId(*b"set/trsy"); pub const TransactionPaymentPalletId: PalletId = PalletId(*b"set/fees"); diff --git a/blockchain/modules/prices/README.md b/blockchain/modules/prices/README.md index 11f30725..1e34c5c1 100644 --- a/blockchain/modules/prices/README.md +++ b/blockchain/modules/prices/README.md @@ -5,6 +5,6 @@ ## Overview The data from Oracle cannot be used in business, prices module will do some process and feed prices for Setheum. Process include: - - specify a fixed price for stable currency; + - specify a fixed price for USSD; - feed price in USD or related price bewteen two currencies; - lock/unlock the price data got from oracle; diff --git a/blockchain/modules/prices/src/lib.rs b/blockchain/modules/prices/src/lib.rs index 1279f6a9..b9bce348 100644 --- a/blockchain/modules/prices/src/lib.rs +++ b/blockchain/modules/prices/src/lib.rs @@ -24,7 +24,7 @@ //! //! The data from Oracle cannot be used in business, prices module will do some //! process and feed prices for Setheum. Process include: -//! - specify a fixed price for stable currency +//! - specify a fixed price for USSD //! - feed price in USD or related price bewteen two currencies //! - lock/unlock the price data got from oracle diff --git a/blockchain/modules/prices/src/tests.rs b/blockchain/modules/prices/src/tests.rs index 42f66356..8d34cc24 100644 --- a/blockchain/modules/prices/src/tests.rs +++ b/blockchain/modules/prices/src/tests.rs @@ -119,7 +119,7 @@ fn lp_token_fair_price_works() { } #[test] -fn access_price_of_stable_currency() { +fn access_price_of_ussd() { ExtBuilder::default().build().execute_with(|| { assert_eq!( PricesModule::access_price(USSD), diff --git a/blockchain/modules/support/src/ecdp.rs b/blockchain/modules/support/src/ecdp.rs index 7967456e..a73c8bc6 100644 --- a/blockchain/modules/support/src/ecdp.rs +++ b/blockchain/modules/support/src/ecdp.rs @@ -1,209 +1,174 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 parity_scale_codec::FullCodec; -use primitives::ECDPPosition; -use sp_core::U256; -use sp_runtime::{DispatchError, DispatchResult}; -use sp_std::{ - cmp::{Eq, PartialEq}, - fmt::Debug, - prelude::*, -}; - -use crate::{dex::*, ExchangeRate, Ratio}; - -pub trait EmergencyShutdown { - fn is_shutdown() -> bool; -} - -pub trait AuctionManager { - type CurrencyId; - type Balance; - type AuctionId: FullCodec + Debug + Clone + Eq + PartialEq; - - fn new_collateral_auction( - refund_recipient: &AccountId, - currency_id: Self::CurrencyId, - amount: Self::Balance, - target: Self::Balance, - ) -> DispatchResult; - fn cancel_auction(id: Self::AuctionId) -> DispatchResult; - fn get_total_collateral_in_auction(id: Self::CurrencyId) -> Self::Balance; - fn get_total_target_in_auction() -> Self::Balance; -} - -pub trait PeggedRiskManager { - fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; - - fn check_position_valid( - currency_id: CurrencyId, - collateral_balance: Balance, - debit_balance: DebitBalance, - check_required_ratio: bool, - ) -> DispatchResult; - - fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; -} - -#[cfg(feature = "std")] -impl PeggedRiskManager - for () -{ - fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { - Default::default() - } - - fn check_position_valid( - _currency_id: CurrencyId, - _collateral_balance: Balance, - _debit_balance: DebitBalance, - _check_required_ratio: bool, - ) -> DispatchResult { - Ok(()) - } - - fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { - Ok(()) - } -} - -// pub trait UnpeggedRiskManager { -// fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; - -// fn check_position_valid( -// currency_id: CurrencyId, -// collateral_balance: Balance, -// debit_balance: DebitBalance, -// check_required_ratio: bool, -// ) -> DispatchResult; - -// fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; -// } - -// #[cfg(feature = "std")] -// impl UnpeggedRiskManager -// for () -// { -// fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { -// Default::default() -// } - -// fn check_position_valid( -// _currency_id: CurrencyId, -// _collateral_balance: Balance, -// _debit_balance: DebitBalance, -// _check_required_ratio: bool, -// ) -> DispatchResult { -// Ok(()) -// } - -// fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { -// Ok(()) -// } -// } - -/// An abstraction of cdp treasury for SlickUSD ECDP Protocol. -pub trait SlickUsdEcdpTreasury { - type Balance; - type CurrencyId; - - /// get surplus amount of cdp treasury - fn get_surplus_pool() -> Self::Balance; - - /// get debit amount of cdp treasury - fn get_debit_pool() -> Self::Balance; - - /// get collateral assets amount of cdp treasury - fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance; - - /// calculate the proportion of specific debit amount for the whole system - fn get_debit_proportion(amount: Self::Balance) -> Ratio; - - /// issue debit for cdp treasury - fn on_system_debit(amount: Self::Balance) -> DispatchResult; - - /// issue surplus(stable currency) for cdp treasury - fn on_system_surplus(amount: Self::Balance) -> DispatchResult; - - /// issue debit to `who` - /// if backed flag is true, means the debit to issue is backed on some - /// assets, otherwise will increase same amount of debit to system debit. - fn issue_debit(who: &AccountId, debit: Self::Balance, backed: bool) -> DispatchResult; - - /// burn debit(stable currency) of `who` - fn burn_debit(who: &AccountId, debit: Self::Balance) -> DispatchResult; - - /// deposit surplus(stable currency) to cdp treasury by `from` - fn deposit_surplus(from: &AccountId, surplus: Self::Balance) -> DispatchResult; - - /// withdraw surplus(stable currency) from cdp treasury to `to` - fn withdraw_surplus(to: &AccountId, surplus: Self::Balance) -> DispatchResult; - - /// deposit collateral assets to cdp treasury by `who` - fn deposit_collateral(from: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; - - /// withdraw collateral assets of cdp treasury to `who` - fn withdraw_collateral(to: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; -} - -pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { - fn swap_collateral_to_stable( - currency_id: Self::CurrencyId, - limit: SwapLimit, - collateral_in_auction: bool, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; - - fn create_collateral_auctions( - currency_id: Self::CurrencyId, - amount: Self::Balance, - target: Self::Balance, - refund_receiver: AccountId, - splited: bool, - ) -> sp_std::result::Result; - - fn remove_liquidity_for_lp_collateral( - currency_id: Self::CurrencyId, - amount: Self::Balance, - ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; - - fn max_auction() -> u32; -} - -/// Functionality of SlickUSD ECDP Protocol to be exposed to EVM+. -pub trait SlickUsdEcdpManager { - /// Adjust ECDP loan - fn adjust_loan( - who: &AccountId, - currency_id: CurrencyId, - collateral_adjustment: Amount, - debit_adjustment: Amount, - ) -> DispatchResult; - /// Close ECDP loan using DEX - fn close_loan_by_dex(who: AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult; - /// Get open ECDP corresponding to an account and collateral `CurrencyId` - fn get_position(who: &AccountId, currency_id: CurrencyId) -> ECDPPosition; - /// Get liquidation ratio for collateral `CurrencyId` - fn get_collateral_parameters(currency_id: CurrencyId) -> Vec; - /// Get current ratio of collateral to debit of open ECDP - fn get_current_collateral_ratio(who: &AccountId, currency_id: CurrencyId) -> Option; - /// Get exchange rate of debit units to debit value for a currency_id - fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate; -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 parity_scale_codec::FullCodec; +use primitives::ECDPPosition; +use sp_core::U256; +use sp_runtime::{DispatchError, DispatchResult}; +use sp_std::{ + cmp::{Eq, PartialEq}, + fmt::Debug, + prelude::*, +}; + +use crate::{dex::*, ExchangeRate, Ratio}; + +pub trait EmergencyShutdown { + fn is_shutdown() -> bool; +} + +pub trait AuctionsManager { + type CurrencyId; + type Balance; + type AuctionId: FullCodec + Debug + Clone + Eq + PartialEq; + + fn new_collateral_auction( + refund_recipient: &AccountId, + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + ) -> DispatchResult; + fn cancel_auction(id: Self::AuctionId) -> DispatchResult; + fn get_total_collateral_in_auction(id: Self::CurrencyId) -> Self::Balance; + fn get_total_target_in_auction() -> Self::Balance; +} + +pub trait SlickUsdRiskManager { + fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; + + fn check_position_valid( + currency_id: CurrencyId, + collateral_balance: Balance, + debit_balance: DebitBalance, + check_required_ratio: bool, + ) -> DispatchResult; + + fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: DebitBalance) -> DispatchResult; +} + +#[cfg(feature = "std")] +impl SlickUsdRiskManager + for () +{ + fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { + Default::default() + } + + fn check_position_valid( + _currency_id: CurrencyId, + _collateral_balance: Balance, + _debit_balance: DebitBalance, + _check_required_ratio: bool, + ) -> DispatchResult { + Ok(()) + } + + fn check_debit_cap(_currency_id: CurrencyId, _total_debit_balance: DebitBalance) -> DispatchResult { + Ok(()) + } +} + +/// An abstraction of cdp treasury for SlickUSD ECDP Protocol. +pub trait SlickUsdEcdpTreasury { + type Balance; + type CurrencyId; + + /// get surplus amount of cdp treasury + fn get_surplus_pool() -> Self::Balance; + + /// get debit amount of cdp treasury + fn get_debit_pool() -> Self::Balance; + + /// get collateral assets amount of cdp treasury + fn get_total_collaterals(id: Self::CurrencyId) -> Self::Balance; + + /// calculate the proportion of specific debit amount for the whole system + fn get_debit_proportion(amount: Self::Balance) -> Ratio; + + /// issue debit for cdp treasury + fn on_system_debit(amount: Self::Balance) -> DispatchResult; + + /// issue surplus(USSD) for cdp treasury + fn on_system_surplus(amount: Self::Balance) -> DispatchResult; + + /// issue debit to `who` + /// if backed flag is true, means the debit to issue is backed on some + /// assets, otherwise will increase same amount of debit to system debit. + fn issue_debit(who: &AccountId, debit: Self::Balance, backed: bool) -> DispatchResult; + + /// burn debit(USSD) of `who` + fn burn_debit(who: &AccountId, debit: Self::Balance) -> DispatchResult; + + /// deposit surplus(USSD) to cdp treasury by `from` + fn deposit_surplus(from: &AccountId, surplus: Self::Balance) -> DispatchResult; + + /// withdraw surplus(USSD) from cdp treasury to `to` + fn withdraw_surplus(to: &AccountId, surplus: Self::Balance) -> DispatchResult; + + /// deposit collateral assets to cdp treasury by `who` + fn deposit_collateral(from: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; + + /// withdraw collateral assets of cdp treasury to `who` + fn withdraw_collateral(to: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; +} + +pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { + fn swap_collateral_to_ussd( + currency_id: Self::CurrencyId, + limit: SwapLimit, + collateral_in_auction: bool, + ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; + + fn create_collateral_auctions( + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + refund_receiver: AccountId, + splited: bool, + ) -> sp_std::result::Result; + + fn remove_liquidity_for_lp_collateral( + currency_id: Self::CurrencyId, + amount: Self::Balance, + ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; + + fn max_auction() -> u32; +} + +/// Functionality of SlickUSD ECDP Protocol to be exposed to EVM+. +pub trait SlickUsdEcdpManager { + /// Adjust ECDP loan + fn adjust_loan( + who: &AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult; + /// Close ECDP loan using DEX + fn close_loan_by_dex(who: AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult; + /// Get open ECDP corresponding to an account and collateral `CurrencyId` + fn get_position(who: &AccountId, currency_id: CurrencyId) -> ECDPPosition; + /// Get liquidation ratio for collateral `CurrencyId` + fn get_collateral_parameters(currency_id: CurrencyId) -> Vec; + /// Get current ratio of collateral to debit of open ECDP + fn get_current_collateral_ratio(who: &AccountId, currency_id: CurrencyId) -> Option; + /// Get exchange rate of debit units to debit value for a currency_id + fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate; +} diff --git a/blockchain/modules/support/src/lib.rs b/blockchain/modules/support/src/lib.rs index 4bff5e59..ba2871a9 100644 --- a/blockchain/modules/support/src/lib.rs +++ b/blockchain/modules/support/src/lib.rs @@ -158,7 +158,7 @@ pub trait LiquidateCollateral { who: &AccountId, currency_id: CurrencyId, amount: Balance, - target_stable_amount: Balance, + target_ussd_amount: Balance, ) -> DispatchResult; } @@ -168,11 +168,11 @@ impl LiquidateCollateral for Tuple { who: &AccountId, currency_id: CurrencyId, amount: Balance, - target_stable_amount: Balance, + target_ussd_amount: Balance, ) -> DispatchResult { let mut last_error = None; for_tuples!( #( - match Tuple::liquidate(who, currency_id, amount, target_stable_amount) { + match Tuple::liquidate(who, currency_id, amount, target_ussd_amount) { Ok(_) => return Ok(()), Err(e) => { last_error = Some(e) } } From dec1c70360b0cf8b26eddf068ebfa2c463fdfe88 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Mon, 4 Mar 2024 01:14:40 +0800 Subject: [PATCH 13/21] Add Rust Setup Docss --- docs/rust-setup.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 docs/rust-setup.md diff --git a/docs/rust-setup.md b/docs/rust-setup.md new file mode 100644 index 00000000..6f279f7b --- /dev/null +++ b/docs/rust-setup.md @@ -0,0 +1,82 @@ +--- +title: Installation +--- + +This page will guide you through the steps needed to prepare a computer for development with the +Substrate Node Template. Since Substrate is built with +[the Rust programming language](https://www.rust-lang.org/), the first thing you will need to do is +prepare the computer for Rust development - these steps will vary based on the computer's operating +system. Once Rust is configured, you will use its toolchains to interact with Rust projects; the +commands for Rust's toolchains will be the same for all supported, Unix-based operating systems. + +## Unix-Based Operating Systems + +Substrate development is easiest on Unix-based operating systems like macOS or Linux. The examples +in the Substrate [Tutorials](https://docs.substrate.io/tutorials/v3) and +[How-to Guides](https://docs.substrate.io/how-to-guides/v3) use Unix-style terminals to demonstrate +how to interact with Substrate from the command line. + +### macOS + +Open the Terminal application and execute the following commands: + +```bash +# Install Homebrew if necessary https://brew.sh/ +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" + +# Make sure Homebrew is up-to-date, install openssl and cmake +brew update +brew install openssl cmake +``` + +### Ubuntu/Debian + +Use a terminal shell to execute the following commands: + +```bash +sudo apt update +# May prompt for location information +sudo apt install -y cmake pkg-config libssl-dev git build-essential clang libclang-dev curl +``` + +### Arch Linux + +Run these commands from a terminal: + +```bash +pacman -Syu --needed --noconfirm cmake gcc openssl-1.0 pkgconf git clang +export OPENSSL_LIB_DIR="/usr/lib/openssl-1.0" +export OPENSSL_INCLUDE_DIR="/usr/include/openssl-1.0" +``` + +### Fedora/RHEL/CentOS + +Use a terminal to run the following commands: + +```bash +# Update +sudo dnf update +# Install packages +sudo dnf install cmake pkgconfig rocksdb rocksdb-devel llvm git libcurl libcurl-devel curl-devel clang +``` + +## Rust Developer Environment + +This project uses [`rustup`](https://rustup.rs/) to help manage the Rust toolchain. First install +and configure `rustup`: + +```bash +# Install +curl https://sh.rustup.rs -sSf | sh +# Configure +source ~/.cargo/env +``` + +Finally, configure the Rust toolchain: + +```bash +rustup default stable +rustup update nightly +rustup update stable +rustup target add wasm32-unknown-unknown --toolchain nightly +``` From 7d41aa4f1e012986e15e5e1db61bfa55dc8b8235 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Mon, 4 Mar 2024 01:22:47 +0800 Subject: [PATCH 14/21] Add TODO.md to all directories --- TODO.md | 31 +++++----- blockchain/modules/airdrop/TODO.md | 56 +++++++++++++++++++ blockchain/modules/asset-registry/TODO.md | 56 +++++++++++++++++++ blockchain/modules/currencies/TODO.md | 56 +++++++++++++++++++ blockchain/modules/ecdp-auctions/TODO.md | 56 +++++++++++++++++++ .../modules/ecdp-emergency-shutdown/TODO.md | 56 +++++++++++++++++++ blockchain/modules/ecdp-setr-engine/TODO.md | 56 +++++++++++++++++++ blockchain/modules/ecdp-setr-loans/TODO.md | 56 +++++++++++++++++++ blockchain/modules/ecdp-setr-treasury/TODO.md | 56 +++++++++++++++++++ blockchain/modules/ecdp-ussd-engine/TODO.md | 56 +++++++++++++++++++ blockchain/modules/ecdp-ussd-loans/TODO.md | 56 +++++++++++++++++++ blockchain/modules/ecdp-ussd-treasury/TODO.md | 56 +++++++++++++++++++ blockchain/modules/edfis-launchpad/TODO.md | 56 +++++++++++++++++++ blockchain/modules/edfis-oracle/TODO.md | 56 +++++++++++++++++++ blockchain/modules/edfis-swap-legacy/TODO.md | 56 +++++++++++++++++++ blockchain/modules/edfis-swap/TODO.md | 56 +++++++++++++++++++ blockchain/modules/evm-bridge/TODO.md | 56 +++++++++++++++++++ blockchain/modules/evm-utility/TODO.md | 56 +++++++++++++++++++ blockchain/modules/evm/TODO.md | 56 +++++++++++++++++++ blockchain/modules/idle-scheduler/TODO.md | 56 +++++++++++++++++++ .../modules/moya-liquid-edf-earn/TODO.md | 56 +++++++++++++++++++ .../moya-liquid-edf-validators/TODO.md | 56 +++++++++++++++++++ blockchain/modules/moya-liquid-edf/TODO.md | 56 +++++++++++++++++++ .../modules/moya-liquid-see-earn/TODO.md | 56 +++++++++++++++++++ .../moya-liquid-see-validators/TODO.md | 56 +++++++++++++++++++ blockchain/modules/moya-liquid-see/TODO.md | 56 +++++++++++++++++++ blockchain/modules/nft/TODO.md | 56 +++++++++++++++++++ blockchain/modules/prices/TODO.md | 56 +++++++++++++++++++ blockchain/modules/support/TODO.md | 56 +++++++++++++++++++ blockchain/modules/transaction-pause/TODO.md | 56 +++++++++++++++++++ .../modules/transaction-payment/TODO.md | 56 +++++++++++++++++++ blockchain/modules/unified-accounts/TODO.md | 56 +++++++++++++++++++ blockchain/modules/vesting/TODO.md | 56 +++++++++++++++++++ blockchain/node/TODO.md | 56 +++++++++++++++++++ blockchain/predeploy-contracts/TODO.md | 56 +++++++++++++++++++ blockchain/primitives/TODO.md | 56 +++++++++++++++++++ blockchain/resources/TODO.md | 56 +++++++++++++++++++ blockchain/rpc/TODO.md | 56 +++++++++++++++++++ blockchain/runtime/TODO.md | 56 +++++++++++++++++++ docs/TODO.md | 56 +++++++++++++++++++ fork/TODO.md | 56 +++++++++++++++++++ media/TODO.md | 56 +++++++++++++++++++ scripts/TODO.md | 56 +++++++++++++++++++ 43 files changed, 2368 insertions(+), 15 deletions(-) create mode 100644 blockchain/modules/airdrop/TODO.md create mode 100644 blockchain/modules/asset-registry/TODO.md create mode 100644 blockchain/modules/currencies/TODO.md create mode 100644 blockchain/modules/ecdp-auctions/TODO.md create mode 100644 blockchain/modules/ecdp-emergency-shutdown/TODO.md create mode 100644 blockchain/modules/ecdp-setr-engine/TODO.md create mode 100644 blockchain/modules/ecdp-setr-loans/TODO.md create mode 100644 blockchain/modules/ecdp-setr-treasury/TODO.md create mode 100644 blockchain/modules/ecdp-ussd-engine/TODO.md create mode 100644 blockchain/modules/ecdp-ussd-loans/TODO.md create mode 100644 blockchain/modules/ecdp-ussd-treasury/TODO.md create mode 100644 blockchain/modules/edfis-launchpad/TODO.md create mode 100644 blockchain/modules/edfis-oracle/TODO.md create mode 100644 blockchain/modules/edfis-swap-legacy/TODO.md create mode 100644 blockchain/modules/edfis-swap/TODO.md create mode 100644 blockchain/modules/evm-bridge/TODO.md create mode 100644 blockchain/modules/evm-utility/TODO.md create mode 100644 blockchain/modules/evm/TODO.md create mode 100644 blockchain/modules/idle-scheduler/TODO.md create mode 100644 blockchain/modules/moya-liquid-edf-earn/TODO.md create mode 100644 blockchain/modules/moya-liquid-edf-validators/TODO.md create mode 100644 blockchain/modules/moya-liquid-edf/TODO.md create mode 100644 blockchain/modules/moya-liquid-see-earn/TODO.md create mode 100644 blockchain/modules/moya-liquid-see-validators/TODO.md create mode 100644 blockchain/modules/moya-liquid-see/TODO.md create mode 100644 blockchain/modules/nft/TODO.md create mode 100644 blockchain/modules/prices/TODO.md create mode 100644 blockchain/modules/support/TODO.md create mode 100644 blockchain/modules/transaction-pause/TODO.md create mode 100644 blockchain/modules/transaction-payment/TODO.md create mode 100644 blockchain/modules/unified-accounts/TODO.md create mode 100644 blockchain/modules/vesting/TODO.md create mode 100644 blockchain/node/TODO.md create mode 100644 blockchain/predeploy-contracts/TODO.md create mode 100644 blockchain/primitives/TODO.md create mode 100644 blockchain/resources/TODO.md create mode 100644 blockchain/rpc/TODO.md create mode 100644 blockchain/runtime/TODO.md create mode 100644 docs/TODO.md create mode 100644 fork/TODO.md create mode 100644 media/TODO.md create mode 100644 scripts/TODO.md diff --git a/TODO.md b/TODO.md index 6d5c2e7e..96e340c7 100644 --- a/TODO.md +++ b/TODO.md @@ -1,13 +1,14 @@ -# ToDo List - The Monofile for Setheum Repo ToDos +# To-Do List This list contains all TODOs in the Repo -- [ToDo List - The Monofile for Setheum Repo ToDos](#setheum---the-monofile-for-setheum-repo-todos) +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) - [1. Introduction](#1-guidelines) - [2. Contribution](#2-contribution) - - [3. Tasks](#3-tasks) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) @@ -29,27 +30,27 @@ for example: Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. -Whenever adding/writing a Task/ToDo, you need to open an [Issue](https://github.com/Setheum-Labs/Setheum/issues) which references the commit that adds/writes the task or vice-versa. The `Issue Title` should be the `task_prefix` andn the `task_details` altogether just as it is written in the TODO. +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. -Whenever you write a TODO in any file, please add a reference to it here. Please make sure to title the task reference here exactly as the `task_prefix` (excluding the `task_details`). +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. -Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and add a reference to the `commit` that completes the task. - -Whenever a task is cancelled (discontinued or not needed for w/e reason), add this `-C` as a suffix to its `file_name`, for example: +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: ```rust //TODO:[TODO.md-C:0] - Add Todo Guidelines ``` -Note > The suffix need not be added to the reference Issue too, but you may if you want. +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. +## 3. Lists -## 2. Contribution +Each module/crate has its own `TODO.md`. -You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. -To search for a todo, you can search for TODOs based on the `task_prefix`. You can also contribute by updating old tasks to the new standard syntax using `task_prefix`, you can also open issues for those Tasks if not opened before, if opened before you can link to those Issues as the reference. +## 4. Tasks -## 3. Tasks +These tasks are just for this file specifically. -- [x] [Add TODO.md File](TODO.md) -- [x] [Categorize the Tasks with a `task_prefix`](/TODO.md/#tasks) +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/airdrop/TODO.md b/blockchain/modules/airdrop/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/airdrop/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/asset-registry/TODO.md b/blockchain/modules/asset-registry/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/asset-registry/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/currencies/TODO.md b/blockchain/modules/currencies/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/currencies/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-auctions/TODO.md b/blockchain/modules/ecdp-auctions/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-auctions/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-emergency-shutdown/TODO.md b/blockchain/modules/ecdp-emergency-shutdown/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-emergency-shutdown/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-setr-engine/TODO.md b/blockchain/modules/ecdp-setr-engine/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-setr-engine/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-setr-loans/TODO.md b/blockchain/modules/ecdp-setr-loans/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-setr-loans/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-setr-treasury/TODO.md b/blockchain/modules/ecdp-setr-treasury/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-setr-treasury/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-ussd-engine/TODO.md b/blockchain/modules/ecdp-ussd-engine/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-engine/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-ussd-loans/TODO.md b/blockchain/modules/ecdp-ussd-loans/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-loans/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp-ussd-treasury/TODO.md b/blockchain/modules/ecdp-ussd-treasury/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-treasury/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/edfis-launchpad/TODO.md b/blockchain/modules/edfis-launchpad/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/edfis-launchpad/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/edfis-oracle/TODO.md b/blockchain/modules/edfis-oracle/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/edfis-oracle/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/edfis-swap-legacy/TODO.md b/blockchain/modules/edfis-swap-legacy/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/edfis-swap-legacy/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/edfis-swap/TODO.md b/blockchain/modules/edfis-swap/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/edfis-swap/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/evm-bridge/TODO.md b/blockchain/modules/evm-bridge/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/evm-bridge/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/evm-utility/TODO.md b/blockchain/modules/evm-utility/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/evm-utility/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/evm/TODO.md b/blockchain/modules/evm/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/evm/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/idle-scheduler/TODO.md b/blockchain/modules/idle-scheduler/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/idle-scheduler/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/moya-liquid-edf-earn/TODO.md b/blockchain/modules/moya-liquid-edf-earn/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/moya-liquid-edf-earn/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/moya-liquid-edf-validators/TODO.md b/blockchain/modules/moya-liquid-edf-validators/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/moya-liquid-edf-validators/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/moya-liquid-edf/TODO.md b/blockchain/modules/moya-liquid-edf/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/moya-liquid-edf/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/moya-liquid-see-earn/TODO.md b/blockchain/modules/moya-liquid-see-earn/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/moya-liquid-see-earn/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/moya-liquid-see-validators/TODO.md b/blockchain/modules/moya-liquid-see-validators/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/moya-liquid-see-validators/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/moya-liquid-see/TODO.md b/blockchain/modules/moya-liquid-see/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/moya-liquid-see/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/nft/TODO.md b/blockchain/modules/nft/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/nft/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/prices/TODO.md b/blockchain/modules/prices/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/prices/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/support/TODO.md b/blockchain/modules/support/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/support/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/transaction-pause/TODO.md b/blockchain/modules/transaction-pause/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/transaction-pause/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/transaction-payment/TODO.md b/blockchain/modules/transaction-payment/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/transaction-payment/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/unified-accounts/TODO.md b/blockchain/modules/unified-accounts/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/unified-accounts/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/vesting/TODO.md b/blockchain/modules/vesting/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/vesting/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/node/TODO.md b/blockchain/node/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/blockchain/node/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/predeploy-contracts/TODO.md b/blockchain/predeploy-contracts/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/blockchain/predeploy-contracts/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/primitives/TODO.md b/blockchain/primitives/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/blockchain/primitives/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/resources/TODO.md b/blockchain/resources/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/blockchain/resources/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/rpc/TODO.md b/blockchain/rpc/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/blockchain/rpc/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/runtime/TODO.md b/blockchain/runtime/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/blockchain/runtime/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/fork/TODO.md b/fork/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/fork/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/media/TODO.md b/media/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/media/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/scripts/TODO.md b/scripts/TODO.md new file mode 100644 index 00000000..96e340c7 --- /dev/null +++ b/scripts/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each module/crate has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. From ad9acca15021f4edcfa191d444d031a15e8bff11 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Mon, 4 Mar 2024 18:01:14 +0800 Subject: [PATCH 15/21] Populate Edfis Launchpad Module --- .../modules/asset-registry/src/weights.rs | 2 +- blockchain/modules/currencies/src/weights.rs | 2 +- .../modules/ecdp-ussd-treasury/src/weights.rs | 6 +- blockchain/modules/edfis-launchpad/Cargo.toml | 4 - blockchain/modules/edfis-launchpad/README.md | 38 +- blockchain/modules/edfis-launchpad/src/lib.rs | 915 +++++++++++++ .../modules/edfis-launchpad/src/mock.rs | 223 ++++ .../modules/edfis-launchpad/src/tests.rs | 1144 +++++++++++++++++ .../modules/edfis-launchpad/src/weights.rs | 149 +++ .../modules/edfis-oracle/src/weights.rs | 2 +- .../modules/edfis-swap-legacy/src/weights.rs | 2 +- blockchain/modules/edfis-swap/src/weights.rs | 2 +- blockchain/modules/evm/src/weights.rs | 2 +- blockchain/modules/nft/src/weights.rs | 2 +- blockchain/modules/prices/src/weights.rs | 2 +- blockchain/modules/support/TODO.md | 2 + .../modules/support/src/edfis_launchpad.rs | 101 ++ .../support/src/{edfis.rs => edfis_swap.rs} | 460 +++---- .../{edfis_legacy.rs => edfis_swap_legacy.rs} | 446 +++---- blockchain/modules/support/src/lib.rs | 277 ++-- .../modules/transaction-pause/src/weights.rs | 2 +- .../transaction-payment/src/weights.rs | 2 +- .../modules/unified-accounts/src/weights.rs | 2 +- blockchain/modules/vesting/src/weights.rs | 120 +- blockchain/primitives/src/edfis_launchpad.rs | 90 ++ blockchain/primitives/src/lib.rs | 423 +++--- blockchain/runtime/src/weights/dex_oracle.rs | 170 +-- 27 files changed, 3624 insertions(+), 966 deletions(-) create mode 100644 blockchain/modules/edfis-launchpad/src/lib.rs create mode 100644 blockchain/modules/edfis-launchpad/src/mock.rs create mode 100644 blockchain/modules/edfis-launchpad/src/tests.rs create mode 100644 blockchain/modules/edfis-launchpad/src/weights.rs create mode 100644 blockchain/modules/support/src/edfis_launchpad.rs rename blockchain/modules/support/src/{edfis.rs => edfis_swap.rs} (96%) rename blockchain/modules/support/src/{edfis_legacy.rs => edfis_swap_legacy.rs} (96%) create mode 100644 blockchain/primitives/src/edfis_launchpad.rs diff --git a/blockchain/modules/asset-registry/src/weights.rs b/blockchain/modules/asset-registry/src/weights.rs index db889bdb..0147cfe3 100644 --- a/blockchain/modules/asset-registry/src/weights.rs +++ b/blockchain/modules/asset-registry/src/weights.rs @@ -25,7 +25,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/currencies/src/weights.rs b/blockchain/modules/currencies/src/weights.rs index eb3eab69..64f3c6d6 100644 --- a/blockchain/modules/currencies/src/weights.rs +++ b/blockchain/modules/currencies/src/weights.rs @@ -26,7 +26,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // pallet // --chain=dev diff --git a/blockchain/modules/ecdp-ussd-treasury/src/weights.rs b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs index b1884981..d3843115 100644 --- a/blockchain/modules/ecdp-ussd-treasury/src/weights.rs +++ b/blockchain/modules/ecdp-ussd-treasury/src/weights.rs @@ -25,7 +25,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 @@ -35,8 +35,8 @@ // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./modules/cdp-treasury/src/weights.rs -// --template=./templates/module-weight-template.hbs +// --output=./blockchain/modules/ecdp-ussd-treasury/src/weights.rs +// --template=.maintain/module-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] diff --git a/blockchain/modules/edfis-launchpad/Cargo.toml b/blockchain/modules/edfis-launchpad/Cargo.toml index 6af98d3f..001b4d09 100644 --- a/blockchain/modules/edfis-launchpad/Cargo.toml +++ b/blockchain/modules/edfis-launchpad/Cargo.toml @@ -6,8 +6,6 @@ authors = ["Setheum Labs"] edition = "2021" [dependencies] -scale-info = { workspace = true } -serde = { workspace = true, optional = true } parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } sp-runtime = { workspace = true } sp-io = { workspace = true } @@ -27,8 +25,6 @@ orml-tokens = { workspace = true } [features] default = ["std"] std = [ - "scale-info/std", - "serde", "parity-scale-codec/std", "sp-runtime/std", "sp-std/std", diff --git a/blockchain/modules/edfis-launchpad/README.md b/blockchain/modules/edfis-launchpad/README.md index bf697908..68380c00 100644 --- a/blockchain/modules/edfis-launchpad/README.md +++ b/blockchain/modules/edfis-launchpad/README.md @@ -1,5 +1,41 @@ # Edfis Launchpad Module +Edfis Launchpad is a platform for projects to offer crowdsales (IDO) of their tokens and raise funds on Edfis while listing their liquidity pool on the exchange. ## Overview -Provides a launchpad crowdsales platform on Edfis. +This module is used to raise funds on launchpad crowdsales. Teams and projects that are just getting started launching their products would need to raise funds and even sell their tokens to the public. They need community backed by token holders of their token, that is the crowd so that they could have a strong start. By creating a crowdfunding campaign that ends with their project Tokens getting sold to the public, they can raise funds and sell their tokens to the public. + +There are four participants in a LaunchPad Crowdsales Protocol, the Campaign Creator, the Campaign Beneficiary, the Crowd/Contributors, and the Governance Council. + +* The Campaign Creator is the person who creates the campaign and the project. +* The Campaign Beneficiary is the person who receives the funds raised. +* The Crowd/Contributors are the people who contribute to the campaign. +* The Governance Council is the people who manage the campaign and the protocol. + +## How the protocol Works + +![Screenshot from 2022-01-23 13-31-41](https://user-images.githubusercontent.com/15086345/150666483-3f9a07b3-2e76-46f9-97f9-729679c03f1c.png) +The HighEnd LaunchPad Protocol lets teams/projects/campaigns achieve two (2) major goals at once, it raises money, and and sell their tokens to the public. +The protocol uses `MultiCurrency` to let the Campaign Creator choose which currency to raise/sell their tokens for. Therefore, Campaign Creator can choose to raise funds in any currency available on the chain. + +There is a `goal` that is set by the Campaign Creator, the beneficiary of the fund and the Period (campaign period - amount of blocks a campaign should stay active) of the campaign and other information that describes the campaign. + +### The Lifecycle of a Campaign + +A Launchpad Campaign has three stages in its lifecycle, they are as follows: + +1. **Pre-Funding/Proposal Stage**: The Campaign Creator creates the campaign and sets the Period/TimeCap and HardCap. In this stage, the Campaign Creator must submit the proposal to the Governance Council along with a `SubmissionDeposit` required by the protocol. + +2. **Waiting Stage**: The Campaign waits for the appropriate time to start the Campaign. The Protocol has a `WaitingPeriod` that is set on runtime, and all campaigns have to wait for that period to start. + +3. **Funding/Active Stage**: The Campaign can raise funds and sell their tokens to the public in this stage. If the `goal` is reached before the `period` to end the campaign, the campaign will be ended and the funds will be available for the public to claim and the raised funds for the Campaign Beneficiary or Creator to claim. + +### Campaign Ctegories + +#### A Successful Launchpad Campaign + +A successful Launchpad Campaign is one that has raised the `goal` and has sold their tokens to the public. Once the `goal` is reached, the Campaign is considered successful. + +#### A Failed Launchpad Campaign + +A failed Launchpad Campaign is one that has not raised the `goal` and has not sold their tokens to the public. Once the `goal` is not reached and the `period` to end the campaign has ended, the Campaign is considered failed and the campaign allocation of tokens is available for the Campaign Creator to claim refund and the raised funds are also available for the Crowd/Contributors/buyers to claim refunds all only before the `RetirementPeriod` of the campaign. diff --git a/blockchain/modules/edfis-launchpad/src/lib.rs b/blockchain/modules/edfis-launchpad/src/lib.rs new file mode 100644 index 00000000..430976f8 --- /dev/null +++ b/blockchain/modules/edfis-launchpad/src/lib.rs @@ -0,0 +1,915 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! # Launchpad Crowdsales Pallet +//! +//! ## Overview +//! +//! Edfis Launchpad is a platform for projects to offer crowdsales (IDO) of their tokens +//! and raise funds on Edfis while listing their liquidity pool on the exchange. + +#![cfg_attr(not(feature = "std"), no_std)] +// Disable the following two lints since they originate from an external macro (namely decl_storage) +#![allow(clippy::string_lit_as_bytes)] +#![allow(clippy::unused_unit)] + +use frame_support::{ + pallet_prelude::*, transactional, PalletId, traits::Get, ensure +}; +use frame_system::{pallet_prelude::*, ensure_signed}; + +use orml_traits::{GetByKey, MultiCurrency, MultiLockableCurrency, LockIdentifier}; +use primitives::{Balance, CampaignId, CampaignInfo, CurrencyId}; +use support::{CampaignManager, Proposal}; + +use sp_std::{ + vec::Vec, +}; +use sp_runtime::{traits::{AccountIdConversion, Zero}, DispatchResult}; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency<::AccountId>>::Balance; +pub(crate) type CurrencyIdOf = + <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; +pub(crate) type CampaignInfoOf = + CampaignInfo<::AccountId, BalanceOf, ::BlockNumber>; + +pub const LAUNCHPAD_LOCK_ID: LockIdentifier = *b"set/lpad"; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + + /// The Currency for managing assets related to the SERP (Setheum Elastic Reserve Protocol). + type MultiCurrency: MultiLockableCurrency; + + #[pallet::constant] + /// Native currency_id. + /// + type GetNativeCurrencyId: Get>; + + /// The Campaign Commission rate taken from successful campaigns + /// The Treasury Commission is transferred to the Network's Treasury account. + /// The first item of the tuple is the numerator of the commission rate, second + /// item is the denominator, fee_rate = numerator / denominator, + /// use (u32, u32) over another type to minimize internal division operation. + #[pallet::constant] + type GetCommission: Get<(u32, u32)>; + + /// The amount to be held on deposit by the owner of a crowdfund + /// - in HighEnd LaunchPad (HELP) currency id. (LaunchPad Token) + type SubmissionDeposit: Get>; + + /// The minimum amount that must be raised in a crowdsales campaign. + /// Campaign Goal must be at least this amount. + /// If this amount is not met, the proposal can be updated by the proposer or will be rejected. + type MinRaise: GetByKey, BalanceOf>; + + /// The minimum amount that may be contributed into a crowdfund - by currency_id. + /// Should almost certainly be at least ExistentialDeposit. + type MinContribution: GetByKey, BalanceOf>; + + /// The maximum number of proposals that could be running at any given time. + /// If set to 0, proposals are disabled and the Module will panic if a proposal is made. + type MaxProposalsCount: Get; + + /// The maximum number of campaigns that could be running at any given time. + /// If set to 0, campaigns are disabled and the Module will panic if a campaign is made. + type MaxCampaignsCount: Get; + + /// The maximum period of time (in blocks) that a crowdfund campaign clould be active. + /// If set to 0, active period is disabled and the Module will panic if a campaign is activated. + type MaxActivePeriod: Get; + + /// The period of time (number of blocks) a campaign is delayed after being Approved by governance. + type CampaignStartDelay: Get; + + /// The period of time (in blocks) after an unsuccessful crowdfund ending during which + /// contributors are able to withdraw their funds. After this period, their funds are lost. + type CampaignRetirementPeriod: Get; + + /// The period of time (in blocks) after a rejected crowdfund proposal during which + /// proposal creators's locked deposits are unlocked and the proposal is set to `is_rejected`. + /// After this period, their proposal is lost. + type ProposalRetirementPeriod: Get; + + /// The origin which may update, approve or reject campaign proposals. + type UpdateOrigin: EnsureOrigin; + + #[pallet::constant] + /// The Airdrop module pallet id, keeps airdrop funds. + type PalletId: Get; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The campaign funds raised already claimed by campaign creator or beneficiary + CampaignAlreadyClaimed, + /// The crowdfund's contribution period has ended; no more contributions will be accepted. + CampaignEnded, + /// Campaign has failed + CampaignFailed, + /// Campaign is not approved + CampaignNotApproved, + /// Campaign is not active + CampaignNotActive, + /// Campaign is not in the list of campaigns. + CampaignNotFound, + /// Campaign has not started + CampaignNotStarted, + /// Campaign is still active + CampaignStillActive, + /// Contributors balance is not enough to contribute + ContributionCurrencyNotEnough, + /// Contribution failed to transfer + ContributionFailedTransfer, + /// Contribution is not in the list of contributions. + ContributionNotFound, + /// Must contribute at least the minimum amount of funds. + ContributionTooSmall, + /// Contribution has duplicate account + DuplicateContribution, + /// Must contribute at least the minimum amount of funds. + GoalBelowMinimumRaise, + /// The Submission Deposit Funds are insufficient + InsufficientBalance, + /// Wrong Currency Type in use. + InvalidCurrencyType, + /// The fund index specified does not exist. + InvalidIndex, + /// The campaign is in waiting period + InWaitingPeriod, + /// Maximum number of simultaneous campaigns has been reached; + /// no more campaigns can be approved until one is closed. + MaxCampaignsExceeded, + /// Crowdsale period has exceeded the maximum active period. + MaxActivePeriodExceeded, + /// Maximum number of simultaneous proposals has been exceeded; + /// no more proposals can be made until one is approved or rejected. + MaxProposalsExceeded, + /// You cannot withdraw funds because you have not contributed any. + NoContribution, + /// Proposal is already approved. + ProposalAlreadyApproved, + /// Proposal is not in the list of proposals. + ProposalNotFound, + /// The origin is not correct + WrongOrigin, + /// Crowdfund period is too short. + ZeroPeriod, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", BalanceOf = "Balance", CurrencyIdOf = "CurrencyId")] + pub enum Event { + /// Created Proposal \[currency_id\] + CreatedProposal(CurrencyIdOf, CampaignInfoOf), + /// Contributed to a campaign \[contributor, currency_id, amount\] + Contributed(T::AccountId, CurrencyIdOf, BalanceOf), + /// Claim contribution allocation \[contributor, currency_id, amount\] + ClaimedContributionAlloc(T::AccountId, CurrencyIdOf, BalanceOf), + /// Claimed Funds Raised \[claimant_account_id, currency_id, amount_claimed\] + ClaimedFundraise(T::AccountId, CurrencyIdOf, BalanceOf), + /// Rejected Proposal \[currency_id\] + RejectedProposal(CurrencyIdOf), + /// Approved Proposal \[currency_id\] + ApprovedProposal(CurrencyIdOf), + /// Activated Campaign \[currency_id\] + ActivatedCampaign(CurrencyIdOf), + /// Campaign Started \[currency_id\] + StartedCampaign(CurrencyIdOf), + /// Ended Campaign Successfully \[currency_id, campaign_info\] + EndedCampaignSuccessful(CurrencyIdOf), + /// Ended Campaign Unsuccessfully \[currency_id, campaign_info\] + EndedCampaignUnsuccessful(CurrencyIdOf), + /// Contributed to Campaign \[currency_id, contribution_amount\] + ContributedToCampaign(CurrencyIdOf, BalanceOf), + /// Claimed Contribution Allocation \[claimant_account_id, currency_id, allocation_claimed\] + ClaimedAllocation(T::AccountId, CurrencyIdOf, BalanceOf), + /// Dissolved Unclaimed Funds \[amount, currency_id, now\] + DissolvedFunds(BalanceOf, CurrencyIdOf, ::BlockNumber), + /// Dispensed Commissions \[amount, currency_id, now\] + DispensedCommissions(BalanceOf, CurrencyIdOf, ::BlockNumber), + } + + /// Info on all of the proposed campaigns. + /// + /// map CurrencyId => CampaignInfo + #[pallet::storage] + #[pallet::getter(fn proposals)] + pub type Proposals = StorageMap<_, Blake2_128Concat, CurrencyIdOf, CampaignInfoOf, OptionQuery>; + + /// Info on all of the approved campaigns. + /// + /// map CurrencyId => CampaignInfo + #[pallet::storage] + #[pallet::getter(fn campaigns)] + pub type Campaigns = StorageMap<_, Blake2_128Concat, CurrencyIdOf, CampaignInfoOf, OptionQuery>; + + // Track the next campaign id to be used. + #[pallet::storage] + #[pallet::getter(fn campaign_index)] + pub type CampaignsIndex = StorageValue<_, CampaignId, ValueQuery>; + + // Track the number of simultaneous Active Campaigns - ActiveCampaignsCount + + #[pallet::storage] + #[pallet::getter(fn active_campaigns_count)] + pub type ActiveCampaignsCount = StorageValue<_, u32, ValueQuery>; + + // Track the number of successful campaigns the protocol has achieved. + #[pallet::storage] + #[pallet::getter(fn successful_campaign_index)] + pub type SuccessfulCampaignsCount = StorageValue<_, u32, ValueQuery>; + + + /// Record of the total amount of funds raised in the protocol + /// under a specific currency_id. currency_id => total_raised + /// + /// TotalAmountRaised: map CurrencyIdOf => BalanceOf + #[pallet::storage] + #[pallet::getter(fn total_amount_raised)] + pub type TotalAmountRaised = StorageMap<_, Twox64Concat, CurrencyIdOf, BalanceOf, ValueQuery>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks for Pallet { + // Call at the start of the block to eventuiate on_proposals and on_campaigns + // on_initialize is called at the start of the block. + fn on_initialize(now: T::BlockNumber) -> Weight { + + // Calls to eventuate proposals and campaigns. + + let mut count: Weight = 0; + count += 1; + + // Eventuate proposals and campaigns + + // If there are proposals, check if to remove rejected and retired proposals. + // Iterate over the proposals + for (id, campaign_info) in Proposals::::iter() { + // If the proposal is rejected, check if to remove it + if campaign_info.is_rejected && now >= campaign_info.proposal_retirement_period { + // Remove the proposal + Self::remove_proposal(id).unwrap(); + count += 1; + } + break; + } + // If there are campaigns, check if to start or end them + // Iterate over the campaigns + for (id, campaign_info) in Campaigns::::iter() { + // If the campaign is waiting, check if to start it + if campaign_info.is_waiting && campaign_info.campaign_start <= now { + // Activate Campaign + Self::activate_campaign(id).unwrap(); + count += 1; + } + // If the campaign is active, check if to end it + if campaign_info.is_active && !campaign_info.is_ended { + // If campaign is successfull, call on successful campaign + if campaign_info.raised >= campaign_info.goal { + Self::on_successful_campaign(now, id).unwrap(); + count += 1; + } else if campaign_info.campaign_end <= now && campaign_info.raised < campaign_info.goal { + // If campaign is failed, call on failed campaign + Self::on_failed_campaign(now, id).unwrap(); + count += 1; + } + } + // If the campaign reaches retirement period, call on retirement + if campaign_info.is_ended && &campaign_info.campaign_retirement_period <= &now { + Self::on_retire(id).unwrap(); + count += 1; + } + break; + } + T::WeightInfo::on_initialize(count as u32) + } + } + + #[pallet::call] + impl Pallet { + /// Make a new proposal + #[pallet::weight((T::WeightInfo::make_proposal(), DispatchClass::Operational))] + #[transactional] + pub fn make_proposal( + origin: OriginFor, + beneficiary: T::AccountId, + raise_currency: CurrencyIdOf, + sale_token: CurrencyIdOf, + token_price: BalanceOf, + crowd_allocation: BalanceOf, + goal: BalanceOf, + period: T::BlockNumber, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Ensure that the period is not zero + ensure!(period > T::BlockNumber::zero(), Error::::ZeroPeriod); + // Ensure that the period is not too long + ensure!(period <= T::MaxActivePeriod::get(), Error::::MaxActivePeriodExceeded); + // Ensure that the goal is not less than the Minimum Raise + ensure!(goal > T::MinRaise::get(&raise_currency), Error::::GoalBelowMinimumRaise); + + // Create proposal and add id. + Self::new_proposal( + who.clone(), + beneficiary, + raise_currency, + sale_token, + token_price, + crowd_allocation, + goal, + period, + )?; + Ok(()) + } + + // Make a contribution to an active campaign + #[pallet::weight((T::WeightInfo::contribute(), DispatchClass::Operational))] + #[transactional] + pub fn contribute( + origin: OriginFor, + id: CurrencyIdOf, + contribution_amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::on_contribution( + who.clone(), + id, + contribution_amount + )?; + Self::deposit_event(Event::Contributed(who, id, contribution_amount)); + Ok(()) + } + + // Claim a contribution allocation + #[pallet::weight((T::WeightInfo::claim_contribution_allocation(), DispatchClass::Operational))] + #[transactional] + pub fn claim_contribution_allocation( + origin: OriginFor, + id: CurrencyIdOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::on_claim_allocation( + who.clone(), + id, + )?; + Ok(()) + } + + // Claim a campaign's raised funds + #[pallet::weight((T::WeightInfo::claim_campaign_fundraise(), DispatchClass::Operational))] + #[transactional] + pub fn claim_campaign_fundraise( + origin: OriginFor, + id: CurrencyIdOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::on_claim_campaign( + who.clone(), + id, + )?; + + let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + Self::deposit_event(Event::ClaimedFundraise(who, id, campaign.raised)); + Ok(()) + } + + // Approve a proposal - origin must be `UpdateOrigin` + #[pallet::weight((T::WeightInfo::approve_proposal(), DispatchClass::Operational))] + #[transactional] + pub fn approve_proposal( + origin: OriginFor, + id: CurrencyIdOf, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + Self::on_approve_proposal( + id, + )?; + + Self::deposit_event(Event::ApprovedProposal(id)); + Ok(()) + } + + // Reject a proposal - origin must be `UpdateOrigin` + #[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))] + #[transactional] + pub fn reject_proposal( + origin: OriginFor, + id: CurrencyIdOf, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + Self::on_reject_proposal( + id, + )?; + + Self::deposit_event(Event::RejectedProposal(id)); + Ok(()) + } + + // Activate a Waiting Campaign - origin must be `UpdateOrigin` + #[pallet::weight((T::WeightInfo::activate_waiting_campaign(), DispatchClass::Operational))] + #[transactional] + pub fn activate_waiting_campaign( + origin: OriginFor, + id: CurrencyIdOf, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + Self::activate_campaign( + id, + )?; + + Self::deposit_event(Event::ActivatedCampaign(id)); + Ok(()) + } + } +} + + +impl Pallet { + /// Get the Launchpad's Treasury Account. + pub fn launchpad_treasury() -> T::AccountId { + T::PalletId::get().into_account() + } + + /// The account ID of the fund pot. + /// + pub fn campaign_pool(id: CampaignId) -> T::AccountId { + T::PalletId::get().into_sub_account(id) + } +} + +impl Proposal for Pallet { + type CurrencyId = CurrencyId; + + /// The Campaign Proposal info of `id` + fn proposal_info(id: Self::CurrencyId) -> Option> { + Self::proposals(id) + } + + /// Get all proposals + fn all_proposals() -> Vec> { + let proposals = Proposals::::iter().into_iter(); + let mut proposals_vec: Vec> = Vec::new(); + for (_, proposal) in proposals { + proposals_vec.push(proposal); + } + proposals_vec + } + + /// Create new Campaign Proposal with specific `CampaignInfo`, return the `id` of the Campaign + fn new_proposal( + origin: T::AccountId, + beneficiary: T::AccountId, + raise_currency: Self::CurrencyId, + sale_token: Self::CurrencyId, + token_price: BalanceOf, + crowd_allocation: BalanceOf, + goal: BalanceOf, + period: T::BlockNumber, + ) -> DispatchResult { + // Generate pool_id - overflow not managed + let pool_id = >::get() + 1; + >::put(pool_id); + + // Generate the CampaignInfo structure + let proposal = CampaignInfo { + id: sale_token, + origin: origin.clone(), + beneficiary: beneficiary, + pool: Self::campaign_pool(pool_id), + raise_currency: raise_currency, + sale_token: sale_token, + token_price: token_price, + crowd_allocation: crowd_allocation, + goal: goal, + raised: Zero::zero(), + contributors_count: Zero::zero(), + contributions: Vec::new(), + period: period, + campaign_start: Zero::zero(), + campaign_end: Zero::zero(), + campaign_retirement_period: Zero::zero(), + proposal_retirement_period: Zero::zero(), + is_approved: false, + is_rejected: false, + is_waiting: false, + is_active: false, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + + // try checks + let try_set_lock = T::MultiCurrency::set_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &origin, T::SubmissionDeposit::get()).is_ok(); + let try_make_transfer = T::MultiCurrency::transfer(sale_token, &origin, &Self::campaign_pool(pool_id), crowd_allocation).is_ok() ; + + if T::MultiCurrency::free_balance(T::GetNativeCurrencyId::get(), &origin) >= T::SubmissionDeposit::get() && + T::MultiCurrency::free_balance(sale_token, &origin) >= crowd_allocation { + if try_set_lock && try_make_transfer { + // set lock + T::MultiCurrency::set_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &origin, T::SubmissionDeposit::get()).unwrap(); + // make transfer + T::MultiCurrency::transfer(sale_token, &origin, &Self::campaign_pool(pool_id), crowd_allocation).unwrap(); + // insert proposal + >::insert(sale_token, proposal.clone()); + } + } else { + return Err(Error::::InsufficientBalance.into()); + } + + Self::deposit_event(Event::CreatedProposal(sale_token, proposal.clone())); + Ok(()) + } + + /// Approve Proposal by `id` at `now`. + fn on_approve_proposal(id: Self::CurrencyId)-> sp_std::result::Result<(), DispatchError> { + // Tag the proposal and ensure it is not already approved. + let mut proposal = Self::proposals(id).ok_or(Error::::ProposalNotFound)?; + ensure!(!proposal.is_approved, Error::::ProposalAlreadyApproved); + + // Approve the proposal in CampaignInfo and set it to waiting + proposal.is_approved = true; + proposal.is_waiting = true; + + // Set campaign start time + proposal.campaign_start = >::block_number() + T::CampaignStartDelay::get(); + + // Set campaign end time + proposal.campaign_end = >::block_number() + T::CampaignStartDelay::get() + proposal.period; + + // Remove from proposals and add to campaigns + >::remove(id); + >::insert(id, proposal); + // Active Campaigns count - overflow not managed + >::put(>::get() + 1); + Ok(()) + } + + /// Reject Proposal by `id` and remove from storage. + fn on_reject_proposal(id: Self::CurrencyId)-> sp_std::result::Result<(), DispatchError> { + // Check that the Proposal exists and tag it + let mut proposal = Self::proposals(id).ok_or(Error::::ProposalNotFound)?; + // Ensure that the proposal is not already approved + ensure!(!proposal.is_approved, Error::::ProposalAlreadyApproved); + + // Set the proposal to rejected + proposal.is_rejected = true; + proposal.proposal_retirement_period = >::block_number() + T::ProposalRetirementPeriod::get(); + // Update proposal storage + >::insert(id, proposal); + Ok(()) + } + + /// Remove proposal from storage by `id` + fn remove_proposal(id: Self::CurrencyId)-> sp_std::result::Result<(), DispatchError> { + // Check that the Proposal exists and tag it + let proposal = Self::proposals(id).ok_or(Error::::ProposalNotFound)?; + // Ensure that the proposal is not already approved + ensure!(!proposal.is_approved, Error::::ProposalAlreadyApproved); + ensure!(proposal.is_rejected, Error::::ProposalAlreadyApproved); + + let try_remove_lock = T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &proposal.origin).is_ok(); + let try_refund_transfer = T::MultiCurrency::transfer( proposal.sale_token, &proposal.pool, &proposal.origin, proposal.crowd_allocation).is_ok(); + // Unlock balances and remove the Proposal from the storage. + if try_remove_lock && try_refund_transfer { + // remove lock and refund proposal + T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &proposal.origin).unwrap(); + T::MultiCurrency::transfer( proposal.sale_token, &proposal.pool, &proposal.origin, proposal.crowd_allocation).unwrap(); + // Remove from proposals + >::remove(id); + }; + Ok(()) + } +} + +impl CampaignManager for Pallet { + type CurrencyId = CurrencyId; + + /// The Campaign info of `id` + fn campaign_info(id: Self::CurrencyId) -> Option> { + Self::campaigns(id) + } + + /// Get all campaigns + fn all_campaigns() -> Vec> { + let campaigns = Campaigns::::iter().into_iter(); + let mut campaigns_vec: Vec> = Vec::new(); + for (_, proposal) in campaigns { + campaigns_vec.push(proposal); + } + campaigns_vec + } + + /// Called when a contribution is received. + fn on_contribution( + who: T::AccountId, + id: Self::CurrencyId, + amount: BalanceOf, + ) -> DispatchResult { + let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + + // Ensure campaign is valid + ensure!(!campaign.is_failed, Error::::CampaignFailed); + ensure!(!campaign.is_ended, Error::::CampaignEnded); + ensure!(campaign.is_approved, Error::::CampaignNotApproved); + ensure!(campaign.is_active, Error::::CampaignNotActive); + + // Make assurances - minimum contribution & free balance + ensure!(amount >= T::MinContribution::get(&campaign.raise_currency), Error::::ContributionTooSmall); + ensure!(T::MultiCurrency::free_balance(campaign.raise_currency, &who) >= amount, Error::::ContributionCurrencyNotEnough); + + // Initiate the Contribution + let transfer_contribution = T::MultiCurrency::transfer(campaign.raise_currency, &who, &campaign.pool, amount).is_ok(); + if transfer_contribution { + // Transfer contribution and tag allocation + T::MultiCurrency::transfer(campaign.raise_currency, &who, &campaign.pool, amount).unwrap(); + let allocated = amount / campaign.token_price; + + // Check if contributor already exists in contributions list + let mut found = false; + // if campaign.contributions exists, check for who's contribution + + for (contributor, contribution, allocation, _) in campaign.contributions.iter_mut() { + if contributor == &who { + + found = true; + *contribution += amount; + *allocation += allocated; + campaign.raised += amount; + } + break; + } + if !found { + campaign.contributions.push((who, amount, allocated, false)); + campaign.raised += amount; + } + + // Tag contributors count + campaign.contributors_count = campaign.contributions.len() as u32; + + // Put campaign in campaigns storage + >::insert(id, campaign); + }; + Ok(()) + } + + /// Called when a contribution allocation is claimed + fn on_claim_allocation( + who: T::AccountId, + id: Self::CurrencyId, + ) -> DispatchResult { + let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + let campaign_p = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + + // Check if the contributor exists in the contributions of the campaign, if not return error + ensure!(campaign.contributions.iter().any(|(contributor, _, _, _)| *contributor == who), Error::::ContributionNotFound); + + // Ensure campaign is successfully ended + Self::ensure_successfully_ended_campaign(id)?; + + // Check if the contributor exists and transfer allocated from pool to contributor + for (contributor, _, allocation, claimed) in campaign.contributions.iter_mut() { + let transfer_allocation = T::MultiCurrency::transfer(campaign.sale_token, &campaign.pool, &who, *allocation).is_ok(); + + if contributor == &who && *claimed == false && transfer_allocation { + // set claimed to true - allocation claimed + *claimed = true; + // complete claim by adding campaign update to storage + >::insert(id, campaign); + + for (contributor_p, _, allocation_p, claimed_p) in campaign_p.contributions.iter() { + if contributor_p == &who && *claimed_p == false { + // transfer allocation + T::MultiCurrency::transfer(campaign_p.sale_token, &campaign_p.pool, &who, *allocation_p).unwrap(); + Self::deposit_event(Event::ClaimedContributionAlloc(who, id, *allocation_p)); + } + + break; + } + } + + break; + } + Ok(()) + } + + /// Called when a campaign's raised fund is claimed + fn on_claim_campaign( + who: T::AccountId, + id: Self::CurrencyId, + ) -> DispatchResult { + let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + + // Ensure origin is who created campaign proposal or beneficiary and its not claimed + ensure!(campaign.origin == who || campaign.beneficiary == who, Error::::WrongOrigin); + ensure!(!campaign.is_claimed, Error::::CampaignAlreadyClaimed); + + + if campaign.is_ended { + // Claim the campaign raised funds and transfer to the beneficiary + let transfer_claim = T::MultiCurrency::transfer( + campaign.raise_currency, + &campaign.pool, + &campaign.beneficiary, + campaign.raised + ) + .is_ok(); + + if campaign.is_successful && transfer_claim { + T::MultiCurrency::transfer( + campaign.raise_currency, + &campaign.pool, + &campaign.beneficiary, + campaign.raised + ).unwrap(); + // Campaign is claimed, update storage + campaign.is_claimed = true; + >::insert(id, campaign); + } + } + Ok(()) + } + + /// Called when a failed campaign is claimed by the proposer + fn on_claim_failed_campaign( + who: T::AccountId, + id: Self::CurrencyId, + ) -> DispatchResult { + let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + + // Ensure origin is who created campaign proposal + ensure!(campaign.origin == who || campaign.beneficiary == who, Error::::WrongOrigin); + + // Ensure campaign is valid and failed + ensure!(campaign.is_failed, Error::::CampaignFailed); + ensure!(campaign.is_ended, Error::::CampaignEnded); + + // Get the total amount of sale_token in the pool + let total_sale_token = T::MultiCurrency::total_balance(campaign.sale_token, &campaign.pool); + + let remove_lock = T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &campaign.origin).is_ok(); + let transfer_claim = T::MultiCurrency::transfer( campaign.sale_token, &campaign.pool, &who, total_sale_token).is_ok(); + // Unlock balances and remove the Proposal from the storage. + if remove_lock && transfer_claim { + T::MultiCurrency::remove_lock(LAUNCHPAD_LOCK_ID, T::GetNativeCurrencyId::get(), &campaign.origin).unwrap(); + T::MultiCurrency::transfer( campaign.sale_token, &campaign.pool, &who, total_sale_token).unwrap(); + // Update campaign in campaigns storage + >::insert(id, campaign); + }; + Ok(()) + } + + /// Activate a campaign by `id` + fn activate_campaign(id: Self::CurrencyId) -> DispatchResult { + // Ensure campaign exists + let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + + // Set campaign to active + campaign.is_waiting = false; + campaign.is_active = true; + // Update campaign storage + >::insert(id, campaign); + Ok(()) + } + + /// Ensure campaign is Valid and Successfully Ended + fn ensure_successfully_ended_campaign(id: Self::CurrencyId) -> DispatchResult { + let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + ensure!(!campaign.is_failed, Error::::CampaignFailed); + ensure!(campaign.is_successful, Error::::CampaignFailed); + ensure!(campaign.is_ended, Error::::CampaignStillActive); + ensure!(campaign.is_approved, Error::::CampaignNotApproved); + + // ensure!(campaign.campaign_start <= >::block_number(), Error::::CampaignNotStarted); + Ok(()) + } + + /// Record Successful Campaign by `id` + fn on_successful_campaign(now: T::BlockNumber, id: Self::CurrencyId) -> DispatchResult { + let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + + // Set to successful and ended + campaign.is_successful = true; + campaign.is_ended = true; + + // Set retirement period + campaign.campaign_retirement_period = now + T::CampaignRetirementPeriod::get(); + + // Tag contributors count + campaign.contributors_count = campaign.contributions.len() as u32; + + + // Success count - overflow not managed + // Add to total successful campaigns + let success_count = >::get() + 1; + >::put(success_count); + + // Add to `TotalAmountRaised` in protocol + >::mutate(campaign.raise_currency, |total| *total += campaign.raised); + + // Update campaign storage + >::insert(id, campaign); + Ok(()) + } + + /// Record Failed Campaign by `id` + fn on_failed_campaign(now: T::BlockNumber, id: Self::CurrencyId) -> DispatchResult { + let mut campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + + // Set to failed and ended + campaign.is_failed = true; + campaign.is_ended = true; + + // Tag contributors count + campaign.contributors_count = campaign.contributions.len() as u32; + + // Set retirement period + campaign.campaign_retirement_period = now + T::CampaignRetirementPeriod::get(); + + // Update campaign storage + >::insert(id, campaign); + Ok(()) + } + + /// Called when pool is retired + /// Only unsuccessful pools are retired + fn on_retire(id: Self::CurrencyId) -> DispatchResult { + // Get campaign in tag + let campaign = Self::campaigns(id).ok_or(Error::::CampaignNotFound)?; + // Get accounts in tag + let treasury = Self::launchpad_treasury(); + + // Get the total amount of raise_currency in the pool + let total_raise_currency = T::MultiCurrency::total_balance(campaign.raise_currency, &campaign.pool); + // Get the total amount of sale_token in the pool + let total_sale_token = T::MultiCurrency::total_balance(campaign.sale_token, &campaign.pool); + + let transfer_allocation = T::MultiCurrency::transfer(campaign.raise_currency, &campaign.pool, &treasury, total_raise_currency).is_ok(); + let transfer_raise = T::MultiCurrency::transfer(campaign.sale_token, &campaign.pool, &treasury, total_sale_token).is_ok(); + // Dissolve unclaimed Fundraise + if transfer_allocation && transfer_raise { + T::MultiCurrency::transfer(campaign.raise_currency, &campaign.pool, &treasury, total_raise_currency).unwrap(); + T::MultiCurrency::transfer(campaign.sale_token, &campaign.pool, &treasury, total_sale_token).unwrap(); + // Remove campaign from campaigns storage + >::remove(id); + } + Ok(()) + } + + /// Get amount of contributors/contributions in a campaign + fn get_contributors_count(id: Self::CurrencyId) -> u32 { + let campaign = Self::campaigns(id).unwrap(); + campaign.contributions.len() as u32 + } + + /// Get the total_amounts_raised for all currencies from `TotalAmountRaised` + fn get_total_amounts_raised() -> Vec<(Self::CurrencyId, BalanceOf)> { + let total_amounts_raised: Vec<(Self::CurrencyId, BalanceOf)> = >::iter() + .into_iter() + .collect::)>>(); + total_amounts_raised + } +} diff --git a/blockchain/modules/edfis-launchpad/src/mock.rs b/blockchain/modules/edfis-launchpad/src/mock.rs new file mode 100644 index 00000000..ddbe1879 --- /dev/null +++ b/blockchain/modules/edfis-launchpad/src/mock.rs @@ -0,0 +1,223 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the Edfis Launchpad module. + +#![cfg(test)] + +use super::*; +use frame_support::{construct_runtime, ord_parameter_types, parameter_types, PalletId}; +use frame_system::EnsureSignedBy; +use orml_traits::parameter_type_with_key; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::IdentityLookup, +}; +use primitives::{Amount, Balance, TokenSymbol}; + +pub type AccountId = u128; +pub type BlockNumber = u64; +// The network Treasury account. +pub const TREASURY: AccountId = 0; +// Mock accounts. +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; +pub const CHARLIE: AccountId = 3; + +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const TEST: CurrencyId = CurrencyId::Token(TokenSymbol::SETR); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); + +mod launchpad_crowdsales { + pub use super::super::*; +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type Origin = Origin; + type Index = u64; + type BlockNumber = BlockNumber; + type Call = Call; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + type MaxLocks = (); + type DustRemovalWhitelist = (); +} + +parameter_type_with_key! { + pub MinRaise: |currency_id: CurrencyId| -> Balance { + match currency_id { + &USSD => 100, + &SEE => 100, + &EDF => 100, + _ => 0, + } + }; +} + +parameter_type_with_key! { + pub MinContribution: |currency_id: CurrencyId| -> Balance { + match currency_id { + &USSD => 100, + &SEE => 100, + &EDF => 100, + _ => 0, + } + }; +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = SEE; // Setheum native currency ticker is SEE/ + pub const GetCommission: (u32, u32) = (10, 100); // 10% + pub const SubmissionDeposit: Balance = 101; + pub const MaxProposalsCount: u32 = 3; + pub const MaxCampaignsCount: u32 = 3; + pub const MaxActivePeriod: BlockNumber = 20; + pub const CampaignStartDelay: BlockNumber = 20; + pub const RetirementPeriod: BlockNumber = 20; + pub const CrowdsalesPalletId: PalletId = PalletId(*b"set/help"); +} + +ord_parameter_types! { + pub const TreasuryAccount: AccountId = TREASURY; + pub const Eleven: AccountId = 11; +} +impl Config for Runtime { + type Event = Event; + type MultiCurrency = Tokens; + type GetNativeCurrencyId = GetNativeCurrencyId; + type GetCommission = GetCommission; + type SubmissionDeposit = SubmissionDeposit; + type MinRaise = MinRaise; + type MinContribution = MinContribution; + type MaxProposalsCount = MaxProposalsCount; + type MaxCampaignsCount = MaxCampaignsCount; + type MaxActivePeriod = MaxActivePeriod; + type CampaignStartDelay = CampaignStartDelay; + type CampaignRetirementPeriod = RetirementPeriod; + type ProposalRetirementPeriod = RetirementPeriod; + type UpdateOrigin = EnsureSignedBy; + type PalletId = CrowdsalesPalletId; + type WeightInfo = (); +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + LaunchPad: launchpad_crowdsales::{Pallet, Storage, Call, Event}, + Tokens: orml_tokens::{Pallet, Storage, Call, Event}, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![] } + } +} + +impl ExtBuilder { + pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn one_hundred_thousand_for_all(self) -> Self { + self.balances(vec![ + (ALICE, SEE, 100_000), + (ALICE, USSD, 100_000), + (ALICE, EDF, 100_000), + (ALICE, TEST, 100_000), + (BOB, SEE, 100_000), + (BOB, USSD, 100_000), + (BOB, EDF, 100_000), + (BOB, TEST, 100_000), + (CHARLIE, SEE, 100_000), + (CHARLIE, USSD, 100_000), + (CHARLIE, EDF, 100_000), + (CHARLIE, TEST, 100_000), + ]) + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self + .balances + .into_iter() + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/blockchain/modules/edfis-launchpad/src/tests.rs b/blockchain/modules/edfis-launchpad/src/tests.rs new file mode 100644 index 00000000..881216bf --- /dev/null +++ b/blockchain/modules/edfis-launchpad/src/tests.rs @@ -0,0 +1,1144 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the Edfis Launchpad module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::*; + +#[test] +fn proposal_info_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 0, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: false, + is_active: false, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_eq!( + LaunchPad::proposal_info(TEST), + Some(CampaignInfo { + id:TEST, + origin: ALICE, + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: vec![], + period: 20, + campaign_start: 0, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: false, + is_active: false, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }) + ) + }); +} + +#[test] +fn campaign_info_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 20, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + LaunchPad::on_initialize(23); + assert_eq!( + LaunchPad::campaign_info(TEST), + Some(CampaignInfo { + id:TEST, + origin: ALICE, + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: vec![], + period: 20, + campaign_start: 20, + campaign_end: 40, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: true, + is_rejected: false, + is_waiting: false, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }) + ) + }); +} + +#[test] +fn make_proposal_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + assert_ok!(LaunchPad::make_proposal( + Origin::signed(ALICE), + BOB, + USSD, + TEST, + 10, + 10_000, + 100_000, + 20 + )); + }); +} + +#[test] +fn make_proposal_does_not_work() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + assert_noop!( + LaunchPad::make_proposal( + Origin::signed(ALICE), + BOB, + USSD, + TEST, + 10, + 10_000, + 100_000, + 0 + ), + Error::::ZeroPeriod + ); + assert_noop!( + LaunchPad::make_proposal( + Origin::signed(ALICE), + BOB, + USSD, + TEST, + 10, + 10_000, + 100_000, + 21 + ), + Error::::MaxActivePeriodExceeded + ); + assert_noop!( + LaunchPad::make_proposal( + Origin::signed(ALICE), + BOB, + USSD, + TEST, + 10, + 10_000, + 99, + 20 + ), + Error::::GoalBelowMinimumRaise + ); + }); +} + +#[test] +fn contribute_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10_000 + )); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 10_000 + )); + assert_noop!( + LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10 + ), + Error::::ContributionTooSmall + ); + assert_noop!( + LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 100_001 + ), + Error::::ContributionCurrencyNotEnough + ); + }); +} + +#[test] +fn contribute_does_not_work() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 0, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: false, + is_active: false, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_noop!( + LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10 + ), + Error::::CampaignNotFound + ); + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + assert_noop!( + LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10_000 + ), + Error::::CampaignNotActive + ); + }); +} + +#[test] +fn claim_contribution_allocation_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 50_000 + )); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 50_000 + )); + + LaunchPad::on_initialize(41); + System::set_block_number(41); + assert_ok!(LaunchPad::claim_contribution_allocation( + Origin::signed(BOB), + TEST, + )); + }); +} + +#[test] +fn claim_contribution_allocation_does_not_work() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 0, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: false, + is_active: false, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + LaunchPad::on_initialize(23); + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10_000 + )); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 10_000 + )); + + assert_noop!( + LaunchPad::claim_contribution_allocation( + Origin::signed(BOB), + TEST, + ), + Error::::CampaignFailed + ); + }); +} + +#[test] +fn claim_campaign_fundraise_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 50_000 + )); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 50_000 + )); + + LaunchPad::on_initialize(41); + System::set_block_number(41); + assert_ok!(LaunchPad::claim_campaign_fundraise( + Origin::signed(ALICE), + TEST, + )); + }); +} + +#[test] +fn claim_campaign_fundraise_does_not_work() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 0, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: false, + is_active: false, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + LaunchPad::on_initialize(23); + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10_000 + )); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 10_000 + )); + + LaunchPad::on_initialize(41); + System::set_block_number(41); + + assert_ok!(LaunchPad::claim_campaign_fundraise( + Origin::signed(ALICE), + TEST, + )); + + assert_noop!( + LaunchPad::claim_campaign_fundraise( + Origin::signed(CHARLIE), + TEST, + ), + Error::::WrongOrigin + ); + }); +} + +#[test] +fn claim_campaign_fundraise_does_not_work_already_claimed() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 0, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: false, + is_active: false, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: true, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + LaunchPad::on_initialize(23); + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10_000 + )); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 10_000 + )); + + LaunchPad::on_initialize(41); + System::set_block_number(41); + + assert_noop!( + LaunchPad::claim_campaign_fundraise( + Origin::signed(BOB), + TEST, + ), + Error::::CampaignAlreadyClaimed + ); + }); +} + +#[test] +fn approve_proposal_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10_000 + )); + }); +} + +#[test] +fn approve_proposal_does_not_work() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: true, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_noop!( + LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + ), + Error::::ProposalAlreadyApproved + ); + assert_noop!( + LaunchPad::approve_proposal( + Origin::signed(11), + USSD, + ), + Error::::ProposalNotFound + ); + }); +} + +#[test] +fn reject_proposal_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::reject_proposal( + Origin::signed(11), + TEST, + )); + }); +} + +#[test] +fn reject_proposal_does_not_work() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: true, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_noop!( + LaunchPad::reject_proposal( + Origin::signed(11), + TEST, + ), + Error::::ProposalAlreadyApproved + ); + assert_noop!( + LaunchPad::reject_proposal( + Origin::signed(11), + USSD, + ), + Error::::ProposalNotFound + ); + }); +} + +#[test] +fn get_contributors_count_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + let proposal = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, proposal.clone()); + assert!( + >::contains_key(TEST), + "Proposal should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 10_000 + )); + assert_ok!(LaunchPad::contribute( + Origin::signed(BOB), + TEST, + 10_000 + )); + assert_ok!(LaunchPad::contribute( + Origin::signed(CHARLIE), + TEST, + 10_000 + )); + + assert_eq!(LaunchPad::get_contributors_count(TEST), 3); + }); +} + +#[test] +fn get_total_amounts_raised_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + assert_eq!( + LaunchPad::get_total_amounts_raised(), + vec![] + ); + let campaign = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, campaign.clone()); + assert!( + >::contains_key(TEST), + "Campaign should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 50_000 + )); + + LaunchPad::on_initialize(40); + + assert_ok!(LaunchPad::on_successful_campaign(>::block_number(), TEST)); + assert_eq!( + LaunchPad::get_total_amounts_raised(), + vec![ + (USSD, 50000), + ] + ); + }); +} + +#[test] +fn on_retire_works() { + ExtBuilder::default() + .one_hundred_thousand_for_all() + .build() + .execute_with(|| { + assert_eq!( + LaunchPad::get_total_amounts_raised(), + vec![] + ); + let campaign = CampaignInfo { + id: TEST, + origin: ALICE.clone(), + beneficiary: BOB, + pool: LaunchPad::campaign_pool(0), + raise_currency: USSD, + sale_token: TEST, + token_price: 10, + crowd_allocation: 10_000, + goal: 100_000, + raised: 0, + contributors_count: 0, + contributions: Vec::new(), + period: 20, + campaign_start: 21, + campaign_end: 0, + campaign_retirement_period: 0, + proposal_retirement_period: 0, + is_approved: false, + is_rejected: false, + is_waiting: true, + is_active: true, + is_successful: false, + is_failed: false, + is_ended: false, + is_claimed: false, + }; + >::insert(TEST, campaign.clone()); + assert!( + >::contains_key(TEST), + "Campaign should be in storage" + ); + + assert_ok!(LaunchPad::approve_proposal( + Origin::signed(11), + TEST, + )); + + LaunchPad::on_initialize(21); + + assert_ok!(LaunchPad::contribute( + Origin::signed(ALICE), + TEST, + 50_000 + )); + + LaunchPad::on_initialize(60); + + assert_ok!(LaunchPad::on_retire(TEST)); + }); +} diff --git a/blockchain/modules/edfis-launchpad/src/weights.rs b/blockchain/modules/edfis-launchpad/src/weights.rs new file mode 100644 index 00000000..22646f55 --- /dev/null +++ b/blockchain/modules/edfis-launchpad/src/weights.rs @@ -0,0 +1,149 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for module_edfis_launchpad +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-04-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum-node +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_edfis_launchpad +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchain/modules/edfis-launchpad/src/weights.rs +// --template=.maintain/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_edfis_launchpad. +pub trait WeightInfo { + fn on_initialize(n: u32, ) -> Weight; + fn make_proposal() -> Weight; + fn contribute() -> Weight; + fn claim_contribution_allocation() -> Weight; + fn claim_campaign_fundraise() -> Weight; + fn approve_proposal() -> Weight; + fn reject_proposal() -> Weight; + fn activate_waiting_campaign() -> Weight; +} + +/// Weights for module_edfis_launchpad using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn on_initialize(n: u32, ) -> Weight { + Weight::from_parts(16_173_000, 0) + // Standard Error: 0 + .saturating_add((2_000 as u64).saturating_mul(n as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + } + fn make_proposal() -> Weight { + Weight::from_parts(180_406_000, 0) + .saturating_add(T::DbWeight::get().reads(6 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + fn contribute() -> Weight { + Weight::from_parts(127_732_000, 0) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn claim_contribution_allocation() -> Weight { + Weight::from_parts(121_893_000, 0) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + fn claim_campaign_fundraise() -> Weight { + Weight::from_parts(38_662_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + } + fn approve_proposal() -> Weight { + Weight::from_parts(47_547_000, 0) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + fn reject_proposal() -> Weight { + Weight::from_parts(38_272_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn activate_waiting_campaign() -> Weight { + Weight::from_parts(35_868_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn on_initialize(n: u32, ) -> Weight { + Weight::from_parts(16_173_000, 0) + // Standard Error: 0 + .saturating_add((2_000 as u64).saturating_mul(n as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + } + fn make_proposal() -> Weight { + Weight::from_parts(180_406_000, 0) + .saturating_add(RocksDbWeight::get().reads(6 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + fn contribute() -> Weight { + Weight::from_parts(127_732_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn claim_contribution_allocation() -> Weight { + Weight::from_parts(121_893_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn claim_campaign_fundraise() -> Weight { + Weight::from_parts(38_662_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + } + fn approve_proposal() -> Weight { + Weight::from_parts(47_547_000, 0) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn reject_proposal() -> Weight { + Weight::from_parts(38_272_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn activate_waiting_campaign() -> Weight { + Weight::from_parts(35_868_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/blockchain/modules/edfis-oracle/src/weights.rs b/blockchain/modules/edfis-oracle/src/weights.rs index e3d35e02..5651ebb6 100644 --- a/blockchain/modules/edfis-oracle/src/weights.rs +++ b/blockchain/modules/edfis-oracle/src/weights.rs @@ -26,7 +26,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/edfis-swap-legacy/src/weights.rs b/blockchain/modules/edfis-swap-legacy/src/weights.rs index 75b8bb18..9acf300a 100644 --- a/blockchain/modules/edfis-swap-legacy/src/weights.rs +++ b/blockchain/modules/edfis-swap-legacy/src/weights.rs @@ -25,7 +25,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/edfis-swap/src/weights.rs b/blockchain/modules/edfis-swap/src/weights.rs index efc167cd..82e2707d 100644 --- a/blockchain/modules/edfis-swap/src/weights.rs +++ b/blockchain/modules/edfis-swap/src/weights.rs @@ -25,7 +25,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/evm/src/weights.rs b/blockchain/modules/evm/src/weights.rs index fe0e560f..e6699e32 100644 --- a/blockchain/modules/evm/src/weights.rs +++ b/blockchain/modules/evm/src/weights.rs @@ -26,7 +26,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // pallet // --chain=dev diff --git a/blockchain/modules/nft/src/weights.rs b/blockchain/modules/nft/src/weights.rs index cb82347f..b04b7238 100644 --- a/blockchain/modules/nft/src/weights.rs +++ b/blockchain/modules/nft/src/weights.rs @@ -25,7 +25,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/prices/src/weights.rs b/blockchain/modules/prices/src/weights.rs index c02f9f62..a98bec52 100644 --- a/blockchain/modules/prices/src/weights.rs +++ b/blockchain/modules/prices/src/weights.rs @@ -26,7 +26,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/support/TODO.md b/blockchain/modules/support/TODO.md index 3e9cd565..9150f0e1 100644 --- a/blockchain/modules/support/TODO.md +++ b/blockchain/modules/support/TODO.md @@ -54,3 +54,5 @@ These tasks are just for this file specifically. - [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. - [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. +- [ ] [[src/lib.rs:0] - Use this as reference to upgrade the existing implementation of the Launchpad](src/lib.rs): Use this as reference to upgrade the existing implementation of the Launchpad. +- [ ] []() diff --git a/blockchain/modules/support/src/edfis_launchpad.rs b/blockchain/modules/support/src/edfis_launchpad.rs new file mode 100644 index 00000000..d5bd5a17 --- /dev/null +++ b/blockchain/modules/support/src/edfis_launchpad.rs @@ -0,0 +1,101 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Traits for the Launchpad Crowdsales Pallet. + +use codec::{Decode, Encode}; +use sp_runtime::{ + DispatchError, DispatchResult, +}; +use sp_std::{ + cmp::{Eq, PartialEq}, +}; + +/// Abstraction over th Launchpad Proposal system. +pub trait Proposal { + type CurrencyId; + + /// The Campaign Proposal info of `id` + fn proposal_info(id: Self::CurrencyId) -> Option>; + /// Get all proposals + fn all_proposals() -> Vec>; + /// Create new Campaign Proposal with specific `CampaignInfo`, return the `id` of the Campaign + fn new_proposal( + origin: AccountId, + beneficiary: AccountId, + raise_currency: CurrencyId, + sale_token: CurrencyId, + token_price: Balance, + crowd_allocation: Balance, + goal: Balance, + period: BlockNumber, + ) -> DispatchResult; + /// Approve Proposal by `id` at `now`. + fn on_approve_proposal(id: Self::CurrencyId) -> sp_std::result::Result<(), DispatchError>; + /// Reject Proposal by `id` and update storage + fn on_reject_proposal(id: Self::CurrencyId) -> sp_std::result::Result<(), DispatchError>; + /// Remove Proposal by `id` from storage + fn remove_proposal(id: Self::CurrencyId) -> sp_std::result::Result<(), DispatchError>; +} + +/// Abstraction over the Launchpad Campaign system. +pub trait CampaignManager { + type CurrencyId; + + /// The Campaign info of `id` + fn campaign_info(id: Self::CurrencyId) -> Option>; + /// Get all proposals + fn all_campaigns() -> Vec>; + /// Called when a contribution is received. + fn on_contribution( + who: AccountId, + id: Self::CurrencyId, + amount: Balance, + ) -> DispatchResult; + /// Called when a contribution allocation is claimed + fn on_claim_allocation( + who: AccountId, + id: Self::CurrencyId, + ) -> DispatchResult; + /// Called when a campaign's raised fund is claimed + fn on_claim_campaign( + who: AccountId, + id: Self::CurrencyId, + ) -> DispatchResult; + /// Called when a failed campaign is claimed by the proposer + fn on_claim_failed_campaign( + who: AccountId, + id: Self::CurrencyId, + ) -> DispatchResult; + /// Activate a campaign by `id` + fn activate_campaign(id: Self::CurrencyId) -> DispatchResult; + /// Ensure campaign is Valid and Successfully Ended + fn ensure_successfully_ended_campaign(id: Self::CurrencyId) -> DispatchResult; + /// Record Successful Campaign by `id` + fn on_successful_campaign(now: BlockNumber, id: Self::CurrencyId) -> DispatchResult ; + /// Record Failed Campaign by `id` + fn on_failed_campaign(now: BlockNumber, id: Self::CurrencyId) -> DispatchResult ; + /// Called when pool is retired + fn on_retire(id: Self::CurrencyId)-> DispatchResult; + /// Get amount of contributors in a campaign + fn get_contributors_count(id: Self::CurrencyId) -> u32; + /// Get the total amounts raised in protocol + fn get_total_amounts_raised() -> Vec<(CurrencyId, Balance)>; +} diff --git a/blockchain/modules/support/src/edfis.rs b/blockchain/modules/support/src/edfis_swap.rs similarity index 96% rename from blockchain/modules/support/src/edfis.rs rename to blockchain/modules/support/src/edfis_swap.rs index fc9206e8..525bbd26 100644 --- a/blockchain/modules/support/src/edfis.rs +++ b/blockchain/modules/support/src/edfis_swap.rs @@ -1,230 +1,230 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 frame_support::{ensure, traits::Get}; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_core::H160; -use sp_runtime::{DispatchError, DispatchResult, RuntimeDebug}; -use sp_std::{cmp::PartialEq, prelude::*, result::Result}; - -#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] -pub enum SwapLimit { - /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) - ExactSupply(Balance, Balance), - /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) - ExactTarget(Balance, Balance), -} - -pub trait SwapManager { - fn get_liquidity_pool( - currency_id_a: CurrencyId, - currency_id_b: CurrencyId - ) -> (Balance, Balance); - - fn get_liquidity_token_address( - currency_id_a: CurrencyId, - currency_id_b: CurrencyId - ) -> Option; - - fn get_swap_amount( - path: &[CurrencyId], - limit: SwapLimit - ) -> Option<(Balance, Balance)>; - - fn get_best_price_swap_path( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - alternative_path_joint_list: Vec>, - ) -> Option<(Vec, Balance, Balance)>; - - fn swap_with_specific_path( - who: &AccountId, - path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; - - fn add_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - max_amount_a: Balance, - max_amount_b: Balance, - min_share_increment: Balance, - ) -> Result<(Balance, Balance, Balance), DispatchError>; - - fn remove_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - remove_share: Balance, - min_withdrawn_a: Balance, - min_withdrawn_b: Balance, - by_unstake: bool, - ) -> Result<(Balance, Balance), DispatchError>; -} - -pub trait Swap -where - CurrencyId: Clone, -{ - fn get_swap_amount( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Option<(Balance, Balance)>; - - fn swap( - who: &AccountId, - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; - - fn swap_by_path( - who: &AccountId, - swap_path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; -} - -#[derive(Eq, PartialEq, RuntimeDebug)] -pub enum SwapError { - CannotSwap, -} - -impl Into for SwapError { - fn into(self) -> DispatchError { - DispatchError::Other("Cannot swap") - } -} - -// Dex wrapper of Swap implementation -pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); - -impl Swap - for SpecificJointsSwap -where - Dex: SwapManager, - Joints: Get>>, - Balance: Clone, - CurrencyId: Clone, -{ - fn get_swap_amount( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Option<(Balance, Balance)> { - >::get_best_price_swap_path( - supply_currency_id, - target_currency_id, - limit, - Joints::get(), - ) - .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) - } - - fn swap( - who: &AccountId, - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - let path = >::get_best_price_swap_path( - supply_currency_id, - target_currency_id, - limit.clone(), - Joints::get(), - ) - .ok_or_else(|| Into::::into(SwapError::CannotSwap))? - .0; - - >::swap_with_specific_path(who, &path, limit) - } - - fn swap_by_path( - who: &AccountId, - swap_path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError> { - >::swap_with_specific_path(who, swap_path, limit) - } -} - -#[cfg(feature = "std")] -impl SwapManager for () -where - Balance: Default, -{ - fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { - Default::default() - } - - fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { - Some(Default::default()) - } - - fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { - Some(Default::default()) - } - - fn get_best_price_swap_path( - _supply_currency_id: CurrencyId, - _target_currency_id: CurrencyId, - _limit: SwapLimit, - _alternative_path_joint_list: Vec>, - ) -> Option<(Vec, Balance, Balance)> { - Some(Default::default()) - } - - fn swap_with_specific_path( - _who: &AccountId, - _path: &[CurrencyId], - _limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn add_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _max_amount_a: Balance, - _max_amount_b: Balance, - _min_share_increment: Balance, - ) -> Result<(Balance, Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn remove_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _remove_share: Balance, - _min_withdrawn_a: Balance, - _min_withdrawn_b: Balance, - _by_unstake: bool, - ) -> Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 frame_support::{ensure, traits::Get}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_core::H160; +use sp_runtime::{DispatchError, DispatchResult, RuntimeDebug}; +use sp_std::{cmp::PartialEq, prelude::*, result::Result}; + +#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] +pub enum SwapLimit { + /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) + ExactSupply(Balance, Balance), + /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) + ExactTarget(Balance, Balance), +} + +pub trait SwapManager { + fn get_liquidity_pool( + currency_id_a: CurrencyId, + currency_id_b: CurrencyId + ) -> (Balance, Balance); + + fn get_liquidity_token_address( + currency_id_a: CurrencyId, + currency_id_b: CurrencyId + ) -> Option; + + fn get_swap_amount( + path: &[CurrencyId], + limit: SwapLimit + ) -> Option<(Balance, Balance)>; + + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)>; + + fn swap_with_specific_path( + who: &AccountId, + path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn add_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + ) -> Result<(Balance, Balance, Balance), DispatchError>; + + fn remove_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError>; +} + +pub trait Swap +where + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)>; + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; +} + +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum SwapError { + CannotSwap, +} + +impl Into for SwapError { + fn into(self) -> DispatchError { + DispatchError::Other("Cannot swap") + } +} + +// Dex wrapper of Swap implementation +pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); + +impl Swap + for SpecificJointsSwap +where + Dex: SwapManager, + Joints: Get>>, + Balance: Clone, + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)> { + >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit, + Joints::get(), + ) + .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) + } + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let path = >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit.clone(), + Joints::get(), + ) + .ok_or_else(|| Into::::into(SwapError::CannotSwap))? + .0; + + >::swap_with_specific_path(who, &path, limit) + } + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + >::swap_with_specific_path(who, swap_path, limit) + } +} + +#[cfg(feature = "std")] +impl SwapManager for () +where + Balance: Default, +{ + fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { + Default::default() + } + + fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { + Some(Default::default()) + } + + fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { + Some(Default::default()) + } + + fn get_best_price_swap_path( + _supply_currency_id: CurrencyId, + _target_currency_id: CurrencyId, + _limit: SwapLimit, + _alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + Some(Default::default()) + } + + fn swap_with_specific_path( + _who: &AccountId, + _path: &[CurrencyId], + _limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn add_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _max_amount_a: Balance, + _max_amount_b: Balance, + _min_share_increment: Balance, + ) -> Result<(Balance, Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn remove_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _remove_share: Balance, + _min_withdrawn_a: Balance, + _min_withdrawn_b: Balance, + _by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } +} diff --git a/blockchain/modules/support/src/edfis_legacy.rs b/blockchain/modules/support/src/edfis_swap_legacy.rs similarity index 96% rename from blockchain/modules/support/src/edfis_legacy.rs rename to blockchain/modules/support/src/edfis_swap_legacy.rs index e9848cdc..aea21420 100644 --- a/blockchain/modules/support/src/edfis_legacy.rs +++ b/blockchain/modules/support/src/edfis_swap_legacy.rs @@ -1,223 +1,223 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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 frame_support::{ensure, traits::Get}; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_core::H160; -use sp_runtime::{DispatchError, RuntimeDebug}; -use sp_std::{cmp::PartialEq, prelude::*, result::Result}; - -#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] -pub enum SwapLimit { - /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) - ExactSupply(Balance, Balance), - /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) - ExactTarget(Balance, Balance), -} - -pub trait SwapManager { - fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance); - - fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option; - - fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)>; - - fn get_best_price_swap_path( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - alternative_path_joint_list: Vec>, - ) -> Option<(Vec, Balance, Balance)>; - - fn swap_with_specific_path( - who: &AccountId, - path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; - - fn add_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - max_amount_a: Balance, - max_amount_b: Balance, - min_share_increment: Balance, - stake_increment_share: bool, - ) -> Result<(Balance, Balance, Balance), DispatchError>; - - fn remove_liquidity( - who: &AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - remove_share: Balance, - min_withdrawn_a: Balance, - min_withdrawn_b: Balance, - by_unstake: bool, - ) -> Result<(Balance, Balance), DispatchError>; -} - -pub trait Swap -where - CurrencyId: Clone, -{ - fn get_swap_amount( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Option<(Balance, Balance)>; - - fn swap( - who: &AccountId, - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; - - fn swap_by_path( - who: &AccountId, - swap_path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError>; -} - -#[derive(Eq, PartialEq, RuntimeDebug)] -pub enum SwapError { - CannotSwap, -} - -impl Into for SwapError { - fn into(self) -> DispatchError { - DispatchError::Other("Cannot swap") - } -} - -// Dex wrapper of Swap implementation -pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); - -impl Swap - for SpecificJointsSwap -where - Dex: SwapManager, - Joints: Get>>, - Balance: Clone, - CurrencyId: Clone, -{ - fn get_swap_amount( - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> Option<(Balance, Balance)> { - >::get_best_price_swap_path( - supply_currency_id, - target_currency_id, - limit, - Joints::get(), - ) - .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) - } - - fn swap( - who: &AccountId, - supply_currency_id: CurrencyId, - target_currency_id: CurrencyId, - limit: SwapLimit, - ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { - let path = >::get_best_price_swap_path( - supply_currency_id, - target_currency_id, - limit.clone(), - Joints::get(), - ) - .ok_or_else(|| Into::::into(SwapError::CannotSwap))? - .0; - - >::swap_with_specific_path(who, &path, limit) - } - - fn swap_by_path( - who: &AccountId, - swap_path: &[CurrencyId], - limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError> { - >::swap_with_specific_path(who, swap_path, limit) - } -} - -#[cfg(feature = "std")] -impl SwapManager for () -where - Balance: Default, -{ - fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { - Default::default() - } - - fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { - Some(Default::default()) - } - - fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { - Some(Default::default()) - } - - fn get_best_price_swap_path( - _supply_currency_id: CurrencyId, - _target_currency_id: CurrencyId, - _limit: SwapLimit, - _alternative_path_joint_list: Vec>, - ) -> Option<(Vec, Balance, Balance)> { - Some(Default::default()) - } - - fn swap_with_specific_path( - _who: &AccountId, - _path: &[CurrencyId], - _limit: SwapLimit, - ) -> Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn add_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _max_amount_a: Balance, - _max_amount_b: Balance, - _min_share_increment: Balance, - _stake_increment_share: bool, - ) -> Result<(Balance, Balance, Balance), DispatchError> { - Ok(Default::default()) - } - - fn remove_liquidity( - _who: &AccountId, - _currency_id_a: CurrencyId, - _currency_id_b: CurrencyId, - _remove_share: Balance, - _min_withdrawn_a: Balance, - _min_withdrawn_b: Balance, - _by_unstake: bool, - ) -> Result<(Balance, Balance), DispatchError> { - Ok(Default::default()) - } -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 frame_support::{ensure, traits::Get}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_core::H160; +use sp_runtime::{DispatchError, RuntimeDebug}; +use sp_std::{cmp::PartialEq, prelude::*, result::Result}; + +#[derive(RuntimeDebug, Encode, Decode, Clone, Copy, PartialEq, Eq, TypeInfo)] +pub enum SwapLimit { + /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) + ExactSupply(Balance, Balance), + /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) + ExactTarget(Balance, Balance), +} + +pub trait SwapManager { + fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance); + + fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option; + + fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)>; + + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)>; + + fn swap_with_specific_path( + who: &AccountId, + path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn add_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, + min_share_increment: Balance, + stake_increment_share: bool, + ) -> Result<(Balance, Balance, Balance), DispatchError>; + + fn remove_liquidity( + who: &AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + remove_share: Balance, + min_withdrawn_a: Balance, + min_withdrawn_b: Balance, + by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError>; +} + +pub trait Swap +where + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)>; + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError>; +} + +#[derive(Eq, PartialEq, RuntimeDebug)] +pub enum SwapError { + CannotSwap, +} + +impl Into for SwapError { + fn into(self) -> DispatchError { + DispatchError::Other("Cannot swap") + } +} + +// Dex wrapper of Swap implementation +pub struct SpecificJointsSwap(sp_std::marker::PhantomData<(Dex, Joints)>); + +impl Swap + for SpecificJointsSwap +where + Dex: SwapManager, + Joints: Get>>, + Balance: Clone, + CurrencyId: Clone, +{ + fn get_swap_amount( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> Option<(Balance, Balance)> { + >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit, + Joints::get(), + ) + .map(|(_, supply_amount, target_amount)| (supply_amount, target_amount)) + } + + fn swap( + who: &AccountId, + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let path = >::get_best_price_swap_path( + supply_currency_id, + target_currency_id, + limit.clone(), + Joints::get(), + ) + .ok_or_else(|| Into::::into(SwapError::CannotSwap))? + .0; + + >::swap_with_specific_path(who, &path, limit) + } + + fn swap_by_path( + who: &AccountId, + swap_path: &[CurrencyId], + limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + >::swap_with_specific_path(who, swap_path, limit) + } +} + +#[cfg(feature = "std")] +impl SwapManager for () +where + Balance: Default, +{ + fn get_liquidity_pool(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> (Balance, Balance) { + Default::default() + } + + fn get_liquidity_token_address(_currency_id_a: CurrencyId, _currency_id_b: CurrencyId) -> Option { + Some(Default::default()) + } + + fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { + Some(Default::default()) + } + + fn get_best_price_swap_path( + _supply_currency_id: CurrencyId, + _target_currency_id: CurrencyId, + _limit: SwapLimit, + _alternative_path_joint_list: Vec>, + ) -> Option<(Vec, Balance, Balance)> { + Some(Default::default()) + } + + fn swap_with_specific_path( + _who: &AccountId, + _path: &[CurrencyId], + _limit: SwapLimit, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn add_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _max_amount_a: Balance, + _max_amount_b: Balance, + _min_share_increment: Balance, + _stake_increment_share: bool, + ) -> Result<(Balance, Balance, Balance), DispatchError> { + Ok(Default::default()) + } + + fn remove_liquidity( + _who: &AccountId, + _currency_id_a: CurrencyId, + _currency_id_b: CurrencyId, + _remove_share: Balance, + _min_withdrawn_a: Balance, + _min_withdrawn_b: Balance, + _by_unstake: bool, + ) -> Result<(Balance, Balance), DispatchError> { + Ok(Default::default()) + } +} diff --git a/blockchain/modules/support/src/lib.rs b/blockchain/modules/support/src/lib.rs index ba2871a9..6b4ef3eb 100644 --- a/blockchain/modules/support/src/lib.rs +++ b/blockchain/modules/support/src/lib.rs @@ -32,8 +32,9 @@ use sp_std::{prelude::*, result::Result}; use xcm::prelude::*; pub mod bounded; -pub mod edfis; -pub mod edfis_legacy; +pub mod edfis_launchpad; +pub mod edfis_swap; +pub mod edfis_swap_legacy; pub mod evm; pub mod liquid_staking; pub mod ecdp; @@ -41,8 +42,9 @@ pub mod incentives; pub mod mocks; pub use crate::bounded::*; -pub use crate::edfis::*; -pub use crate::edfis_legacy::*; +pub use crate::edfis_launchpad::*; +pub use crate::edfis_swap::*; +pub use crate::edfis_swap_legacy::*; pub use crate::evm::*; pub use crate::liquid_staking::*; pub use crate::ecdp::*; @@ -186,138 +188,139 @@ pub trait BuyWeightRate { fn calculate_rate(location: MultiLocation) -> Option; } -/// The Structure of a Campaign info. -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode, Debug, Clone))] -pub struct CampaignInfo { - /// The Campaign Id - pub id: CampaignId, - /// Campaign Creator - pub origin: AccountId, - /// Project Name - pub project_name: Vec, - /// Project Logo - pub project_logo: Vec, - /// Project Description - pub project_description: Vec, - /// Project Website - pub project_website: Vec, - /// Campaign Beneficiary - pub beneficiary: AccountId, - /// Campaign Pool AccountId - pub pool: AccountId, - /// Currency type for the fundraise - pub raise_currency: CurrencyId, - /// Currency type (Token) for crowdsale - pub sale_token: CurrencyId, - /// Crowdsale Token Price - Amount of raise_currency per sale_token - pub token_price: Balance, - /// Crowdsale Token amount for sale - pub crowd_allocation: Balance, - /// The Fundraise Goal - HardCap - pub goal: Balance, - /// The Fundraise Amount raised - HardCap - pub raised: Balance, - /// The number of contributors to the campaign - pub contributors_count: u32, - /// The Campaign contributions - /// account_id, contribution, allocation, bool:claimed_allocation - pub contributions: Vec<(AccountId, Balance, Balance, bool)>, - /// The period that the campaign runs for. - pub period: BlockNumber, - /// The time when the campaign starts. - pub campaign_start: BlockNumber, - /// The time when the campaign ends. - pub campaign_end: BlockNumber, - /// The time when the campaign fund retires. - pub campaign_retirement_period: BlockNumber, - /// The time when a rejected proposal is removed from storage. - pub proposal_retirement_period: BlockNumber, - /// Is the campaign approved? - pub is_approved: bool, - /// Is the proposal rejected? - pub is_rejected: bool, - /// Is the campaign in waiting period? - pub is_waiting: bool, - /// Is the campaign active? - pub is_active: bool, - /// Is the campaign Successful? - pub is_successful: bool, - /// Is the campaign Failed? - pub is_failed: bool, - /// Is the campaign Ended? - pub is_ended: bool, - /// Is the campaign funds raised claimed - pub is_claimed: bool, -} +// TODO:[src/lib.rs:0] - Use this as reference to upgrade the existing implementation of the Launchpad +// /// The Structure of a Campaign info. +// #[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode, Debug, Clone))] +// pub struct CampaignInfo { +// /// The Campaign Id +// pub id: CampaignId, +// /// Campaign Creator +// pub origin: AccountId, +// /// Project Name +// pub project_name: Vec, +// /// Project Logo +// pub project_logo: Vec, +// /// Project Description +// pub project_description: Vec, +// /// Project Website +// pub project_website: Vec, +// /// Campaign Beneficiary +// pub beneficiary: AccountId, +// /// Campaign Pool AccountId +// pub pool: AccountId, +// /// Currency type for the fundraise +// pub raise_currency: CurrencyId, +// /// Currency type (Token) for crowdsale +// pub sale_token: CurrencyId, +// /// Crowdsale Token Price - Amount of raise_currency per sale_token +// pub token_price: Balance, +// /// Crowdsale Token amount for sale +// pub crowd_allocation: Balance, +// /// The Fundraise Goal - HardCap +// pub goal: Balance, +// /// The Fundraise Amount raised - HardCap +// pub raised: Balance, +// /// The number of contributors to the campaign +// pub contributors_count: u32, +// /// The Campaign contributions +// /// account_id, contribution, allocation, bool:claimed_allocation +// pub contributions: Vec<(AccountId, Balance, Balance, bool)>, +// /// The period that the campaign runs for. +// pub period: BlockNumber, +// /// The time when the campaign starts. +// pub campaign_start: BlockNumber, +// /// The time when the campaign ends. +// pub campaign_end: BlockNumber, +// /// The time when the campaign fund retires. +// pub campaign_retirement_period: BlockNumber, +// /// The time when a rejected proposal is removed from storage. +// pub proposal_retirement_period: BlockNumber, +// /// Is the campaign approved? +// pub is_approved: bool, +// /// Is the proposal rejected? +// pub is_rejected: bool, +// /// Is the campaign in waiting period? +// pub is_waiting: bool, +// /// Is the campaign active? +// pub is_active: bool, +// /// Is the campaign Successful? +// pub is_successful: bool, +// /// Is the campaign Failed? +// pub is_failed: bool, +// /// Is the campaign Ended? +// pub is_ended: bool, +// /// Is the campaign funds raised claimed +// pub is_claimed: bool, +// } -/// Abstraction over th Launchpad Proposal system. -pub trait Proposal { - /// Get all proposals - fn all_proposals() -> Vec>; - /// The Campaign Proposal info of `id` - fn proposal_info(id: CampaignId) -> Option>; - /// Create new Campaign Proposal with specific `CampaignInfo`, return the `id` of the Campaign - fn new_proposal( - origin: AccountId, - project_name: Vec, - project_logo: Vec, - project_description: Vec, - project_website: Vec, - beneficiary: AccountId, - raise_currency: CurrencyId, - sale_token: CurrencyId, - token_price: AsBalance, - crowd_allocation: AsBalance, - goal: AsBalance, - period: BlockNumber, - ) -> DispatchResult; - /// Approve Proposal by `id` at `now`. - fn on_approve_proposal(id: CampaignId) -> sp_std::result::Result<(), DispatchError>; - /// Reject Proposal by `id` and update storage - fn on_reject_proposal(id: CampaignId) -> sp_std::result::Result<(), DispatchError>; - /// Remove Proposal by `id` from storage - fn remove_proposal(id: CampaignId) -> sp_std::result::Result<(), DispatchError>; -} +// /// Abstraction over th Launchpad Proposal system. +// pub trait Proposal { +// /// Get all proposals +// fn all_proposals() -> Vec>; +// /// The Campaign Proposal info of `id` +// fn proposal_info(id: CampaignId) -> Option>; +// /// Create new Campaign Proposal with specific `CampaignInfo`, return the `id` of the Campaign +// fn new_proposal( +// origin: AccountId, +// project_name: Vec, +// project_logo: Vec, +// project_description: Vec, +// project_website: Vec, +// beneficiary: AccountId, +// raise_currency: CurrencyId, +// sale_token: CurrencyId, +// token_price: AsBalance, +// crowd_allocation: AsBalance, +// goal: AsBalance, +// period: BlockNumber, +// ) -> DispatchResult; +// /// Approve Proposal by `id` at `now`. +// fn on_approve_proposal(id: CampaignId) -> sp_std::result::Result<(), DispatchError>; +// /// Reject Proposal by `id` and update storage +// fn on_reject_proposal(id: CampaignId) -> sp_std::result::Result<(), DispatchError>; +// /// Remove Proposal by `id` from storage +// fn remove_proposal(id: CampaignId) -> sp_std::result::Result<(), DispatchError>; +// } -/// Abstraction over the Launchpad Campaign system. -pub trait CampaignManager { - /// The Campaign info of `id` - fn campaign_info(id: CampaignId) -> Option>; - /// Get all proposals - fn all_campaigns() -> Vec>; - /// Called when a contribution is received. - fn on_contribution( - who: AccountId, - id: CampaignId, - amount: AsBalance, - ) -> DispatchResult; - /// Called when a contribution allocation is claimed - fn on_claim_allocation( - who: AccountId, - id: CampaignId, - ) -> DispatchResult; - /// Called when a campaign's raised fund is claimed - fn on_claim_campaign( - who: AccountId, - id: CampaignId, - ) -> DispatchResult; - /// Called when a failed campaign is claimed by the proposer - fn on_claim_failed_campaign( - who: AccountId, - id: CampaignId, - ) -> DispatchResult; - /// Activate a campaign by `id` - fn activate_campaign(id: CampaignId) -> DispatchResult; - /// Ensure campaign is Valid and Successfully Ended - fn ensure_successfully_ended_campaign(id: CampaignId) -> DispatchResult; - /// Record Successful Campaign by `id` - fn on_successful_campaign(now: BlockNumber, id: CampaignId) -> DispatchResult ; - /// Record Failed Campaign by `id` - fn on_failed_campaign(now: BlockNumber, id: CampaignId) -> DispatchResult ; - /// Called when pool is retired - fn on_retire(id: CampaignId)-> DispatchResult; - /// Get amount of contributors in a campaign - fn get_contributors_count(id: CampaignId) -> u32; - /// Get the total amounts raised in protocol - fn get_total_amounts_raised() -> Vec<(CurrencyId, AsBalance)>; -} +// /// Abstraction over the Launchpad Campaign system. +// pub trait CampaignManager { +// /// The Campaign info of `id` +// fn campaign_info(id: CampaignId) -> Option>; +// /// Get all proposals +// fn all_campaigns() -> Vec>; +// /// Called when a contribution is received. +// fn on_contribution( +// who: AccountId, +// id: CampaignId, +// amount: AsBalance, +// ) -> DispatchResult; +// /// Called when a contribution allocation is claimed +// fn on_claim_allocation( +// who: AccountId, +// id: CampaignId, +// ) -> DispatchResult; +// /// Called when a campaign's raised fund is claimed +// fn on_claim_campaign( +// who: AccountId, +// id: CampaignId, +// ) -> DispatchResult; +// /// Called when a failed campaign is claimed by the proposer +// fn on_claim_failed_campaign( +// who: AccountId, +// id: CampaignId, +// ) -> DispatchResult; +// /// Activate a campaign by `id` +// fn activate_campaign(id: CampaignId) -> DispatchResult; +// /// Ensure campaign is Valid and Successfully Ended +// fn ensure_successfully_ended_campaign(id: CampaignId) -> DispatchResult; +// /// Record Successful Campaign by `id` +// fn on_successful_campaign(now: BlockNumber, id: CampaignId) -> DispatchResult ; +// /// Record Failed Campaign by `id` +// fn on_failed_campaign(now: BlockNumber, id: CampaignId) -> DispatchResult ; +// /// Called when pool is retired +// fn on_retire(id: CampaignId)-> DispatchResult; +// /// Get amount of contributors in a campaign +// fn get_contributors_count(id: CampaignId) -> u32; +// /// Get the total amounts raised in protocol +// fn get_total_amounts_raised() -> Vec<(CurrencyId, AsBalance)>; +// } diff --git a/blockchain/modules/transaction-pause/src/weights.rs b/blockchain/modules/transaction-pause/src/weights.rs index fd3262f1..a85c7f3b 100644 --- a/blockchain/modules/transaction-pause/src/weights.rs +++ b/blockchain/modules/transaction-pause/src/weights.rs @@ -25,7 +25,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/transaction-payment/src/weights.rs b/blockchain/modules/transaction-payment/src/weights.rs index b9540f6d..95fa0154 100644 --- a/blockchain/modules/transaction-payment/src/weights.rs +++ b/blockchain/modules/transaction-payment/src/weights.rs @@ -25,7 +25,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/unified-accounts/src/weights.rs b/blockchain/modules/unified-accounts/src/weights.rs index 85bc3f9e..6d3a9d3e 100644 --- a/blockchain/modules/unified-accounts/src/weights.rs +++ b/blockchain/modules/unified-accounts/src/weights.rs @@ -26,7 +26,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/setheum +// target/release/setheum-node // benchmark // --chain=dev // --steps=50 diff --git a/blockchain/modules/vesting/src/weights.rs b/blockchain/modules/vesting/src/weights.rs index 251132c6..616257c7 100644 --- a/blockchain/modules/vesting/src/weights.rs +++ b/blockchain/modules/vesting/src/weights.rs @@ -1,60 +1,60 @@ -//! Autogenerated weights for orlm_vesting -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-05-04, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// /target/release/setheum -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module_vesting -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./vesting/src/weights.rs -// --template -// ..maintain/orml-weight-template.hbs - - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for orml_vesting. -pub trait WeightInfo { - fn vested_transfer() -> Weight; - fn claim(i: u32, ) -> Weight; - fn update_vesting_schedules(i: u32, ) -> Weight; -} - -/// Default weights. -impl WeightInfo for () { - fn vested_transfer() -> Weight { - Weight::from_parts(69_000_000, 0) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - fn claim(i: u32, ) -> Weight { - Weight::from_parts(31_747_000, 0) - // Standard Error: 4_000 - .saturating_add(Weight::from_parts(63_000, 0).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - fn update_vesting_schedules(i: u32, ) -> Weight { - Weight::from_parts(29_457_000, 0) - // Standard Error: 4_000 - .saturating_add(Weight::from_parts(117_000, 0).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } -} +//! Autogenerated weights for orlm_vesting +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-05-04, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// /target/release/setheum-node +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_vesting +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./vesting/src/weights.rs +// --template +// ..maintain/orml-weight-template.hbs + + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for orml_vesting. +pub trait WeightInfo { + fn vested_transfer() -> Weight; + fn claim(i: u32, ) -> Weight; + fn update_vesting_schedules(i: u32, ) -> Weight; +} + +/// Default weights. +impl WeightInfo for () { + fn vested_transfer() -> Weight { + Weight::from_parts(69_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn claim(i: u32, ) -> Weight { + Weight::from_parts(31_747_000, 0) + // Standard Error: 4_000 + .saturating_add(Weight::from_parts(63_000, 0).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn update_vesting_schedules(i: u32, ) -> Weight { + Weight::from_parts(29_457_000, 0) + // Standard Error: 4_000 + .saturating_add(Weight::from_parts(117_000, 0).saturating_mul(i as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +} diff --git a/blockchain/primitives/src/edfis_launchpad.rs b/blockchain/primitives/src/edfis_launchpad.rs new file mode 100644 index 00000000..87c15428 --- /dev/null +++ b/blockchain/primitives/src/edfis_launchpad.rs @@ -0,0 +1,90 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Primitives for the Edfis Launchpad module. + +use codec::{Decode, Encode}; +use sp_runtime::{ + DispatchError, DispatchResult, RuntimeDebug, +}; +use sp_std::{ + cmp::{Eq, PartialEq}, +}; + +/// Launchpad Campaign ID +pub type CampaignId = u32; + +/// The Structure of a Campaign info. +// #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct CampaignInfo { + /// The Campaign Id + pub id: CurrencyId, + /// Campaign Creator + pub origin: AccountId, + /// Campaign Beneficiary + pub beneficiary: AccountId, + /// Campaign Pool AccountId + pub pool: AccountId, + /// Currency type for the fundraise + pub raise_currency: CurrencyId, + /// Currency type (Token) for crowdsale + pub sale_token: CurrencyId, + /// Crowdsale Token Price - Amount of raise_currency per sale_token + pub token_price: Balance, + /// Crowdsale Token amount for sale + pub crowd_allocation: Balance, + /// The Fundraise Goal - HardCap + pub goal: Balance, + /// The Fundraise Amount raised - HardCap + pub raised: Balance, + /// The number of contributors to the campaign + pub contributors_count: u32, + /// The Campaign contributions + /// account_id, contribution, allocation, bool:claimed_allocation + pub contributions: Vec<(AccountId, Balance, Balance, bool)>, + /// The period that the campaign runs for. + pub period: BlockNumber, + /// The time when the campaign starts. + pub campaign_start: BlockNumber, + /// The time when the campaign ends. + pub campaign_end: BlockNumber, + /// The time when the campaign fund retires. + pub campaign_retirement_period: BlockNumber, + /// The time when a rejected proposal is removed from storage. + pub proposal_retirement_period: BlockNumber, + /// Is the campaign approved? + pub is_approved: bool, + /// Is the proposal rejected? + pub is_rejected: bool, + /// Is the campaign in waiting period? + pub is_waiting: bool, + /// Is the campaign active? + pub is_active: bool, + /// Is the campaign Successful? + pub is_successful: bool, + /// Is the campaign Failed? + pub is_failed: bool, + /// Is the campaign Ended? + pub is_ended: bool, + /// Is the campaign funds raised claimed + pub is_claimed: bool, +} diff --git a/blockchain/primitives/src/lib.rs b/blockchain/primitives/src/lib.rs index 60b87042..14a973b1 100644 --- a/blockchain/primitives/src/lib.rs +++ b/blockchain/primitives/src/lib.rs @@ -1,212 +1,211 @@ -// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم - -// This file is part of Setheum. - -// Copyright (C) 2019-Present Setheum Labs. -// 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_attr(not(feature = "std"), no_std)] -#![allow(clippy::unnecessary_cast)] -#![allow(clippy::upper_case_acronyms)] - -pub mod bonding; -pub mod currency; -pub mod evm; -pub mod nft; -pub mod signature; -pub mod task; -pub mod testing; -pub mod unchecked_extrinsic; - -pub use testing::*; - -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; -use sp_core::U256; -use sp_runtime::{ - generic, - traits::{BlakeTwo256, IdentifyAccount, Verify}, - FixedU128, RuntimeDebug, -}; -use sp_std::prelude::*; - -pub use currency::{CurrencyId, DexShare, TokenSymbol}; -pub use evm::{convert_decimals_from_evm, convert_decimals_to_evm}; - -#[cfg(test)] -mod tests; - -/// An index to a block. -pub type BlockNumber = u32; - -/// Alias to 512-bit hash when used in the context of a transaction signature on -/// the chain. -pub type Signature = signature::SetheumMultiSignature; - -/// Alias to the public key used for this chain, actually a `MultiSigner`. Like -/// the signature, this also isn't a fixed size when encoded, as different -/// cryptos have different size public keys. -pub type AccountPublic = ::Signer; - -/// Alias to the opaque account ID type for this chain, actually a -/// `AccountId32`. This is always 32 bytes. -pub type AccountId = ::AccountId; - -/// The type for looking up accounts. We don't expect more than 4 billion of -/// them. -pub type AccountIndex = u32; - -/// The address format for describing accounts. -pub type Address = sp_runtime::MultiAddress; - -/// Index of a transaction in the chain. 32-bit should be plenty. -pub type Nonce = u32; - -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - -/// An instant or duration in time. -pub type Moment = u64; - -/// Counter for the number of eras that have passed. -pub type EraIndex = u32; - -/// Balance of an account. -pub type Balance = u128; - -/// Signed version of Balance -pub type Amount = i128; - -/// Fees type primarily for Edfis `ExchangeFee` and `TradingFee`. -pub type Fees = u128; - -/// Auction ID -pub type AuctionId = u32; - -/// Launchpad Campaign ID -pub type CampaignId = u32; - -/// Share type -pub type Share = u128; - -/// Header type. -pub type Header = generic::Header; - -/// Block type. -pub type Block = generic::Block; - -/// Block ID. -pub type BlockId = generic::BlockId; - -/// Opaque, encoded, unchecked extrinsic. -pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; - -/// Fee multiplier. -pub type Multiplier = FixedU128; - -#[derive( - Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, Serialize, Deserialize, -)] -pub enum AuthoritysOriginId { - Root, - Treasury, - LiquidSeeStakingTreasury, - LiquidEdfStakingTreasury, - SetterEcdpTreasury, - SlickUsdEcdpTreasury, - TreasuryReserve, -} - -#[derive( - Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, Serialize, Deserialize, -)] -pub enum DataProviderId { - Aggregated = 0, - Setheum = 1, -} - -#[derive( - Encode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, MaxEncodedLen, Serialize, Deserialize, -)] -pub struct TradingPair(CurrencyId, CurrencyId); - -impl TradingPair { - pub fn from_currency_ids(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option { - if currency_id_a.is_trading_pair_currency_id() - && currency_id_b.is_trading_pair_currency_id() - && currency_id_a != currency_id_b - { - if currency_id_a > currency_id_b { - Some(TradingPair(currency_id_b, currency_id_a)) - } else { - Some(TradingPair(currency_id_a, currency_id_b)) - } - } else { - None - } - } - - pub fn first(&self) -> CurrencyId { - self.0 - } - - pub fn second(&self) -> CurrencyId { - self.1 - } - - pub fn dex_share_currency_id(&self) -> CurrencyId { - CurrencyId::join_dex_share_currency_id(self.first(), self.second()) - .expect("shouldn't be invalid! guaranteed by construction") - } -} - -impl Decode for TradingPair { - fn decode(input: &mut I) -> sp_std::result::Result { - let (first, second): (CurrencyId, CurrencyId) = Decode::decode(input)?; - TradingPair::from_currency_ids(first, second) - .ok_or_else(|| parity_scale_codec::Error::from("invalid currency id")) - } -} - -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)] -pub struct ECDPPosition { - /// The amount of collateral. - pub collateral: Balance, - /// The amount of debit. - pub debit: Balance, -} - -#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, MaxEncodedLen, TypeInfo)] -#[repr(u8)] -pub enum ReserveIdentifier { - CollatorSelection, - EvmStorageDeposit, - EvmDeveloperDeposit, - SetterEcdp, - SlickUsdEcdp, - Nft, - TransactionPayment, - TransactionPaymentDeposit, - - // always the last, indicate number of variants - Count, -} - -/// Convert any type that implements Into into byte representation ([u8, 32]) -pub fn to_bytes>(value: T) -> [u8; 32] { - Into::<[u8; 32]>::into(value.into()) -} +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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_attr(not(feature = "std"), no_std)] +#![allow(clippy::unnecessary_cast)] +#![allow(clippy::upper_case_acronyms)] + +pub mod bonding; +pub mod currency; +pub mod edfis_launchpad; +pub mod evm; +pub mod nft; +pub mod signature; +pub mod task; +pub mod testing; +pub mod unchecked_extrinsic; + +pub use testing::*; +pub use edfis_launchpad::*; + +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_core::U256; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, Verify}, + FixedU128, RuntimeDebug, +}; +use sp_std::prelude::*; + +pub use currency::{CurrencyId, DexShare, TokenSymbol}; +pub use evm::{convert_decimals_from_evm, convert_decimals_to_evm}; + +#[cfg(test)] +mod tests; + +/// An index to a block. +pub type BlockNumber = u32; + +/// Alias to 512-bit hash when used in the context of a transaction signature on +/// the chain. +pub type Signature = signature::SetheumMultiSignature; + +/// Alias to the public key used for this chain, actually a `MultiSigner`. Like +/// the signature, this also isn't a fixed size when encoded, as different +/// cryptos have different size public keys. +pub type AccountPublic = ::Signer; + +/// Alias to the opaque account ID type for this chain, actually a +/// `AccountId32`. This is always 32 bytes. +pub type AccountId = ::AccountId; + +/// The type for looking up accounts. We don't expect more than 4 billion of +/// them. +pub type AccountIndex = u32; + +/// The address format for describing accounts. +pub type Address = sp_runtime::MultiAddress; + +/// Index of a transaction in the chain. 32-bit should be plenty. +pub type Nonce = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// An instant or duration in time. +pub type Moment = u64; + +/// Counter for the number of eras that have passed. +pub type EraIndex = u32; + +/// Balance of an account. +pub type Balance = u128; + +/// Signed version of Balance +pub type Amount = i128; + +/// Fees type primarily for Edfis `ExchangeFee` and `TradingFee`. +pub type Fees = u128; + +/// Auction ID +pub type AuctionId = u32; + +/// Share type +pub type Share = u128; + +/// Header type. +pub type Header = generic::Header; + +/// Block type. +pub type Block = generic::Block; + +/// Block ID. +pub type BlockId = generic::BlockId; + +/// Opaque, encoded, unchecked extrinsic. +pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + +/// Fee multiplier. +pub type Multiplier = FixedU128; + +#[derive( + Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, Serialize, Deserialize, +)] +pub enum AuthoritysOriginId { + Root, + Treasury, + LiquidSeeStakingTreasury, + LiquidEdfStakingTreasury, + SetterEcdpTreasury, + SlickUsdEcdpTreasury, + TreasuryReserve, +} + +#[derive( + Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, Serialize, Deserialize, +)] +pub enum DataProviderId { + Aggregated = 0, + Setheum = 1, +} + +#[derive( + Encode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo, MaxEncodedLen, Serialize, Deserialize, +)] +pub struct TradingPair(CurrencyId, CurrencyId); + +impl TradingPair { + pub fn from_currency_ids(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option { + if currency_id_a.is_trading_pair_currency_id() + && currency_id_b.is_trading_pair_currency_id() + && currency_id_a != currency_id_b + { + if currency_id_a > currency_id_b { + Some(TradingPair(currency_id_b, currency_id_a)) + } else { + Some(TradingPair(currency_id_a, currency_id_b)) + } + } else { + None + } + } + + pub fn first(&self) -> CurrencyId { + self.0 + } + + pub fn second(&self) -> CurrencyId { + self.1 + } + + pub fn dex_share_currency_id(&self) -> CurrencyId { + CurrencyId::join_dex_share_currency_id(self.first(), self.second()) + .expect("shouldn't be invalid! guaranteed by construction") + } +} + +impl Decode for TradingPair { + fn decode(input: &mut I) -> sp_std::result::Result { + let (first, second): (CurrencyId, CurrencyId) = Decode::decode(input)?; + TradingPair::from_currency_ids(first, second) + .ok_or_else(|| parity_scale_codec::Error::from("invalid currency id")) + } +} + +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)] +pub struct ECDPPosition { + /// The amount of collateral. + pub collateral: Balance, + /// The amount of debit. + pub debit: Balance, +} + +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, MaxEncodedLen, TypeInfo)] +#[repr(u8)] +pub enum ReserveIdentifier { + CollatorSelection, + EvmStorageDeposit, + EvmDeveloperDeposit, + SetterEcdp, + SlickUsdEcdp, + Nft, + TransactionPayment, + TransactionPaymentDeposit, + + // always the last, indicate number of variants + Count, +} + +/// Convert any type that implements Into into byte representation ([u8, 32]) +pub fn to_bytes>(value: T) -> [u8; 32] { + Into::<[u8; 32]>::into(value.into()) +} diff --git a/blockchain/runtime/src/weights/dex_oracle.rs b/blockchain/runtime/src/weights/dex_oracle.rs index 81462a85..ef91ae5e 100644 --- a/blockchain/runtime/src/weights/dex_oracle.rs +++ b/blockchain/runtime/src/weights/dex_oracle.rs @@ -1,85 +1,85 @@ -// This file is part of Setheum. - -// Copyright (C) 2020-Present Setheum Labs. -// 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 . - -//! Autogenerated weights for dex_oracle -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-21, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/setheum -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=dex_oracle -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --template=.maintain/runtime-weight-template.hbs -// --output=./runtime/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; - -/// Weight functions for dex_oracle. -pub struct WeightInfo(PhantomData); -impl dex_oracle::WeightInfo for WeightInfo { - // Storage: DexOracle AveragePrices (r:1 w:0) - // Storage: Timestamp Now (r:0 w:1) - // Storage: Dex LiquidityPool (r:1 w:0) - // Storage: DexOracle Cumulatives (r:1 w:1) - fn on_initialize_with_update_average_prices(n: u32, u: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 162_000 - .saturating_add((32_749_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 162_000 - .saturating_add((22_671_000 as Weight).saturating_mul(u as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(u as Weight))) - .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(u as Weight))) - } - // Storage: DexOracle AveragePrices (r:1 w:1) - // Storage: Dex LiquidityPool (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: DexOracle Cumulatives (r:0 w:1) - fn enable_average_price() -> Weight { - (24_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: DexOracle AveragePrices (r:1 w:1) - // Storage: DexOracle Cumulatives (r:0 w:1) - fn disable_average_price() -> Weight { - (13_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - // Storage: DexOracle AveragePrices (r:1 w:1) - fn update_average_price_interval() -> Weight { - (12_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } -} +// This file is part of Setheum. + +// Copyright (C) 2020-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for dex_oracle +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-01-21, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum-node +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=dex_oracle +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=.maintain/runtime-weight-template.hbs +// --output=./runtime/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for dex_oracle. +pub struct WeightInfo(PhantomData); +impl dex_oracle::WeightInfo for WeightInfo { + // Storage: DexOracle AveragePrices (r:1 w:0) + // Storage: Timestamp Now (r:0 w:1) + // Storage: Dex LiquidityPool (r:1 w:0) + // Storage: DexOracle Cumulatives (r:1 w:1) + fn on_initialize_with_update_average_prices(n: u32, u: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 162_000 + .saturating_add((32_749_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 162_000 + .saturating_add((22_671_000 as Weight).saturating_mul(u as Weight)) + .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(u as Weight))) + .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(u as Weight))) + } + // Storage: DexOracle AveragePrices (r:1 w:1) + // Storage: Dex LiquidityPool (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + // Storage: DexOracle Cumulatives (r:0 w:1) + fn enable_average_price() -> Weight { + (24_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: DexOracle AveragePrices (r:1 w:1) + // Storage: DexOracle Cumulatives (r:0 w:1) + fn disable_average_price() -> Weight { + (13_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: DexOracle AveragePrices (r:1 w:1) + fn update_average_price_interval() -> Weight { + (12_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} From 61139533ebda6b2e7bbfd6ec0eed76862fedc7c1 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Mon, 4 Mar 2024 20:29:08 +0800 Subject: [PATCH 16/21] Init Edfis Launchpool Module --- .../modules/edfis-launchpool/Cargo.toml | 47 ++++++++++++++++ blockchain/modules/edfis-launchpool/README.md | 10 ++++ blockchain/modules/edfis-launchpool/TODO.md | 56 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 blockchain/modules/edfis-launchpool/Cargo.toml create mode 100644 blockchain/modules/edfis-launchpool/README.md create mode 100644 blockchain/modules/edfis-launchpool/TODO.md diff --git a/blockchain/modules/edfis-launchpool/Cargo.toml b/blockchain/modules/edfis-launchpool/Cargo.toml new file mode 100644 index 00000000..237946d1 --- /dev/null +++ b/blockchain/modules/edfis-launchpool/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "module-edfis-launchpool" +description = "Provides a launchpool platform on Edfis." +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } +orml-traits = { path = "../submodules/orml/traits", default-features = false } + +[dev-dependencies] +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true } +orml-tokens = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "support/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/blockchain/modules/edfis-launchpool/README.md b/blockchain/modules/edfis-launchpool/README.md new file mode 100644 index 00000000..ed5aa4e6 --- /dev/null +++ b/blockchain/modules/edfis-launchpool/README.md @@ -0,0 +1,10 @@ +# Edfis Launchpool Module +Edfis Launchpool is a platform for projects to offer their tokens to their community (IDO) on Edfis while listing their liquidity pool on the exchange. + +## Overview + +This module is used for Edfis Launchpools. Teams and projects that are just getting started launching their products would need to foster a community and provide their tokens to the public. They need community backed by token holders of their token, that is the crowd so that they could have a strong start. + +The Launchpool does not replace the Launchpad but is however complementary to it. + +The Launchpool module allows teams to offer their tokens to users in a pool wherein they stake `SEE`, `EDF`, `SETR` or `USSD` in order to earn the team's new token passively. diff --git a/blockchain/modules/edfis-launchpool/TODO.md b/blockchain/modules/edfis-launchpool/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/edfis-launchpool/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. From 096e9967af2679ce92ea65f4469b8844adddb2b8 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Mon, 4 Mar 2024 20:29:39 +0800 Subject: [PATCH 17/21] Init Lockdrop Module --- blockchain/modules/airdrop/Cargo.toml | 2 +- blockchain/modules/lockdrop/Cargo.toml | 41 +++++++++++++++++++ blockchain/modules/lockdrop/README.md | 5 +++ blockchain/modules/lockdrop/TODO.md | 56 ++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 blockchain/modules/lockdrop/Cargo.toml create mode 100644 blockchain/modules/lockdrop/README.md create mode 100644 blockchain/modules/lockdrop/TODO.md diff --git a/blockchain/modules/airdrop/Cargo.toml b/blockchain/modules/airdrop/Cargo.toml index 96b88a18..4216401a 100644 --- a/blockchain/modules/airdrop/Cargo.toml +++ b/blockchain/modules/airdrop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-airdrop" -version = "0.1.0" +version = "0.9.81-dev" authors = ["Setheum Labs"] edition = "2021" diff --git a/blockchain/modules/lockdrop/Cargo.toml b/blockchain/modules/lockdrop/Cargo.toml new file mode 100644 index 00000000..ed905e12 --- /dev/null +++ b/blockchain/modules/lockdrop/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "module-lockdrop" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +orml-traits = { workspace = true } +primitives = { workspace = true } + +[dev-dependencies] +orml-tokens = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "orml-tokens/std", + "orml-traits/std", + "parity-scale-codec/std", + "primitives/std", + "sp-runtime/std", + "sp-core/std", + "sp-std/std", + "scale-info/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "module-dex/try-runtime", +] diff --git a/blockchain/modules/lockdrop/README.md b/blockchain/modules/lockdrop/README.md new file mode 100644 index 00000000..fafe7bd8 --- /dev/null +++ b/blockchain/modules/lockdrop/README.md @@ -0,0 +1,5 @@ +# Airdrop Module + +## Overview + +This module creates locked airdrops and distributes them to the - acccounts in the lockdrop list from a drop origin. The module for distributing Setheum Lockdrops. diff --git a/blockchain/modules/lockdrop/TODO.md b/blockchain/modules/lockdrop/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/lockdrop/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. From 389d8392ba6f461f6c92150b6ad2f19c3a193858 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Mon, 4 Mar 2024 21:12:59 +0800 Subject: [PATCH 18/21] Add Launchpool Commission Teers in README and allow all token stake pools. --- blockchain/modules/edfis-launchpool/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/blockchain/modules/edfis-launchpool/README.md b/blockchain/modules/edfis-launchpool/README.md index ed5aa4e6..187dbe59 100644 --- a/blockchain/modules/edfis-launchpool/README.md +++ b/blockchain/modules/edfis-launchpool/README.md @@ -5,6 +5,16 @@ Edfis Launchpool is a platform for projects to offer their tokens to their commu This module is used for Edfis Launchpools. Teams and projects that are just getting started launching their products would need to foster a community and provide their tokens to the public. They need community backed by token holders of their token, that is the crowd so that they could have a strong start. -The Launchpool does not replace the Launchpad but is however complementary to it. +The Launchpool does not replace the Launchpad but is however complementary to it. The Launchpool module allows teams to offer their tokens to users in a pool wherein users stake any currency in order to earn the team's new token passively. -The Launchpool module allows teams to offer their tokens to users in a pool wherein they stake `SEE`, `EDF`, `SETR` or `USSD` in order to earn the team's new token passively. +Based on the `LaunchpoolStakingCurrency` set by teams, the protocol takes a `LaunchpoolCommission` from the tokens offered. The commissions taken are deposited to the `EthicalDeFiTreasury`. + +### Launchpool Commission Teers +Below are the commission teers: + - 1. `LSEE` Pools: No Commissions taken; + - 2. `LEDF` Pools: No Commissions taken; + - 3. `SETR` Pools: No Commissions taken; + - 4. `USSD` Pools: `USSDLaunchpoolCommission` (should be 0.25%) + - 5. `SEE` Pools: `SEELaunchpoolCommission` (should be 0.5%); + - 6. `EDF` Pools: `EDFLaunchpoolCommission` (should be 0.5%) + - 7. Other Tokens Pools: `OtherTokensLaunchpoolCommission` (should be 5%) From e28319126b0702c7b04cabb839ed5ec6ce5847f2 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Thu, 7 Mar 2024 09:28:33 +0800 Subject: [PATCH 19/21] Populate ECDP USSD Loans Module --- blockchain/modules/ecdp-ussd-loans/Cargo.toml | 43 +- blockchain/modules/ecdp-ussd-loans/TODO.md | 1 + blockchain/modules/ecdp-ussd-loans/src/lib.rs | 378 ++++++++++++++++++ .../modules/ecdp-ussd-loans/src/mock.rs | 285 +++++++++++++ .../modules/ecdp-ussd-loans/src/tests.rs | 238 +++++++++++ blockchain/modules/evm/src/lib.rs | 2 +- blockchain/modules/support/src/ecdp.rs | 22 +- 7 files changed, 943 insertions(+), 26 deletions(-) create mode 100644 blockchain/modules/ecdp-ussd-loans/src/lib.rs create mode 100644 blockchain/modules/ecdp-ussd-loans/src/mock.rs create mode 100644 blockchain/modules/ecdp-ussd-loans/src/tests.rs diff --git a/blockchain/modules/ecdp-ussd-loans/Cargo.toml b/blockchain/modules/ecdp-ussd-loans/Cargo.toml index b3edf82e..ea8cb6a2 100644 --- a/blockchain/modules/ecdp-ussd-loans/Cargo.toml +++ b/blockchain/modules/ecdp-ussd-loans/Cargo.toml @@ -5,46 +5,41 @@ authors = ["Setheum Labs"] edition = "2021" [dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true, features = ["max-encoded-len"] } scale-info = { workspace = true } -serde = { workspace = true, optional = true } -parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } -sp-runtime = { workspace = true } -sp-io = { workspace = true } -sp-std = { workspace = true } -frame-support = { workspace = true } + frame-system = { workspace = true } +frame-support = { workspace = true } +sp-std = { workspace = true } +sp-runtime = { workspace = true } -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } +orml-traits = { workspace = true } +primitives = { workspace = true } +module-support = { workspace = true } [dev-dependencies] sp-core = { workspace = true, features = ["std"] } -pallet-balances = { workspace = true } -orml-tokens = { workspace = true } +sp-io = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +orml-currencies = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } +module-ecdp-ussd-treasury = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ - "scale-info/std", - "serde", "parity-scale-codec/std", - "sp-runtime/std", - "sp-std/std", - "sp-io/std", "frame-support/std", "frame-system/std", - "primitives/std", - "support/std", "orml-traits/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "primitives/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/blockchain/modules/ecdp-ussd-loans/TODO.md b/blockchain/modules/ecdp-ussd-loans/TODO.md index 3e9cd565..35e42ffa 100644 --- a/blockchain/modules/ecdp-ussd-loans/TODO.md +++ b/blockchain/modules/ecdp-ussd-loans/TODO.md @@ -54,3 +54,4 @@ These tasks are just for this file specifically. - [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. - [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. +- [ ] [[src/lib.rs:0] - Remove this from this module and add it to `EcdpSetrLoans` module.](/blockchain/modules/ecdp-ussd-loans/src/lib.rs): Add `OnLoanUpdate` to [EcdpSetrLoans Module](/blockchain/modules/ecdp-setr-loans/). diff --git a/blockchain/modules/ecdp-ussd-loans/src/lib.rs b/blockchain/modules/ecdp-ussd-loans/src/lib.rs new file mode 100644 index 00000000..72eaf42a --- /dev/null +++ b/blockchain/modules/ecdp-ussd-loans/src/lib.rs @@ -0,0 +1,378 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! # ECDP USSD Loans Module +//! +//! ## Overview +//! +//! ECDP USSD Loans module manages ECDP's collateral assets and the debits backed by these +//! assets. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] +#![allow(clippy::collapsible_if)] + +use frame_support::{pallet_prelude::*, transactional, PalletId}; +use module_support::{SlickUsdEcdpTreasury, SlickUsdRiskManager}; +use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; +use primitives::{Amount, Balance, CurrencyId, Position}; +use sp_runtime::{ + traits::{AccountIdConversion, Zero}, + ArithmeticError, DispatchResult, +}; + +mod mock; +mod tests; + +pub use module::*; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency type for deposit/withdraw collateral assets to/from USSD Loans module + type Currency: MultiCurrencyExtended< + Self::AccountId, + CurrencyId = CurrencyId, + Balance = Balance, + Amount = Amount, + >; + + /// Risk manager is used to limit the debit size of CDP. + type SlickUsdRiskManager: SlickUsdRiskManager; + + /// CDP treasury for issuing/burning USSD and debit value adjustment. + type SlickUsdEcdpTreasury: SlickUsdEcdpTreasury; + + /// The loan's module id, keep all collaterals of CDPs. + #[pallet::constant] + type PalletId: Get; + + // Remove it based on `TODO:[src/lib.rs:0]`. + /// Event handler which calls when update loan. + // type OnUpdateLoan: Happened<(Self::AccountId, CurrencyId, Amount, Balance)>; + } + + #[pallet::error] + pub enum Error { + AmountConvertFailed, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Position updated. + PositionUpdated { + owner: T::AccountId, + collateral_type: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + }, + /// Confiscate CDP's collateral assets and eliminate its debit. + ConfiscateCollateralAndDebit { + owner: T::AccountId, + collateral_type: CurrencyId, + confiscated_collateral_amount: Balance, + deduct_debit_amount: Balance, + }, + /// Transfer loan. + TransferLoan { + from: T::AccountId, + to: T::AccountId, + currency_id: CurrencyId, + }, + } + + /// The collateralized debit positions, map from + /// Owner -> CollateralType -> Position + /// + /// Positions: double_map CurrencyId, AccountId => Position + #[pallet::storage] + #[pallet::getter(fn positions)] + pub type Positions = + StorageDoubleMap<_, Twox64Concat, CurrencyId, Twox64Concat, T::AccountId, Position, ValueQuery>; + + /// The total collateralized debit positions, map from + /// CollateralType -> Position + /// + /// TotalPositions: CurrencyId => Position + #[pallet::storage] + #[pallet::getter(fn total_positions)] + pub type TotalPositions = StorageMap<_, Twox64Concat, CurrencyId, Position, ValueQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet {} +} + +impl Pallet { + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + /// confiscate collateral and debit to cdp treasury. + /// + /// Ensured atomic. + #[transactional] + pub fn confiscate_collateral_and_debit( + who: &T::AccountId, + currency_id: CurrencyId, + collateral_confiscate: Balance, + debit_decrease: Balance, + ) -> DispatchResult { + // convert balance type to amount type + let collateral_adjustment = Self::amount_try_from_balance(collateral_confiscate)?; + let debit_adjustment = Self::amount_try_from_balance(debit_decrease)?; + + // transfer collateral to cdp treasury + T::SlickUsdEcdpTreasury::deposit_collateral(&Self::account_id(), currency_id, collateral_confiscate)?; + + // deposit debit to cdp treasury + let bad_debt_value = T::SlickUsdRiskManager::get_debit_value(currency_id, debit_decrease); + T::SlickUsdEcdpTreasury::on_system_debit(bad_debt_value)?; + + // update loan + Self::update_loan( + who, + currency_id, + collateral_adjustment.saturating_neg(), + debit_adjustment.saturating_neg(), + )?; + + Self::deposit_event(Event::ConfiscateCollateralAndDebit { + owner: who.clone(), + collateral_type: currency_id, + confiscated_collateral_amount: collateral_confiscate, + deduct_debit_amount: debit_decrease, + }); + Ok(()) + } + + /// adjust the position. + /// + /// Ensured atomic. + #[transactional] + pub fn adjust_position( + who: &T::AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult { + // mutate collateral and debit + // Note: if a new position, will inc consumer + Self::update_loan(who, currency_id, collateral_adjustment, debit_adjustment)?; + + let collateral_balance_adjustment = Self::balance_try_from_amount_abs(collateral_adjustment)?; + let debit_balance_adjustment = Self::balance_try_from_amount_abs(debit_adjustment)?; + let module_account = Self::account_id(); + + if collateral_adjustment.is_positive() { + T::Currency::transfer(currency_id, who, &module_account, collateral_balance_adjustment)?; + } else if collateral_adjustment.is_negative() { + T::Currency::transfer(currency_id, &module_account, who, collateral_balance_adjustment)?; + } + + if debit_adjustment.is_positive() { + // check debit cap when increase debit + T::SlickUsdRiskManager::check_debit_cap(currency_id, Self::total_positions(currency_id).debit)?; + + // issue debit with collateral backed by cdp treasury + T::SlickUsdEcdpTreasury::issue_debit( + who, + T::SlickUsdRiskManager::get_debit_value(currency_id, debit_balance_adjustment), + true, + )?; + } else if debit_adjustment.is_negative() { + // repay debit + // burn debit by cdp treasury + T::SlickUsdEcdpTreasury::burn_debit( + who, + T::SlickUsdRiskManager::get_debit_value(currency_id, debit_balance_adjustment), + )?; + } + + // ensure pass risk check + let Position { collateral, debit } = Self::positions(currency_id, who); + T::SlickUsdRiskManager::check_position_valid( + currency_id, + collateral, + debit, + collateral_adjustment.is_negative() || debit_adjustment.is_positive(), + )?; + + Ok(()) + } + + /// transfer whole loan of `from` to `to` + pub fn transfer_loan(from: &T::AccountId, to: &T::AccountId, currency_id: CurrencyId) -> DispatchResult { + // get `from` position data + let Position { collateral, debit } = Self::positions(currency_id, from); + + let Position { + collateral: to_collateral, + debit: to_debit, + } = Self::positions(currency_id, to); + let new_to_collateral_balance = to_collateral + .checked_add(collateral) + .expect("existing collateral balance cannot overflow; qed"); + let new_to_debit_balance = to_debit + .checked_add(debit) + .expect("existing debit balance cannot overflow; qed"); + + // check new position + T::SlickUsdRiskManager::check_position_valid(currency_id, new_to_collateral_balance, new_to_debit_balance, true)?; + + // balance -> amount + let collateral_adjustment = Self::amount_try_from_balance(collateral)?; + let debit_adjustment = Self::amount_try_from_balance(debit)?; + + Self::update_loan( + from, + currency_id, + collateral_adjustment.saturating_neg(), + debit_adjustment.saturating_neg(), + )?; + Self::update_loan(to, currency_id, collateral_adjustment, debit_adjustment)?; + + Self::deposit_event(Event::TransferLoan { + from: from.clone(), + to: to.clone(), + currency_id, + }); + Ok(()) + } + + /// mutate records of collaterals and debits + pub fn update_loan( + who: &T::AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult { + let collateral_balance = Self::balance_try_from_amount_abs(collateral_adjustment)?; + let debit_balance = Self::balance_try_from_amount_abs(debit_adjustment)?; + + >::try_mutate_exists(currency_id, who, |may_be_position| -> DispatchResult { + let mut p = may_be_position.take().unwrap_or_default(); + let new_collateral = if collateral_adjustment.is_positive() { + p.collateral + .checked_add(collateral_balance) + .ok_or(ArithmeticError::Overflow) + } else { + p.collateral + .checked_sub(collateral_balance) + .ok_or(ArithmeticError::Underflow) + }?; + let new_debit = if debit_adjustment.is_positive() { + p.debit.checked_add(debit_balance).ok_or(ArithmeticError::Overflow) + } else { + p.debit.checked_sub(debit_balance).ok_or(ArithmeticError::Underflow) + }?; + + // increase account ref if new position + if p.collateral.is_zero() && p.debit.is_zero() { + if frame_system::Pallet::::inc_consumers(who).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + "Warning: Attempt to introduce lock consumer reference, yet no providers. \ + This is unexpected but should be safe." + ); + } + } + + // TODO:[src/lib.rs:0] - Remove this from this module and add it to `EcdpUssdLoans` module. + // Use the collateral amount (of a position that has fulfilled the `PositionCloudCreditRequirements` - + // this is only offered for SETR) as the shares for Cloud Credit. + // + // T::OnUpdateLoan::happened(&(who.clone(), currency_id, collateral_adjustment, p.collateral)); + + p.collateral = new_collateral; + p.debit = new_debit; + + if p.collateral.is_zero() && p.debit.is_zero() { + // decrease account ref if zero position + frame_system::Pallet::::dec_consumers(who); + + // remove position storage if zero position + *may_be_position = None; + } else { + *may_be_position = Some(p); + } + + Ok(()) + })?; + + TotalPositions::::try_mutate(currency_id, |total_positions| -> DispatchResult { + total_positions.collateral = if collateral_adjustment.is_positive() { + total_positions + .collateral + .checked_add(collateral_balance) + .ok_or(ArithmeticError::Overflow) + } else { + total_positions + .collateral + .checked_sub(collateral_balance) + .ok_or(ArithmeticError::Underflow) + }?; + + total_positions.debit = if debit_adjustment.is_positive() { + total_positions + .debit + .checked_add(debit_balance) + .ok_or(ArithmeticError::Overflow) + } else { + total_positions + .debit + .checked_sub(debit_balance) + .ok_or(ArithmeticError::Underflow) + }?; + + Ok(()) + })?; + + Self::deposit_event(Event::PositionUpdated { + owner: who.clone(), + collateral_type: currency_id, + collateral_adjustment, + debit_adjustment, + }); + Ok(()) + } +} + +impl Pallet { + /// Convert `Balance` to `Amount`. + pub fn amount_try_from_balance(b: Balance) -> Result> { + TryInto::::try_into(b).map_err(|_| Error::::AmountConvertFailed) + } + + /// Convert the absolute value of `Amount` to `Balance`. + pub fn balance_try_from_amount_abs(a: Amount) -> Result> { + TryInto::::try_into(a.saturating_abs()).map_err(|_| Error::::AmountConvertFailed) + } +} diff --git a/blockchain/modules/ecdp-ussd-loans/src/mock.rs b/blockchain/modules/ecdp-ussd-loans/src/mock.rs new file mode 100644 index 00000000..512bb0a9 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-loans/src/mock.rs @@ -0,0 +1,285 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the ecdp_ussd_loans module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, Nothing}, + PalletId, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockStableAsset, AuctionManager, SlickUsdRiskManager, SpecificJointsSwap}; +use orml_traits::parameter_type_with_key; +use primitives::TokenSymbol; +use sp_runtime::{ + traits::{AccountIdConversion, IdentityLookup}, + BuildStorage, +}; +use sp_std::cell::RefCell; +use std::collections::HashMap; + +pub type AccountId = u128; +pub type AuctionId = u32; +pub type BlockNumber = u64; + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); +pub const BTC: CurrencyId = CurrencyId::ForeignAsset(255); + +mod ecdp_ussd_loans { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + 100 + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = SEE; +} + +impl orml_currencies::Config for Runtime { + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +pub struct MockAuctionManager; +impl AuctionManager for MockAuctionManager { + type CurrencyId = CurrencyId; + type Balance = Balance; + type AuctionId = AuctionId; + + fn new_collateral_auction( + _refund_recipient: &AccountId, + _currency_id: Self::CurrencyId, + _amount: Self::Balance, + _target: Self::Balance, + ) -> DispatchResult { + Ok(()) + } + + fn cancel_auction(_id: Self::AuctionId) -> DispatchResult { + Ok(()) + } + + fn get_total_target_in_auction() -> Self::Balance { + Default::default() + } + + fn get_total_collateral_in_auction(_id: Self::CurrencyId) -> Self::Balance { + Default::default() + } +} + +ord_parameter_types! { + pub const One: AccountId = 1; +} + +parameter_types! { + pub const GetStableCurrencyId: CurrencyId = USSD; + pub const SlickUsdEcdpTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account_truncating(); + pub AlternativeSwapPathJointList: Vec> = vec![]; +} + +impl module_ussd_ecdp_treasury::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetStableCurrencyId = GetStableCurrencyId; + type AuctionManagerHandler = MockAuctionManager; + type UpdateOrigin = EnsureSignedBy; + type DEX = (); + type Swap = SpecificJointsSwap<(), AlternativeSwapPathJointList>; + type MaxAuctionsCount = ConstU32<10_000>; + type PalletId = SlickUsdEcdpTreasuryPalletId; + type TreasuryAccount = TreasuryAccount; + type WeightInfo = (); + type StableAsset = MockStableAsset; +} + +// mock risk manager +pub struct MockSlickUsdRiskManager; +impl SlickUsdRiskManager for MockSlickUsdRiskManager { + fn get_debit_value(_currency_id: CurrencyId, debit_balance: Balance) -> Balance { + debit_balance / Balance::from(2u64) + } + + fn check_position_valid( + currency_id: CurrencyId, + _collateral_balance: Balance, + _debit_balance: Balance, + check_required_ratio: bool, + ) -> DispatchResult { + match currency_id { + EDF => { + if check_required_ratio { + Err(sp_runtime::DispatchError::Other( + "mock below required collateral ratio error", + )) + } else { + Err(sp_runtime::DispatchError::Other("mock below liquidation ratio error")) + } + } + BTC => Ok(()), + _ => Err(sp_runtime::DispatchError::Other("mock below liquidation ratio error")), + } + } + + fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: Balance) -> DispatchResult { + match (currency_id, total_debit_balance) { + (EDF, 1000) => Err(sp_runtime::DispatchError::Other("mock exceed debit value cap error")), + (BTC, 1000) => Err(sp_runtime::DispatchError::Other("mock exceed debit value cap error")), + (_, _) => Ok(()), + } + } +} + +thread_local! { + pub static EDF_SHARES: RefCell> = RefCell::new(HashMap::new()); +} + +// Remove it based on `TODO:[src/lib.rs:0]`. +// pub struct MockOnUpdateLoan; +// impl Happened<(AccountId, CurrencyId, Amount, Balance)> for MockOnUpdateLoan { +// fn happened(info: &(AccountId, CurrencyId, Amount, Balance)) { +// let (who, currency_id, adjustment, previous_amount) = info; +// let adjustment_abs = TryInto::::try_into(adjustment.saturating_abs()).unwrap_or_default(); +// let new_share_amount = if adjustment.is_positive() { +// previous_amount.saturating_add(adjustment_abs) +// } else { +// previous_amount.saturating_sub(adjustment_abs) +// }; + +// if *currency_id == EDF { +// EDF_SHARES.with(|v| { +// let mut old_map = v.borrow().clone(); +// old_map.insert(*who, new_share_amount); +// *v.borrow_mut() = old_map; +// }); +// } +// } +// } + +parameter_types! { + pub const EcdpUssdLoansPalletId: PalletId = PalletId(*b"aca/loan"); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type SlickUsdRiskManager = MockSlickUsdRiskManager; + type SlickUsdEcdpTreasury = SlickUsdEcdpTreasuryModule; + type PalletId = EcdpUssdLoansPalletId; +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + EcdpUssdLoansModule: ecdp_ussd_loans, + Tokens: orml_tokens, + PalletBalances: pallet_balances, + Currencies: orml_currencies, + SlickUsdEcdpTreasuryModule: module_ussd_ecdp_treasury, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, EDF, 1000), + (ALICE, BTC, 1000), + (BOB, EDF, 1000), + (BOB, BTC, 1000), + ], + } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + t.into() + } +} diff --git a/blockchain/modules/ecdp-ussd-loans/src/tests.rs b/blockchain/modules/ecdp-ussd-loans/src/tests.rs new file mode 100644 index 00000000..848cc256 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-loans/src/tests.rs @@ -0,0 +1,238 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the ecdp_ussd_loans module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{RuntimeEvent, *}; + +#[test] +fn debits_key() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 0); + assert_ok!(EcdpUssdLoansModule::adjust_position(&ALICE, BTC, 200, 200)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 200); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdLoansModule::account_id()), 200); + assert_ok!(EcdpUssdLoansModule::adjust_position(&ALICE, BTC, -100, -100)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 100); + }); +} + +#[test] +fn check_update_loan_underflow_work() { + ExtBuilder::default().build().execute_with(|| { + // collateral underflow + assert_noop!( + EcdpUssdLoansModule::update_loan(&ALICE, BTC, -100, 0), + ArithmeticError::Underflow, + ); + + // debit underflow + assert_noop!( + EcdpUssdLoansModule::update_loan(&ALICE, BTC, 0, -100), + ArithmeticError::Underflow, + ); + }); +} + +#[test] +fn adjust_position_should_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + + // balance too low + assert_noop!( + EcdpUssdLoansModule::adjust_position(&ALICE, BTC, 2000, 0), + orml_tokens::Error::::BalanceTooLow + ); + + // mock can't pass liquidation ratio check + assert_noop!( + EcdpUssdLoansModule::adjust_position(&ALICE, EDF, 500, 0), + sp_runtime::DispatchError::Other("mock below liquidation ratio error") + ); + + // mock can't pass required ratio check + assert_noop!( + EcdpUssdLoansModule::adjust_position(&ALICE, EDF, 500, 1), + sp_runtime::DispatchError::Other("mock below required collateral ratio error") + ); + + // mock exceed debit value cap + assert_noop!( + EcdpUssdLoansModule::adjust_position(&ALICE, BTC, 1000, 1000), + sp_runtime::DispatchError::Other("mock exceed debit value cap error") + ); + + // failed because ED of collateral + assert_noop!( + EcdpUssdLoansModule::adjust_position(&ALICE, BTC, 99, 0), + orml_tokens::Error::::ExistentialDeposit, + ); + + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).debit, 0); + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).collateral, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 0); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + + // success + assert_ok!(EcdpUssdLoansModule::adjust_position(&ALICE, BTC, 500, 300)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 500); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdLoansModule::account_id()), 500); + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).debit, 300); + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).collateral, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 300); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 500); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 150); + System::assert_has_event(RuntimeEvent::EcdpUssdLoansModule(crate::Event::PositionUpdated { + owner: ALICE, + collateral_type: BTC, + collateral_adjustment: 500, + debit_adjustment: 300, + })); + + // collateral_adjustment is negatives + assert_eq!(Currencies::total_balance(BTC, &EcdpUssdLoansModule::account_id()), 500); + assert_ok!(EcdpUssdLoansModule::adjust_position(&ALICE, BTC, -500, 0)); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdLoansModule::account_id()), 0); + }); +} + +#[test] +fn update_loan_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).debit, 0); + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).collateral, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 0); + assert!(!>::contains_key(BTC, &ALICE)); + + let alice_ref_count_0 = System::consumers(&ALICE); + + assert_ok!(EcdpUssdLoansModule::update_loan(&ALICE, BTC, 3000, 2000)); + + // just update records + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).debit, 2000); + assert_eq!(EcdpUssdLoansModule::total_positions(BTC).collateral, 3000); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 2000); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 3000); + + // increase ref count when open new position + let alice_ref_count_1 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_1, alice_ref_count_0 + 1); + + // dot not manipulate balance + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + + // should remove position storage if zero + assert!(>::contains_key(BTC, &ALICE)); + assert_ok!(EcdpUssdLoansModule::update_loan(&ALICE, BTC, -3000, -2000)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 0); + assert!(!>::contains_key(BTC, &ALICE)); + + // decrease ref count after remove position + let alice_ref_count_2 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_2, alice_ref_count_1 - 1); + }); +} + +#[test] +fn transfer_loan_should_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdLoansModule::update_loan(&ALICE, BTC, 400, 500)); + assert_ok!(EcdpUssdLoansModule::update_loan(&BOB, BTC, 100, 600)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 400); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &BOB).debit, 600); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &BOB).collateral, 100); + + assert_ok!(EcdpUssdLoansModule::transfer_loan(&ALICE, &BOB, BTC)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &BOB).debit, 1100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &BOB).collateral, 500); + System::assert_last_event(RuntimeEvent::EcdpUssdLoansModule(crate::Event::TransferLoan { + from: ALICE, + to: BOB, + currency_id: BTC, + })); + }); +} + +#[test] +fn confiscate_collateral_and_debit_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdLoansModule::update_loan(&BOB, BTC, 5000, 1000)); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdLoansModule::account_id()), 0); + + // have no sufficient balance + assert_noop!( + EcdpUssdLoansModule::confiscate_collateral_and_debit(&BOB, BTC, 5000, 1000), + orml_tokens::Error::::BalanceTooLow + ); + + assert_ok!(EcdpUssdLoansModule::adjust_position(&ALICE, BTC, 500, 300)); + assert_eq!(SlickUsdEcdpTreasuryModule::get_total_collaterals(BTC), 0); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 300); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 500); + + assert_ok!(EcdpUssdLoansModule::confiscate_collateral_and_debit(&ALICE, BTC, 300, 200)); + assert_eq!(SlickUsdEcdpTreasuryModule::get_total_collaterals(BTC), 300); + assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 200); + System::assert_last_event(RuntimeEvent::EcdpUssdLoansModule(crate::Event::ConfiscateCollateralAndDebit { + owner: ALICE, + collateral_type: BTC, + confiscated_collateral_amount: 300, + deduct_debit_amount: 200, + })); + }); +} + +// #[test] +// fn loan_updated_updated_when_adjust_collateral() { +// ExtBuilder::default().build().execute_with(|| { +// assert_eq!(EDF_SHARES.with(|v| *v.borrow().get(&BOB).unwrap_or(&0)), 0); + +// assert_ok!(EcdpUssdLoansModule::update_loan(&BOB, EDF, 1000, 0)); +// assert_eq!(EDF_SHARES.with(|v| *v.borrow().get(&BOB).unwrap_or(&0)), 1000); + +// assert_ok!(EcdpUssdLoansModule::update_loan(&BOB, EDF, 0, 200)); +// assert_eq!(EDF_SHARES.with(|v| *v.borrow().get(&BOB).unwrap_or(&0)), 1000); + +// assert_ok!(EcdpUssdLoansModule::update_loan(&BOB, EDF, -800, 500)); +// assert_eq!(EDF_SHARES.with(|v| *v.borrow().get(&BOB).unwrap_or(&0)), 200); +// }); +// } diff --git a/blockchain/modules/evm/src/lib.rs b/blockchain/modules/evm/src/lib.rs index 056699cb..ce2b4f02 100644 --- a/blockchain/modules/evm/src/lib.rs +++ b/blockchain/modules/evm/src/lib.rs @@ -1319,7 +1319,7 @@ impl Pallet { // /// We extend the principle of this EIP to also prevent `tx.sender` to be the address /// of a precompile. While mainnet Ethereum currently only has stateless precompiles, - /// Setheum EVM+ can have stateful precompiles that can manage funds or + /// Setheum EVM can have stateful precompiles that can manage funds or /// which calls other contracts that expects this precompile address to be trustworthy. fn ensure_eoa(caller: &EvmAddress) -> DispatchResult { if is_system_contract(caller) || Self::is_contract(caller) { diff --git a/blockchain/modules/support/src/ecdp.rs b/blockchain/modules/support/src/ecdp.rs index a73c8bc6..cbef19cb 100644 --- a/blockchain/modules/support/src/ecdp.rs +++ b/blockchain/modules/support/src/ecdp.rs @@ -152,7 +152,7 @@ pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { fn max_auction() -> u32; } -/// Functionality of SlickUSD ECDP Protocol to be exposed to EVM+. +/// Functionality of SlickUSD ECDP Protocol to be exposed to EVM. pub trait SlickUsdEcdpManager { /// Adjust ECDP loan fn adjust_loan( @@ -172,3 +172,23 @@ pub trait SlickUsdEcdpManager { /// Get exchange rate of debit units to debit value for a currency_id fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate; } + +/// Functionality of Setter ECDP Protocol to be exposed to EVM. +pub trait SetterEcdpManager { + /// Adjust ECDP loan + fn adjust_loan( + who: &AccountId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult; + /// Close ECDP loan using DEX + fn close_loan_by_dex(who: AccountId, max_collateral_amount: Balance) -> DispatchResult; + /// Get open ECDP corresponding to an account and collateral + fn get_position(who: &AccountId) -> ECDPPosition; + /// Get liquidation ratio for collateral + fn get_collateral_parameters() -> Vec; + /// Get current ratio of collateral to debit of open ECDP + fn get_current_collateral_ratio(who: &AccountId) -> Option; + /// Get exchange rate of debit units to debit value for a currency_id + fn get_debit_exchange_rate() -> ExchangeRate; +} From 86471970dd0db27f06c5a56537a9664eb53c1587 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Fri, 8 Mar 2024 02:08:42 +0800 Subject: [PATCH 20/21] Populate `module-ecdp-ussd-loans` --- blockchain/modules/ecdp-ussd-loans/src/lib.rs | 60 +++++++++---------- .../modules/ecdp-ussd-loans/src/mock.rs | 29 +++++---- .../modules/ecdp-ussd-loans/src/tests.rs | 16 ++--- 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/blockchain/modules/ecdp-ussd-loans/src/lib.rs b/blockchain/modules/ecdp-ussd-loans/src/lib.rs index 72eaf42a..14ac8976 100644 --- a/blockchain/modules/ecdp-ussd-loans/src/lib.rs +++ b/blockchain/modules/ecdp-ussd-loans/src/lib.rs @@ -30,9 +30,9 @@ #![allow(clippy::collapsible_if)] use frame_support::{pallet_prelude::*, transactional, PalletId}; -use module_support::{SlickUsdEcdpTreasury, SlickUsdRiskManager}; +use module_support::{EcdpUssdTreasury, EcdpUssdRiskManager}; use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; -use primitives::{Amount, Balance, CurrencyId, Position}; +use primitives::{Amount, Balance, CurrencyId, EcdpPosition}; use sp_runtime::{ traits::{AccountIdConversion, Zero}, ArithmeticError, DispatchResult, @@ -60,10 +60,10 @@ pub mod module { >; /// Risk manager is used to limit the debit size of CDP. - type SlickUsdRiskManager: SlickUsdRiskManager; + type EcdpUssdRiskManager: EcdpUssdRiskManager; /// CDP treasury for issuing/burning USSD and debit value adjustment. - type SlickUsdEcdpTreasury: SlickUsdEcdpTreasury; + type EcdpUssdTreasury: EcdpUssdTreasury; /// The loan's module id, keep all collaterals of CDPs. #[pallet::constant] @@ -82,8 +82,8 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Position updated. - PositionUpdated { + /// EcdpPosition updated. + EcdpPositionUpdated { owner: T::AccountId, collateral_type: CurrencyId, collateral_adjustment: Amount, @@ -105,21 +105,21 @@ pub mod module { } /// The collateralized debit positions, map from - /// Owner -> CollateralType -> Position + /// Owner -> CollateralType -> EcdpPosition /// - /// Positions: double_map CurrencyId, AccountId => Position + /// EcdpPositions: double_map CurrencyId, AccountId => EcdpPosition #[pallet::storage] #[pallet::getter(fn positions)] - pub type Positions = - StorageDoubleMap<_, Twox64Concat, CurrencyId, Twox64Concat, T::AccountId, Position, ValueQuery>; + pub type EcdpPositions = + StorageDoubleMap<_, Twox64Concat, CurrencyId, Twox64Concat, T::AccountId, EcdpPosition, ValueQuery>; /// The total collateralized debit positions, map from - /// CollateralType -> Position + /// CollateralType -> EcdpPosition /// - /// TotalPositions: CurrencyId => Position + /// TotalEcdpPositions: CurrencyId => EcdpPosition #[pallet::storage] #[pallet::getter(fn total_positions)] - pub type TotalPositions = StorageMap<_, Twox64Concat, CurrencyId, Position, ValueQuery>; + pub type TotalEcdpPositions = StorageMap<_, Twox64Concat, CurrencyId, EcdpPosition, ValueQuery>; #[pallet::pallet] pub struct Pallet(_); @@ -148,11 +148,11 @@ impl Pallet { let debit_adjustment = Self::amount_try_from_balance(debit_decrease)?; // transfer collateral to cdp treasury - T::SlickUsdEcdpTreasury::deposit_collateral(&Self::account_id(), currency_id, collateral_confiscate)?; + T::EcdpUssdTreasury::deposit_collateral(&Self::account_id(), currency_id, collateral_confiscate)?; // deposit debit to cdp treasury - let bad_debt_value = T::SlickUsdRiskManager::get_debit_value(currency_id, debit_decrease); - T::SlickUsdEcdpTreasury::on_system_debit(bad_debt_value)?; + let bad_debt_value = T::EcdpUssdRiskManager::get_debit_value(currency_id, debit_decrease); + T::EcdpUssdTreasury::on_system_debit(bad_debt_value)?; // update loan Self::update_loan( @@ -197,26 +197,26 @@ impl Pallet { if debit_adjustment.is_positive() { // check debit cap when increase debit - T::SlickUsdRiskManager::check_debit_cap(currency_id, Self::total_positions(currency_id).debit)?; + T::EcdpUssdRiskManager::check_debit_cap(currency_id, Self::total_positions(currency_id).debit)?; // issue debit with collateral backed by cdp treasury - T::SlickUsdEcdpTreasury::issue_debit( + T::EcdpUssdTreasury::issue_debit( who, - T::SlickUsdRiskManager::get_debit_value(currency_id, debit_balance_adjustment), + T::EcdpUssdRiskManager::get_debit_value(currency_id, debit_balance_adjustment), true, )?; } else if debit_adjustment.is_negative() { // repay debit // burn debit by cdp treasury - T::SlickUsdEcdpTreasury::burn_debit( + T::EcdpUssdTreasury::burn_debit( who, - T::SlickUsdRiskManager::get_debit_value(currency_id, debit_balance_adjustment), + T::EcdpUssdRiskManager::get_debit_value(currency_id, debit_balance_adjustment), )?; } // ensure pass risk check - let Position { collateral, debit } = Self::positions(currency_id, who); - T::SlickUsdRiskManager::check_position_valid( + let EcdpPosition { collateral, debit } = Self::positions(currency_id, who); + T::EcdpUssdRiskManager::check_position_valid( currency_id, collateral, debit, @@ -229,9 +229,9 @@ impl Pallet { /// transfer whole loan of `from` to `to` pub fn transfer_loan(from: &T::AccountId, to: &T::AccountId, currency_id: CurrencyId) -> DispatchResult { // get `from` position data - let Position { collateral, debit } = Self::positions(currency_id, from); + let EcdpPosition { collateral, debit } = Self::positions(currency_id, from); - let Position { + let EcdpPosition { collateral: to_collateral, debit: to_debit, } = Self::positions(currency_id, to); @@ -243,7 +243,7 @@ impl Pallet { .expect("existing debit balance cannot overflow; qed"); // check new position - T::SlickUsdRiskManager::check_position_valid(currency_id, new_to_collateral_balance, new_to_debit_balance, true)?; + T::EcdpUssdRiskManager::check_position_valid(currency_id, new_to_collateral_balance, new_to_debit_balance, true)?; // balance -> amount let collateral_adjustment = Self::amount_try_from_balance(collateral)?; @@ -275,7 +275,7 @@ impl Pallet { let collateral_balance = Self::balance_try_from_amount_abs(collateral_adjustment)?; let debit_balance = Self::balance_try_from_amount_abs(debit_adjustment)?; - >::try_mutate_exists(currency_id, who, |may_be_position| -> DispatchResult { + >::try_mutate_exists(currency_id, who, |may_be_position| -> DispatchResult { let mut p = may_be_position.take().unwrap_or_default(); let new_collateral = if collateral_adjustment.is_positive() { p.collateral @@ -306,7 +306,7 @@ impl Pallet { } // TODO:[src/lib.rs:0] - Remove this from this module and add it to `EcdpUssdLoans` module. - // Use the collateral amount (of a position that has fulfilled the `PositionCloudCreditRequirements` - + // Use the collateral amount (of a position that has fulfilled the `EcdpPositionCloudCreditRequirements` - // this is only offered for SETR) as the shares for Cloud Credit. // // T::OnUpdateLoan::happened(&(who.clone(), currency_id, collateral_adjustment, p.collateral)); @@ -327,7 +327,7 @@ impl Pallet { Ok(()) })?; - TotalPositions::::try_mutate(currency_id, |total_positions| -> DispatchResult { + TotalEcdpPositions::::try_mutate(currency_id, |total_positions| -> DispatchResult { total_positions.collateral = if collateral_adjustment.is_positive() { total_positions .collateral @@ -355,7 +355,7 @@ impl Pallet { Ok(()) })?; - Self::deposit_event(Event::PositionUpdated { + Self::deposit_event(Event::EcdpPositionUpdated { owner: who.clone(), collateral_type: currency_id, collateral_adjustment, diff --git a/blockchain/modules/ecdp-ussd-loans/src/mock.rs b/blockchain/modules/ecdp-ussd-loans/src/mock.rs index 512bb0a9..c893b024 100644 --- a/blockchain/modules/ecdp-ussd-loans/src/mock.rs +++ b/blockchain/modules/ecdp-ussd-loans/src/mock.rs @@ -29,7 +29,7 @@ use frame_support::{ PalletId, }; use frame_system::EnsureSignedBy; -use module_support::{mocks::MockStableAsset, AuctionManager, SlickUsdRiskManager, SpecificJointsSwap}; +use module_support::{EcdpAuctionsManager, EcdpUssdRiskManager, SpecificJointsSwap}; use orml_traits::parameter_type_with_key; use primitives::TokenSymbol; use sp_runtime::{ @@ -111,8 +111,8 @@ impl orml_currencies::Config for Runtime { } pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; -pub struct MockAuctionManager; -impl AuctionManager for MockAuctionManager { +pub struct MockEcdpAuctionsManager; +impl EcdpAuctionsManager for MockEcdpAuctionsManager { type CurrencyId = CurrencyId; type Balance = Balance; type AuctionId = AuctionId; @@ -144,30 +144,29 @@ ord_parameter_types! { } parameter_types! { - pub const GetStableCurrencyId: CurrencyId = USSD; - pub const SlickUsdEcdpTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub const GetUSSDCurrencyId: CurrencyId = USSD; + pub const EcdpUssdTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account_truncating(); pub AlternativeSwapPathJointList: Vec> = vec![]; } -impl module_ussd_ecdp_treasury::Config for Runtime { +impl module_ecdp_ussd_treasury::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Currencies; - type GetStableCurrencyId = GetStableCurrencyId; - type AuctionManagerHandler = MockAuctionManager; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type EcdpAuctionsManagerHandler = MockEcdpAuctionsManager; type UpdateOrigin = EnsureSignedBy; type DEX = (); type Swap = SpecificJointsSwap<(), AlternativeSwapPathJointList>; type MaxAuctionsCount = ConstU32<10_000>; - type PalletId = SlickUsdEcdpTreasuryPalletId; + type PalletId = EcdpUssdTreasuryPalletId; type TreasuryAccount = TreasuryAccount; type WeightInfo = (); - type StableAsset = MockStableAsset; } // mock risk manager -pub struct MockSlickUsdRiskManager; -impl SlickUsdRiskManager for MockSlickUsdRiskManager { +pub struct MockEcdpUssdRiskManager; +impl EcdpUssdRiskManager for MockEcdpUssdRiskManager { fn get_debit_value(_currency_id: CurrencyId, debit_balance: Balance) -> Balance { debit_balance / Balance::from(2u64) } @@ -235,8 +234,8 @@ parameter_types! { impl Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Currencies; - type SlickUsdRiskManager = MockSlickUsdRiskManager; - type SlickUsdEcdpTreasury = SlickUsdEcdpTreasuryModule; + type EcdpUssdRiskManager = MockEcdpUssdRiskManager; + type EcdpUssdTreasury = EcdpUssdTreasuryModule; type PalletId = EcdpUssdLoansPalletId; } @@ -249,7 +248,7 @@ construct_runtime!( Tokens: orml_tokens, PalletBalances: pallet_balances, Currencies: orml_currencies, - SlickUsdEcdpTreasuryModule: module_ussd_ecdp_treasury, + EcdpUssdTreasuryModule: module_ecdp_ussd_treasury, } ); diff --git a/blockchain/modules/ecdp-ussd-loans/src/tests.rs b/blockchain/modules/ecdp-ussd-loans/src/tests.rs index 848cc256..7a379d24 100644 --- a/blockchain/modules/ecdp-ussd-loans/src/tests.rs +++ b/blockchain/modules/ecdp-ussd-loans/src/tests.rs @@ -108,7 +108,7 @@ fn adjust_position_should_work() { assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 300); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 500); assert_eq!(Currencies::free_balance(USSD, &ALICE), 150); - System::assert_has_event(RuntimeEvent::EcdpUssdLoansModule(crate::Event::PositionUpdated { + System::assert_has_event(RuntimeEvent::EcdpUssdLoansModule(crate::Event::EcdpPositionUpdated { owner: ALICE, collateral_type: BTC, collateral_adjustment: 500, @@ -131,7 +131,7 @@ fn update_loan_should_work() { assert_eq!(EcdpUssdLoansModule::total_positions(BTC).collateral, 0); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 0); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 0); - assert!(!>::contains_key(BTC, &ALICE)); + assert!(!>::contains_key(BTC, &ALICE)); let alice_ref_count_0 = System::consumers(&ALICE); @@ -152,11 +152,11 @@ fn update_loan_should_work() { assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); // should remove position storage if zero - assert!(>::contains_key(BTC, &ALICE)); + assert!(>::contains_key(BTC, &ALICE)); assert_ok!(EcdpUssdLoansModule::update_loan(&ALICE, BTC, -3000, -2000)); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 0); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 0); - assert!(!>::contains_key(BTC, &ALICE)); + assert!(!>::contains_key(BTC, &ALICE)); // decrease ref count after remove position let alice_ref_count_2 = System::consumers(&ALICE); @@ -202,14 +202,14 @@ fn confiscate_collateral_and_debit_work() { ); assert_ok!(EcdpUssdLoansModule::adjust_position(&ALICE, BTC, 500, 300)); - assert_eq!(SlickUsdEcdpTreasuryModule::get_total_collaterals(BTC), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::get_total_collaterals(BTC), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 300); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 500); assert_ok!(EcdpUssdLoansModule::confiscate_collateral_and_debit(&ALICE, BTC, 300, 200)); - assert_eq!(SlickUsdEcdpTreasuryModule::get_total_collaterals(BTC), 300); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); + assert_eq!(EcdpUssdTreasuryModule::get_total_collaterals(BTC), 300); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 100); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).debit, 100); assert_eq!(EcdpUssdLoansModule::positions(BTC, &ALICE).collateral, 200); System::assert_last_event(RuntimeEvent::EcdpUssdLoansModule(crate::Event::ConfiscateCollateralAndDebit { From f1a8f046d3bbdfea920a699f15475bad380fdf15 Mon Sep 17 00:00:00 2001 From: "Muhammad-Jibril B.A. (Khalifa MBA)" Date: Fri, 8 Mar 2024 02:34:36 +0800 Subject: [PATCH 21/21] Populate ECDP Auctions Module, do some naming changes too --- blockchain/modules/airdrop/Cargo.toml | 2 +- blockchain/modules/ecdp-auctions/Cargo.toml | 45 +- blockchain/modules/ecdp-auctions/README.md | 3 + blockchain/modules/ecdp-auctions/src/lib.rs | 852 ++++++++ blockchain/modules/ecdp-auctions/src/mock.rs | 278 +++ blockchain/modules/ecdp-auctions/src/tests.rs | 738 +++++++ .../modules/ecdp-auctions/src/weights.rs | 72 + .../modules/ecdp-ussd-engine/Cargo.toml | 56 +- .../modules/ecdp-ussd-engine/src/lib.rs | 1482 +++++++++++++ .../modules/ecdp-ussd-engine/src/mock.rs | 533 +++++ .../modules/ecdp-ussd-engine/src/tests.rs | 1878 +++++++++++++++++ .../modules/ecdp-ussd-engine/src/weights.rs | 128 ++ .../modules/ecdp-ussd-treasury/Cargo.toml | 2 +- .../modules/ecdp-ussd-treasury/src/lib.rs | 20 +- .../modules/ecdp-ussd-treasury/src/mock.rs | 30 +- .../modules/ecdp-ussd-treasury/src/tests.rs | 348 +-- blockchain/modules/ecdp/Cargo.toml | 53 + blockchain/modules/ecdp/README.md | 5 + blockchain/modules/ecdp/TODO.md | 56 + blockchain/modules/ecdp/src/lib.rs | 455 ++++ blockchain/modules/ecdp/src/mock.rs | 395 ++++ blockchain/modules/ecdp/src/tests.rs | 345 +++ blockchain/modules/ecdp/src/weights.rs | 284 +++ .../modules/edfis-swap-legacy/src/lib.rs | 2 +- .../modules/edfis-swap-legacy/src/mock.rs | 4 +- blockchain/modules/edfis-swap/src/lib.rs | 2 +- blockchain/modules/edfis-swap/src/mock.rs | 4 +- blockchain/modules/evm/Cargo.toml | 8 +- blockchain/modules/evm/src/bench/mock.rs | 4 +- blockchain/modules/lockdrop/Cargo.toml | 2 +- blockchain/modules/support/src/ecdp.rs | 22 +- .../modules/transaction-payment/Cargo.toml | 2 +- .../modules/transaction-payment/src/mock.rs | 4 +- blockchain/primitives/src/lib.rs | 5 +- 34 files changed, 7840 insertions(+), 279 deletions(-) create mode 100644 blockchain/modules/ecdp-auctions/src/lib.rs create mode 100644 blockchain/modules/ecdp-auctions/src/mock.rs create mode 100644 blockchain/modules/ecdp-auctions/src/tests.rs create mode 100644 blockchain/modules/ecdp-auctions/src/weights.rs create mode 100644 blockchain/modules/ecdp-ussd-engine/src/lib.rs create mode 100644 blockchain/modules/ecdp-ussd-engine/src/mock.rs create mode 100644 blockchain/modules/ecdp-ussd-engine/src/tests.rs create mode 100644 blockchain/modules/ecdp-ussd-engine/src/weights.rs create mode 100644 blockchain/modules/ecdp/Cargo.toml create mode 100644 blockchain/modules/ecdp/README.md create mode 100644 blockchain/modules/ecdp/TODO.md create mode 100644 blockchain/modules/ecdp/src/lib.rs create mode 100644 blockchain/modules/ecdp/src/mock.rs create mode 100644 blockchain/modules/ecdp/src/tests.rs create mode 100644 blockchain/modules/ecdp/src/weights.rs diff --git a/blockchain/modules/airdrop/Cargo.toml b/blockchain/modules/airdrop/Cargo.toml index 4216401a..916ad5d5 100644 --- a/blockchain/modules/airdrop/Cargo.toml +++ b/blockchain/modules/airdrop/Cargo.toml @@ -37,5 +37,5 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "module-dex/try-runtime", + "module-edfis_swap_legacy/try-runtime", ] diff --git a/blockchain/modules/ecdp-auctions/Cargo.toml b/blockchain/modules/ecdp-auctions/Cargo.toml index ab1c301a..51435c50 100644 --- a/blockchain/modules/ecdp-auctions/Cargo.toml +++ b/blockchain/modules/ecdp-auctions/Cargo.toml @@ -5,46 +5,43 @@ authors = ["Setheum Labs"] edition = "2021" [dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true } scale-info = { workspace = true } -serde = { workspace = true, optional = true } -parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } -sp-runtime = { workspace = true } -sp-io = { workspace = true } -sp-std = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +orml-traits = { workspace = true } +orml-utilities = { workspace = true } +module-support = { workspace = true } +primitives = { workspace = true } [dev-dependencies] sp-core = { workspace = true, features = ["std"] } -pallet-balances = { workspace = true } -orml-tokens = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } +orml-auction = { workspace = true, features = ["std"] } +module-ecdp-ussd-treasury = { workspace = true, features = ["std"] } +module-edfis-legacy = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ - "scale-info/std", - "serde", "parity-scale-codec/std", - "sp-runtime/std", - "sp-std/std", - "sp-io/std", "frame-support/std", "frame-system/std", - "primitives/std", - "support/std", "orml-traits/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "orml-utilities/std", + "primitives/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/blockchain/modules/ecdp-auctions/README.md b/blockchain/modules/ecdp-auctions/README.md index db9ef649..19879444 100644 --- a/blockchain/modules/ecdp-auctions/README.md +++ b/blockchain/modules/ecdp-auctions/README.md @@ -3,3 +3,6 @@ ## Overview Provides an Auctions protocol for ECDP Stablecoin on Ethical DeFi. + +Auctions the assets of the system to maintain the normal operation of the business. Auction types include: + - `collateral auction`: sell collateral assets for getting stable currency to eliminate the system's bad debit by auction \ No newline at end of file diff --git a/blockchain/modules/ecdp-auctions/src/lib.rs b/blockchain/modules/ecdp-auctions/src/lib.rs new file mode 100644 index 00000000..8edb461b --- /dev/null +++ b/blockchain/modules/ecdp-auctions/src/lib.rs @@ -0,0 +1,852 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! # Auction Manager Module +//! +//! ## Overview +//! +//! Auctions the assets of the system to maintain the normal operation of the +//! business. Auction types include: +//! - `collateral auction`: sell collateral assets for stablecoin to eliminate the +//! system's bad debit by auction + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] +#![allow(clippy::upper_case_acronyms)] +#![allow(clippy::unnecessary_unwrap)] + +use frame_support::{pallet_prelude::*, transactional}; +use frame_system::{ + offchain::{SendTransactionTypes, SubmitTransaction}, + pallet_prelude::*, +}; +use module_support::{ + EcdpAuctionsManager, EcdpUssdTreasury, EcdpUssdTreasuryExtended, EcdpEmergencyShutdown, PriceProvider, Rate, SwapLimit, +}; +use orml_traits::{Auction, AuctionHandler, Change, MultiCurrency, OnNewBidResult}; +use orml_utilities::OffchainErr; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use primitives::{AuctionId, Balance, CurrencyId}; +use scale_info::TypeInfo; +use sp_runtime::{ + offchain::{ + storage::StorageValueRef, + storage_lock::{StorageLock, Time}, + Duration, + }, + traits::{CheckedDiv, Saturating, Zero}, + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, ValidTransaction, + }, + DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, +}; +use sp_std::prelude::*; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +pub const OFFCHAIN_WORKER_DATA: &[u8] = b"setheum/ecdp-auctions-manager/data/"; +pub const OFFCHAIN_WORKER_LOCK: &[u8] = b"setheum/ecdp-auctions-manager/lock/"; +pub const OFFCHAIN_WORKER_MAX_ITERATIONS: &[u8] = b"setheum/ecdp-auctions-manager/max-iterations/"; +pub const LOCK_DURATION: u64 = 100; +pub const DEFAULT_MAX_ITERATIONS: u32 = 1000; + +/// Information of an collateral auction +#[cfg_attr(feature = "std", derive(PartialEq, Eq))] +#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct CollateralAuctionItem { + /// Refund recipient for may receive refund + refund_recipient: AccountId, + /// Collateral type for sale + currency_id: CurrencyId, + /// Initial collateral amount for sale + #[codec(compact)] + initial_amount: Balance, + /// Current collateral amount for sale + #[codec(compact)] + amount: Balance, + /// Target sales amount of this auction + /// if zero, collateral auction will never be reverse stage, + /// otherwise, target amount is the actual payment amount of active + /// bidder + #[codec(compact)] + target: Balance, + /// Auction start time + start_time: BlockNumber, +} + +impl CollateralAuctionItem { + /// Return the collateral auction will never be reverse stage + fn always_forward(&self) -> bool { + self.target.is_zero() + } + + /// Return whether the collateral auction is in reverse stage at + /// specific bid price + fn in_reverse_stage(&self, bid_price: Balance) -> bool { + !self.always_forward() && bid_price >= self.target + } + + /// Return the actual number of stablecoins to be paid + fn payment_amount(&self, bid_price: Balance) -> Balance { + if self.always_forward() { + bid_price + } else { + sp_std::cmp::min(self.target, bid_price) + } + } + + /// Return new collateral amount at specific last bid price and new bid + /// price + fn collateral_amount(&self, last_bid_price: Balance, new_bid_price: Balance) -> Balance { + if self.in_reverse_stage(new_bid_price) && new_bid_price > last_bid_price { + Rate::checked_from_rational(sp_std::cmp::max(last_bid_price, self.target), new_bid_price) + .and_then(|n| n.checked_mul_int(self.amount)) + .unwrap_or(self.amount) + } else { + self.amount + } + } +} + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config + SendTransactionTypes> { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The minimum increment size of each bid compared to the previous one + #[pallet::constant] + type MinimumIncrementSize: Get; + + /// The extended time for the auction to end after each successful bid + #[pallet::constant] + type AuctionTimeToClose: Get>; + + /// When the total duration of the auction exceeds this soft cap, push + /// the auction to end more faster + #[pallet::constant] + type AuctionDurationSoftCap: Get>; + + /// The stable currency id + #[pallet::constant] + type GetUSSDCurrencyId: Get; + + /// Currency to transfer assets + type Currency: MultiCurrency; + + /// Auction to manager the auction process + type Auction: Auction, AuctionId = AuctionId, Balance = Balance>; + + /// CDP treasury to escrow assets related to auction + type EcdpUssdTreasury: EcdpUssdTreasuryExtended; + + /// The price source of currencies + type PriceSource: PriceProvider; + + /// A configuration for base priority of unsigned transactions. + /// + /// This is exposed so that it can be tuned for particular runtime, when + /// multiple modules send unsigned transactions. + #[pallet::constant] + type UnsignedPriority: Get; + + /// Emergency shutdown. + type EcdpEmergencyShutdown: EcdpEmergencyShutdown; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The auction dose not exist + AuctionNotExists, + /// The collateral auction is in reverse stage now + InReverseStage, + /// Feed price is invalid + InvalidFeedPrice, + /// Must after system shutdown + MustAfterShutdown, + /// Bid price is invalid + InvalidBidPrice, + /// Invalid input amount + InvalidAmount, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Collateral auction created. + NewCollateralAuction { + auction_id: AuctionId, + collateral_type: CurrencyId, + collateral_amount: Balance, + target_bid_price: Balance, + }, + /// Active auction cancelled. + CancelAuction { auction_id: AuctionId }, + /// Collateral auction dealt. + CollateralAuctionDealt { + auction_id: AuctionId, + collateral_type: CurrencyId, + collateral_amount: Balance, + winner: T::AccountId, + payment_amount: Balance, + }, + /// Dex take collateral auction. + DEXTakeCollateralAuction { + auction_id: AuctionId, + collateral_type: CurrencyId, + collateral_amount: Balance, + supply_collateral_amount: Balance, + target_stable_amount: Balance, + }, + /// Collateral auction aborted. + CollateralAuctionAborted { + auction_id: AuctionId, + collateral_type: CurrencyId, + collateral_amount: Balance, + target_stable_amount: Balance, + refund_recipient: T::AccountId, + }, + } + + /// Mapping from auction id to collateral auction info + /// + /// CollateralAuctions: map AuctionId => Option + #[pallet::storage] + #[pallet::getter(fn collateral_auctions)] + pub type CollateralAuctions = + StorageMap<_, Twox64Concat, AuctionId, CollateralAuctionItem>, OptionQuery>; + + /// Record of the total collateral amount of all active collateral auctions + /// under specific collateral type CollateralType -> TotalAmount + /// + /// TotalCollateralInAuction: map CurrencyId => Balance + #[pallet::storage] + #[pallet::getter(fn total_collateral_in_auction)] + pub type TotalCollateralInAuction = StorageMap<_, Twox64Concat, CurrencyId, Balance, ValueQuery>; + + /// Record of total target sales of all active collateral auctions + /// + /// TotalTargetInAuction: Balance + #[pallet::storage] + #[pallet::getter(fn total_target_in_auction)] + pub type TotalTargetInAuction = StorageValue<_, Balance, ValueQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet { + /// Start offchain worker in order to submit unsigned tx to cancel + /// active auction after system shutdown. + fn offchain_worker(now: BlockNumberFor) { + if T::EcdpEmergencyShutdown::is_shutdown() && sp_io::offchain::is_validator() { + if let Err(e) = Self::_offchain_worker() { + log::info!( + target: "auction-manager", + "offchain worker: cannot run offchain worker at {:?}: {:?}", + now, e, + ); + } else { + log::debug!( + target: "auction-manager", + "offchain worker: offchain worker start at block: {:?} already done!", + now, + ); + } + } + } + } + + #[pallet::call] + impl Pallet { + /// Cancel active auction after system shutdown + /// + /// The dispatch origin of this call must be _None_. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::cancel_collateral_auction())] + pub fn cancel(origin: OriginFor, id: AuctionId) -> DispatchResult { + ensure_none(origin)?; + ensure!(T::EcdpEmergencyShutdown::is_shutdown(), Error::::MustAfterShutdown); + >::cancel_auction(id)?; + Self::deposit_event(Event::CancelAuction { auction_id: id }); + Ok(()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::cancel { id: auction_id } = call { + if !T::EcdpEmergencyShutdown::is_shutdown() { + return InvalidTransaction::Call.into(); + } + + if let Some(collateral_auction) = Self::collateral_auctions(auction_id) { + if let Some((_, bid_price)) = Self::get_last_bid(*auction_id) { + // if collateral auction is in reverse stage, shouldn't cancel + if collateral_auction.in_reverse_stage(bid_price) { + return InvalidTransaction::Stale.into(); + } + } + } else { + return InvalidTransaction::Stale.into(); + } + + ValidTransaction::with_tag_prefix("EcdpAuctionsManagerOffchainWorker") + .priority(T::UnsignedPriority::get()) + .and_provides(auction_id) + .longevity(64_u64) + .propagate(true) + .build() + } else { + InvalidTransaction::Call.into() + } + } + } +} + +impl Pallet { + fn get_last_bid(auction_id: AuctionId) -> Option<(T::AccountId, Balance)> { + T::Auction::auction_info(auction_id).and_then(|auction_info| auction_info.bid) + } + + fn submit_cancel_auction_tx(auction_id: AuctionId) { + let call = Call::::cancel { id: auction_id }; + if let Err(err) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { + log::info!( + target: "auction-manager", + "offchain worker: submit unsigned auction cancel tx for AuctionId {:?} failed: {:?}", + auction_id, err, + ); + } + } + + fn _offchain_worker() -> Result<(), OffchainErr> { + // acquire offchain worker lock. + let lock_expiration = Duration::from_millis(LOCK_DURATION); + let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); + let mut guard = lock.try_lock().map_err(|_| OffchainErr::OffchainLock)?; + + let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + + // get to_be_continue record, + // if it exsits, iterator map storage start with previous key + let start_key = to_be_continue.get::>().unwrap_or_default(); + + // get the max iterationns config + let max_iterations = StorageValueRef::persistent(OFFCHAIN_WORKER_MAX_ITERATIONS) + .get::() + .unwrap_or(Some(DEFAULT_MAX_ITERATIONS)) + .unwrap_or(DEFAULT_MAX_ITERATIONS); + + log::debug!( + target: "auction-manager", + "offchain worker: max iterations is {:?}", + max_iterations + ); + + // start iterations to cancel collateral auctions + let mut iterator = match start_key { + Some(key) => >::iter_from(key), + None => >::iter(), + }; + + let mut iteration_count = 0; + let mut finished = true; + + #[allow(clippy::while_let_on_iterator)] + while let Some((collateral_auction_id, _)) = iterator.next() { + iteration_count += 1; + + if let (Some(collateral_auction), Some((_, last_bid_price))) = ( + Self::collateral_auctions(collateral_auction_id), + Self::get_last_bid(collateral_auction_id), + ) { + // if collateral auction has already been in reverse stage, + // should skip it. + if collateral_auction.in_reverse_stage(last_bid_price) { + if iteration_count == max_iterations { + finished = false; + break; + } + continue; + } + } + Self::submit_cancel_auction_tx(collateral_auction_id); + + if iteration_count == max_iterations { + finished = false; + break; + } + guard.extend_lock().map_err(|_| OffchainErr::OffchainLock)?; + } + + if finished { + to_be_continue.clear(); + } else { + to_be_continue.set(&iterator.last_raw_key()); + } + + // Consume the guard but **do not** unlock the underlying lock. + guard.forget(); + + Ok(()) + } + + fn cancel_collateral_auction( + id: AuctionId, + collateral_auction: CollateralAuctionItem>, + ) -> DispatchResult { + let last_bid = Self::get_last_bid(id); + + // collateral auction must not be in reverse stage + if let Some((_, bid_price)) = last_bid { + ensure!( + !collateral_auction.in_reverse_stage(bid_price), + Error::::InReverseStage, + ); + } + + // calculate how much collateral to offset target in settle price + let settle_price = + T::PriceSource::get_relative_price(T::GetUSSDCurrencyId::get(), collateral_auction.currency_id) + .ok_or(Error::::InvalidFeedPrice)?; + let confiscate_collateral_amount = if collateral_auction.always_forward() { + collateral_auction.amount + } else { + sp_std::cmp::min( + settle_price.saturating_mul_int(collateral_auction.target), + collateral_auction.amount, + ) + }; + let refund_collateral_amount = collateral_auction.amount.saturating_sub(confiscate_collateral_amount); + + // refund remain collateral to refund recipient from CDP treasury + T::EcdpUssdTreasury::withdraw_collateral( + &collateral_auction.refund_recipient, + collateral_auction.currency_id, + refund_collateral_amount, + )?; + + // if there's bid + if let Some((bidder, bid_price)) = last_bid { + // refund stable token to the bidder + T::EcdpUssdTreasury::issue_debit(&bidder, bid_price, false)?; + + // decrease account ref of bidder + frame_system::Pallet::::dec_consumers(&bidder); + } + + // decrease account ref of refund recipient + frame_system::Pallet::::dec_consumers(&collateral_auction.refund_recipient); + + // decrease total collateral and target in auction + TotalCollateralInAuction::::mutate(collateral_auction.currency_id, |balance| { + *balance = balance.saturating_sub(collateral_auction.amount) + }); + TotalTargetInAuction::::mutate(|balance| *balance = balance.saturating_sub(collateral_auction.target)); + + Ok(()) + } + + /// Return `true` if price increment rate is greater than or equal to + /// minimum. + /// + /// Formula: new_price - last_price >= + /// max(last_price, target_price) * minimum_increment + fn check_minimum_increment( + new_price: Balance, + last_price: Balance, + target_price: Balance, + minimum_increment: Rate, + ) -> bool { + if let (Some(target), Some(result)) = ( + minimum_increment.checked_mul_int(sp_std::cmp::max(target_price, last_price)), + new_price.checked_sub(last_price), + ) { + result >= target + } else { + false + } + } + + fn get_minimum_increment_size(now: BlockNumberFor, start_block: BlockNumberFor) -> Rate { + if now >= start_block + T::AuctionDurationSoftCap::get() { + // double the minimum increment size when reach soft cap + T::MinimumIncrementSize::get().saturating_mul(Rate::saturating_from_integer(2)) + } else { + T::MinimumIncrementSize::get() + } + } + + fn get_auction_time_to_close(now: BlockNumberFor, start_block: BlockNumberFor) -> BlockNumberFor { + if now >= start_block + T::AuctionDurationSoftCap::get() { + // halve the extended time of bid when reach soft cap + T::AuctionTimeToClose::get() + .checked_div(&2u32.into()) + .expect("cannot overflow with positive divisor; qed") + } else { + T::AuctionTimeToClose::get() + } + } + + /// Handles collateral auction new bid. Returns + /// `Ok(new_auction_end_time)` if bid accepted. + /// + /// Ensured atomic. + #[transactional] + pub fn collateral_auction_bid_handler( + now: BlockNumberFor, + id: AuctionId, + new_bid: (T::AccountId, Balance), + last_bid: Option<(T::AccountId, Balance)>, + ) -> sp_std::result::Result, DispatchError> { + let (new_bidder, new_bid_price) = new_bid; + ensure!(!new_bid_price.is_zero(), Error::::InvalidBidPrice); + + >::try_mutate_exists( + id, + |collateral_auction| -> sp_std::result::Result, DispatchError> { + let collateral_auction = collateral_auction.as_mut().ok_or(Error::::AuctionNotExists)?; + let last_bid_price = last_bid.clone().map_or(Zero::zero(), |(_, price)| price); // get last bid price + + // ensure new bid price is valid + ensure!( + Self::check_minimum_increment( + new_bid_price, + last_bid_price, + collateral_auction.target, + Self::get_minimum_increment_size(now, collateral_auction.start_time), + ), + Error::::InvalidBidPrice + ); + + let last_bidder = last_bid.as_ref().map(|(who, _)| who); + + let mut payment = collateral_auction.payment_amount(new_bid_price); + + // if there's bid before, return stablecoin from new bidder to last bidder + if let Some(last_bidder) = last_bidder { + let refund = collateral_auction.payment_amount(last_bid_price); + T::Currency::transfer(T::GetUSSDCurrencyId::get(), &new_bidder, last_bidder, refund)?; + + payment = payment + .checked_sub(refund) + // This should never fail because new bid payment are always greater or equal to last bid + // payment. + .ok_or(Error::::InvalidBidPrice)?; + } + + // transfer remain payment from new bidder to CDP treasury + T::EcdpUssdTreasury::deposit_surplus(&new_bidder, payment)?; + + // if collateral auction will be in reverse stage, refund collateral to it's + // origin from auction CDP treasury + if collateral_auction.in_reverse_stage(new_bid_price) { + let new_collateral_amount = collateral_auction.collateral_amount(last_bid_price, new_bid_price); + let refund_collateral_amount = collateral_auction.amount.saturating_sub(new_collateral_amount); + + if !refund_collateral_amount.is_zero() { + T::EcdpUssdTreasury::withdraw_collateral( + &(collateral_auction.refund_recipient), + collateral_auction.currency_id, + refund_collateral_amount, + )?; + + // update total collateral in auction after refund + TotalCollateralInAuction::::mutate(collateral_auction.currency_id, |balance| { + *balance = balance.saturating_sub(refund_collateral_amount) + }); + collateral_auction.amount = new_collateral_amount; + } + } + + Self::swap_bidders(&new_bidder, last_bidder); + + Ok(now + Self::get_auction_time_to_close(now, collateral_auction.start_time)) + }, + ) + } + + fn collateral_auction_end_handler( + auction_id: AuctionId, + collateral_auction: CollateralAuctionItem>, + last_bid: Option<(T::AccountId, Balance)>, + ) { + let (last_bidder, bid_price) = if let Some((bidder, bid_price)) = last_bid.clone() { + (Some(bidder), bid_price) + } else { + (None, Zero::zero()) + }; + + let swap_limit = if collateral_auction.always_forward() { + SwapLimit::ExactSupply(collateral_auction.amount, bid_price) + } else { + SwapLimit::ExactTarget(collateral_auction.amount, collateral_auction.target) + }; + + // if Edfis Exchange gives a price no less than the last_bidder for swap target + if let Ok((actual_supply_amount, actual_target_amount)) = + T::EcdpUssdTreasury::swap_collateral_to_stable(collateral_auction.currency_id, swap_limit, true) + { + Self::try_refund_collateral( + collateral_auction.currency_id, + &collateral_auction.refund_recipient, + collateral_auction.amount.saturating_sub(actual_supply_amount), + ); + Self::try_refund_bid(&collateral_auction, last_bid); + + // regardless of this swap_limit params. There will be excess stablecoins that + // need to be returned to the refund_recipient from cdp treasury account. + if let SwapLimit::ExactTarget(_, target_limit) = swap_limit { + if actual_target_amount > target_limit { + let _ = T::EcdpUssdTreasury::withdraw_surplus( + &collateral_auction.refund_recipient, + actual_target_amount.saturating_sub(target_limit), + ); + } + } + + Self::deposit_event(Event::DEXTakeCollateralAuction { + auction_id, + collateral_type: collateral_auction.currency_id, + collateral_amount: collateral_auction.amount, + supply_collateral_amount: actual_supply_amount, + target_stable_amount: actual_target_amount, + }); + } else if last_bidder.is_some() && bid_price >= collateral_auction.target { + // if these's bid which is gte target, auction should dealt by the last bidder. + let winner = last_bidder.expect("ensured last bidder not empty; qed"); + + Self::try_refund_collateral(collateral_auction.currency_id, &winner, collateral_auction.amount); + let payment_amount = collateral_auction.payment_amount(bid_price); + + Self::deposit_event(Event::CollateralAuctionDealt { + auction_id, + collateral_type: collateral_auction.currency_id, + collateral_amount: collateral_auction.amount, + winner, + payment_amount, + }); + } else { + // abort this collateral auction, these collateral can be reprocessed by cdp treausry. + Self::try_refund_bid(&collateral_auction, last_bid); + + Self::deposit_event(Event::CollateralAuctionAborted { + auction_id, + collateral_type: collateral_auction.currency_id, + collateral_amount: collateral_auction.amount, + target_stable_amount: collateral_auction.target, + refund_recipient: collateral_auction.refund_recipient.clone(), + }); + } + + // decrement recipient account reference + frame_system::Pallet::::dec_consumers(&collateral_auction.refund_recipient); + + // update auction records + TotalCollateralInAuction::::mutate(collateral_auction.currency_id, |balance| { + *balance = balance.saturating_sub(collateral_auction.amount) + }); + TotalTargetInAuction::::mutate(|balance| *balance = balance.saturating_sub(collateral_auction.target)); + } + + // Refund stable to the last_bidder. + fn try_refund_bid( + collateral_auction: &CollateralAuctionItem>, + last_bid: Option<(T::AccountId, Balance)>, + ) { + if let Some((bidder, bid_price)) = last_bid { + // If failed, just the bid did not get the stable. It can be fixed by treasury council. + let res = T::EcdpUssdTreasury::issue_debit(&bidder, collateral_auction.payment_amount(bid_price), false); + if let Err(e) = res { + log::warn!( + target: "auction-manager", + "issue_debit: failed to issue stable {:?} to {:?}: {:?}. \ + This is unexpected but should be safe", + collateral_auction.payment_amount(bid_price), bidder, e + ); + debug_assert!(false); + } + } + } + + // Refund collateral to the refund_recipient. + fn try_refund_collateral(collateral_type: CurrencyId, refund_recipient: &T::AccountId, refund_collateral: Balance) { + if !refund_collateral.is_zero() { + // If failed, just the refund_recipient did not get the refund collateral. It can be fixed by + // treasury council. + let res = T::EcdpUssdTreasury::withdraw_collateral(refund_recipient, collateral_type, refund_collateral); + if let Err(e) = res { + log::warn!( + target: "auction-manager", + "withdraw_collateral: failed to withdraw {:?} {:?} from CDP treasury to {:?}: {:?}. \ + This is unexpected but should be safe", + refund_collateral, collateral_type, refund_recipient, e + ); + debug_assert!(false); + } + } + } + + /// increment `new_bidder` reference and decrement `last_bidder` + /// reference if any + fn swap_bidders(new_bidder: &T::AccountId, last_bidder: Option<&T::AccountId>) { + if frame_system::Pallet::::inc_consumers(new_bidder).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + target: "auction-manager", + "inc_consumers: failed for {:?}. \ + This is impossible under normal circumstances.", + new_bidder.clone() + ); + } + + if let Some(who) = last_bidder { + frame_system::Pallet::::dec_consumers(who); + } + } +} + +impl AuctionHandler, AuctionId> for Pallet { + fn on_new_bid( + now: BlockNumberFor, + id: AuctionId, + new_bid: (T::AccountId, Balance), + last_bid: Option<(T::AccountId, Balance)>, + ) -> OnNewBidResult> { + let bid_result = Self::collateral_auction_bid_handler(now, id, new_bid, last_bid); + + match bid_result { + Ok(new_auction_end_time) => OnNewBidResult { + accept_bid: true, + auction_end_change: Change::NewValue(Some(new_auction_end_time)), + }, + Err(_) => OnNewBidResult { + accept_bid: false, + auction_end_change: Change::NoChange, + }, + } + } + + fn on_auction_ended(id: AuctionId, winner: Option<(T::AccountId, Balance)>) { + if let Some(collateral_auction) = >::take(id) { + Self::collateral_auction_end_handler(id, collateral_auction, winner.clone()); + } + + if let Some((bidder, _)) = &winner { + // decrease account ref of winner + frame_system::Pallet::::dec_consumers(bidder); + } + } +} + +impl EcdpAuctionsManager for Pallet { + type CurrencyId = CurrencyId; + type Balance = Balance; + type AuctionId = AuctionId; + + fn new_collateral_auction( + refund_recipient: &T::AccountId, + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + ) -> DispatchResult { + ensure!(!amount.is_zero(), Error::::InvalidAmount); + TotalCollateralInAuction::::try_mutate(currency_id, |total| -> DispatchResult { + *total = total.checked_add(amount).ok_or(Error::::InvalidAmount)?; + Ok(()) + })?; + + if !target.is_zero() { + // no-op if target is zero + TotalTargetInAuction::::try_mutate(|total| -> DispatchResult { + *total = total.checked_add(target).ok_or(Error::::InvalidAmount)?; + Ok(()) + })?; + } + + let start_time = >::block_number(); + // use start_time + AuctionDurationSoftCap as the initial end-time of collateral auction. + let end_time = start_time.saturating_add(T::AuctionDurationSoftCap::get()); + let auction_id = T::Auction::new_auction(start_time, Some(end_time))?; + + >::insert( + auction_id, + CollateralAuctionItem { + refund_recipient: refund_recipient.clone(), + currency_id, + initial_amount: amount, + amount, + target, + start_time, + }, + ); + + // increment recipient account reference + if frame_system::Pallet::::inc_consumers(refund_recipient).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + target: "auction-manager", + "Attempt to `inc_consumers` for {:?} failed. \ + This is unexpected but should be safe.", + refund_recipient.clone() + ); + } + + Self::deposit_event(Event::NewCollateralAuction { + auction_id, + collateral_type: currency_id, + collateral_amount: amount, + target_bid_price: target, + }); + Ok(()) + } + + fn cancel_auction(id: Self::AuctionId) -> DispatchResult { + let collateral_auction = >::take(id).ok_or(Error::::AuctionNotExists)?; + Self::cancel_collateral_auction(id, collateral_auction)?; + T::Auction::remove_auction(id); + Ok(()) + } + + fn get_total_collateral_in_auction(id: Self::CurrencyId) -> Self::Balance { + Self::total_collateral_in_auction(id) + } + + fn get_total_target_in_auction() -> Self::Balance { + Self::total_target_in_auction() + } +} diff --git a/blockchain/modules/ecdp-auctions/src/mock.rs b/blockchain/modules/ecdp-auctions/src/mock.rs new file mode 100644 index 00000000..02c4ae1d --- /dev/null +++ b/blockchain/modules/ecdp-auctions/src/mock.rs @@ -0,0 +1,278 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the auction manager module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, Nothing}, + PalletId, +}; +use frame_system::EnsureSignedBy; +pub use module_support::Price; +use module_support::SpecificJointsSwap; +use orml_traits::parameter_type_with_key; +use primitives::{TokenSymbol, TradingPair}; +use sp_runtime::{ + testing::{Header, TestXt}, + traits::{AccountIdConversion, IdentityLookup, One as OneT}, + BuildStorage, +}; +use sp_std::cell::RefCell; + +pub type AccountId = u128; +pub type BlockNumber = u64; +pub type AuctionId = u32; +pub type Amount = i64; + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; +pub const CAROL: AccountId = 3; +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const BTC: CurrencyId = CurrencyId::ForeignAsset(255); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); + +mod auction_manager { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; +} + +impl orml_auction::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AuctionId = AuctionId; + type Handler = EcdpAuctionsManagerModule; + type WeightInfo = (); +} + +ord_parameter_types! { + pub const One: AccountId = 1; +} + +parameter_types! { + pub const GetUSSDCurrencyId: CurrencyId = USSD; + pub const MaxAuctionsCount: u32 = 10_000; + pub const EcdpUssdTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account_truncating(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![EDF], + ]; +} + +impl module_ecdp_ussd_treasury::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Tokens; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type EcdpAuctionsManagerHandler = EcdpAuctionsManagerModule; + type UpdateOrigin = EnsureSignedBy; + type DEX = EdfisSwapModule; + type Swap = SpecificJointsSwap; + type MaxAuctionsCount = MaxAuctionsCount; + type PalletId = EcdpUssdTreasuryPalletId; + type TreasuryAccount = TreasuryAccount; + type WeightInfo = (); +} + +thread_local! { + static RELATIVE_PRICE: RefCell> = RefCell::new(Some(Price::one())); +} + +pub struct MockPriceSource; +impl MockPriceSource { + pub fn set_relative_price(price: Option) { + RELATIVE_PRICE.with(|v| *v.borrow_mut() = price); + } +} +impl PriceProvider for MockPriceSource { + fn get_relative_price(_base: CurrencyId, _quote: CurrencyId) -> Option { + RELATIVE_PRICE.with(|v| *v.borrow_mut()) + } + + fn get_price(_currency_id: CurrencyId) -> Option { + None + } +} + +parameter_types! { + pub const EdfisSwapPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const GetExchangeFee: (u32, u32) = (0, 100); + pub EnabledTradingPairs: Vec = vec![ + TradingPair::from_currency_ids(USSD, BTC).unwrap(), + TradingPair::from_currency_ids(EDF, BTC).unwrap(), + TradingPair::from_currency_ids(USSD, EDF).unwrap() + ]; +} + +impl module_edfis_swap_legacy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Tokens; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = ConstU32<4>; + type PalletId = EdfisSwapPalletId; + type Erc20InfoMapping = (); + type SwapDexIncentives = (); + type WeightInfo = (); + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU64<0>; + type OnLiquidityPoolUpdated = (); +} + +thread_local! { + static IS_SHUTDOWN: RefCell = RefCell::new(false); +} + +pub fn mock_shutdown() { + IS_SHUTDOWN.with(|v| *v.borrow_mut() = true) +} + +pub struct MockEcdpEmergencyShutdown; +impl EcdpEmergencyShutdown for MockEcdpEmergencyShutdown { + fn is_shutdown() -> bool { + IS_SHUTDOWN.with(|v| *v.borrow_mut()) + } +} + +parameter_types! { + pub MinimumIncrementSize: Rate = Rate::saturating_from_rational(1, 20); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Tokens; + type Auction = AuctionModule; + type MinimumIncrementSize = MinimumIncrementSize; + type AuctionTimeToClose = ConstU64<100>; + type AuctionDurationSoftCap = ConstU64<2000>; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type EcdpUssdTreasury = EcdpUssdTreasuryModule; + type PriceSource = MockPriceSource; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 + type EcdpEmergencyShutdown = MockEcdpEmergencyShutdown; + type WeightInfo = (); +} + +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + EcdpAuctionsManagerModule: auction_manager, + Tokens: orml_tokens, + AuctionModule: orml_auction, + EcdpUssdTreasuryModule: module_ecdp_ussd_treasury, + EdfisSwapModule: module_edfis_swap_legacy, + } +); + +pub type Extrinsic = TestXt; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, USSD, 1000), + (BOB, USSD, 1000), + (CAROL, USSD, 1000), + (ALICE, BTC, 1000), + (BOB, BTC, 1000), + (CAROL, BTC, 1000), + (ALICE, EDF, 1000), + (BOB, EDF, 1000), + (CAROL, EDF, 1000), + ], + } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + module_edfis_swap_legacy::GenesisConfig:: { + initial_listing_trading_pairs: vec![], + initial_enabled_trading_pairs: EnabledTradingPairs::get(), + initial_added_liquidity_pools: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } + + pub fn lots_of_accounts() -> Self { + let mut balances = Vec::new(); + for i in 0..1001 { + let account_id: AccountId = i; + balances.push((account_id, BTC, 1000)); + } + Self { balances } + } +} diff --git a/blockchain/modules/ecdp-auctions/src/tests.rs b/blockchain/modules/ecdp-auctions/src/tests.rs new file mode 100644 index 00000000..7be309b5 --- /dev/null +++ b/blockchain/modules/ecdp-auctions/src/tests.rs @@ -0,0 +1,738 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the auction manager module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{RuntimeCall as MockCall, RuntimeEvent, *}; +use module_support::SwapManager; +use sp_core::offchain::{testing, DbExternalities, OffchainDbExt, OffchainWorkerExt, StorageKind, TransactionPoolExt}; +use sp_io::offchain; +use sp_runtime::traits::One; + +fn run_to_block_offchain(n: u64) { + while System::block_number() < n { + System::set_block_number(System::block_number() + 1); + EcdpAuctionsManagerModule::offchain_worker(System::block_number()); + // this unlocks the concurrency storage lock so offchain_worker will fire next block + offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(LOCK_DURATION + 200))); + } +} + +#[test] +fn get_auction_time_to_close_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(EcdpAuctionsManagerModule::get_auction_time_to_close(2000, 1), 100); + assert_eq!(EcdpAuctionsManagerModule::get_auction_time_to_close(2001, 1), 50); + }); +} + +#[test] +fn collateral_auction_methods() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + assert_eq!( + AuctionModule::auctions(0), + Some(orml_traits::AuctionInfo { + bid: None, + start: 0, + end: Some(2000) + }) + ); + let collateral_auction_with_positive_target = EcdpAuctionsManagerModule::collateral_auctions(0).unwrap(); + assert!(!collateral_auction_with_positive_target.always_forward()); + assert!(!collateral_auction_with_positive_target.in_reverse_stage(99)); + assert!(collateral_auction_with_positive_target.in_reverse_stage(100)); + assert!(collateral_auction_with_positive_target.in_reverse_stage(101)); + assert_eq!(collateral_auction_with_positive_target.payment_amount(99), 99); + assert_eq!(collateral_auction_with_positive_target.payment_amount(100), 100); + assert_eq!(collateral_auction_with_positive_target.payment_amount(101), 100); + assert_eq!(collateral_auction_with_positive_target.collateral_amount(80, 100), 10); + assert_eq!(collateral_auction_with_positive_target.collateral_amount(100, 200), 5); + + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 0)); + let collateral_auction_with_zero_target = EcdpAuctionsManagerModule::collateral_auctions(1).unwrap(); + assert!(collateral_auction_with_zero_target.always_forward()); + assert!(!collateral_auction_with_zero_target.in_reverse_stage(0)); + assert!(!collateral_auction_with_zero_target.in_reverse_stage(100)); + assert_eq!(collateral_auction_with_zero_target.payment_amount(99), 99); + assert_eq!(collateral_auction_with_zero_target.payment_amount(101), 101); + assert_eq!(collateral_auction_with_zero_target.collateral_amount(100, 200), 10); + }); +} + +#[test] +fn new_collateral_auction_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + let ref_count_0 = System::consumers(&ALICE); + assert_noop!( + EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 0, 100), + Error::::InvalidAmount, + ); + + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule(crate::Event::NewCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 10, + target_bid_price: 100, + })); + + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 10); + assert_eq!(EcdpAuctionsManagerModule::total_target_in_auction(), 100); + assert_eq!(AuctionModule::auctions_index(), 1); + assert_eq!(System::consumers(&ALICE), ref_count_0 + 1); + + assert_noop!( + EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, Balance::max_value(), Balance::max_value()), + Error::::InvalidAmount, + ); + }); +} + +#[test] +fn collateral_auction_bid_handler_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EcdpAuctionsManagerModule::collateral_auction_bid_handler(1, 0, (BOB, 4), None), + Error::::AuctionNotExists, + ); + + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&ALICE, BTC, 10)); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1000); + + let bob_ref_count_0 = System::consumers(&BOB); + + assert_noop!( + EcdpAuctionsManagerModule::collateral_auction_bid_handler(1, 0, (BOB, 4), None), + Error::::InvalidBidPrice, + ); + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 1, + 0, + (BOB, 5), + None + )); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 5); + assert_eq!(Tokens::free_balance(USSD, &BOB), 995); + + let bob_ref_count_1 = System::consumers(&BOB); + assert_eq!(bob_ref_count_1, bob_ref_count_0 + 1); + let carol_ref_count_0 = System::consumers(&CAROL); + + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 2, + 0, + (CAROL, 10), + Some((BOB, 5)) + )); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 10); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1000); + assert_eq!(Tokens::free_balance(USSD, &CAROL), 990); + assert_eq!(EcdpAuctionsManagerModule::collateral_auctions(0).unwrap().amount, 10); + + let bob_ref_count_2 = System::consumers(&BOB); + assert_eq!(bob_ref_count_2, bob_ref_count_1 - 1); + let carol_ref_count_1 = System::consumers(&CAROL); + assert_eq!(carol_ref_count_1, carol_ref_count_0 + 1); + + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 3, + 0, + (BOB, 200), + Some((CAROL, 10)) + )); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 100); + assert_eq!(Tokens::free_balance(USSD, &BOB), 900); + assert_eq!(Tokens::free_balance(USSD, &CAROL), 1000); + assert_eq!(EcdpAuctionsManagerModule::collateral_auctions(0).unwrap().amount, 5); + + let bob_ref_count_3 = System::consumers(&BOB); + assert_eq!(bob_ref_count_3, bob_ref_count_2 + 1); + let carol_ref_count_2 = System::consumers(&CAROL); + assert_eq!(carol_ref_count_2, carol_ref_count_1 - 1); + }); +} + +#[test] +fn bid_when_soft_cap_for_collateral_auction_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + assert_eq!( + EcdpAuctionsManagerModule::on_new_bid(1, 0, (BOB, 100), None).auction_end_change, + Change::NewValue(Some(101)) + ); + assert!(!EcdpAuctionsManagerModule::on_new_bid(2001, 0, (CAROL, 10), Some((BOB, 5))).accept_bid,); + assert_eq!( + EcdpAuctionsManagerModule::on_new_bid(2001, 0, (CAROL, 15), Some((BOB, 5))).auction_end_change, + Change::NewValue(Some(2051)) + ); + }); +} + +#[test] +fn always_forward_collateral_auction_without_bid_taked_by_dex() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 100)); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + USSD, + 100, + 1000, + 0, + false + )); + + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction( + &EcdpUssdTreasuryModule::account_id(), + BTC, + 100, + 0 + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 100); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (100, 1000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + let ref_count_0 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + + EcdpAuctionsManagerModule::on_auction_ended(0, None); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule( + crate::Event::DEXTakeCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + supply_collateral_amount: 100, + target_stable_amount: 500, + }, + )); + + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (200, 500)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 500); + let ref_count_1 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + assert_eq!(ref_count_1, ref_count_0 - 1); + }); +} + +#[test] +fn always_forward_collateral_auction_without_bid_aborted() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 100)); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction( + &EcdpUssdTreasuryModule::account_id(), + BTC, + 100, + 0 + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 100); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + let ref_count_0 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + + EcdpAuctionsManagerModule::on_auction_ended(0, None); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule( + crate::Event::CollateralAuctionAborted { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + target_stable_amount: 0, + refund_recipient: EcdpUssdTreasuryModule::account_id(), + }, + )); + + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + let ref_count_1 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + assert_eq!(ref_count_1, ref_count_0 - 1); + }); +} + +#[test] +fn always_forward_collateral_auction_dealt() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 100)); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction( + &EcdpUssdTreasuryModule::account_id(), + BTC, + 100, + 0 + )); + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 1, + 0, + (BOB, 200), + None + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 100); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 200); + assert_eq!(Tokens::free_balance(BTC, &BOB), 1000); + let ref_count_0 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + let bob_ref_count_0 = System::consumers(&BOB); + + EcdpAuctionsManagerModule::on_auction_ended(0, Some((BOB, 200))); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule( + crate::Event::CollateralAuctionDealt { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + winner: BOB, + payment_amount: 200, + }, + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 200); + assert_eq!(Tokens::free_balance(BTC, &BOB), 1100); + let ref_count_1 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + let bob_ref_count_1 = System::consumers(&BOB); + assert_eq!(ref_count_1, ref_count_0 - 1); + assert_eq!(bob_ref_count_1, bob_ref_count_0 - 1); + }); +} + +#[test] +fn always_forward_collateral_auction_with_bid_taked_by_dex() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 100)); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + USSD, + 100, + 1000, + 0, + false + )); + + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction( + &EcdpUssdTreasuryModule::account_id(), + BTC, + 100, + 0 + )); + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 1, + 0, + (BOB, 500), + None + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 100); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (100, 1000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 500); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 500); + let ref_count_0 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + let bob_ref_count_0 = System::consumers(&BOB); + + EcdpAuctionsManagerModule::on_auction_ended(0, Some((BOB, 500))); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule( + crate::Event::DEXTakeCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + supply_collateral_amount: 100, + target_stable_amount: 500, + }, + )); + + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (200, 500)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 500); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1000); + let ref_count_1 = System::consumers(&EcdpUssdTreasuryModule::account_id()); + let bob_ref_count_1 = System::consumers(&BOB); + assert_eq!(ref_count_1, ref_count_0 - 1); + assert_eq!(bob_ref_count_1, bob_ref_count_0 - 1); + }); +} + +#[test] +fn reverse_collateral_auction_with_bid_taked_by_dex() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 100)); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + USSD, + 100, + 1000, + 0, + false + )); + + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 100, 200)); + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 1, + 0, + (BOB, 200), + None + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 100); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (100, 1000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 200); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 800); + assert_eq!(Tokens::free_balance(BTC, &ALICE), 1000); + let bob_ref_count_0 = System::consumers(&BOB); + + EcdpAuctionsManagerModule::on_auction_ended(0, Some((BOB, 200))); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule( + crate::Event::DEXTakeCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + supply_collateral_amount: 26, + target_stable_amount: 200, + }, + )); + + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (126, 800)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 400); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 200); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1000); + assert_eq!(Tokens::free_balance(BTC, &ALICE), 1074); + let bob_ref_count_1 = System::consumers(&BOB); + assert_eq!(bob_ref_count_1, bob_ref_count_0 - 1); + }); +} + +#[test] +fn reverse_collateral_auction_with_bid_dealt() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 100)); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 100, 200)); + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 1, + 0, + (BOB, 250), + None + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 80); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 80); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 200); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Tokens::free_balance(BTC, &BOB), 1000); + assert_eq!(Tokens::free_balance(USSD, &BOB), 800); + assert_eq!(Tokens::free_balance(BTC, &ALICE), 1020); + let alice_ref_count_0 = System::consumers(&ALICE); + + EcdpAuctionsManagerModule::on_auction_ended(0, Some((BOB, 250))); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule( + crate::Event::CollateralAuctionDealt { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 80, + winner: BOB, + payment_amount: 200, + }, + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 200); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 800); + assert_eq!(Tokens::free_balance(BTC, &BOB), 1080); + assert_eq!(Tokens::free_balance(BTC, &ALICE), 1020); + let alice_ref_count_1 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_1, alice_ref_count_0 - 1); + }); +} + +#[test] +fn collateral_auction_with_bid_aborted() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 100)); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + USSD, + 500, + 1000, + 0, + false + )); + + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 100, 200)); + assert_ok!(EcdpAuctionsManagerModule::collateral_auction_bid_handler( + 1, + 0, + (BOB, 180), + None + )); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 100); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (500, 1000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 180); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 820); + assert_eq!(Tokens::free_balance(BTC, &ALICE), 1000); + let alice_ref_count_0 = System::consumers(&ALICE); + + EcdpAuctionsManagerModule::on_auction_ended(0, Some((BOB, 180))); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule( + crate::Event::CollateralAuctionAborted { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + target_stable_amount: 200, + refund_recipient: ALICE, + }, + )); + + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (500, 1000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 180); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 180); + assert_eq!(Tokens::free_balance(USSD, &BOB), 1000); + assert_eq!(Tokens::free_balance(BTC, &ALICE), 1000); + let alice_ref_count_1 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_1, alice_ref_count_0 - 1); + }); +} + +#[test] +fn swap_bidders_works() { + ExtBuilder::default().build().execute_with(|| { + let alice_ref_count_0 = System::consumers(&ALICE); + let bob_ref_count_0 = System::consumers(&BOB); + + EcdpAuctionsManagerModule::swap_bidders(&BOB, None); + + let bob_ref_count_1 = System::consumers(&BOB); + assert_eq!(bob_ref_count_1, bob_ref_count_0 + 1); + + EcdpAuctionsManagerModule::swap_bidders(&ALICE, Some(&BOB)); + + let alice_ref_count_1 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_1, alice_ref_count_0 + 1); + let bob_ref_count_2 = System::consumers(&BOB); + assert_eq!(bob_ref_count_2, bob_ref_count_1 - 1); + + EcdpAuctionsManagerModule::swap_bidders(&BOB, Some(&ALICE)); + + let alice_ref_count_2 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_2, alice_ref_count_1 - 1); + let bob_ref_count_3 = System::consumers(&BOB); + assert_eq!(bob_ref_count_3, bob_ref_count_2 + 1); + }); +} + +#[test] +fn cancel_collateral_auction_failed() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 10)); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + MockPriceSource::set_relative_price(None); + assert_noop!( + EcdpAuctionsManagerModule::cancel_collateral_auction(0, EcdpAuctionsManagerModule::collateral_auctions(0).unwrap()), + Error::::InvalidFeedPrice, + ); + MockPriceSource::set_relative_price(Some(Price::one())); + + assert_ok!(AuctionModule::bid(RuntimeOrigin::signed(ALICE), 0, 100)); + let collateral_auction = EcdpAuctionsManagerModule::collateral_auctions(0).unwrap(); + assert!(!collateral_auction.always_forward()); + assert_eq!(EcdpAuctionsManagerModule::get_last_bid(0), Some((ALICE, 100))); + assert!(collateral_auction.in_reverse_stage(100)); + assert_noop!( + EcdpAuctionsManagerModule::cancel_collateral_auction(0, collateral_auction), + Error::::InReverseStage, + ); + }); +} + +#[test] +fn cancel_collateral_auction_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CAROL, BTC, 10)); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 10); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 10); + assert_eq!(EcdpAuctionsManagerModule::total_target_in_auction(), 100); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_ok!(AuctionModule::bid(RuntimeOrigin::signed(BOB), 0, 80)); + assert_eq!(Tokens::free_balance(USSD, &BOB), 920); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 10); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 80); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Tokens::free_balance(USSD, &BOB), 920); + + let alice_ref_count_0 = System::consumers(&ALICE); + let bob_ref_count_0 = System::consumers(&BOB); + + mock_shutdown(); + assert_ok!(EcdpAuctionsManagerModule::cancel(RuntimeOrigin::none(), 0)); + System::assert_last_event(RuntimeEvent::EcdpAuctionsManagerModule(crate::Event::CancelAuction { + auction_id: 0, + })); + + assert_eq!(Tokens::free_balance(USSD, &BOB), 1000); + assert_eq!(EcdpAuctionsManagerModule::total_collateral_in_auction(BTC), 0); + assert_eq!(EcdpAuctionsManagerModule::total_target_in_auction(), 0); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 10); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 80); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 80); + assert!(!EcdpAuctionsManagerModule::collateral_auctions(0).is_some()); + assert!(!AuctionModule::auction_info(0).is_some()); + + let alice_ref_count_1 = System::consumers(&ALICE); + assert_eq!(alice_ref_count_1, alice_ref_count_0 - 1); + let bob_ref_count_1 = System::consumers(&BOB); + assert_eq!(bob_ref_count_1, bob_ref_count_0 - 1); + }); +} + +#[test] +fn offchain_worker_cancels_auction_in_shutdown() { + let (offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain)); + + ext.execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + assert!(EcdpAuctionsManagerModule::collateral_auctions(0).is_some()); + run_to_block_offchain(2); + // offchain worker does not have any tx because shutdown is false + assert!(!MockEcdpEmergencyShutdown::is_shutdown()); + assert!(pool_state.write().transactions.pop().is_none()); + mock_shutdown(); + assert!(MockEcdpEmergencyShutdown::is_shutdown()); + + // now offchain worker will cancel auction as shutdown is true + run_to_block_offchain(3); + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::EcdpAuctionsManagerModule(crate::Call::cancel { id: auction_id }) = tx.call { + assert_ok!(EcdpAuctionsManagerModule::cancel(RuntimeOrigin::none(), auction_id)); + } + + // auction is canceled + assert!(EcdpAuctionsManagerModule::collateral_auctions(0).is_none()); + assert!(pool_state.write().transactions.pop().is_none()); + }); +} + +#[test] +fn offchain_worker_max_iterations_check() { + let (mut offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + // sets max iterations value to 1 + offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&BOB, BTC, 10, 100)); + assert!(EcdpAuctionsManagerModule::collateral_auctions(1).is_some()); + assert!(EcdpAuctionsManagerModule::collateral_auctions(0).is_some()); + mock_shutdown(); + assert!(MockEcdpEmergencyShutdown::is_shutdown()); + + run_to_block_offchain(2); + // now offchain worker will cancel one auction but the other one will cancel next block + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::EcdpAuctionsManagerModule(crate::Call::cancel { id: auction_id }) = tx.call { + assert_ok!(EcdpAuctionsManagerModule::cancel(RuntimeOrigin::none(), auction_id)); + } + assert!( + EcdpAuctionsManagerModule::collateral_auctions(1).is_some() + || EcdpAuctionsManagerModule::collateral_auctions(0).is_some() + ); + // only one auction canceled so offchain tx pool is empty + assert!(pool_state.write().transactions.pop().is_none()); + + run_to_block_offchain(3); + // now offchain worker will cancel the next auction + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::EcdpAuctionsManagerModule(crate::Call::cancel { id: auction_id }) = tx.call { + assert_ok!(EcdpAuctionsManagerModule::cancel(RuntimeOrigin::none(), auction_id)); + } + assert!(EcdpAuctionsManagerModule::collateral_auctions(1).is_none()); + assert!(EcdpAuctionsManagerModule::collateral_auctions(0).is_none()); + assert!(pool_state.write().transactions.pop().is_none()); + }); +} + +#[test] +fn offchain_default_max_iterator_works() { + let (mut offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::lots_of_accounts().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + // checks that max iterations is stored as none + assert!(offchain + .local_storage_get(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS) + .is_none()); + + for i in 0..1001 { + let account_id: AccountId = i; + assert_ok!(EcdpAuctionsManagerModule::new_collateral_auction(&account_id, BTC, 1, 10)); + } + + mock_shutdown(); + run_to_block_offchain(2); + // should only run 1000 iterations stopping due to DEFAULT_MAX_ITERATION + assert_eq!(pool_state.write().transactions.len(), 1000); + run_to_block_offchain(3); + // next block iterator starts where it left off and adds the final account to tx pool + assert_eq!(pool_state.write().transactions.len(), 1001); + }); +} diff --git a/blockchain/modules/ecdp-auctions/src/weights.rs b/blockchain/modules/ecdp-auctions/src/weights.rs new file mode 100644 index 00000000..9c8cda09 --- /dev/null +++ b/blockchain/modules/ecdp-auctions/src/weights.rs @@ -0,0 +1,72 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for module_auction_manager +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum-node +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_auction_manager +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchains/modules/auction-manager/src/weights.rs +// --template=.maintain/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_auction_manager. +pub trait WeightInfo { + fn cancel_collateral_auction() -> Weight; +} + +/// Weights for module_auction_manager using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn cancel_collateral_auction() -> Weight { + Weight::from_parts(78_000_000, 0) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn cancel_collateral_auction() -> Weight { + Weight::from_parts(78_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } +} diff --git a/blockchain/modules/ecdp-ussd-engine/Cargo.toml b/blockchain/modules/ecdp-ussd-engine/Cargo.toml index e7a05660..d3a6438e 100644 --- a/blockchain/modules/ecdp-ussd-engine/Cargo.toml +++ b/blockchain/modules/ecdp-ussd-engine/Cargo.toml @@ -5,46 +5,54 @@ authors = ["Setheum Labs"] edition = "2021" [dependencies] +log = { workspace = true } +rand_chacha = { workspace = true } +parity-scale-codec = { workspace = true } scale-info = { workspace = true } -serde = { workspace = true, optional = true } -parity-scale-codec = { version = "3.0.0", default-features = false, features = ["max-encoded-len"] } -sp-runtime = { workspace = true } -sp-io = { workspace = true } -sp-std = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } - -primitives = { package = "setheum-primitives", path = "../primitives", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -orml-traits = { path = "../submodules/orml/traits", default-features = false } +sp-application-crypto = {workspace = true } +orml-traits = { workspace = true } +orml-utilities = { workspace = true } +sp-io = {workspace = true } +sp-runtime = {workspace = true } +sp-std = {workspace = true } +module-support = { workspace = true } +module-ecdp-ussd-loans = { workspace = true } +primitives = { workspace = true } [dev-dependencies] sp-core = { workspace = true, features = ["std"] } -pallet-balances = { workspace = true } -orml-tokens = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +orml-currencies = { workspace = true, features = ["std"] } +module-edfis_swap_legacy = { workspace = true, features = ["std"] } +module-ecdp-ussd-treasury = { workspace = true, features = ["std"] } +module-evm-accounts = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } +module-evm = { workspace = true, features = ["std"] } +module-evm-bridge = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ - "scale-info/std", - "serde", "parity-scale-codec/std", - "sp-runtime/std", - "sp-std/std", - "sp-io/std", "frame-support/std", "frame-system/std", - "primitives/std", - "support/std", + "module-ecdp-ussd-loans/std", "orml-traits/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "orml-utilities/std", + "primitives/std", + "rand_chacha/std", + "scale-info/std", + "sp-application-crypto/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "sp-runtime/try-runtime", + "module-ecdp-ussd-loans/try-runtime", ] diff --git a/blockchain/modules/ecdp-ussd-engine/src/lib.rs b/blockchain/modules/ecdp-ussd-engine/src/lib.rs new file mode 100644 index 00000000..5fd17ba0 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-engine/src/lib.rs @@ -0,0 +1,1482 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! # ECDP USSD Engine Module +//! +//! ## Overview +//! +//! The core module of Slick USD ECDP Protocol. ECDP USSD Engine is responsible for handling +//! internal processes about CDPs, including liquidation, settlement and risk +//! management. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] +#![allow(clippy::upper_case_acronyms)] + +use frame_support::{pallet_prelude::*, traits::UnixTime, transactional, BoundedVec, PalletId}; +use frame_system::{ + offchain::{SendTransactionTypes, SubmitTransaction}, + pallet_prelude::*, +}; +use module_support::{ + AddressMapping, EcdpUssdTreasury, EcdpUssdTreasuryExtended, SwapManager, EVMBridge, EcdpEmergencyShutdown, ExchangeRate, + FractionalRate, InvokeContext, LiquidateCollateral, LiquidationEvmBridge, Price, PriceProvider, Rate, Ratio, + EcdpUssdRiskManager, Swap, SwapLimit, +}; +use orml_traits::{Change, GetByKey, MultiCurrency}; +use orml_utilities::OffchainErr; +use parity_scale_codec::MaxEncodedLen; +use primitives::{evm::EvmAddress, Amount, Balance, CurrencyId, EcdpPosition}; +use rand_chacha::{ + rand_core::{RngCore, SeedableRng}, + ChaChaRng, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + offchain::{ + storage::StorageValueRef, + storage_lock::{StorageLock, Time}, + Duration, + }, + traits::{ + AccountIdConversion, BlockNumberProvider, Bounded, One, Saturating, StaticLookup, UniqueSaturatedInto, Zero, + }, + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, ValidTransaction, + }, + ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +pub const OFFCHAIN_WORKER_DATA: &[u8] = b"setheum/ecdp-ussd-engine/data/"; +pub const OFFCHAIN_WORKER_LOCK: &[u8] = b"setheum/ecdp-ussd-engine/lock/"; +pub const OFFCHAIN_WORKER_MAX_ITERATIONS: &[u8] = b"setheum/ecdp-ussd-engine/max-iterations/"; +pub const LOCK_DURATION: u64 = 100; +pub const DEFAULT_MAX_ITERATIONS: u32 = 1000; + +pub type LoansOf = module_ecdp_ussd_loans::Pallet; +pub type CurrencyOf = ::Currency; + +/// Risk management params +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, Default, TypeInfo, MaxEncodedLen)] +pub struct RiskManagementParams { + /// Maximum total debit value generated from it, when reach the hard + /// cap, ECDP's owner cannot issue more stablecoin under the collateral + /// type. + pub maximum_total_debit_value: Balance, + + /// Liquidation ratio, when the collateral ratio of + /// ECDP under this collateral type is below the liquidation ratio, this + /// ECDP is unsafe and can be liquidated. `None` value means not set + pub liquidation_ratio: Option, + + /// Liquidation penalty rate, when liquidation occurs, + /// ECDP will be deducted an additional penalty base on the product of + /// penalty rate and debit value. `None` value means not set + pub liquidation_penalty: Option, + + /// Required collateral ratio, if it's set, cannot adjust the position + /// of ECDP so that the current collateral ratio is lower than the + /// required collateral ratio. `None` value means not set + pub required_collateral_ratio: Option, +} + +// typedef to help polkadot.js disambiguate Change with different generic +// parameters +type ChangeOptionRate = Change>; +type ChangeOptionRatio = Change>; +type ChangeBalance = Change; + +/// Status of SCDP +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub enum CDPStatus { + Safe, + Unsafe, + ChecksFailed(DispatchError), +} + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config + module_ecdp_ussd_loans::Config + SendTransactionTypes> { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The origin which may update risk management parameters. Root can + /// always do this. + type UpdateOrigin: EnsureOrigin; + + /// The default liquidation ratio for all collateral types of ECDP + #[pallet::constant] + type DefaultLiquidationRatio: Get; + + /// The default debit exchange rate for all collateral types + #[pallet::constant] + type DefaultDebitExchangeRate: Get; + + /// The default liquidation penalty rate when liquidate unsafe ECDP + #[pallet::constant] + type DefaultLiquidationPenalty: Get; + + /// The minimum debit value to avoid debit dust + #[pallet::constant] + type MinimumDebitValue: Get; + + /// Gets the minimum collateral value for the given currency. + type MinimumCollateralAmount: GetByKey; + + /// Stablecoin currency id + #[pallet::constant] + type GetUSSDCurrencyId: Get; + + /// When swap with DEX, the acceptable max slippage for the price from oracle. + #[pallet::constant] + type MaxSwapSlippageCompareToOracle: Get; + + /// The ECDP USSD treasury to maintain bad debts and surplus generated by CDPs + type EcdpUssdTreasury: EcdpUssdTreasuryExtended; + + /// The price source of all types of currencies related to ECDP + type PriceSource: PriceProvider; + + /// A configuration for base priority of unsigned transactions. + /// + /// This is exposed so that it can be tuned for particular runtime, when + /// multiple modules send unsigned transactions. + #[pallet::constant] + type UnsignedPriority: Get; + + /// Emergency shutdown. + type EcdpEmergencyShutdown: EcdpEmergencyShutdown; + + /// Time used for computing era duration. + /// + /// It is guaranteed to start being called from the first `on_finalize`. + /// Thus value at genesis is not used. + type UnixTime: UnixTime; + + /// Currency for transfer assets + type Currency: MultiCurrency; + + /// Dex + type DEX: SwapManager; + + /// Swap + type Swap: Swap; + + /// The origin for liquidation contracts registering and deregistering. + type LiquidationContractsUpdateOrigin: EnsureOrigin; + + /// When settle collateral with smart contracts, the acceptable max slippage for the price + /// from oracle. + #[pallet::constant] + type MaxLiquidationContractSlippage: Get; + + #[pallet::constant] + type MaxLiquidationContracts: Get; + + type LiquidationEvmBridge: LiquidationEvmBridge; + + #[pallet::constant] + type PalletId: Get; + + type EvmAddressMapping: AddressMapping; + + /// Evm Bridge for getting info of contracts from the EVM. + type EVMBridge: EVMBridge; + + /// Evm Origin account when settle ERC20 type ECDP + type SettleErc20EvmOrigin: Get; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The total debit value of specific collateral type already exceed the + /// hard cap + ExceedDebitValueHardCap, + /// The collateral ratio below the required collateral ratio + BelowRequiredCollateralRatio, + /// The collateral ratio below the liquidation ratio + BelowLiquidationRatio, + /// The ECDP must be unsafe status + MustBeUnsafe, + /// The ECDP must be safe status + MustBeSafe, + /// Invalid collateral type + InvalidCollateralType, + /// Remain debit value in ECDP below the dust amount + RemainDebitValueTooSmall, + /// Remain collateral value in ECDP below the dust amount. + /// Withdraw all collateral or leave more than the minimum. + CollateralAmountBelowMinimum, + /// Feed price is invalid + InvalidFeedPrice, + /// No debit value in ECDP so that it cannot be settled + NoDebitValue, + /// System has already been shutdown + AlreadyShutdown, + /// Must after system shutdown + MustAfterShutdown, + /// Collateral in ECDP is not enough + CollateralNotEnough, + /// debit value decrement is not enough + NotEnoughDebitDecrement, + /// convert debit value to debit balance failed + ConvertDebitBalanceFailed, + /// Collateral liquidation failed. + LiquidationFailed, + /// Exceeds `T::MaxLiquidationContracts`. + TooManyLiquidationContracts, + /// Collateral ERC20 contract not found. + CollateralContractNotFound, + /// Invalid rate + InvalidRate, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Liquidate the unsafe ECDP. + LiquidateUnsafeCDP { + collateral_type: CurrencyId, + owner: T::AccountId, + collateral_amount: Balance, + bad_debt_value: Balance, + target_amount: Balance, + }, + /// Settle the ECDP that has debit. + SettleCDPInDebit { + collateral_type: CurrencyId, + owner: T::AccountId, + }, + /// Directly close the ECDP that has debit by handle debit with DEX. + CloseCDPInDebitByDEX { + collateral_type: CurrencyId, + owner: T::AccountId, + sold_collateral_amount: Balance, + refund_collateral_amount: Balance, + debit_value: Balance, + }, + /// The liquidation fee for specific collateral type updated. + LiquidationRatioUpdated { + collateral_type: CurrencyId, + new_liquidation_ratio: Option, + }, + /// The liquidation penalty rate for specific collateral type updated. + LiquidationPenaltyUpdated { + collateral_type: CurrencyId, + new_liquidation_penalty: Option, + }, + /// The required collateral penalty rate for specific collateral type updated. + RequiredCollateralRatioUpdated { + collateral_type: CurrencyId, + new_required_collateral_ratio: Option, + }, + /// The hard cap of total debit value for specific collateral type updated. + MaximumTotalDebitValueUpdated { + collateral_type: CurrencyId, + new_total_debit_value: Balance, + }, + /// A new liquidation contract is registered. + LiquidationContractRegistered { address: EvmAddress }, + /// A new liquidation contract is deregistered. + LiquidationContractDeregistered { address: EvmAddress }, + } + + /// Mapping from collateral type to its exchange rate of debit units and + /// debit value + /// + /// DebitExchangeRate: CurrencyId => Option + #[pallet::storage] + #[pallet::getter(fn debit_exchange_rate)] + pub type DebitExchangeRate = StorageMap<_, Twox64Concat, CurrencyId, ExchangeRate, OptionQuery>; + + /// Mapping from valid collateral type to its risk management params + /// + /// CollateralParams: CurrencyId => Option + #[pallet::storage] + #[pallet::getter(fn collateral_params)] + pub type CollateralParams = StorageMap<_, Twox64Concat, CurrencyId, RiskManagementParams, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn liquidation_contracts)] + pub type LiquidationContracts = + StorageValue<_, BoundedVec, ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + #[allow(clippy::type_complexity)] + pub collaterals_params: Vec<( + CurrencyId, + Option, + Option, + Option, + Option, + Balance, + )>, + pub _phantom: PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + self.collaterals_params.iter().for_each( + |( + currency_id, + liquidation_ratio, + liquidation_penalty, + required_collateral_ratio, + maximum_total_debit_value, + )| { + CollateralParams::::insert( + currency_id, + RiskManagementParams { + maximum_total_debit_value: *maximum_total_debit_value, + liquidation_ratio: *liquidation_ratio, + liquidation_penalty: liquidation_penalty + .map(|v| FractionalRate::try_from(v).expect("liquidation_penalty out of bound")), + required_collateral_ratio: *required_collateral_ratio, + }, + ); + }, + ); + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet { + /// Runs after every block. Start offchain worker to check ECDP and + /// submit unsigned tx to trigger liquidation or settlement. + fn offchain_worker(now: BlockNumberFor) { + if let Err(e) = Self::_offchain_worker() { + log::info!( + target: "ecdp-ussd-engine offchain worker", + "cannot run offchain worker at {:?}: {:?}", + now, + e, + ); + } else { + log::debug!( + target: "ecdp-ussd-engine offchain worker", + "offchain worker start at block: {:?} already done!", + now, + ); + } + } + } + + #[pallet::call] + impl Pallet { + /// Liquidate unsafe ECDP + /// + /// The dispatch origin of this call must be _None_. + /// + /// - `currency_id`: ECDP's collateral type. + /// - `who`: ECDP's owner. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::liquidate_by_auction(::EcdpUssdTreasury::max_auction()))] + pub fn liquidate( + origin: OriginFor, + currency_id: CurrencyId, + who: ::Source, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + let who = T::Lookup::lookup(who)?; + ensure!(!T::EcdpEmergencyShutdown::is_shutdown(), Error::::AlreadyShutdown); + let consumed_weight: Weight = Self::liquidate_unsafe_cdp(who, currency_id)?; + Ok(Some(consumed_weight).into()) + } + + /// Settle ECDP that has debit after system shutdown + /// + /// The dispatch origin of this call must be _None_. + /// + /// - `currency_id`: ECDP's collateral type. + /// - `who`: ECDP's owner. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::settle())] + pub fn settle( + origin: OriginFor, + currency_id: CurrencyId, + who: ::Source, + ) -> DispatchResult { + ensure_none(origin)?; + let who = T::Lookup::lookup(who)?; + ensure!(T::EcdpEmergencyShutdown::is_shutdown(), Error::::MustAfterShutdown); + Self::settle_cdp_has_debit(who, currency_id)?; + Ok(()) + } + + /// Update parameters related to risk management of ECDP under specific + /// collateral type + /// + /// The dispatch origin of this call must be `UpdateOrigin`. + /// + /// - `currency_id`: collateral type. + /// - `liquidation_ratio`: liquidation ratio, `None` means do not update, `Some(None)` means + /// update it to `None`. + /// - `liquidation_penalty`: liquidation penalty, `None` means do not update, `Some(None)` + /// means update it to `None`. + /// - `required_collateral_ratio`: required collateral ratio, `None` means do not update, + /// `Some(None)` means update it to `None`. + /// - `maximum_total_debit_value`: maximum total debit value. + #[pallet::call_index(2)] + #[pallet::weight((::WeightInfo::set_collateral_params(), DispatchClass::Operational))] + pub fn set_collateral_params( + origin: OriginFor, + currency_id: CurrencyId, + liquidation_ratio: ChangeOptionRatio, + liquidation_penalty: ChangeOptionRate, + required_collateral_ratio: ChangeOptionRatio, + maximum_total_debit_value: ChangeBalance, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + let mut collateral_params = Self::collateral_params(currency_id).unwrap_or_default(); + if let Change::NewValue(update) = liquidation_ratio { + collateral_params.liquidation_ratio = update; + Self::deposit_event(Event::LiquidationRatioUpdated { + collateral_type: currency_id, + new_liquidation_ratio: update, + }); + } + if let Change::NewValue(maybe_rate) = liquidation_penalty { + match (collateral_params.liquidation_penalty.as_mut(), maybe_rate) { + (Some(existing), Some(rate)) => existing.try_set(rate).map_err(|_| Error::::InvalidRate)?, + (None, Some(rate)) => { + let fractional_rate = FractionalRate::try_from(rate).map_err(|_| Error::::InvalidRate)?; + collateral_params.liquidation_penalty = Some(fractional_rate); + } + _ => collateral_params.liquidation_penalty = None, + } + Self::deposit_event(Event::LiquidationPenaltyUpdated { + collateral_type: currency_id, + new_liquidation_penalty: maybe_rate, + }); + } + if let Change::NewValue(update) = required_collateral_ratio { + collateral_params.required_collateral_ratio = update; + Self::deposit_event(Event::RequiredCollateralRatioUpdated { + collateral_type: currency_id, + new_required_collateral_ratio: update, + }); + } + if let Change::NewValue(val) = maximum_total_debit_value { + collateral_params.maximum_total_debit_value = val; + Self::deposit_event(Event::MaximumTotalDebitValueUpdated { + collateral_type: currency_id, + new_total_debit_value: val, + }); + } + CollateralParams::::insert(currency_id, collateral_params); + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::register_liquidation_contract())] + pub fn register_liquidation_contract(origin: OriginFor, address: EvmAddress) -> DispatchResult { + T::LiquidationContractsUpdateOrigin::ensure_origin(origin)?; + LiquidationContracts::::try_append(address).map_err(|()| Error::::TooManyLiquidationContracts)?; + Self::deposit_event(Event::LiquidationContractRegistered { address }); + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::deregister_liquidation_contract())] + pub fn deregister_liquidation_contract(origin: OriginFor, address: EvmAddress) -> DispatchResult { + T::LiquidationContractsUpdateOrigin::ensure_origin(origin)?; + LiquidationContracts::::mutate(|contracts| { + contracts.retain(|c| c != &address); + }); + Self::deposit_event(Event::LiquidationContractDeregistered { address }); + Ok(()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + match call { + Call::liquidate { currency_id, who } => { + let account = T::Lookup::lookup(who.clone())?; + let EcdpPosition { collateral, debit } = >::positions(currency_id, &account); + if !matches!( + Self::check_cdp_status(*currency_id, collateral, debit), + CDPStatus::Unsafe + ) || T::EcdpEmergencyShutdown::is_shutdown() + { + return InvalidTransaction::Stale.into(); + } + + ValidTransaction::with_tag_prefix("EcdpUssdEngineOffchainWorker") + .priority(T::UnsignedPriority::get()) + .and_provides((>::block_number(), currency_id, who)) + .longevity(64_u64) + .propagate(true) + .build() + } + Call::settle { currency_id, who } => { + let account = T::Lookup::lookup(who.clone())?; + let EcdpPosition { debit, .. } = >::positions(currency_id, account); + if debit.is_zero() || !T::EcdpEmergencyShutdown::is_shutdown() { + return InvalidTransaction::Stale.into(); + } + + ValidTransaction::with_tag_prefix("EcdpUssdEngineOffchainWorker") + .priority(T::UnsignedPriority::get()) + .and_provides((currency_id, who)) + .longevity(64_u64) + .propagate(true) + .build() + } + _ => InvalidTransaction::Call.into(), + } + } + } +} + +impl Pallet { + fn submit_unsigned_liquidation_tx(currency_id: CurrencyId, who: T::AccountId) { + let who = T::Lookup::unlookup(who); + let call = Call::::liquidate { + currency_id, + who: who.clone(), + }; + if SubmitTransaction::>::submit_unsigned_transaction(call.into()).is_err() { + log::info!( + target: "ecdp-ussd-engine offchain worker", + "submit unsigned liquidation tx for \nCDP - AccountId {:?} CurrencyId {:?} \nfailed!", + who, currency_id, + ); + } + } + + fn submit_unsigned_settlement_tx(currency_id: CurrencyId, who: T::AccountId) { + let who = T::Lookup::unlookup(who); + let call = Call::::settle { + currency_id, + who: who.clone(), + }; + if SubmitTransaction::>::submit_unsigned_transaction(call.into()).is_err() { + log::info!( + target: "ecdp-ussd-engine offchain worker", + "submit unsigned settlement tx for \nCDP - AccountId {:?} CurrencyId {:?} \nfailed!", + who, currency_id, + ); + } + } + + fn _offchain_worker() -> Result<(), OffchainErr> { + let collateral_currency_ids = Self::get_collateral_currency_ids(); + if collateral_currency_ids.len().is_zero() { + return Ok(()); + } + + // check if we are a potential validator + if !sp_io::offchain::is_validator() { + return Err(OffchainErr::NotValidator); + } + + // acquire offchain worker lock + let lock_expiration = Duration::from_millis(LOCK_DURATION); + let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); + let mut guard = lock.try_lock().map_err(|_| OffchainErr::OffchainLock)?; + let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + + // get to_be_continue record + let (collateral_position, start_key) = + if let Ok(Some((last_collateral_position, maybe_last_iterator_previous_key))) = + to_be_continue.get::<(u32, Option>)>() + { + (last_collateral_position, maybe_last_iterator_previous_key) + } else { + let mut rng = ChaChaRng::from_seed(sp_io::offchain::random_seed()); + (pick_u32(&mut rng, collateral_currency_ids.len() as u32), None) + }; + + // get the max iterations config + let max_iterations = StorageValueRef::persistent(OFFCHAIN_WORKER_MAX_ITERATIONS) + .get::() + .unwrap_or(Some(DEFAULT_MAX_ITERATIONS)) + .unwrap_or(DEFAULT_MAX_ITERATIONS); + + let currency_id = match collateral_currency_ids.get(collateral_position as usize) { + Some(currency_id) => *currency_id, + None => { + log::debug!( + target: "ecdp-ussd-engine offchain worker", + "collateral_currency was removed, need to reset the offchain worker: collateral_position is {:?}, collateral_currency_ids: {:?}", + collateral_position, + collateral_currency_ids + ); + to_be_continue.set(&(0, Option::>::None)); + return Ok(()); + } + }; + + let is_shutdown = T::EcdpEmergencyShutdown::is_shutdown(); + + // If start key is Some(value) continue iterating from that point in storage otherwise start + // iterating from the beginning of > + let mut map_iterator = match start_key.clone() { + Some(key) => >::iter_prefix_from(currency_id, key), + None => >::iter_prefix(currency_id), + }; + + let mut finished = true; + let mut iteration_count = 0; + let iteration_start_time = sp_io::offchain::timestamp(); + + #[allow(clippy::while_let_on_iterator)] + while let Some((who, EcdpPosition { collateral, debit })) = map_iterator.next() { + if !is_shutdown + && matches!( + Self::check_cdp_status(currency_id, collateral, debit), + CDPStatus::Unsafe + ) { + // liquidate unsafe CDPs before emergency shutdown occurs + Self::submit_unsigned_liquidation_tx(currency_id, who); + } else if is_shutdown && !debit.is_zero() { + // settle CDPs with debit after emergency shutdown occurs. + Self::submit_unsigned_settlement_tx(currency_id, who); + } + + iteration_count += 1; + if iteration_count == max_iterations { + finished = false; + break; + } + // extend offchain worker lock + guard.extend_lock().map_err(|_| OffchainErr::OffchainLock)?; + } + let iteration_end_time = sp_io::offchain::timestamp(); + log::debug!( + target: "ecdp-ussd-engine offchain worker", + "iteration info:\n max iterations is {:?}\n currency id: {:?}, start key: {:?}, iterate count: {:?}\n iteration start at: {:?}, end at: {:?}, execution time: {:?}\n", + max_iterations, + currency_id, + start_key, + iteration_count, + iteration_start_time, + iteration_end_time, + iteration_end_time.diff(&iteration_start_time) + ); + + // if iteration for map storage finished, clear to be continue record + // otherwise, update to be continue record + if finished { + let next_collateral_position = + if collateral_position < collateral_currency_ids.len().saturating_sub(1) as u32 { + collateral_position + 1 + } else { + 0 + }; + to_be_continue.set(&(next_collateral_position, Option::>::None)); + } else { + to_be_continue.set(&(collateral_position, Some(map_iterator.last_raw_key()))); + } + + // Consume the guard but **do not** unlock the underlying lock. + guard.forget(); + + Ok(()) + } + + pub fn check_cdp_status(currency_id: CurrencyId, collateral_amount: Balance, debit_amount: Balance) -> CDPStatus { + let stable_currency_id = T::GetUSSDCurrencyId::get(); + if let Some(feed_price) = T::PriceSource::get_relative_price(currency_id, stable_currency_id) { + let collateral_ratio = + Self::calculate_collateral_ratio(currency_id, collateral_amount, debit_amount, feed_price); + match Self::get_liquidation_ratio(currency_id) { + Ok(liquidation_ratio) => { + if collateral_ratio < liquidation_ratio { + CDPStatus::Unsafe + } else { + CDPStatus::Safe + } + } + Err(e) => CDPStatus::ChecksFailed(e), + } + } else { + CDPStatus::ChecksFailed(Error::::InvalidFeedPrice.into()) + } + } + + pub fn maximum_total_debit_value(currency_id: CurrencyId) -> Result { + let params = Self::collateral_params(currency_id).ok_or(Error::::InvalidCollateralType)?; + Ok(params.maximum_total_debit_value) + } + + pub fn required_collateral_ratio(currency_id: CurrencyId) -> Result, DispatchError> { + let params = Self::collateral_params(currency_id).ok_or(Error::::InvalidCollateralType)?; + Ok(params.required_collateral_ratio) + } + + pub fn get_liquidation_ratio(currency_id: CurrencyId) -> Result { + let params = Self::collateral_params(currency_id).ok_or(Error::::InvalidCollateralType)?; + Ok(params.liquidation_ratio.unwrap_or_else(T::DefaultLiquidationRatio::get)) + } + + pub fn get_liquidation_penalty(currency_id: CurrencyId) -> Result { + let params = Self::collateral_params(currency_id).ok_or(Error::::InvalidCollateralType)?; + Ok(params + .liquidation_penalty + .map(|v| v.into_inner()) + .unwrap_or_else(|| T::DefaultLiquidationPenalty::get().into_inner())) + } + + pub fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate { + Self::debit_exchange_rate(currency_id).unwrap_or_else(T::DefaultDebitExchangeRate::get) + } + + pub fn convert_to_debit_value(currency_id: CurrencyId, debit_balance: Balance) -> Balance { + Self::get_debit_exchange_rate(currency_id).saturating_mul_int(debit_balance) + } + + pub fn try_convert_to_debit_balance(currency_id: CurrencyId, debit_value: Balance) -> Option { + Self::get_debit_exchange_rate(currency_id) + .reciprocal() + .map(|n| n.saturating_mul_int(debit_value)) + } + + pub fn calculate_collateral_ratio( + currency_id: CurrencyId, + collateral_balance: Balance, + debit_balance: Balance, + price: Price, + ) -> Ratio { + let locked_collateral_value = price.saturating_mul_int(collateral_balance); + let debit_value = Self::get_debit_value(currency_id, debit_balance); + + Ratio::checked_from_rational(locked_collateral_value, debit_value).unwrap_or_else(Ratio::max_value) + } + + pub fn adjust_position( + who: &T::AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult { + ensure!( + CollateralParams::::contains_key(currency_id), + Error::::InvalidCollateralType, + ); + >::adjust_position(who, currency_id, collateral_adjustment, debit_adjustment)?; + Ok(()) + } + + pub fn adjust_position_by_debit_value( + who: &T::AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_value_adjustment: Amount, + ) -> DispatchResult { + let debit_value_adjustment_abs = >::balance_try_from_amount_abs(debit_value_adjustment)?; + let debit_adjustment_abs = Self::try_convert_to_debit_balance(currency_id, debit_value_adjustment_abs) + .ok_or(Error::::ConvertDebitBalanceFailed)?; + + if debit_value_adjustment.is_negative() { + let EcdpPosition { collateral: _, debit } = >::positions(currency_id, who); + let actual_adjustment_abs = debit.min(debit_adjustment_abs); + let debit_adjustment = >::amount_try_from_balance(actual_adjustment_abs)?; + + Self::adjust_position( + who, + currency_id, + collateral_adjustment, + debit_adjustment.saturating_neg(), + )?; + } else { + let debit_adjustment = >::amount_try_from_balance(debit_adjustment_abs)?; + Self::adjust_position(who, currency_id, collateral_adjustment, debit_adjustment)?; + } + + Ok(()) + } + + /// If reverse is false, swap stable coin to given `token`. + /// If reverse is true, swap given `token` to stable coin. + fn swap_stable_and_lp_token( + token: CurrencyId, + amount: Balance, + reverse: bool, + ) -> sp_std::result::Result { + let stable_currency_id = T::GetUSSDCurrencyId::get(); + let loans_module_account = >::account_id(); + + // do nothing if given token is stable coin + if token == stable_currency_id { + return Ok(amount); + } + + let (supply, target) = if reverse { + (token, stable_currency_id) + } else { + (stable_currency_id, token) + }; + + T::Swap::swap( + &loans_module_account, + supply, + target, + SwapLimit::ExactSupply(amount, Zero::zero()), + ) + .map(|e| e.1) + } + + /// Generate new debit in advance, buy collateral and deposit it into ECDP, + /// and the collateral ratio will be reduced but ECDP must still be at valid risk. + /// For single token collateral, try to swap collateral by DEX. For lp token collateral, + /// try to swap lp components by DEX first, then add liquidity to obtain lp token, + /// ECDP owner may receive some remainder assets. + #[transactional] + pub fn expand_position_collateral( + who: &T::AccountId, + currency_id: CurrencyId, + increase_debit_value: Balance, + min_increase_collateral: Balance, + ) -> DispatchResult { + ensure!( + CollateralParams::::contains_key(currency_id), + Error::::InvalidCollateralType, + ); + let loans_module_account = >::account_id(); + + // issue stable coin in advance + ::EcdpUssdTreasury::issue_debit(&loans_module_account, increase_debit_value, true)?; + + // get the actual increased collateral amount + let increase_collateral = match currency_id { + CurrencyId::DexShare(dex_share_0, dex_share_1) => { + let token_0: CurrencyId = dex_share_0.into(); + let token_1: CurrencyId = dex_share_1.into(); + + // NOTE: distribute half of the new issued stable coin to each components of lp token, + // need better distribution methods to avoid unused component tokens. + let stable_for_token_0 = increase_debit_value / 2; + let stable_for_token_1 = increase_debit_value.saturating_sub(stable_for_token_0); + + // swap stable coin to lp component tokens. + let available_0 = Self::swap_stable_and_lp_token(token_0, stable_for_token_0, false)?; + let available_1 = Self::swap_stable_and_lp_token(token_1, stable_for_token_1, false)?; + let (consumption_0, consumption_1, actual_increase_lp) = T::DEX::add_liquidity( + &loans_module_account, + token_0, + token_1, + available_0, + available_1, + min_increase_collateral, + false, + )?; + + // refund unused lp component tokens + if let Some(remainer) = available_0.checked_sub(consumption_0) { + ::Currency::transfer(token_0, &loans_module_account, who, remainer)?; + } + if let Some(remainer) = available_1.checked_sub(consumption_1) { + ::Currency::transfer(token_1, &loans_module_account, who, remainer)?; + } + + actual_increase_lp + } + _ => { + // swap stable coin to collateral + let limit = SwapLimit::ExactSupply(increase_debit_value, min_increase_collateral); + let (_, actual_increase_collateral) = + T::Swap::swap(&loans_module_account, T::GetUSSDCurrencyId::get(), currency_id, limit)?; + + actual_increase_collateral + } + }; + + // update ECDP state + let collateral_adjustment = >::amount_try_from_balance(increase_collateral)?; + let increase_debit_balance = Self::try_convert_to_debit_balance(currency_id, increase_debit_value) + .ok_or(Error::::ConvertDebitBalanceFailed)?; + let debit_adjustment = >::amount_try_from_balance(increase_debit_balance)?; + >::update_loan(who, currency_id, collateral_adjustment, debit_adjustment)?; + + let EcdpPosition { collateral, debit } = >::positions(currency_id, who); + // check the ECDP if is still at valid risk + Self::check_position_valid(currency_id, collateral, debit, false)?; + // debit cap check due to new issued stable coin + Self::check_debit_cap(currency_id, >::total_positions(currency_id).debit)?; + Ok(()) + } + + /// Sell the collateral locked in ECDP to get stable coin to repay the debit, + /// and the collateral ratio will be increased. For single token collateral, + /// try to swap stable coin by DEX. For lp token collateral, try to remove liquidity + /// for lp token first, then swap the non-stable coin to get stable coin. If all + /// debit are repaid, the extra stable coin will be transferred back to the ECDP + /// owner directly. + #[transactional] + pub fn shrink_position_debit( + who: &T::AccountId, + currency_id: CurrencyId, + decrease_collateral: Balance, + min_decrease_debit_value: Balance, + ) -> DispatchResult { + ensure!( + CollateralParams::::contains_key(currency_id), + Error::::InvalidCollateralType, + ); + + let loans_module_account = >::account_id(); + let stable_currency_id = T::GetUSSDCurrencyId::get(); + let EcdpPosition { collateral, debit } = >::positions(currency_id, who); + + // ensure collateral of ECDP is enough + ensure!(decrease_collateral <= collateral, Error::::CollateralNotEnough); + + let actual_stable_amount = match currency_id { + CurrencyId::DexShare(dex_share_0, dex_share_1) => { + let token_0: CurrencyId = dex_share_0.into(); + let token_1: CurrencyId = dex_share_1.into(); + + // remove liquidity to get component tokens of lp token + let (available_0, available_1) = T::DEX::remove_liquidity( + &loans_module_account, + token_0, + token_1, + decrease_collateral, + Zero::zero(), + Zero::zero(), + false, + )?; + + let stable_0 = Self::swap_stable_and_lp_token(token_0, available_0, true)?; + let stable_1 = Self::swap_stable_and_lp_token(token_1, available_1, true)?; + let total_stable = stable_0.saturating_add(stable_1); + + // check whether the amount of stable token obtained by selling lptokens is enough as expected + ensure!( + total_stable >= min_decrease_debit_value, + Error::::NotEnoughDebitDecrement + ); + + total_stable + } + _ => { + // swap collateral to stable coin + let limit = SwapLimit::ExactSupply(decrease_collateral, min_decrease_debit_value); + let (_, actual_stable) = T::Swap::swap(&loans_module_account, currency_id, stable_currency_id, limit)?; + + actual_stable + } + }; + + // update ECDP state + let collateral_adjustment = >::amount_try_from_balance(decrease_collateral)?.saturating_neg(); + let previous_debit_value = Self::get_debit_value(currency_id, debit); + let (decrease_debit_value, decrease_debit_balance) = if actual_stable_amount >= previous_debit_value { + // refund extra stable coin to the ECDP owner + ::Currency::transfer( + stable_currency_id, + &loans_module_account, + who, + actual_stable_amount.saturating_sub(previous_debit_value), + )?; + + (previous_debit_value, debit) + } else { + ( + actual_stable_amount, + Self::try_convert_to_debit_balance(currency_id, actual_stable_amount) + .ok_or(Error::::ConvertDebitBalanceFailed)?, + ) + }; + + let debit_adjustment = >::amount_try_from_balance(decrease_debit_balance)?.saturating_neg(); + >::update_loan(who, currency_id, collateral_adjustment, debit_adjustment)?; + + // repay the debit of ECDP + ::EcdpUssdTreasury::burn_debit(&loans_module_account, decrease_debit_value)?; + + // check the ECDP if it is still at valid risk. + Self::check_position_valid( + currency_id, + collateral.saturating_sub(decrease_collateral), + debit.saturating_sub(decrease_debit_balance), + false, + )?; + Ok(()) + } + + // settle cdp has debit when emergency shutdown + pub fn settle_cdp_has_debit(who: T::AccountId, currency_id: CurrencyId) -> DispatchResult { + let EcdpPosition { collateral, debit } = >::positions(currency_id, &who); + ensure!(!debit.is_zero(), Error::::NoDebitValue); + + // confiscate collateral in cdp to cdp treasury + // and decrease ECDP's debit to zero + let settle_price: Price = T::PriceSource::get_relative_price(T::GetUSSDCurrencyId::get(), currency_id) + .ok_or(Error::::InvalidFeedPrice)?; + let bad_debt_value = Self::get_debit_value(currency_id, debit); + let confiscate_collateral_amount = + sp_std::cmp::min(settle_price.saturating_mul_int(bad_debt_value), collateral); + + if let CurrencyId::Erc20(_) = currency_id { + T::EVMBridge::set_origin(T::SettleErc20EvmOrigin::get()); + } + + // confiscate collateral and all debit + >::confiscate_collateral_and_debit(&who, currency_id, confiscate_collateral_amount, debit)?; + + if let CurrencyId::Erc20(_) = currency_id { + T::EVMBridge::kill_origin(); + } + + Self::deposit_event(Event::SettleCDPInDebit { + collateral_type: currency_id, + owner: who, + }); + Ok(()) + } + + // close cdp has debit by swap collateral to exact debit + #[transactional] + pub fn close_cdp_has_debit_by_dex( + who: T::AccountId, + currency_id: CurrencyId, + max_collateral_amount: Balance, + ) -> DispatchResult { + let EcdpPosition { collateral, debit } = >::positions(currency_id, &who); + ensure!(!debit.is_zero(), Error::::NoDebitValue); + ensure!( + matches!(Self::check_cdp_status(currency_id, collateral, debit), CDPStatus::Safe), + Error::::MustBeSafe + ); + + // confiscate all collateral and debit of unsafe cdp to cdp treasury + >::confiscate_collateral_and_debit(&who, currency_id, collateral, debit)?; + + // swap exact stable with DEX in limit of price impact + let debit_value = Self::get_debit_value(currency_id, debit); + let collateral_supply = collateral.min(max_collateral_amount); + + let (actual_supply_collateral, _) = ::EcdpUssdTreasury::swap_collateral_to_stable( + currency_id, + SwapLimit::ExactTarget(collateral_supply, debit_value), + false, + )?; + + // refund remainder collateral to ECDP owner + let refund_collateral_amount = collateral + .checked_sub(actual_supply_collateral) + .expect("swap success means collateral >= actual_supply_collateral; qed"); + ::EcdpUssdTreasury::withdraw_collateral(&who, currency_id, refund_collateral_amount)?; + + Self::deposit_event(Event::CloseCDPInDebitByDEX { + collateral_type: currency_id, + owner: who, + sold_collateral_amount: actual_supply_collateral, + refund_collateral_amount, + debit_value, + }); + Ok(()) + } + + // liquidate unsafe cdp + pub fn liquidate_unsafe_cdp(who: T::AccountId, currency_id: CurrencyId) -> Result { + let EcdpPosition { collateral, debit } = >::positions(currency_id, &who); + + // ensure the cdp is unsafe + ensure!( + matches!( + Self::check_cdp_status(currency_id, collateral, debit), + CDPStatus::Unsafe + ), + Error::::MustBeUnsafe + ); + + // confiscate all collateral and debit of unsafe cdp to cdp treasury + >::confiscate_collateral_and_debit(&who, currency_id, collateral, debit)?; + + let bad_debt_value = Self::get_debit_value(currency_id, debit); + let liquidation_penalty = Self::get_liquidation_penalty(currency_id)?; + let target_stable_amount = liquidation_penalty.saturating_mul_acc_int(bad_debt_value); + + match currency_id { + CurrencyId::DexShare(dex_share_0, dex_share_1) => { + let token_0: CurrencyId = dex_share_0.into(); + let token_1: CurrencyId = dex_share_1.into(); + + // remove liquidity first + let (amount_0, amount_1) = + ::EcdpUssdTreasury::remove_liquidity_for_lp_collateral(currency_id, collateral)?; + + // if these's stable + let stable_currency_id = T::GetUSSDCurrencyId::get(); + if token_0 == stable_currency_id || token_1 == stable_currency_id { + let (existing_stable, need_handle_currency, handle_amount) = if token_0 == stable_currency_id { + (amount_0, token_1, amount_1) + } else { + (amount_1, token_0, amount_0) + }; + + // these's stable refund + if existing_stable > target_stable_amount { + ::EcdpUssdTreasury::withdraw_collateral( + &who, + stable_currency_id, + existing_stable + .checked_sub(target_stable_amount) + .expect("ensured existing stable amount greater than target; qed"), + )?; + } + + let remain_target = target_stable_amount.saturating_sub(existing_stable); + Self::handle_liquidated_collateral(&who, need_handle_currency, handle_amount, remain_target)?; + } else { + // token_0 and token_1 each take half target_stable + let target_0 = target_stable_amount / 2; + let target_1 = target_stable_amount.saturating_sub(target_0); + Self::handle_liquidated_collateral(&who, token_0, amount_0, target_0)?; + Self::handle_liquidated_collateral(&who, token_1, amount_1, target_1)?; + } + } + _ => { + Self::handle_liquidated_collateral(&who, currency_id, collateral, target_stable_amount)?; + } + } + + Self::deposit_event(Event::LiquidateUnsafeCDP { + collateral_type: currency_id, + owner: who, + collateral_amount: collateral, + bad_debt_value, + target_amount: target_stable_amount, + }); + Ok(T::WeightInfo::liquidate_by_dex()) + } + + pub fn handle_liquidated_collateral( + who: &T::AccountId, + currency_id: CurrencyId, + amount: Balance, + target_stable_amount: Balance, + ) -> DispatchResult { + if target_stable_amount.is_zero() { + // refund collateral to ECDP owner + if !amount.is_zero() { + ::EcdpUssdTreasury::withdraw_collateral(who, currency_id, amount)?; + } + return Ok(()); + } + LiquidateByPriority::::liquidate(who, currency_id, amount, target_stable_amount) + } + + pub fn get_collateral_currency_ids() -> Vec { + CollateralParams::::iter_keys().collect() + } + + fn account_id() -> T::AccountId { + ::PalletId::get().into_account_truncating() + } + + /// Pallet EVM address, derived from pallet id. + fn evm_address() -> EvmAddress { + T::EvmAddressMapping::get_or_create_evm_address(&Self::account_id()) + } +} + +type LiquidateByPriority = (LiquidateViaDex, LiquidateViaContracts, LiquidateViaAuction); + +pub struct LiquidateViaDex(PhantomData); +impl LiquidateCollateral for LiquidateViaDex { + fn liquidate( + who: &T::AccountId, + currency_id: CurrencyId, + amount: Balance, + target_stable_amount: Balance, + ) -> DispatchResult { + // calculate the supply limit by slippage limit for the price of oracle, + let max_supply_limit = Ratio::one() + .saturating_sub(T::MaxSwapSlippageCompareToOracle::get()) + .reciprocal() + .unwrap_or_else(Ratio::max_value) + .saturating_mul_int( + T::PriceSource::get_relative_price(T::GetUSSDCurrencyId::get(), currency_id) + .expect("the oracle price should be available because liquidation are triggered by it.") + .saturating_mul_int(target_stable_amount), + ); + let collateral_supply = amount.min(max_supply_limit); + + let (actual_supply_collateral, actual_target_amount) = ::EcdpUssdTreasury::swap_collateral_to_stable( + currency_id, + SwapLimit::ExactTarget(collateral_supply, target_stable_amount), + false, + )?; + + let refund_collateral_amount = amount + .checked_sub(actual_supply_collateral) + .expect("swap success means collateral >= actual_supply_collateral; qed"); + // refund remainder collateral to ECDP owner + if !refund_collateral_amount.is_zero() { + ::EcdpUssdTreasury::withdraw_collateral(who, currency_id, refund_collateral_amount)?; + } + + if actual_target_amount > target_stable_amount { + ::EcdpUssdTreasury::withdraw_surplus( + who, + actual_target_amount.saturating_sub(target_stable_amount), + )?; + } + + Ok(()) + } +} + +pub struct LiquidateViaContracts(PhantomData); +impl LiquidateCollateral for LiquidateViaContracts { + fn liquidate( + who: &T::AccountId, + currency_id: CurrencyId, + amount: Balance, + target_stable_amount: Balance, + ) -> DispatchResult { + let liquidation_contracts = Pallet::::liquidation_contracts(); + let liquidation_contracts_len = liquidation_contracts.len(); + if liquidation_contracts_len.is_zero() { + return Err(Error::::LiquidationFailed.into()); + } + + let max_supply_limit = Ratio::one() + .saturating_sub(T::MaxLiquidationContractSlippage::get()) + .reciprocal() + .unwrap_or_else(Ratio::max_value) + .saturating_mul_int( + T::PriceSource::get_relative_price(T::GetUSSDCurrencyId::get(), currency_id) + .expect("the oracle price should be available because liquidation are triggered by it.") + .saturating_mul_int(target_stable_amount), + ); + let collateral_supply = amount.min(max_supply_limit); + + let collateral = currency_id + .erc20_address() + .ok_or(Error::::CollateralContractNotFound)?; + let repay_dest = Pallet::::evm_address(); + let repay_dest_account_id = Pallet::::account_id(); + + let stable_coin = T::GetUSSDCurrencyId::get(); + + let contracts_by_priority = { + let now: usize = frame_system::Pallet::::current_block_number() + .try_into() + .map_err(|_| ArithmeticError::Overflow)?; + // can't fail as ensured `liquidation_contracts_len` non-zero + let start_at = now % liquidation_contracts_len; + let mut all: Vec = liquidation_contracts.into(); + let mut right = all.split_off(start_at); + right.append(&mut all); + right + }; + + // try liquidation on each contract + for contract in contracts_by_priority.into_iter() { + let repay_dest_balance = CurrencyOf::::free_balance(stable_coin, &repay_dest_account_id); + if T::LiquidationEvmBridge::liquidate( + InvokeContext { + contract, + sender: repay_dest, + origin: contract, + }, + collateral, + repay_dest, + collateral_supply, + target_stable_amount, + ) + .is_ok() + { + let repayment = CurrencyOf::::free_balance(stable_coin, &repay_dest_account_id) + .saturating_sub(repay_dest_balance); + let contract_account_id = T::EvmAddressMapping::get_account_id(&contract); + if repayment >= target_stable_amount { + // sufficient repayment, transfer collateral to contract and notify + if let Err(e) = ::EcdpUssdTreasury::withdraw_collateral( + &contract_account_id, + currency_id, + collateral_supply, + ) { + log::error!( + target: "ecdp-ussd-engine", + "LiquidateViaContracts: transfer collateral to contract failed. \ + Collateral: {:?}, amount: {:?} contract: {:?}, error: {:?}. \ + This is unexpected, need extra action.", + currency_id, collateral_supply, contract, e, + ); + } else { + // notify liquidation success + T::LiquidationEvmBridge::on_collateral_transfer( + InvokeContext { + contract, + sender: repay_dest, + origin: contract, + }, + collateral, + target_stable_amount, + ); + } + // refund the remaining collateral to ECDP owner + let refund_collateral_amount = amount + .checked_sub(collateral_supply) + .expect("Ensured collateral supply <= amount; qed"); + if !refund_collateral_amount.is_zero() { + if let Err(e) = + ::EcdpUssdTreasury::withdraw_collateral(who, currency_id, refund_collateral_amount) + { + log::error!( + target: "ecdp-ussd-engine", + "LiquidateViaContracts: refund the remaining collateral to ECDP owner failed. \ + Collateral: {:?}, amount: {:?} error: {:?}. \ + This is unexpected, need extra action.", + currency_id, refund_collateral_amount, e, + ); + } + } + return Ok(()); + } else if repayment > 0 { + // insufficient repayment, refund + CurrencyOf::::transfer(stable_coin, &repay_dest_account_id, &contract_account_id, repayment)?; + // notify liquidation failed + T::LiquidationEvmBridge::on_repayment_refund( + InvokeContext { + contract, + sender: Pallet::::evm_address(), + origin: contract, + }, + collateral, + repayment, + ); + } + } + } + + Err(Error::::LiquidationFailed.into()) + } +} + +pub struct LiquidateViaAuction(PhantomData); +impl LiquidateCollateral for LiquidateViaAuction { + fn liquidate( + who: &T::AccountId, + currency_id: CurrencyId, + amount: Balance, + target_stable_amount: Balance, + ) -> DispatchResult { + ::EcdpUssdTreasury::create_collateral_auctions( + currency_id, + amount, + target_stable_amount, + who.clone(), + true, + ) + .map(|_| ()) + } +} + +impl EcdpUssdRiskManager for Pallet { + fn get_debit_value(currency_id: CurrencyId, debit_balance: Balance) -> Balance { + Self::convert_to_debit_value(currency_id, debit_balance) + } + + fn check_position_valid( + currency_id: CurrencyId, + collateral_balance: Balance, + debit_balance: Balance, + check_required_ratio: bool, + ) -> DispatchResult { + if !debit_balance.is_zero() { + let debit_value = Self::get_debit_value(currency_id, debit_balance); + let feed_price = ::PriceSource::get_relative_price(currency_id, T::GetUSSDCurrencyId::get()) + .ok_or(Error::::InvalidFeedPrice)?; + let collateral_ratio = + Self::calculate_collateral_ratio(currency_id, collateral_balance, debit_balance, feed_price); + + // check the required collateral ratio + if check_required_ratio { + if let Some(required_collateral_ratio) = Self::required_collateral_ratio(currency_id)? { + ensure!( + collateral_ratio >= required_collateral_ratio, + Error::::BelowRequiredCollateralRatio + ); + } + } + + // check the liquidation ratio + let liquidation_ratio = Self::get_liquidation_ratio(currency_id)?; + ensure!(collateral_ratio >= liquidation_ratio, Error::::BelowLiquidationRatio); + + // check the minimum_debit_value + ensure!( + debit_value >= T::MinimumDebitValue::get(), + Error::::RemainDebitValueTooSmall, + ); + } else if !collateral_balance.is_zero() { + // If there are any collateral remaining, then it must be above the minimum + ensure!( + collateral_balance >= T::MinimumCollateralAmount::get(¤cy_id), + Error::::CollateralAmountBelowMinimum, + ); + } + + Ok(()) + } + + fn check_debit_cap(currency_id: CurrencyId, total_debit_balance: Balance) -> DispatchResult { + let hard_cap = Self::maximum_total_debit_value(currency_id)?; + let total_debit_value = Self::get_debit_value(currency_id, total_debit_balance); + + ensure!(total_debit_value <= hard_cap, Error::::ExceedDebitValueHardCap); + + Ok(()) + } +} + +pub struct CollateralCurrencyIds(PhantomData); +// Returns a list of currently supported/configured collateral currency +impl Get> for CollateralCurrencyIds { + fn get() -> Vec { + Pallet::::get_collateral_currency_ids() + } +} + +/// Pick a new PRN, in the range [0, `max`) (exclusive). +fn pick_u32(rng: &mut R, max: u32) -> u32 { + rng.next_u32() % max +} diff --git a/blockchain/modules/ecdp-ussd-engine/src/mock.rs b/blockchain/modules/ecdp-ussd-engine/src/mock.rs new file mode 100644 index 00000000..ad5ba161 --- /dev/null +++ b/blockchain/modules/ecdp-ussd-engine/src/mock.rs @@ -0,0 +1,533 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the cdp engine module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Nothing}, + PalletId, +}; +use frame_system::EnsureSignedBy; +use module_support::{uctionManager, EcdpEmergencyShutdown, SpecificJointsSwap}; +use orml_traits::parameter_type_with_key; +use primitives::{evm::convert_decimals_to_evm, DexShare, Moment, ReserveIdentifier, TokenSymbol, TradingPair}; +use sp_core::crypto::AccountId32; +use sp_runtime::{ + testing::TestXt, + traits::{AccountIdConversion, IdentityLookup, One as OneT}, + BuildStorage, +}; +use sp_std::{cell::RefCell, str::FromStr}; + +pub type AccountId = AccountId32; +pub type BlockNumber = u64; +pub type AuctionId = u32; + +pub const ALICE: AccountId = AccountId32::new([1u8; 32]); +pub const BOB: AccountId = AccountId32::new([2u8; 32]); +pub const CAROL: AccountId = AccountId32::new([3u8; 32]); +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const BTC: CurrencyId = CurrencyId::ForeignAsset(255); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); +pub const LP_USSD_EDF: CurrencyId = + CurrencyId::DexShare(DexShare::Token(TokenSymbol::USSD), DexShare::Token(TokenSymbol::EDF)); +pub const LP_EDF_BTC: CurrencyId = CurrencyId::DexShare(DexShare::ForeignAsset(255), DexShare::Token(TokenSymbol::EDF)); + +mod ecdp_ussd_engine { + pub use super::super::*; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = ReserveIdentifier; + type DustRemovalWhitelist = Nothing; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = SEE; +} + +impl orml_currencies::Config for Runtime { + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} + +parameter_types! { + pub const EcdpUssdLoansPalletId: PalletId = PalletId(*b"aca/loan"); +} + +impl module_ecdp_ussd_loans::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type EcdpUssdRiskManager = EcdpUssdEngineModule; + type EcdpUssdTreasury = EcdpUssdTreasuryModule; + type PalletId = EcdpUssdLoansPalletId; + type OnUpdateLoan = (); +} + +thread_local! { + static BTC_PRICE: RefCell> = RefCell::new(Some(Price::one())); + static EDF_PRICE: RefCell> = RefCell::new(Some(Price::one())); + static LP_USSD_EDF_PRICE: RefCell> = RefCell::new(Some(Price::one())); + static LP_EDF_BTC_PRICE: RefCell> = RefCell::new(Some(Price::one())); +} + +pub struct MockPriceSource; +impl MockPriceSource { + pub fn set_price(currency_id: CurrencyId, price: Option) { + match currency_id { + BTC => BTC_PRICE.with(|v| *v.borrow_mut() = price), + EDF => EDF_PRICE.with(|v| *v.borrow_mut() = price), + LP_USSD_EDF => LP_USSD_EDF_PRICE.with(|v| *v.borrow_mut() = price), + LP_EDF_BTC => LP_EDF_BTC_PRICE.with(|v| *v.borrow_mut() = price), + _ => {} + } + } +} +impl PriceProvider for MockPriceSource { + fn get_price(currency_id: CurrencyId) -> Option { + match currency_id { + BTC => BTC_PRICE.with(|v| *v.borrow()), + EDF => EDF_PRICE.with(|v| *v.borrow()), + USSD => Some(Price::one()), + LP_USSD_EDF => LP_USSD_EDF_PRICE.with(|v| *v.borrow()), + LP_EDF_BTC => LP_EDF_BTC_PRICE.with(|v| *v.borrow()), + _ => None, + } + } +} + +thread_local! { + pub static AUCTION: RefCell> = RefCell::new(None); +} + +pub struct MockEcdpAuctionsManager; +impl MockEcdpAuctionsManager { + pub fn auction() -> Option<(AccountId, CurrencyId, Balance, Balance)> { + AUCTION.with(|v| { + let cloned = v.borrow().clone(); + cloned + }) + } +} +impl EcdpAuctionsManager for MockEcdpAuctionsManager { + type Balance = Balance; + type CurrencyId = CurrencyId; + type AuctionId = AuctionId; + + fn new_collateral_auction( + refund_recipient: &AccountId, + currency_id: Self::CurrencyId, + amount: Self::Balance, + target: Self::Balance, + ) -> DispatchResult { + AUCTION.with(|v| *v.borrow_mut() = Some((refund_recipient.clone(), currency_id, amount, target))); + Ok(()) + } + + fn cancel_auction(_id: Self::AuctionId) -> DispatchResult { + AUCTION.with(|v| *v.borrow_mut() = None); + Ok(()) + } + + fn get_total_target_in_auction() -> Self::Balance { + Self::auction().map(|auction| auction.3).unwrap_or_default() + } + + fn get_total_collateral_in_auction(_id: Self::CurrencyId) -> Self::Balance { + Self::auction().map(|auction| auction.2).unwrap_or_default() + } +} + +parameter_types! { + pub const GetUSSDCurrencyId: CurrencyId = USSD; + pub const EcdpUssdTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account_truncating(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![SEE], + ]; +} + +impl module_ecdp_ussd_treasury::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type EcdpAuctionsManagerHandler = MockEcdpAuctionsManager; + type UpdateOrigin = EnsureSignedBy; + type EdfisSwap = EdfisSwapModule; + type Swap = SpecificJointsSwap; + type MaxAuctionsCount = ConstU32<10_000>; + type PalletId = EcdpUssdTreasuryPalletId; + type TreasuryAccount = TreasuryAccount; + type WeightInfo = (); +} + +parameter_types! { + pub const EdfisSwapPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const GetExchangeFee: (u32, u32) = (0, 100); + pub EnabledTradingPairs: Vec = vec![ + TradingPair::from_currency_ids(USSD, BTC).unwrap(), + TradingPair::from_currency_ids(USSD, EDF).unwrap(), + TradingPair::from_currency_ids(SEE, BTC).unwrap(), + TradingPair::from_currency_ids(SEE, EDF).unwrap(), + TradingPair::from_currency_ids(SEE, USSD).unwrap(), + ]; +} + +impl module_edfis_swap_legacy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = ConstU32<4>; + type PalletId = EdfisSwapPalletId; + type Erc20InfoMapping = (); + type SwapDexIncentives = (); + type WeightInfo = (); + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU64<0>; + type OnLiquidityPoolUpdated = (); +} + +impl pallet_timestamp::Config for Runtime { + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1_000>; + type WeightInfo = (); +} + +impl module_evm_accounts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = PalletBalances; + type ChainId = (); + type AddressMapping = module_evm_accounts::EvmAddressMapping; + type TransferAll = Currencies; + type WeightInfo = (); +} + +parameter_types! { + pub NetworkContractSource: EvmAddress = EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap(); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId = AccountId::from([1u8; 32]); + pub const NetworkContractAccount: AccountId = AccountId::from([0u8; 32]); + pub const StorageDepositPerByte: u128 = convert_decimals_to_evm(10); +} + +impl module_evm::Config for Runtime { + type AddressMapping = module_evm_accounts::EvmAddressMapping; + type Currency = PalletBalances; + type TransferAll = (); + type NewContractExtraBytes = ConstU32<1>; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = ConstU128<10>; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = (); + type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + + type DeveloperDeposit = ConstU128<1000>; + type PublicationFee = ConstU128<200>; + type TreasuryAccount = TreasuryAccount; + type FreePublicationOrigin = EnsureSignedBy; + + type Runner = module_evm::runner::stack::Runner; + type FindAuthor = (); + type Task = (); + type IdleScheduler = (); + type WeightInfo = (); +} + +impl module_evm_bridge::Config for Runtime { + type EVM = EVM; +} + +thread_local! { + static IS_SHUTDOWN: RefCell = RefCell::new(false); +} + +pub fn mock_shutdown() { + IS_SHUTDOWN.with(|v| *v.borrow_mut() = true) +} + +pub fn liquidation_contract_addr() -> EvmAddress { + EvmAddress::from_str(&"0x1000000000000000000000000000000000000000").unwrap() +} + +pub struct MockEcdpEmergencyShutdown; +impl EcdpEmergencyShutdown for MockEcdpEmergencyShutdown { + fn is_shutdown() -> bool { + IS_SHUTDOWN.with(|v| *v.borrow_mut()) + } +} + +thread_local! { + static LIQUIDATED: RefCell<(EvmAddress, EvmAddress, Balance, Balance)> = RefCell::new((EvmAddress::default(), EvmAddress::default(), 0, 0)); + static TRANSFERRED: RefCell<(EvmAddress, Balance)> = RefCell::new((EvmAddress::default(), 0)); + static REFUNDED: RefCell<(EvmAddress, Balance)> = RefCell::new((EvmAddress::default(), 0)); + static LIQUIDATION_RESULT: RefCell = RefCell::new(Err(Error::::LiquidationFailed.into())); + static REPAYMENT: RefCell> = RefCell::new(None); +} + +pub struct MockLiquidationEvmBridge; +impl MockLiquidationEvmBridge { + pub fn liquidated() -> (EvmAddress, EvmAddress, Balance, Balance) { + LIQUIDATED.with(|v| v.borrow().clone()) + } + pub fn transferred() -> (EvmAddress, Balance) { + TRANSFERRED.with(|v| v.borrow().clone()) + } + pub fn refunded() -> (EvmAddress, Balance) { + REFUNDED.with(|v| v.borrow().clone()) + } + pub fn reset() { + LIQUIDATION_RESULT.with(|v| *v.borrow_mut() = Err(Error::::LiquidationFailed.into())); + REPAYMENT.with(|v| *v.borrow_mut() = None); + } + pub fn set_liquidation_result(r: DispatchResult) { + LIQUIDATION_RESULT.with(|v| *v.borrow_mut() = r); + } + pub fn set_repayment(repayment: Balance) { + REPAYMENT.with(|v| *v.borrow_mut() = Some(repayment)); + } +} +impl LiquidationEvmBridge for MockLiquidationEvmBridge { + fn liquidate( + _context: InvokeContext, + collateral: EvmAddress, + repay_dest: EvmAddress, + amount: Balance, + min_repayment: Balance, + ) -> DispatchResult { + let result = LIQUIDATION_RESULT.with(|v| v.borrow().clone()); + if result.is_ok() { + let repayment = if let Some(r) = REPAYMENT.with(|v| v.borrow().clone()) { + r + } else { + min_repayment + }; + let _ = Currencies::deposit(GetUSSDCurrencyId::get(), &EcdpUssdEngineModule::account_id(), repayment); + } + LIQUIDATED.with(|v| *v.borrow_mut() = (collateral, repay_dest, amount, min_repayment)); + result + } + fn on_collateral_transfer(_context: InvokeContext, collateral: EvmAddress, amount: Balance) { + TRANSFERRED.with(|v| *v.borrow_mut() = (collateral, amount)); + } + fn on_repayment_refund(_context: InvokeContext, collateral: EvmAddress, repayment: Balance) { + REFUNDED.with(|v| *v.borrow_mut() = (collateral, repayment)); + } +} + +ord_parameter_types! { + pub const One: AccountId = ALICE; +} + +parameter_type_with_key! { + pub MinimumCollateralAmount: |_currency_id: CurrencyId| -> Balance { + 10 + }; +} + +parameter_types! { + pub DefaultLiquidationRatio: Ratio = Ratio::saturating_from_rational(3, 2); + pub DefaultDebitExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); + pub DefaultLiquidationPenalty: FractionalRate = FractionalRate::try_from(Rate::saturating_from_rational(10, 100)).unwrap(); + pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::saturating_from_rational(50, 100); + pub MaxLiquidationContractSlippage: Ratio = Ratio::saturating_from_rational(80, 100); + pub const EcdpUssdEnginePalletId: PalletId = PalletId(*b"aca/cdpe"); + pub const SettleErc20EvmOrigin: AccountId = AccountId32::new([255u8; 32]); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PriceSource = MockPriceSource; + type DefaultLiquidationRatio = DefaultLiquidationRatio; + type DefaultDebitExchangeRate = DefaultDebitExchangeRate; + type DefaultLiquidationPenalty = DefaultLiquidationPenalty; + type MinimumDebitValue = ConstU128<2>; + type MinimumCollateralAmount = MinimumCollateralAmount; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type EcdpUssdTreasury = EcdpUssdTreasuryModule; + type UpdateOrigin = EnsureSignedBy; + type MaxSwapSlippageCompareToOracle = MaxSwapSlippageCompareToOracle; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 + type EcdpEmergencyShutdown = MockEcdpEmergencyShutdown; + type UnixTime = Timestamp; + type Currency = Currencies; + type EdfisSwap = EdfisSwapModule; + type LiquidationContractsUpdateOrigin = EnsureSignedBy; + type MaxLiquidationContractSlippage = MaxLiquidationContractSlippage; + type MaxLiquidationContracts = ConstU32<10>; + type LiquidationEvmBridge = MockLiquidationEvmBridge; + type PalletId = EcdpUssdEnginePalletId; + type EvmAddressMapping = module_evm_accounts::EvmAddressMapping; + type Swap = SpecificJointsSwap; + type EVMBridge = module_evm_bridge::EVMBridge; + type SettleErc20EvmOrigin = SettleErc20EvmOrigin; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + EcdpUssdEngineModule: ecdp_ussd_engine, + EcdpUssdTreasuryModule: module_ecdp_ussd_treasury, + Currencies: orml_currencies, + Tokens: orml_tokens, + EcdpUssdLoansModule: module_ecdp_ussd_loans, + PalletBalances: pallet_balances, + EdfisSwapModule: module_edfis_swap_legacy, + Timestamp: pallet_timestamp, + EvmAccounts: module_evm_accounts, + EVM: module_evm, + EVMBridge: module_evm_bridge, + } +); + +/// An extrinsic type used for tests. +pub type Extrinsic = TestXt; + +impl SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![ + (ALICE, BTC, 1000), + (BOB, BTC, 1000), + (CAROL, BTC, 10000), + (ALICE, EDF, 1000), + (BOB, EDF, 1000), + (CAROL, EDF, 10000), + (CAROL, USSD, 10000), + ], + } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(CAROL, 10000)], + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + module_edfis_swap_legacy::GenesisConfig:: { + initial_listing_trading_pairs: vec![], + initial_enabled_trading_pairs: EnabledTradingPairs::get(), + initial_added_liquidity_pools: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + + MockLiquidationEvmBridge::reset(); + + t.into() + } + + pub fn lots_of_accounts() -> Self { + let mut balances = Vec::new(); + for i in 0..1001u32 { + balances.push((account_id_from_u32(i), BTC, 1000)); + } + Self { balances } + } +} + +pub fn account_id_from_u32(num: u32) -> AccountId { + let mut data = [0u8; 32]; + let index = num.to_le_bytes(); + data[0..4].copy_from_slice(&index[..]); + AccountId::new(data) +} diff --git a/blockchain/modules/ecdp-ussd-engine/src/tests.rs b/blockchain/modules/ecdp-ussd-engine/src/tests.rs new file mode 100644 index 00000000..12cfe36b --- /dev/null +++ b/blockchain/modules/ecdp-ussd-engine/src/tests.rs @@ -0,0 +1,1878 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the cdp engine module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_err, assert_noop, assert_ok}; +use mock::{RuntimeCall as MockCall, RuntimeEvent, *}; +use module_support::{SwapManager, SwapError}; +use orml_traits::MultiCurrency; +use sp_core::offchain::{testing, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}; +use sp_io::offchain; +use sp_runtime::{ + offchain::{DbExternalities, StorageKind}, + traits::BadOrigin, +}; + +pub const INIT_TIMESTAMP: u64 = 30_000; +pub const BLOCK_TIME: u64 = 1000; + +fn run_to_block_offchain(n: u64) { + while System::block_number() < n { + System::set_block_number(System::block_number() + 1); + Timestamp::set_timestamp((System::block_number() as u64 * BLOCK_TIME) + INIT_TIMESTAMP); + EcdpUssdEngineModule::on_initialize(System::block_number()); + EcdpUssdEngineModule::offchain_worker(System::block_number()); + // this unlocks the concurrency storage lock so offchain_worker will fire next block + offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(LOCK_DURATION + 200))); + } +} + +fn setup_default_collateral(currency_id: CurrencyId) { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + currency_id, + Change::NewValue(Some(Default::default())), + Change::NoChange, + Change::NoChange, + Change::NoChange, + Change::NewValue(10000), + )); +} + +#[test] +fn check_cdp_status_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_eq!(EcdpUssdEngineModule::check_cdp_status(BTC, 100, 500), CDPStatus::Safe); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(3, 1))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + assert_eq!(EcdpUssdEngineModule::check_cdp_status(BTC, 100, 500), CDPStatus::Unsafe); + + MockPriceSource::set_price(BTC, None); + assert_eq!( + EcdpUssdEngineModule::check_cdp_status(BTC, 100, 500), + CDPStatus::ChecksFailed(Error::::InvalidFeedPrice.into()) + ); + }); +} + +#[test] +fn get_debit_exchange_rate_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(EcdpUssdEngineModule::debit_exchange_rate(BTC), None); + assert_eq!( + EcdpUssdEngineModule::get_debit_exchange_rate(BTC), + ExchangeRate::saturating_from_rational(1, 10) + ); + + DebitExchangeRate::::insert(BTC, ExchangeRate::one()); + assert_eq!(EcdpUssdEngineModule::debit_exchange_rate(BTC), Some(ExchangeRate::one())); + assert_eq!(EcdpUssdEngineModule::get_debit_exchange_rate(BTC), ExchangeRate::one()); + }); +} + +#[test] +fn get_liquidation_penalty_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EcdpUssdEngineModule::get_liquidation_penalty(BTC), + Error::::InvalidCollateralType + ); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(5, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_eq!( + EcdpUssdEngineModule::get_liquidation_penalty(BTC), + Ok(Rate::saturating_from_rational(2, 10)) + ); + }); +} + +#[test] +fn get_liquidation_ratio_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EcdpUssdEngineModule::get_liquidation_ratio(BTC), + Error::::InvalidCollateralType + ); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(5, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_eq!( + EcdpUssdEngineModule::get_liquidation_ratio(BTC), + Ok(Ratio::saturating_from_rational(5, 2)) + ); + }); +} + +#[test] +fn set_collateral_params_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_noop!( + EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(AccountId::new([5u8; 32])), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + ), + BadOrigin + ); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + System::assert_has_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidationRatioUpdated { + collateral_type: BTC, + new_liquidation_ratio: Some(Ratio::saturating_from_rational(3, 2)), + })); + System::assert_has_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidationPenaltyUpdated { + collateral_type: BTC, + new_liquidation_penalty: Some(Rate::saturating_from_rational(2, 10)), + })); + System::assert_has_event(RuntimeEvent::EcdpUssdEngineModule( + crate::Event::RequiredCollateralRatioUpdated { + collateral_type: BTC, + new_required_collateral_ratio: Some(Ratio::saturating_from_rational(9, 5)), + }, + )); + System::assert_has_event(RuntimeEvent::EcdpUssdEngineModule( + crate::Event::MaximumTotalDebitValueUpdated { + collateral_type: BTC, + new_total_debit_value: 10000, + }, + )); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + let new_collateral_params = EcdpUssdEngineModule::collateral_params(BTC).unwrap(); + + assert_eq!( + new_collateral_params.liquidation_ratio, + Some(Ratio::saturating_from_rational(3, 2)) + ); + assert_eq!( + new_collateral_params.liquidation_penalty.map(|v| v.into_inner()), + Some(Rate::saturating_from_rational(2, 10)) + ); + assert_eq!( + new_collateral_params.required_collateral_ratio, + Some(Ratio::saturating_from_rational(9, 5)) + ); + assert_eq!(new_collateral_params.maximum_total_debit_value, 10000); + }); +} + +#[test] +fn calculate_collateral_ratio_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_eq!( + EcdpUssdEngineModule::calculate_collateral_ratio(BTC, 100, 500, Price::saturating_from_rational(1, 1)), + Ratio::saturating_from_rational(100, 50) + ); + }); +} + +#[test] +fn check_debit_cap_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpUssdEngineModule::check_debit_cap(BTC, 100000)); + assert_noop!( + EcdpUssdEngineModule::check_debit_cap(BTC, 100010), + Error::::ExceedDebitValueHardCap, + ); + }); +} + +#[test] +fn check_position_valid_failed_when_invalid_feed_price() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(1, 1))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(10000), + )); + + MockPriceSource::set_price(BTC, None); + assert_noop!( + EcdpUssdEngineModule::check_position_valid(BTC, 100, 500, true), + Error::::InvalidFeedPrice + ); + + MockPriceSource::set_price(BTC, Some(Price::one())); + assert_ok!(EcdpUssdEngineModule::check_position_valid(BTC, 100, 500, true)); + }); +} + +#[test] +fn check_position_valid_failed_when_remain_debit_value_too_small() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(1, 1))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(10000), + )); + assert_noop!( + EcdpUssdEngineModule::check_position_valid(BTC, 2, 10, true), + Error::::RemainDebitValueTooSmall, + ); + }); +} + +#[test] +fn check_position_valid_ratio_below_liquidate_ratio() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(10, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_noop!( + EcdpUssdEngineModule::check_position_valid(BTC, 91, 500, true), + Error::::BelowLiquidationRatio, + ); + }); +} + +#[test] +fn check_position_valid_ratio_below_required_ratio() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpUssdEngineModule::check_position_valid(BTC, 89, 500, false)); + assert_noop!( + EcdpUssdEngineModule::check_position_valid(BTC, 89, 500, true), + Error::::BelowRequiredCollateralRatio + ); + }); +} + +#[test] +fn adjust_position_work() { + ExtBuilder::default().build().execute_with(|| { + setup_default_collateral(BTC); + setup_default_collateral(USSD); + + assert_noop!( + EcdpUssdEngineModule::adjust_position(&ALICE, SEE, 100, 500), + Error::::InvalidCollateralType, + ); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert!(!EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 0, 200).is_ok()); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 0, -200)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 30); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 300); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + }); +} + +#[test] +fn adjust_position_by_debit_value_work() { + ExtBuilder::default().build().execute_with(|| { + setup_default_collateral(BTC); + + assert_noop!( + EcdpUssdEngineModule::adjust_position_by_debit_value(&ALICE, SEE, 100, 5000), + Error::::InvalidCollateralType, + ); + + assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + + assert_ok!(EcdpUssdEngineModule::adjust_position_by_debit_value(&ALICE, BTC, 100, 0)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + + assert_ok!(EcdpUssdEngineModule::adjust_position_by_debit_value(&ALICE, BTC, 100, 100)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 800); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 200); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 1000); + + assert_ok!(EcdpUssdEngineModule::adjust_position_by_debit_value(&ALICE, BTC, 0, -30)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 800); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 70); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 200); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 700); + + assert_noop!( + EcdpUssdEngineModule::adjust_position_by_debit_value(&ALICE, BTC, 0, -69), + Error::::RemainDebitValueTooSmall + ); + + // if payback value is over the actual debit, just payback the actual debit. + assert_ok!(EcdpUssdEngineModule::adjust_position_by_debit_value(&ALICE, BTC, 0, -999999)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 800); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 200); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + }); +} + +#[test] +fn expand_position_collateral_work() { + ExtBuilder::default().build().execute_with(|| { + MockPriceSource::set_price(EDF, Some(Price::saturating_from_rational(10, 1))); + setup_default_collateral(USSD); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, EDF, 100, 2500)); + assert_eq!( + EcdpUssdLoansModule::positions(EDF, ALICE), + EcdpPosition { + collateral: 100, + debit: 2500 + } + ); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 250); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 100); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + + assert_noop!( + EcdpUssdEngineModule::expand_position_collateral(&ALICE, EDF, 0, 1), + SwapError::CannotSwap + ); + + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + USSD, + EDF, + 10000, + 1000, + 0, + false + )); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1000, 10000)); + assert_noop!( + EcdpUssdEngineModule::expand_position_collateral(&ALICE, EDF, 250, 100), + SwapError::CannotSwap + ); + + assert_ok!(EcdpUssdEngineModule::expand_position_collateral(&ALICE, EDF, 250, 20)); + assert_eq!( + EcdpUssdLoansModule::positions(EDF, ALICE), + EcdpPosition { + collateral: 124, + debit: 5000 + } + ); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 250); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 124); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (976, 10250)); + + assert_ok!(EcdpUssdEngineModule::expand_position_collateral(&ALICE, EDF, 200, 18)); + assert_eq!( + EcdpUssdLoansModule::positions(EDF, ALICE), + EcdpPosition { + collateral: 142, + debit: 7000 + } + ); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 142); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (958, 10450)); + + // make position below the RequireCollateralRatio + assert_ok!(EcdpUssdEngineModule::expand_position_collateral(&ALICE, EDF, 100, 0)); + assert_eq!( + EcdpUssdLoansModule::positions(EDF, ALICE), + EcdpPosition { + collateral: 151, + debit: 8000, + } + ); + + assert_noop!( + EcdpUssdEngineModule::expand_position_collateral(&ALICE, EDF, 800, 0), + Error::::BelowLiquidationRatio + ); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + EDF, + Change::NoChange, + Change::NoChange, + Change::NoChange, + Change::NoChange, + Change::NewValue(900), + )); + assert_noop!( + EcdpUssdEngineModule::expand_position_collateral(&ALICE, EDF, 101, 0), + Error::::ExceedDebitValueHardCap + ); + }); +} + +#[test] +fn expand_position_collateral_for_lp_ausd_dot_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + USSD, + EDF, + 10000, + 1000, + 0, + false + )); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 20000); + assert_ok!(Currencies::transfer( + RuntimeOrigin::signed(CAROL), + ALICE, + LP_USSD_EDF, + 1000 + )); + + MockPriceSource::set_price(LP_USSD_EDF, Some(Price::saturating_from_rational(1, 1))); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + setup_default_collateral(EDF); + setup_default_collateral(USSD); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, LP_USSD_EDF, 1000, 2000)); + assert_eq!( + EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE), + EcdpPosition { + collateral: 1000, + debit: 2000 + } + ); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 200); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 1000); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1000, 10000)); + + assert_noop!( + EcdpUssdEngineModule::expand_position_collateral(&ALICE, LP_USSD_EDF, 200, 200), + module_edfis_swap_legacy::Error::::UnacceptableShareIncrement + ); + + assert_ok!(EcdpUssdEngineModule::expand_position_collateral( + &ALICE, + LP_USSD_EDF, + 300, + 100 + )); + assert_eq!( + EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE), + EcdpPosition { + collateral: 1283, + debit: 5000 + } + ); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 206); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 1283); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1000, 10294)); + }); +} + +#[test] +fn shrink_position_debit_work() { + ExtBuilder::default().build().execute_with(|| { + MockPriceSource::set_price(EDF, Some(Price::saturating_from_rational(10, 1))); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + setup_default_collateral(USSD); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, EDF, 100, 5000)); + assert_eq!( + EcdpUssdLoansModule::positions(EDF, ALICE), + EcdpPosition { + collateral: 100, + debit: 5000 + } + ); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 100); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + + MockPriceSource::set_price(EDF, Some(Price::saturating_from_rational(8, 1))); + assert_noop!( + EcdpUssdEngineModule::shrink_position_debit(&ALICE, EDF, 10, 0), + SwapError::CannotSwap + ); + + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + USSD, + EDF, + 8000, + 1000, + 0, + false + )); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1000, 8000)); + assert_noop!( + EcdpUssdEngineModule::shrink_position_debit(&ALICE, EDF, 10, 80), + SwapError::CannotSwap + ); + + assert_ok!(EcdpUssdEngineModule::shrink_position_debit(&ALICE, EDF, 10, 70)); + assert_eq!( + EcdpUssdLoansModule::positions(EDF, ALICE), + EcdpPosition { + collateral: 90, + debit: 4210 + } + ); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 90); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1010, 7921)); + + assert_ok!(EcdpUssdEngineModule::shrink_position_debit(&ALICE, EDF, 70, 0)); + assert_eq!( + EcdpUssdLoansModule::positions(EDF, ALICE), + EcdpPosition { + collateral: 20, + debit: 0 + } + ); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 592); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 20); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1080, 7408)); + }); +} + +#[test] +fn shrink_position_debit_for_lp_ausd_dot_work() { + ExtBuilder::default().build().execute_with(|| { + MockPriceSource::set_price(LP_USSD_EDF, Some(Price::saturating_from_rational(1, 1))); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + USSD, + EDF, + 10000, + 1000, + 0, + false + )); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 20000); + assert_ok!(Currencies::transfer( + RuntimeOrigin::signed(CAROL), + ALICE, + LP_USSD_EDF, + 1000 + )); + + MockPriceSource::set_price(LP_USSD_EDF, Some(Price::saturating_from_rational(1, 1))); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + setup_default_collateral(EDF); + setup_default_collateral(USSD); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, LP_USSD_EDF, 1000, 5000)); + assert_eq!( + EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE), + EcdpPosition { + collateral: 1000, + debit: 5000 + } + ); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 1000); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1000, 10000)); + + assert_noop!( + EcdpUssdEngineModule::shrink_position_debit(&ALICE, LP_USSD_EDF, 200, 200), + Error::::NotEnoughDebitDecrement + ); + + assert_ok!(EcdpUssdEngineModule::shrink_position_debit(&ALICE, LP_USSD_EDF, 100, 80)); + assert_eq!( + EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE), + EcdpPosition { + collateral: 900, + debit: 4010 + } + ); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 900); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1000, 9901)); + + assert_ok!(EcdpUssdEngineModule::shrink_position_debit(&ALICE, LP_USSD_EDF, 600, 500)); + assert_eq!( + EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE), + EcdpPosition { + collateral: 300, + debit: 0 + } + ); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 685); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 300); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EdfisSwapModule::get_liquidity_pool(EDF, USSD), (1000, 9315)); + }); +} + +#[test] +fn remain_debit_value_too_small_check() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_noop!( + EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 0, -490), + crate::Error::::RemainDebitValueTooSmall + ); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, -90, -500)); + }); +} + +#[test] +fn liquidate_unsafe_cdp_by_collateral_auction() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + setup_default_collateral(USSD); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_noop!( + EcdpUssdEngineModule::liquidate_unsafe_cdp(ALICE, BTC), + Error::::MustBeUnsafe, + ); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(3, 1))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + assert_ok!(EcdpUssdEngineModule::liquidate_unsafe_cdp(ALICE, BTC)); + + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: BTC, + owner: ALICE, + collateral_amount: 100, + bad_debt_value: 50, + target_amount: 60, + })); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 50); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + + mock_shutdown(); + assert_noop!( + EcdpUssdEngineModule::liquidate(RuntimeOrigin::none(), BTC, ALICE), + Error::::AlreadyShutdown + ); + }); +} + +#[test] +fn liquidate_unsafe_cdp_by_collateral_auction_when_limited_by_slippage() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + setup_default_collateral(USSD); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + USSD, + 100, + 121, + 0, + false + )); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (100, 121)); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::max_value())), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + + // pool is enough, but slippage limit the swap + MockPriceSource::set_price(BTC, Some(Price::saturating_from_rational(2, 1))); + assert_eq!( + EdfisSwapModule::get_swap_amount(&[BTC, USSD], SwapLimit::ExactTarget(Balance::MAX, 60)), + Some((99, 60)) + ); + assert_eq!( + EdfisSwapModule::get_swap_amount(&[BTC, USSD], SwapLimit::ExactSupply(100, 0)), + Some((100, 60)) + ); + assert_ok!(EcdpUssdEngineModule::liquidate_unsafe_cdp(ALICE, BTC)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: BTC, + owner: ALICE, + collateral_amount: 100, + bad_debt_value: 50, + target_amount: 60, + })); + + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (100, 121)); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 50); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + }); +} + +#[test] +fn liquidate_unsafe_cdp_by_swap() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + setup_default_collateral(EDF); + setup_default_collateral(USSD); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + USSD, + 100, + 121, + 0, + false + )); + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (100, 121)); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::max_value())), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + + assert_ok!(EcdpUssdEngineModule::liquidate_unsafe_cdp(ALICE, BTC)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: BTC, + owner: ALICE, + collateral_amount: 100, + bad_debt_value: 50, + target_amount: 60, + })); + + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (199, 61)); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 50); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 901); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + }); +} + +#[test] +fn liquidate_unsafe_cdp_of_lp_ausd_dot_and_swap_dot() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + setup_default_collateral(EDF); + setup_default_collateral(USSD); + + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + USSD, + EDF, + 10000, + 500, + 0, + false + )); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (10000, 500)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 20000); + assert_ok!(Currencies::transfer( + RuntimeOrigin::signed(CAROL), + ALICE, + LP_USSD_EDF, + 1000 + )); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + + MockPriceSource::set_price(EDF, Price::checked_from_rational(20, 1)); + MockPriceSource::set_price(LP_USSD_EDF, Price::checked_from_rational(1, 1)); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, LP_USSD_EDF, 1000, 5000)); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).debit, 5000); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).collateral, 1000); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), + 0 + ); + assert_eq!(MockEcdpAuctionsManager::auction(), None); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NoChange, + Change::NewValue(Some(Ratio::max_value())), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + + assert_ok!(EcdpUssdEngineModule::liquidate_unsafe_cdp(ALICE, LP_USSD_EDF)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: LP_USSD_EDF, + owner: ALICE, + collateral_amount: 1000, + bad_debt_value: 500, + target_amount: 600, + })); + + assert_eq!( + MockPriceSource::get_relative_price(USSD, EDF), + Price::checked_from_rational(1, 20) + ); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (9400, 481)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 19000); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1019); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).collateral, 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 500); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 600); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), + 0 + ); + assert_eq!(MockEcdpAuctionsManager::auction(), None); + }); +} + +#[test] +fn liquidate_unsafe_cdp_of_lp_ausd_dot_and_ausd_take_whole_target() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + setup_default_collateral(EDF); + setup_default_collateral(USSD); + + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + USSD, + EDF, + 10000, + 500, + 0, + false + )); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (10000, 500)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 20000); + assert_ok!(Currencies::transfer( + RuntimeOrigin::signed(CAROL), + ALICE, + LP_USSD_EDF, + 1000 + )); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + + MockPriceSource::set_price(EDF, Price::checked_from_rational(20, 1)); + MockPriceSource::set_price(LP_USSD_EDF, Price::checked_from_rational(1, 1)); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, LP_USSD_EDF, 1000, 2000)); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 200); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).debit, 2000); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).collateral, 1000); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), + 0 + ); + assert_eq!(MockEcdpAuctionsManager::auction(), None); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NoChange, + Change::NewValue(Some(Ratio::max_value())), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + + assert_ok!(EcdpUssdEngineModule::liquidate_unsafe_cdp(ALICE, LP_USSD_EDF)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: LP_USSD_EDF, + owner: ALICE, + collateral_amount: 1000, + bad_debt_value: 200, + target_amount: 240, + })); + + assert_eq!( + MockPriceSource::get_relative_price(USSD, EDF), + Price::checked_from_rational(1, 20) + ); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (9500, 475)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 19000); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1025); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 460); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).collateral, 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 200); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 240); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), + 0 + ); + assert_eq!(MockEcdpAuctionsManager::auction(), None); + }); +} + +#[test] +fn liquidate_unsafe_cdp_of_lp_ausd_dot_and_create_dot_auction() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + setup_default_collateral(EDF); + setup_default_collateral(USSD); + + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + USSD, + EDF, + 500, + 25, + 0, + false + )); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (500, 25)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 1000); + assert_ok!(Currencies::transfer( + RuntimeOrigin::signed(CAROL), + ALICE, + LP_USSD_EDF, + 1000 + )); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + + MockPriceSource::set_price(EDF, Price::checked_from_rational(20, 1)); + MockPriceSource::set_price(LP_USSD_EDF, Price::checked_from_rational(1, 1)); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, LP_USSD_EDF, 1000, 5000)); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).debit, 5000); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).collateral, 1000); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), + 0 + ); + assert_eq!(MockEcdpAuctionsManager::auction(), None); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + LP_USSD_EDF, + Change::NoChange, + Change::NewValue(Some(Ratio::max_value())), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + + assert_ok!(EcdpUssdEngineModule::liquidate_unsafe_cdp(ALICE, LP_USSD_EDF)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: LP_USSD_EDF, + owner: ALICE, + collateral_amount: 1000, + bad_debt_value: 500, + target_amount: 600, + })); + + assert_eq!( + MockPriceSource::get_relative_price(USSD, EDF), + Price::checked_from_rational(1, 20) + ); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (0, 0)); + assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &ALICE), 0); + assert_eq!(Currencies::free_balance(EDF, &ALICE), 1000); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 500); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(LP_USSD_EDF, ALICE).collateral, 0); + assert_eq!(Currencies::free_balance(LP_USSD_EDF, &EcdpUssdLoansModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 500); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 500); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 25); + assert_eq!( + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), + 0 + ); + assert_eq!(MockEcdpAuctionsManager::auction(), Some((ALICE, EDF, 25, 100))); + }); +} + +#[test] +fn settle_cdp_has_debit_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 0)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_noop!( + EcdpUssdEngineModule::settle_cdp_has_debit(ALICE, BTC), + Error::::NoDebitValue, + ); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 0, 500)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_ok!(EcdpUssdEngineModule::settle_cdp_has_debit(ALICE, BTC)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::SettleCDPInDebit { + collateral_type: BTC, + owner: ALICE, + })); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 50); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 50); + + assert_noop!( + EcdpUssdEngineModule::settle(RuntimeOrigin::none(), BTC, ALICE), + Error::::MustAfterShutdown + ); + }); +} + +#[test] +fn close_cdp_has_debit_by_dex_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + USSD, + 100, + 1000, + 0, + false + )); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 0)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + + assert_noop!( + EcdpUssdEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100), + Error::::NoDebitValue + ); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 0, 500)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdTreasuryModule::get_surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::get_debit_pool(), 0); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(5, 2))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + assert_noop!( + EcdpUssdEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100), + Error::::MustBeSafe + ); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + + // max collateral amount limit swap + assert_noop!( + EcdpUssdEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 5), + SwapError::CannotSwap + ); + + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (100, 1000)); + assert_ok!(EcdpUssdEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 6)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::CloseCDPInDebitByDEX { + collateral_type: BTC, + owner: ALICE, + sold_collateral_amount: 6, + refund_collateral_amount: 94, + debit_value: 50, + })); + + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, USSD), (106, 950)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 994); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + assert_eq!(EcdpUssdTreasuryModule::get_surplus_pool(), 50); + assert_eq!(EcdpUssdTreasuryModule::get_debit_pool(), 50); + }); +} + +#[test] +fn close_cdp_has_debit_by_swap_on_alternative_path() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + BTC, + SEE, + 100, + 1000, + 0, + false + )); + assert_ok!(EdfisSwapModule::add_liquidity( + RuntimeOrigin::signed(CAROL), + SEE, + USSD, + 1000, + 1000, + 0, + false + )); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, SEE), (100, 1000)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (1000, 1000)); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdTreasuryModule::get_surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::get_debit_pool(), 0); + + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + assert_ok!(EcdpUssdEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100)); + System::assert_last_event(RuntimeEvent::EcdpUssdEngineModule(crate::Event::CloseCDPInDebitByDEX { + collateral_type: BTC, + owner: ALICE, + sold_collateral_amount: 6, + refund_collateral_amount: 94, + debit_value: 50, + })); + + assert_eq!(EdfisSwapModule::get_liquidity_pool(BTC, SEE), (106, 947)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(SEE, USSD), (1053, 950)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 994); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + assert_eq!(EcdpUssdTreasuryModule::get_surplus_pool(), 50); + assert_eq!(EcdpUssdTreasuryModule::get_debit_pool(), 50); + }); +} + +#[test] +fn offchain_worker_works_cdp() { + let (offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain)); + + ext.execute_with(|| { + // number of currencies allowed as collateral (cycles through all of them) + setup_default_collateral(BTC); + setup_default_collateral(LP_USSD_EDF); + setup_default_collateral(EDF); + + let collateral_currencies_num = CollateralCurrencyIds::::get().len() as u64; + + System::set_block_number(1); + + // offchain worker will not liquidate alice + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_ok!(EcdpUssdEngineModule::adjust_position(&BOB, BTC, 100, 100)); + assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 50); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + // jump 2 blocks at a time because code rotates through the different supported collateral + // currencies + run_to_block_offchain(System::block_number() + collateral_currencies_num); + + // checks that offchain worker tx pool is empty (therefore tx to liquidate alice is not present) + assert!(pool_state.write().transactions.pop().is_none()); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + + // changes alice into unsafe position + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(3, 1))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + run_to_block_offchain(System::block_number() + collateral_currencies_num); + + // offchain worker will liquidate alice + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::EcdpUssdEngineModule(crate::Call::liquidate { + currency_id: currency_call, + who: who_call, + }) = tx.call + { + assert_ok!(EcdpUssdEngineModule::liquidate( + RuntimeOrigin::none(), + currency_call, + who_call + )); + } + // empty offchain tx pool (Bob was not liquidated) + assert!(pool_state.write().transactions.pop().is_none()); + // alice is liquidated but bob is not + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).debit, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).collateral, 100); + + // emergency shutdown will settle Bobs debit position + mock_shutdown(); + assert!(MockEcdpEmergencyShutdown::is_shutdown()); + run_to_block_offchain(System::block_number() + collateral_currencies_num); + // offchain worker will settle bob's position + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::EcdpUssdEngineModule(crate::Call::settle { + currency_id: currency_call, + who: who_call, + }) = tx.call + { + assert_ok!(EcdpUssdEngineModule::settle(RuntimeOrigin::none(), currency_call, who_call)); + } + // emergency shutdown settles bob's debit position + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).collateral, 90); + }); +} + +#[test] +fn offchain_worker_iteration_limit_works() { + let (mut offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + // sets max iterations value to 1 + offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 100, 500)); + assert_ok!(EcdpUssdEngineModule::adjust_position(&BOB, BTC, 100, 500)); + // make both positions unsafe + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(3, 1))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + run_to_block_offchain(2); + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::EcdpUssdEngineModule(crate::Call::liquidate { + currency_id: currency_call, + who: who_call, + }) = tx.call + { + assert_ok!(EcdpUssdEngineModule::liquidate( + RuntimeOrigin::none(), + currency_call, + who_call + )); + } + // alice is liquidated but not bob, he will get liquidated next block due to iteration limit + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + // only one tx is submitted due to iteration limit + assert!(pool_state.write().transactions.pop().is_none()); + + // Iterator continues where it was from storage and now liquidates bob + run_to_block_offchain(3); + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::EcdpUssdEngineModule(crate::Call::liquidate { + currency_id: currency_call, + who: who_call, + }) = tx.call + { + assert_ok!(EcdpUssdEngineModule::liquidate( + RuntimeOrigin::none(), + currency_call, + who_call + )); + } + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).debit, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).collateral, 0); + assert!(pool_state.write().transactions.pop().is_none()); + }); +} + +#[test] +fn offchain_default_max_iterator_works() { + let (mut offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::lots_of_accounts().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + System::set_block_number(1); + + // checks that max iterations is stored as none + assert!(offchain + .local_storage_get(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS) + .is_none()); + + for i in 0..1001u32 { + let acount_id: AccountId = account_id_from_u32(i); + assert_ok!(EcdpUssdEngineModule::adjust_position(&acount_id, BTC, 10, 50)); + } + + // make all positions unsafe + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NoChange, + Change::NewValue(Some(Ratio::saturating_from_rational(3, 1))), + Change::NoChange, + Change::NoChange, + Change::NoChange, + )); + run_to_block_offchain(2); + // should only run 1000 iterations stopping due to DEFAULT_MAX_ITERATIONS + assert_eq!(pool_state.write().transactions.len(), 1000); + // should only now run 1 iteration to finish off where it ended last block + run_to_block_offchain(3); + assert_eq!(pool_state.write().transactions.len(), 1001); + }); +} + +#[test] +fn minimal_collateral_works() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + // Check position fails if collateral is too small + assert_noop!( + EcdpUssdEngineModule::check_position_valid(BTC, 9, 0, true), + Error::::CollateralAmountBelowMinimum, + ); + assert_ok!(EcdpUssdEngineModule::check_position_valid(BTC, 9, 20, true)); + assert_ok!(EcdpUssdEngineModule::check_position_valid(BTC, 10, 0, true)); + assert_ok!(EcdpUssdEngineModule::check_position_valid(BTC, 0, 0, true)); + + // Adjust position fails if collateral is too small + assert_noop!( + EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 9, 0), + Error::::CollateralAmountBelowMinimum, + ); + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 10, 0)); + + // Cannot reduce collateral amount below the minimum. + assert_noop!( + EcdpUssdEngineModule::adjust_position(&ALICE, BTC, -1, 0), + Error::::CollateralAmountBelowMinimum, + ); + + // Allow the user to withdraw all assets + assert_ok!(EcdpUssdEngineModule::adjust_position(&ALICE, BTC, 0, 0)); + }); +} + +#[test] +fn register_liquidation_contract_works() { + let address = liquidation_contract_addr(); + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(EcdpUssdEngineModule::register_liquidation_contract( + RuntimeOrigin::signed(ALICE), + address, + )); + assert_eq!(EcdpUssdEngineModule::liquidation_contracts(), vec![address],); + System::assert_has_event(RuntimeEvent::EcdpUssdEngineModule( + crate::Event::LiquidationContractRegistered { address }, + )); + }); +} + +#[test] +fn register_liquidation_contract_fails_if_not_update_origin() { + let address = liquidation_contract_addr(); + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EcdpUssdEngineModule::register_liquidation_contract(RuntimeOrigin::signed(BOB), address,), + BadOrigin + ); + }); +} + +#[test] +fn deregister_liquidation_contract_works() { + let address = liquidation_contract_addr(); + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(LiquidationContracts::::try_append(address)); + assert_eq!(EcdpUssdEngineModule::liquidation_contracts(), vec![address],); + + assert_ok!(EcdpUssdEngineModule::deregister_liquidation_contract( + RuntimeOrigin::signed(ALICE), + address, + )); + assert_eq!(EcdpUssdEngineModule::liquidation_contracts(), vec![],); + System::assert_has_event(RuntimeEvent::EcdpUssdEngineModule( + crate::Event::LiquidationContractDeregistered { address }, + )); + }); +} + +#[test] +fn deregister_liquidation_contract_fails_if_not_update_origin() { + let address = liquidation_contract_addr(); + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(LiquidationContracts::::try_append(address)); + assert_eq!(EcdpUssdEngineModule::liquidation_contracts(), vec![address],); + + assert_noop!( + EcdpUssdEngineModule::deregister_liquidation_contract(RuntimeOrigin::signed(BOB), address,), + BadOrigin + ); + }); +} + +#[test] +fn liquidation_via_contracts_works() { + let address = liquidation_contract_addr(); + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(Currencies::deposit(EDF, &EcdpUssdTreasuryModule::account_id(), 1000)); + assert_ok!(LiquidationContracts::::try_append(address)); + assert_eq!(EcdpUssdEngineModule::liquidation_contracts(), vec![address],); + MockLiquidationEvmBridge::set_liquidation_result(Ok(())); + + assert_ok!(LiquidateViaContracts::::liquidate(&ALICE, EDF, 100, 1_000)); + let contract_account_id = + as AddressMapping>::get_account_id(&address); + assert_eq!(Currencies::free_balance(EDF, &contract_account_id), 100); + }); +} + +#[test] +fn liquidation_fails_if_no_liquidation_contracts() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(Currencies::deposit(EDF, &EcdpUssdTreasuryModule::account_id(), 1000)); + MockLiquidationEvmBridge::set_liquidation_result(Ok(())); + + assert_noop!( + LiquidateViaContracts::::liquidate(&ALICE, EDF, 100, 1_000), + Error::::LiquidationFailed + ); + }); +} + +#[test] +fn liquidation_fails_if_no_liquidation_contracts_can_liquidate() { + let address = liquidation_contract_addr(); + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(Currencies::deposit(EDF, &EcdpUssdTreasuryModule::account_id(), 1000)); + assert_ok!(LiquidationContracts::::try_append(address)); + assert_eq!(EcdpUssdEngineModule::liquidation_contracts(), vec![address],); + + assert_err!( + LiquidateViaContracts::::liquidate(&ALICE, EDF, 100, 1_000), + Error::::LiquidationFailed + ); + }); +} + +#[test] +fn liquidation_fails_if_insufficient_repayment() { + let address = liquidation_contract_addr(); + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(Currencies::deposit(EDF, &EcdpUssdTreasuryModule::account_id(), 1000)); + assert_ok!(LiquidationContracts::::try_append(address)); + assert_eq!(EcdpUssdEngineModule::liquidation_contracts(), vec![address],); + MockLiquidationEvmBridge::set_liquidation_result(Ok(())); + MockLiquidationEvmBridge::set_repayment(1); + + assert_err!( + LiquidateViaContracts::::liquidate(&ALICE, EDF, 100, 1_000), + Error::::LiquidationFailed + ); + }); +} diff --git a/blockchain/modules/ecdp-ussd-engine/src/weights.rs b/blockchain/modules/ecdp-ussd-engine/src/weights.rs new file mode 100644 index 00000000..d790ae5a --- /dev/null +++ b/blockchain/modules/ecdp-ussd-engine/src/weights.rs @@ -0,0 +1,128 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for module_cdp_engine +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/setheum-node +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_cdp_engine +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchains/modules/cdp-engine/src/weights.rs +// --template=.maintain/module-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_cdp_engine. +pub trait WeightInfo { + fn set_collateral_params() -> Weight; + fn liquidate_by_auction(b: u32) -> Weight; + fn liquidate_by_dex() -> Weight; + fn settle() -> Weight; + fn register_liquidation_contract() -> Weight; + fn deregister_liquidation_contract() -> Weight; +} + +/// Weights for module_cdp_engine using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + fn set_collateral_params() -> Weight { + Weight::from_parts(37_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn liquidate_by_auction(_b: u32) -> Weight { + Weight::from_parts(203_000_000, 0) + .saturating_add(T::DbWeight::get().reads(28 as u64)) + .saturating_add(T::DbWeight::get().writes(17 as u64)) + } + fn liquidate_by_dex() -> Weight { + Weight::from_parts(252_000_000, 0) + .saturating_add(T::DbWeight::get().reads(29 as u64)) + .saturating_add(T::DbWeight::get().writes(15 as u64)) + } + fn settle() -> Weight { + Weight::from_parts(97_000_000, 0) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) + } + fn register_liquidation_contract() -> Weight { + Weight::from_parts(97_000_000, 0) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) + } + fn deregister_liquidation_contract() -> Weight { + Weight::from_parts(97_000_000, 0) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn set_collateral_params() -> Weight { + Weight::from_parts(37_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn liquidate_by_auction(_b: u32) -> Weight { + Weight::from_parts(203_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(28 as u64)) + .saturating_add(RocksDbWeight::get().writes(17 as u64)) + } + fn liquidate_by_dex() -> Weight { + Weight::from_parts(252_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(29 as u64)) + .saturating_add(RocksDbWeight::get().writes(15 as u64)) + } + fn settle() -> Weight { + Weight::from_parts(97_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } + fn register_liquidation_contract() -> Weight { + Weight::from_parts(97_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } + + fn deregister_liquidation_contract() -> Weight { + Weight::from_parts(97_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } +} diff --git a/blockchain/modules/ecdp-ussd-treasury/Cargo.toml b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml index 4e2afaac..329ef32b 100644 --- a/blockchain/modules/ecdp-ussd-treasury/Cargo.toml +++ b/blockchain/modules/ecdp-ussd-treasury/Cargo.toml @@ -22,7 +22,7 @@ sp-io = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } orml-currencies = { workspace = true, features = ["std"] } orml-auction = { workspace = true, features = ["std"] } -module-dex = { workspace = true, features = ["std"] } +module-edfis_swap_legacy = { workspace = true, features = ["std"] } orml-tokens = { workspace = true, features = ["std"] } [features] diff --git a/blockchain/modules/ecdp-ussd-treasury/src/lib.rs b/blockchain/modules/ecdp-ussd-treasury/src/lib.rs index 4571c1bb..272fcf2f 100644 --- a/blockchain/modules/ecdp-ussd-treasury/src/lib.rs +++ b/blockchain/modules/ecdp-ussd-treasury/src/lib.rs @@ -22,7 +22,7 @@ //! //! ## Overview //! -//! The ECDP USSD Treasury manages the accumulated interest and bad debts generated by ECDPs, +//! The ECDP USSD Treasury manages the bad debts generated by ECDPs, //! and handle excessive surplus or debits timely in order to keep the system healthy with low risk. //! It's the only entry for issuing/burning USSD. @@ -32,7 +32,7 @@ use frame_support::{pallet_prelude::*, transactional, PalletId}; use frame_system::pallet_prelude::*; -use module_support::{AuctionsManager, SlickUsdEcdpTreasury, SlickUsdEcdpTreasuryExtended, SwapManager, Ratio, Swap, SwapLimit}; +use module_support::{EcdpAuctionsManager, EcdpUssdTreasury, EcdpUssdTreasuryExtended, SwapManager, Ratio, Swap, SwapLimit}; use orml_traits::{MultiCurrency, MultiCurrencyExtended}; use primitives::{Balance, CurrencyId}; use sp_runtime::{ @@ -68,7 +68,7 @@ pub mod module { type GetUSSDCurrencyId: Get; /// Auction manager creates auction to handle system surplus and debit - type AuctionsManagerHandler: AuctionsManager; + type EcdpAuctionsManagerHandler: EcdpAuctionsManager; /// Dex manager type DEX: SwapManager; @@ -212,7 +212,7 @@ pub mod module { splited: bool, ) -> DispatchResultWithPostInfo { T::UpdateOrigin::ensure_origin(origin)?; - let created_auctions = >::create_collateral_auctions( + let created_auctions = >::create_collateral_auctions( currency_id, amount, target, @@ -303,7 +303,7 @@ impl Pallet { /// Get collateral amount not in auction pub fn total_collaterals_not_in_auction(currency_id: CurrencyId) -> Balance { T::Currency::free_balance(currency_id, &Self::account_id()) - .saturating_sub(T::AuctionsManagerHandler::get_total_collateral_in_auction(currency_id)) + .saturating_sub(T::EcdpAuctionsManagerHandler::get_total_collateral_in_auction(currency_id)) } fn offset_surplus_and_debit() { @@ -326,7 +326,7 @@ impl Pallet { } Err(e) => { log::warn!( - target: "cdp-treasury", + target: "ecdp-ussd-treasury", "get_swap_supply_amount: Attempt to burn surplus {:?} failed: {:?}, this is unexpected but should be safe", offset_amount, e ); @@ -336,7 +336,7 @@ impl Pallet { } } -impl SlickUsdEcdpTreasury for Pallet { +impl EcdpUssdTreasury for Pallet { type Balance = Balance; type CurrencyId = CurrencyId; @@ -401,7 +401,7 @@ impl SlickUsdEcdpTreasury for Pallet { } } -impl SlickUsdEcdpTreasuryExtended for Pallet { +impl EcdpUssdTreasuryExtended for Pallet { #[transactional] fn swap_collateral_to_ussd( currency_id: CurrencyId, @@ -420,7 +420,7 @@ impl SlickUsdEcdpTreasuryExtended for Pallet { if collateral_in_auction { ensure!( Self::total_collaterals(currency_id) >= supply_limit - && T::AuctionsManagerHandler::get_total_collateral_in_auction(currency_id) >= supply_limit, + && T::EcdpAuctionsManagerHandler::get_total_collateral_in_auction(currency_id) >= supply_limit, Error::::CollateralNotEnough, ); } else { @@ -481,7 +481,7 @@ impl SlickUsdEcdpTreasuryExtended for Pallet { (average_amount_per_lot, average_target_per_lot) }; - T::AuctionsManagerHandler::new_collateral_auction( + T::EcdpAuctionsManagerHandler::new_collateral_auction( &refund_receiver, currency_id, lot_collateral_amount, diff --git a/blockchain/modules/ecdp-ussd-treasury/src/mock.rs b/blockchain/modules/ecdp-ussd-treasury/src/mock.rs index e365ba3f..288104e0 100644 --- a/blockchain/modules/ecdp-ussd-treasury/src/mock.rs +++ b/blockchain/modules/ecdp-ussd-treasury/src/mock.rs @@ -49,7 +49,7 @@ pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); pub const LP_USSD_EDF: CurrencyId = CurrencyId::DexShare(DexShare::Token(TokenSymbol::USSD), DexShare::Token(TokenSymbol::EDF)); -mod cdp_treasury { +mod ecdp_ussd_treasury { pub use super::super::*; } @@ -118,17 +118,17 @@ parameter_types! { TradingPair::from_currency_ids(USSD, EDF).unwrap(), TradingPair::from_currency_ids(BTC, EDF).unwrap(), ]; - pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const EdfisSwapPalletId: PalletId = PalletId(*b"aca/dexm"); } -impl module_dex::Config for Runtime { +impl module_edfis_swap_legacy::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; - type PalletId = DEXPalletId; + type PalletId = EdfisSwapPalletId; type Erc20InfoMapping = (); - type DEXIncentives = (); + type SwapDexIncentives = (); type WeightInfo = (); type ListingOrigin = EnsureSignedBy; type ExtendedProvisioningBlocks = ConstU64<0>; @@ -140,8 +140,8 @@ thread_local! { pub static TOTAL_COLLATERAL_IN_AUCTION: RefCell = RefCell::new(0); } -pub struct MockAuctionsManager; -impl AuctionsManager for MockAuctionsManager { +pub struct MockEcdpAuctionsManager; +impl EcdpAuctionsManager for MockEcdpAuctionsManager { type CurrencyId = CurrencyId; type Balance = Balance; type AuctionId = AuctionId; @@ -175,7 +175,7 @@ ord_parameter_types! { } parameter_types! { - pub const SlickUsdEcdpTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub const EcdpUssdTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub const TreasuryAccount: AccountId = 10; pub AlternativeSwapPathJointList: Vec> = vec![ vec![EDF], @@ -190,12 +190,12 @@ impl Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Currencies; type GetUSSDCurrencyId = GetUSSDCurrencyId; - type AuctionsManagerHandler = MockAuctionsManager; + type EcdpAuctionsManagerHandler = MockEcdpAuctionsManager; type UpdateOrigin = EitherOfDiverse, EnsureSignedBy>; - type DEX = DEXModule; - type Swap = SpecificJointsSwap; + type DEX = EdfisSwapModule; + type Swap = SpecificJointsSwap; type MaxAuctionsCount = ConstU32<5>; - type PalletId = SlickUsdEcdpTreasuryPalletId; + type PalletId = EcdpUssdTreasuryPalletId; type TreasuryAccount = TreasuryAccount; type WeightInfo = (); } @@ -205,11 +205,11 @@ type Block = frame_system::mocking::MockBlock; construct_runtime!( pub enum Runtime { System: frame_system, - SlickUsdEcdpTreasuryModule: cdp_treasury, + EcdpUssdTreasuryModule: ecdp_ussd_treasury, Currencies: orml_currencies, Tokens: orml_tokens, PalletBalances: pallet_balances, - DEXModule: module_dex, + EdfisSwapModule: module_edfis_swap_legacy, } ); @@ -246,7 +246,7 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - module_dex::GenesisConfig:: { + module_edfis_swap_legacy::GenesisConfig:: { initial_listing_trading_pairs: vec![], initial_enabled_trading_pairs: EnabledTradingPairs::get(), initial_added_liquidity_pools: vec![], diff --git a/blockchain/modules/ecdp-ussd-treasury/src/tests.rs b/blockchain/modules/ecdp-ussd-treasury/src/tests.rs index 8f70d9bc..35241f55 100644 --- a/blockchain/modules/ecdp-ussd-treasury/src/tests.rs +++ b/blockchain/modules/ecdp-ussd-treasury/src/tests.rs @@ -31,33 +31,33 @@ use sp_runtime::traits::BadOrigin; #[test] fn surplus_pool_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); assert_ok!(Currencies::deposit( GetUSSDCurrencyId::get(), - &SlickUsdEcdpTreasuryModule::account_id(), + &EcdpUssdTreasuryModule::account_id(), 500 )); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 500); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 500); }); } #[test] fn total_collaterals_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 0); - assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10)); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_ok!(Currencies::deposit(BTC, &EcdpUssdTreasuryModule::account_id(), 10)); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 10); }); } #[test] fn on_system_debit_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(1000)); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_ok!(EcdpUssdTreasuryModule::on_system_debit(1000)); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 1000); assert_noop!( - SlickUsdEcdpTreasuryModule::on_system_debit(Balance::max_value()), + EcdpUssdTreasuryModule::on_system_debit(Balance::max_value()), ArithmeticError::Overflow, ); }); @@ -66,39 +66,39 @@ fn on_system_debit_work() { #[test] fn on_system_surplus_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_ok!(EcdpUssdTreasuryModule::on_system_surplus(1000)); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 1000); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 1000); }); } #[test] fn offset_surplus_and_debit_on_finalize_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); - SlickUsdEcdpTreasuryModule::on_finalize(1); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(300)); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 300); - SlickUsdEcdpTreasuryModule::on_finalize(2); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 700); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 700); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(800)); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 800); - SlickUsdEcdpTreasuryModule::on_finalize(3); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_ok!(EcdpUssdTreasuryModule::on_system_surplus(1000)); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 1000); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 1000); + EcdpUssdTreasuryModule::on_finalize(1); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 1000); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_ok!(EcdpUssdTreasuryModule::on_system_debit(300)); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 300); + EcdpUssdTreasuryModule::on_finalize(2); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 700); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 700); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_ok!(EcdpUssdTreasuryModule::on_system_debit(800)); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 800); + EcdpUssdTreasuryModule::on_finalize(3); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 100); }); } @@ -106,15 +106,15 @@ fn offset_surplus_and_debit_on_finalize_work() { fn issue_debit_work() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::issue_debit(&ALICE, 1000, true)); + assert_ok!(EcdpUssdTreasuryModule::issue_debit(&ALICE, 1000, true)); assert_eq!(Currencies::free_balance(USSD, &ALICE), 2000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::issue_debit(&ALICE, 1000, false)); + assert_ok!(EcdpUssdTreasuryModule::issue_debit(&ALICE, 1000, false)); assert_eq!(Currencies::free_balance(USSD, &ALICE), 3000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 1000); }); } @@ -122,10 +122,10 @@ fn issue_debit_work() { fn burn_debit_work() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::burn_debit(&ALICE, 300)); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); + assert_ok!(EcdpUssdTreasuryModule::burn_debit(&ALICE, 300)); assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 0); }); } @@ -133,40 +133,40 @@ fn burn_debit_work() { fn deposit_surplus_work() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Currencies::free_balance(USSD, &ALICE), 1000); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_surplus(&ALICE, 300)); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_ok!(EcdpUssdTreasuryModule::deposit_surplus(&ALICE, 300)); assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 300); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 300); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 300); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 300); }); } #[test] fn withdraw_surplus_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_surplus(&ALICE, 300)); + assert_ok!(EcdpUssdTreasuryModule::deposit_surplus(&ALICE, 300)); assert_eq!(Currencies::free_balance(USSD, &ALICE), 700); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 300); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 300); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 300); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 300); - assert_ok!(SlickUsdEcdpTreasuryModule::withdraw_surplus(&ALICE, 200)); + assert_ok!(EcdpUssdTreasuryModule::withdraw_surplus(&ALICE, 200)); assert_eq!(Currencies::free_balance(USSD, &ALICE), 900); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 100); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 100); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 100); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 100); }); } #[test] fn deposit_collateral_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 0); - assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 0); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdTreasuryModule::account_id()), 0); assert_eq!(Currencies::free_balance(BTC, &ALICE), 1000); - assert!(!SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 10000).is_ok()); - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 500); - assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 500); + assert!(!EcdpUssdTreasuryModule::deposit_collateral(&ALICE, BTC, 10000).is_ok()); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 500); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdTreasuryModule::account_id()), 500); assert_eq!(Currencies::free_balance(BTC, &ALICE), 500); }); } @@ -174,14 +174,14 @@ fn deposit_collateral_work() { #[test] fn withdraw_collateral_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 500); - assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 500); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 500); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdTreasuryModule::account_id()), 500); assert_eq!(Currencies::free_balance(BTC, &BOB), 1000); - assert!(!SlickUsdEcdpTreasuryModule::withdraw_collateral(&BOB, BTC, 501).is_ok()); - assert_ok!(SlickUsdEcdpTreasuryModule::withdraw_collateral(&BOB, BTC, 400)); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 100); - assert_eq!(Currencies::free_balance(BTC, &SlickUsdEcdpTreasuryModule::account_id()), 100); + assert!(!EcdpUssdTreasuryModule::withdraw_collateral(&BOB, BTC, 501).is_ok()); + assert_ok!(EcdpUssdTreasuryModule::withdraw_collateral(&BOB, BTC, 400)); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(Currencies::free_balance(BTC, &EcdpUssdTreasuryModule::account_id()), 100); assert_eq!(Currencies::free_balance(BTC, &BOB), 1400); }); } @@ -189,8 +189,8 @@ fn withdraw_collateral_work() { #[test] fn get_total_collaterals_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); - assert_eq!(SlickUsdEcdpTreasuryModule::get_total_collaterals(BTC), 500); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&ALICE, BTC, 500)); + assert_eq!(EcdpUssdTreasuryModule::get_total_collaterals(BTC), 500); }); } @@ -198,7 +198,7 @@ fn get_total_collaterals_work() { fn get_debit_proportion_work() { ExtBuilder::default().build().execute_with(|| { assert_eq!( - SlickUsdEcdpTreasuryModule::get_debit_proportion(100), + EcdpUssdTreasuryModule::get_debit_proportion(100), Ratio::saturating_from_rational(100, Currencies::total_issuance(USSD)) ); }); @@ -207,12 +207,12 @@ fn get_debit_proportion_work() { #[test] fn swap_collateral_to_ussd_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&BOB, BTC, 200)); - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&CHARLIE, EDF, 1000)); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 200); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(EDF), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&BOB, BTC, 200)); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&CHARLIE, EDF, 1000)); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(BTC), 200); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(EDF), 1000); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(BOB), EDF, USSD, @@ -223,19 +223,19 @@ fn swap_collateral_to_ussd_work() { )); assert_noop!( - SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(201, 200), false), + EcdpUssdTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(201, 200), false), Error::::CollateralNotEnough, ); assert_noop!( - SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1001, 0), false), + EcdpUssdTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1001, 0), false), Error::::CollateralNotEnough, ); assert_noop!( - SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false), + EcdpUssdTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false), SwapError::CannotSwap ); - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(ALICE), BTC, EDF, @@ -246,45 +246,45 @@ fn swap_collateral_to_ussd_work() { )); assert_eq!( - SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false).unwrap(), + EcdpUssdTreasuryModule::swap_collateral_to_ussd(BTC, SwapLimit::ExactTarget(200, 399), false).unwrap(), (198, 399) ); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 399); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 2); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 399); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(BTC), 2); assert_noop!( - SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 1000), false), + EcdpUssdTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 1000), false), SwapError::CannotSwap ); assert_eq!( - SlickUsdEcdpTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 0), false).unwrap(), + EcdpUssdTreasuryModule::swap_collateral_to_ussd(EDF, SwapLimit::ExactSupply(1000, 0), false).unwrap(), (1000, 225) ); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 624); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(EDF), 0); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 624); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(EDF), 0); }); } #[test] fn create_collateral_auctions_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10000)); - assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_ok!(Currencies::deposit(BTC, &EcdpUssdTreasuryModule::account_id(), 10000)); + assert_eq!(EcdpUssdTreasuryModule::expected_collateral_auction_size(BTC), 0); assert_noop!( - SlickUsdEcdpTreasuryModule::create_collateral_auctions(BTC, 10001, 1000, ALICE, true), + EcdpUssdTreasuryModule::create_collateral_auctions(BTC, 10001, 1000, ALICE, true), Error::::CollateralNotEnough, ); // without collateral auction maximum size - assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + assert_ok!(EcdpUssdTreasuryModule::create_collateral_auctions( BTC, 1000, 1000, ALICE, true )); assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 1); assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1000); // set collateral auction maximum size - assert_ok!(SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size( + assert_ok!(EcdpUssdTreasuryModule::set_expected_collateral_auction_size( RuntimeOrigin::signed(1), BTC, 300 @@ -292,7 +292,7 @@ fn create_collateral_auctions_work() { // amount < collateral auction maximum size // auction + 1 - assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + assert_ok!(EcdpUssdTreasuryModule::create_collateral_auctions( BTC, 200, 1000, ALICE, true )); assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 2); @@ -300,7 +300,7 @@ fn create_collateral_auctions_work() { // not exceed lots count cap // auction + 4 - assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + assert_ok!(EcdpUssdTreasuryModule::create_collateral_auctions( BTC, 1000, 1000, ALICE, true )); assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 6); @@ -308,7 +308,7 @@ fn create_collateral_auctions_work() { // exceed lots count cap // auction + 5 - assert_ok!(SlickUsdEcdpTreasuryModule::create_collateral_auctions( + assert_ok!(EcdpUssdTreasuryModule::create_collateral_auctions( BTC, 2000, 1000, ALICE, true )); assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 11); @@ -319,7 +319,7 @@ fn create_collateral_auctions_work() { #[test] fn remove_liquidity_for_lp_collateral_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(BOB), USSD, EDF, @@ -328,33 +328,33 @@ fn remove_liquidity_for_lp_collateral_work() { 0, false )); - assert_ok!(SlickUsdEcdpTreasuryModule::deposit_collateral(&BOB, LP_USSD_EDF, 200)); + assert_ok!(EcdpUssdTreasuryModule::deposit_collateral(&BOB, LP_USSD_EDF, 200)); assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 2000); - assert_eq!(DEXModule::get_liquidity_pool(USSD, EDF), (1000, 100)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (1000, 100)); assert_eq!( - Currencies::free_balance(LP_USSD_EDF, &SlickUsdEcdpTreasuryModule::account_id()), + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), 200 ); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 0); - assert_eq!(Currencies::free_balance(EDF, &SlickUsdEcdpTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 0); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 0); assert_noop!( - SlickUsdEcdpTreasuryModule::remove_liquidity_for_lp_collateral(EDF, 200), + EcdpUssdTreasuryModule::remove_liquidity_for_lp_collateral(EDF, 200), Error::::NotDexShare ); assert_eq!( - SlickUsdEcdpTreasuryModule::remove_liquidity_for_lp_collateral(LP_USSD_EDF, 120), + EcdpUssdTreasuryModule::remove_liquidity_for_lp_collateral(LP_USSD_EDF, 120), Ok((60, 6)) ); assert_eq!(Currencies::total_issuance(LP_USSD_EDF), 1880); - assert_eq!(DEXModule::get_liquidity_pool(USSD, EDF), (940, 94)); + assert_eq!(EdfisSwapModule::get_liquidity_pool(USSD, EDF), (940, 94)); assert_eq!( - Currencies::free_balance(LP_USSD_EDF, &SlickUsdEcdpTreasuryModule::account_id()), + Currencies::free_balance(LP_USSD_EDF, &EcdpUssdTreasuryModule::account_id()), 80 ); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 60); - assert_eq!(Currencies::free_balance(EDF, &SlickUsdEcdpTreasuryModule::account_id()), 6); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 60); + assert_eq!(Currencies::free_balance(EDF, &EcdpUssdTreasuryModule::account_id()), 6); }); } @@ -362,17 +362,17 @@ fn remove_liquidity_for_lp_collateral_work() { fn set_expected_collateral_auction_size_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_eq!(EcdpUssdTreasuryModule::expected_collateral_auction_size(BTC), 0); assert_noop!( - SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size(RuntimeOrigin::signed(5), BTC, 200), + EcdpUssdTreasuryModule::set_expected_collateral_auction_size(RuntimeOrigin::signed(5), BTC, 200), BadOrigin ); - assert_ok!(SlickUsdEcdpTreasuryModule::set_expected_collateral_auction_size( + assert_ok!(EcdpUssdTreasuryModule::set_expected_collateral_auction_size( RuntimeOrigin::signed(1), BTC, 200 )); - System::assert_last_event(RuntimeEvent::SlickUsdEcdpTreasuryModule( + System::assert_last_event(RuntimeEvent::EcdpUssdTreasuryModule( crate::Event::ExpectedCollateralAuctionSizeUpdated { collateral_type: BTC, new_size: 200, @@ -384,21 +384,21 @@ fn set_expected_collateral_auction_size_work() { #[test] fn extract_surplus_to_treasury_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 1000); + assert_ok!(EcdpUssdTreasuryModule::on_system_surplus(1000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 1000); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 1000); assert_eq!(Currencies::free_balance(USSD, &TreasuryAccount::get()), 0); assert_noop!( - SlickUsdEcdpTreasuryModule::extract_surplus_to_treasury(RuntimeOrigin::signed(5), 200), + EcdpUssdTreasuryModule::extract_surplus_to_treasury(RuntimeOrigin::signed(5), 200), BadOrigin ); - assert_ok!(SlickUsdEcdpTreasuryModule::extract_surplus_to_treasury( + assert_ok!(EcdpUssdTreasuryModule::extract_surplus_to_treasury( RuntimeOrigin::signed(1), 200 )); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 800); - assert_eq!(Currencies::free_balance(USSD, &SlickUsdEcdpTreasuryModule::account_id()), 800); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 800); + assert_eq!(Currencies::free_balance(USSD, &EcdpUssdTreasuryModule::account_id()), 800); assert_eq!(Currencies::free_balance(USSD, &TreasuryAccount::get()), 200); }); } @@ -406,20 +406,20 @@ fn extract_surplus_to_treasury_work() { #[test] fn auction_collateral_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 10000)); - assert_eq!(SlickUsdEcdpTreasuryModule::expected_collateral_auction_size(BTC), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10000); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 10000); + assert_ok!(Currencies::deposit(BTC, &EcdpUssdTreasuryModule::account_id(), 10000)); + assert_eq!(EcdpUssdTreasuryModule::expected_collateral_auction_size(BTC), 0); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 10000); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(BTC), 10000); assert_noop!( - SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(5), BTC, 10000, 1000, false), + EcdpUssdTreasuryModule::auction_collateral(RuntimeOrigin::signed(5), BTC, 10000, 1000, false), BadOrigin, ); assert_noop!( - SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 10001, 1000, false), + EcdpUssdTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 10001, 1000, false), Error::::CollateralNotEnough, ); - assert_ok!(SlickUsdEcdpTreasuryModule::auction_collateral( + assert_ok!(EcdpUssdTreasuryModule::auction_collateral( RuntimeOrigin::signed(1), BTC, 1000, @@ -429,10 +429,10 @@ fn auction_collateral_work() { assert_eq!(TOTAL_COLLATERAL_AUCTION.with(|v| *v.borrow_mut()), 1); assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 10000); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 9000); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 10000); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(BTC), 9000); assert_noop!( - SlickUsdEcdpTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 9001, 1000, false), + EcdpUssdTreasuryModule::auction_collateral(RuntimeOrigin::signed(1), BTC, 9001, 1000, false), Error::::CollateralNotEnough, ); }); @@ -441,7 +441,7 @@ fn auction_collateral_work() { #[test] fn exchange_collateral_to_ussd_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(DEXModule::add_liquidity( + assert_ok!(EdfisSwapModule::add_liquidity( RuntimeOrigin::signed(BOB), BTC, USSD, @@ -451,20 +451,20 @@ fn exchange_collateral_to_ussd_work() { false )); - assert_ok!(Currencies::deposit(BTC, &SlickUsdEcdpTreasuryModule::account_id(), 1000)); - assert_ok!(SlickUsdEcdpTreasuryModule::auction_collateral( + assert_ok!(Currencies::deposit(BTC, &EcdpUssdTreasuryModule::account_id(), 1000)); + assert_ok!(EcdpUssdTreasuryModule::auction_collateral( RuntimeOrigin::signed(1), BTC, 800, 1000, false )); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 200); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 1000); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(BTC), 200); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); assert_noop!( - SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + EcdpUssdTreasuryModule::exchange_collateral_to_ussd( RuntimeOrigin::signed(5), BTC, SwapLimit::ExactTarget(200, 200) @@ -472,7 +472,7 @@ fn exchange_collateral_to_ussd_work() { BadOrigin, ); assert_noop!( - SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + EcdpUssdTreasuryModule::exchange_collateral_to_ussd( RuntimeOrigin::signed(1), BTC, SwapLimit::ExactTarget(201, 200) @@ -480,7 +480,7 @@ fn exchange_collateral_to_ussd_work() { Error::::CollateralNotEnough, ); assert_noop!( - SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + EcdpUssdTreasuryModule::exchange_collateral_to_ussd( RuntimeOrigin::signed(1), BTC, SwapLimit::ExactSupply(201, 0) @@ -488,7 +488,7 @@ fn exchange_collateral_to_ussd_work() { Error::::CollateralNotEnough, ); assert_noop!( - SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + EcdpUssdTreasuryModule::exchange_collateral_to_ussd( RuntimeOrigin::signed(1), BTC, SwapLimit::ExactTarget(200, 1000) @@ -496,14 +496,14 @@ fn exchange_collateral_to_ussd_work() { SwapError::CannotSwap ); - assert_ok!(SlickUsdEcdpTreasuryModule::exchange_collateral_to_ussd( + assert_ok!(EcdpUssdTreasuryModule::exchange_collateral_to_ussd( RuntimeOrigin::signed(1), BTC, SwapLimit::ExactTarget(200, 399) )); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 399); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals(BTC), 867); - assert_eq!(SlickUsdEcdpTreasuryModule::total_collaterals_not_in_auction(BTC), 67); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 399); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals(BTC), 867); + assert_eq!(EcdpUssdTreasuryModule::total_collaterals_not_in_auction(BTC), 67); }); } @@ -511,16 +511,16 @@ fn exchange_collateral_to_ussd_work() { fn set_debit_offset_buffer_work() { ExtBuilder::default().build().execute_with(|| { System::set_block_number(1); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_offset_buffer(), 0); assert_noop!( - SlickUsdEcdpTreasuryModule::set_debit_offset_buffer(RuntimeOrigin::signed(5), 200), + EcdpUssdTreasuryModule::set_debit_offset_buffer(RuntimeOrigin::signed(5), 200), BadOrigin ); - assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + assert_ok!(EcdpUssdTreasuryModule::set_debit_offset_buffer( RuntimeOrigin::signed(1), 200 )); - System::assert_last_event(RuntimeEvent::SlickUsdEcdpTreasuryModule( + System::assert_last_event(RuntimeEvent::EcdpUssdTreasuryModule( crate::Event::DebitOffsetBufferUpdated { amount: 200 }, )); }); @@ -529,43 +529,43 @@ fn set_debit_offset_buffer_work() { #[test] fn offset_surplus_and_debit_limited_by_debit_offset_buffer() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(1000)); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(2000)); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 2000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + assert_ok!(EcdpUssdTreasuryModule::on_system_surplus(1000)); + assert_ok!(EcdpUssdTreasuryModule::on_system_debit(2000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 2000); + assert_eq!(EcdpUssdTreasuryModule::debit_offset_buffer(), 0); // offset all debit pool when surplus is enough - SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1000); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 0); + EcdpUssdTreasuryModule::offset_surplus_and_debit(); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 1000); + assert_eq!(EcdpUssdTreasuryModule::debit_offset_buffer(), 0); - assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + assert_ok!(EcdpUssdTreasuryModule::set_debit_offset_buffer( RuntimeOrigin::signed(1), 100 )); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 100); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_surplus(2000)); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 2000); + assert_eq!(EcdpUssdTreasuryModule::debit_offset_buffer(), 100); + assert_ok!(EcdpUssdTreasuryModule::on_system_surplus(2000)); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 2000); // keep the buffer for debit pool when surplus is enough - SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 1100); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 100); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 100); + EcdpUssdTreasuryModule::offset_surplus_and_debit(); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 1100); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 100); + assert_eq!(EcdpUssdTreasuryModule::debit_offset_buffer(), 100); - assert_ok!(SlickUsdEcdpTreasuryModule::set_debit_offset_buffer( + assert_ok!(EcdpUssdTreasuryModule::set_debit_offset_buffer( RuntimeOrigin::signed(1), 200 )); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 200); - assert_ok!(SlickUsdEcdpTreasuryModule::on_system_debit(1400)); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 1500); - - SlickUsdEcdpTreasuryModule::offset_surplus_and_debit(); - assert_eq!(SlickUsdEcdpTreasuryModule::surplus_pool(), 0); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_pool(), 400); - assert_eq!(SlickUsdEcdpTreasuryModule::debit_offset_buffer(), 200); + assert_eq!(EcdpUssdTreasuryModule::debit_offset_buffer(), 200); + assert_ok!(EcdpUssdTreasuryModule::on_system_debit(1400)); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 1500); + + EcdpUssdTreasuryModule::offset_surplus_and_debit(); + assert_eq!(EcdpUssdTreasuryModule::surplus_pool(), 0); + assert_eq!(EcdpUssdTreasuryModule::debit_pool(), 400); + assert_eq!(EcdpUssdTreasuryModule::debit_offset_buffer(), 200); }); } diff --git a/blockchain/modules/ecdp/Cargo.toml b/blockchain/modules/ecdp/Cargo.toml new file mode 100644 index 00000000..2d4916b4 --- /dev/null +++ b/blockchain/modules/ecdp/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "module-ecdp-core" +version = "0.9.81-dev" +authors = ["Setheum Labs"] +edition = "2021" + +[dependencies] +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-std = { workspace = true } +module-ecdp-ussd-engine = { workspace = true } +module-ecdp-ussd-loans = { workspace = true } +module-support = { workspace = true } +primitives = { workspace = true } + +[dev-dependencies] +sp-io = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +orml-currencies = { workspace = true, features = ["std"] } +orml-traits = { workspace = true, features = ["std"] } +orml-tokens = { workspace = true, features = ["std"] } +module-ecdp-ussd-treasury = { workspace = true, features = ["std"] } +module-evm-accounts = { workspace = true, features = ["std"] } +module-evm = { workspace = true, features = ["std"] } +module-evm-bridge = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "module-ecdp-ussd-engine/std", + "parity-scale-codec/std", + "frame-support/std", + "frame-system/std", + "module-ecdp-ussd-loans/std", + "primitives/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "module-support/std", +] +try-runtime = [ + "module-ecdp-ussd-engine/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "module-ecdp-ussd-loans/try-runtime", +] diff --git a/blockchain/modules/ecdp/README.md b/blockchain/modules/ecdp/README.md new file mode 100644 index 00000000..37e3ea53 --- /dev/null +++ b/blockchain/modules/ecdp/README.md @@ -0,0 +1,5 @@ +# ECDP Manager Module + +## Overview + +Provides USD-Pegged ECDP `Slick USD (USSD)` Stablecoin on Ethical DeFi. \ No newline at end of file diff --git a/blockchain/modules/ecdp/TODO.md b/blockchain/modules/ecdp/TODO.md new file mode 100644 index 00000000..3e9cd565 --- /dev/null +++ b/blockchain/modules/ecdp/TODO.md @@ -0,0 +1,56 @@ +# To-Do List + +This list contains all TODOs in the Repo + + + +- [ToDo List - The Monofile for Setheum Repo ToDos](#to-do-list) + - [1. Introduction](#1-guidelines) + - [2. Contribution](#2-contribution) + - [3. Lists](#3-lists) + - [4. Tasks](#3-tasks) + + + +## 1. Guidelines + +Note: Before you write a ToDo in this repo, please read the below guidelines carefully. + +Whenever you write a ToDo, you need to follow this standard syntax + +```rust +//TODO:[file_name:task_number] - task_details +``` + +for example: + +```rust +//TODO:[TODO.md:0] - Add Todo Guidelines +``` + +Note > the `//TODO:[filename:task_number] - ` is what we call the `task_prefix`. + +Whenever adding/writing a Task/ToDo, you need to describe the task on this list. Whenever you write a TODO in any file, add a reference to it here. Please make sure the task reference here is titled correctly and as detailed as possible\. + +Whenever you `complete` a task/TODO from any file, please tick/complete its reference here and make sure you do it in the same `commit` that completes the task. + +Whenever a task is cancelled (discontinued or not needed for w/e reason), please note in the details why it is cancelled, make sure you do it in the same `commit` that removes/cancels the TODO, and add this `-C` as a suffix to its `file_name` in the list here, for example: + +```rust +//TODO:[TODO.md-C:0] - Add Todo Guidelines +``` + +## 2. Contribution + +You can contribute to this list by completing tasks or by adding tasks(TODOs) that are currently in the repo but not on the list. You can also contribute by updating old tasks to the new Standard. + +## 3. Lists + +Each package/module/directory has its own `TODO.md`. + +## 4. Tasks + +These tasks are just for this file specifically. + +- [x] [[TODO.md:0] - Add TODO.md File](TODO.md): Add a TODO.md file to organise TODOs in the repo. +- [x] [[TODO.md:1] - Add a `task_title`](/TODO.md/#tasks): Adda `task_title`. diff --git a/blockchain/modules/ecdp/src/lib.rs b/blockchain/modules/ecdp/src/lib.rs new file mode 100644 index 00000000..47ed456b --- /dev/null +++ b/blockchain/modules/ecdp/src/lib.rs @@ -0,0 +1,455 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! # Ecdp Module +//! +//! ## Overview +//! +//! The entry of the Ecdp protocol for users, user can manipulate their ECDP +//! position to loan/payback, and can also authorize others to manage the their +//! ECDP under specific collateral type. +//! +//! After system shutdown, some operations will be restricted. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use frame_support::{pallet_prelude::*, traits::NamedReservableCurrency}; +use frame_system::pallet_prelude::*; +use module_support::{EcdpUssdTreasury, EcdpEmergencyShutdown, ExchangeRate, EcdpUssdManager, PriceProvider, Ratio}; +use primitives::{Amount, Balance, CurrencyId, EcdpPosition, ReserveIdentifier}; +use sp_core::U256; +use sp_runtime::{ + traits::{StaticLookup, Zero}, + ArithmeticError, DispatchResult, +}; +use sp_std::prelude::*; + +mod mock; +mod tests; +pub mod weights; + +pub use module::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod module { + use super::*; + + pub const RESERVE_ID: ReserveIdentifier = ReserveIdentifier::Ecdp; + + #[pallet::config] + pub trait Config: frame_system::Config + module_cdp_engine::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency for authorization reserved. + type Currency: NamedReservableCurrency< + Self::AccountId, + Balance = Balance, + ReserveIdentifier = ReserveIdentifier, + >; + + /// Reserved amount per authorization. + #[pallet::constant] + type DepositPerAuthorization: Get; + + /// The list of valid collateral currency types + type CollateralCurrencyIds: Get>; + + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + // No permisson + NoPermission, + // The system has been shutdown + AlreadyShutdown, + // Authorization not exists + AuthorizationNotExists, + // Have authorized already + AlreadyAuthorized, + } + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event { + /// Authorize someone to operate the loan of specific collateral. + Authorization { + authorizer: T::AccountId, + authorizee: T::AccountId, + collateral_type: CurrencyId, + }, + /// Cancel the authorization of specific collateral for someone. + UnAuthorization { + authorizer: T::AccountId, + authorizee: T::AccountId, + collateral_type: CurrencyId, + }, + /// Cancel all authorization. + UnAuthorizationAll { authorizer: T::AccountId }, + /// Transfers debit between two CDPs + TransferDebit { + from_currency: CurrencyId, + to_currency: CurrencyId, + amount: Balance, + }, + } + + /// The authorization relationship map from + /// Authorizer -> (CollateralType, Authorizee) -> Authorized + /// + /// Authorization: double_map AccountId, (CurrencyId, T::AccountId) => Option + #[pallet::storage] + #[pallet::getter(fn authorization)] + pub type Authorization = StorageDoubleMap< + _, + Twox64Concat, + T::AccountId, + Blake2_128Concat, + (CurrencyId, T::AccountId), + Balance, + OptionQuery, + >; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Adjust the loans of `currency_id` by specific + /// `collateral_adjustment` and `debit_adjustment` + /// + /// - `currency_id`: collateral currency id. + /// - `collateral_adjustment`: signed amount, positive means to deposit collateral currency + /// into ECDP, negative means withdraw collateral currency from ECDP. + /// - `debit_adjustment`: signed amount, positive means to issue some amount of stablecoin + /// to caller according to the debit adjustment, negative means caller will payback some + /// amount of stablecoin to ECDP according to to the debit adjustment. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::adjust_loan())] + pub fn adjust_loan( + origin: OriginFor, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_adjust_loan(&who, currency_id, collateral_adjustment, debit_adjustment) + } + + /// Close caller's ECDP which has debit but still in safe by use collateral to swap + /// stable token on Edfis for clearing debit. + /// + /// - `currency_id`: collateral currency id. + /// - `max_collateral_amount`: the max collateral amount which is used to swap enough + /// stable token to clear debit. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::close_loan_has_debit_by_dex())] + pub fn close_loan_has_debit_by_dex( + origin: OriginFor, + currency_id: CurrencyId, + #[pallet::compact] max_collateral_amount: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_close_loan_by_dex(who, currency_id, max_collateral_amount) + } + + /// Transfer the whole ECDP of `from` under `currency_id` to caller's ECDP + /// under the same `currency_id`, caller must have the authorization of + /// `from` for the specific collateral type + /// + /// - `currency_id`: collateral currency id. + /// - `from`: authorizer account + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::transfer_loan_from())] + pub fn transfer_loan_from( + origin: OriginFor, + currency_id: CurrencyId, + from: ::Source, + ) -> DispatchResult { + let to = ensure_signed(origin)?; + let from = T::Lookup::lookup(from)?; + ensure!(!T::EcdpEmergencyShutdown::is_shutdown(), Error::::AlreadyShutdown); + Self::check_authorization(&from, &to, currency_id)?; + >::transfer_loan(&from, &to, currency_id)?; + Ok(()) + } + + /// Authorize `to` to manipulate the loan under `currency_id` + /// + /// - `currency_id`: collateral currency id. + /// - `to`: authorizee account + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::authorize())] + pub fn authorize( + origin: OriginFor, + currency_id: CurrencyId, + to: ::Source, + ) -> DispatchResult { + let from = ensure_signed(origin)?; + let to = T::Lookup::lookup(to)?; + if from == to { + return Ok(()); + } + + Authorization::::try_mutate_exists(&from, (currency_id, &to), |maybe_reserved| -> DispatchResult { + ensure!(maybe_reserved.is_none(), Error::::AlreadyAuthorized); + + let reserve_amount = T::DepositPerAuthorization::get(); + ::Currency::reserve_named(&RESERVE_ID, &from, reserve_amount)?; + *maybe_reserved = Some(reserve_amount); + Self::deposit_event(Event::Authorization { + authorizer: from.clone(), + authorizee: to.clone(), + collateral_type: currency_id, + }); + Ok(()) + })?; + Ok(()) + } + + /// Cancel the authorization for `to` under `currency_id` + /// + /// - `currency_id`: collateral currency id. + /// - `to`: authorizee account + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::unauthorize())] + pub fn unauthorize( + origin: OriginFor, + currency_id: CurrencyId, + to: ::Source, + ) -> DispatchResult { + let from = ensure_signed(origin)?; + let to = T::Lookup::lookup(to)?; + let reserved = + Authorization::::take(&from, (currency_id, &to)).ok_or(Error::::AuthorizationNotExists)?; + ::Currency::unreserve_named(&RESERVE_ID, &from, reserved); + Self::deposit_event(Event::UnAuthorization { + authorizer: from, + authorizee: to, + collateral_type: currency_id, + }); + Ok(()) + } + + /// Cancel all authorization of caller + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::unauthorize_all(T::CollateralCurrencyIds::get().len() as u32))] + pub fn unauthorize_all(origin: OriginFor) -> DispatchResult { + let from = ensure_signed(origin)?; + let _ = Authorization::::clear_prefix(&from, u32::MAX, None); + ::Currency::unreserve_all_named(&RESERVE_ID, &from); + Self::deposit_event(Event::UnAuthorizationAll { authorizer: from }); + Ok(()) + } + + /// Generate new debit in advance, buy collateral and deposit it into ECDP. + /// + /// - `currency_id`: collateral currency id. + /// - `increase_debit_value`: the specific increased debit value for ECDP + /// - `min_increase_collateral`: the minimal increased collateral amount for ECDP + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::expand_position_collateral())] + pub fn expand_position_collateral( + origin: OriginFor, + currency_id: CurrencyId, + increase_debit_value: Balance, + min_increase_collateral: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + >::expand_position_collateral( + &who, + currency_id, + increase_debit_value, + min_increase_collateral, + )?; + Ok(()) + } + + /// Sell the collateral locked in ECDP to get stable coin to repay the debit. + /// + /// - `currency_id`: collateral currency id. + /// - `decrease_collateral`: the specific decreased collateral amount for ECDP + /// - `min_decrease_debit_value`: the minimal decreased debit value for ECDP + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::shrink_position_debit())] + pub fn shrink_position_debit( + origin: OriginFor, + currency_id: CurrencyId, + decrease_collateral: Balance, + min_decrease_debit_value: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + >::shrink_position_debit( + &who, + currency_id, + decrease_collateral, + min_decrease_debit_value, + )?; + Ok(()) + } + + /// Adjust the loans of `currency_id` by specific + /// `collateral_adjustment` and `debit_value_adjustment` + /// + /// - `currency_id`: collateral currency id. + /// - `collateral_adjustment`: signed amount, positive means to deposit collateral currency + /// into ECDP, negative means withdraw collateral currency from ECDP. + /// - `debit_value_adjustment`: signed amount, positive means to issue some amount of + /// stablecoin, negative means caller will payback some amount of stablecoin to ECDP. + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::adjust_loan())] + pub fn adjust_loan_by_debit_value( + origin: OriginFor, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_value_adjustment: Amount, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // not allowed to adjust the debit after system shutdown + if !debit_value_adjustment.is_zero() { + ensure!(!T::EcdpEmergencyShutdown::is_shutdown(), Error::::AlreadyShutdown); + } + >::adjust_position_by_debit_value( + &who, + currency_id, + collateral_adjustment, + debit_value_adjustment, + )?; + Ok(()) + } + + /// Transfers debit between two CDPs + /// + /// - `from_currency`: Currency id that debit is transfered from + /// - `to_currency`: Currency id that debit is transfered to + /// - `debit_transfer`: Debit transfered across two CDPs + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::transfer_debit())] + pub fn transfer_debit( + origin: OriginFor, + from_currency: CurrencyId, + to_currency: CurrencyId, + debit_transfer: Balance, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let debit_amount: Amount = debit_transfer.try_into().map_err(|_| ArithmeticError::Overflow)?; + let negative_debit = debit_amount.checked_neg().ok_or(ArithmeticError::Overflow)?; + // Adds ausd to user account momentarily to adjust loan + ::EcdpUssdTreasury::issue_debit(&who, debit_transfer, true)?; + + >::adjust_position(&who, from_currency, Zero::zero(), negative_debit)?; + >::adjust_position(&who, to_currency, Zero::zero(), debit_amount)?; + // Removes debit issued for debit transfer + ::EcdpUssdTreasury::burn_debit(&who, debit_transfer)?; + + Self::deposit_event(Event::TransferDebit { + from_currency, + to_currency, + amount: debit_transfer, + }); + Ok(()) + } + } +} + +impl Pallet { + /// Check if `from` has the authorization of `to` under `currency_id` + fn check_authorization(from: &T::AccountId, to: &T::AccountId, currency_id: CurrencyId) -> DispatchResult { + ensure!( + from == to || Authorization::::contains_key(from, (currency_id, to)), + Error::::NoPermission + ); + Ok(()) + } + + fn do_adjust_loan( + who: &T::AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult { + // not allowed to adjust the debit after system shutdown + if !debit_adjustment.is_zero() { + ensure!(!T::EcdpEmergencyShutdown::is_shutdown(), Error::::AlreadyShutdown); + } + >::adjust_position(who, currency_id, collateral_adjustment, debit_adjustment)?; + Ok(()) + } + + fn do_close_loan_by_dex( + who: T::AccountId, + currency_id: CurrencyId, + max_collateral_amount: Balance, + ) -> DispatchResult { + ensure!(!T::EcdpEmergencyShutdown::is_shutdown(), Error::::AlreadyShutdown); + >::close_cdp_has_debit_by_dex(who, currency_id, max_collateral_amount)?; + Ok(()) + } +} + +impl EcdpUssdManager for Pallet { + fn adjust_loan( + who: &T::AccountId, + currency_id: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + ) -> DispatchResult { + Self::do_adjust_loan(who, currency_id, collateral_adjustment, debit_adjustment) + } + + fn close_loan_by_dex(who: T::AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult { + Self::do_close_loan_by_dex(who, currency_id, max_collateral_amount) + } + + fn get_position(who: &T::AccountId, currency_id: CurrencyId) -> EcdpPosition { + >::positions(currency_id, who) + } + + fn get_collateral_parameters(currency_id: CurrencyId) -> Vec { + let params = >::collateral_params(currency_id).unwrap_or_default(); + + vec![ + U256::from(params.maximum_total_debit_value), + U256::from(params.liquidation_ratio.unwrap_or_default().into_inner()), + U256::from(params.liquidation_penalty.unwrap_or_default().into_inner().into_inner()), + U256::from(params.required_collateral_ratio.unwrap_or_default().into_inner()), + ] + } + + fn get_current_collateral_ratio(who: &T::AccountId, currency_id: CurrencyId) -> Option { + let EcdpPosition { collateral, debit } = >::positions(currency_id, who); + let stable_currency_id = T::GetUSSDCurrencyId::get(); + + T::PriceSource::get_relative_price(currency_id, stable_currency_id).map(|price| { + >::calculate_collateral_ratio(currency_id, collateral, debit, price) + }) + } + + fn get_debit_exchange_rate(currency_id: CurrencyId) -> ExchangeRate { + >::get_debit_exchange_rate(currency_id) + } +} diff --git a/blockchain/modules/ecdp/src/mock.rs b/blockchain/modules/ecdp/src/mock.rs new file mode 100644 index 00000000..dabe6e46 --- /dev/null +++ b/blockchain/modules/ecdp/src/mock.rs @@ -0,0 +1,395 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Mocks for the ECDP module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + construct_runtime, derive_impl, ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Nothing}, + PalletId, +}; +use frame_system::{offchain::SendTransactionTypes, EnsureSignedBy}; +use module_cdp_engine::CollateralCurrencyIds; +use module_support::{ + EcdpAuctionsManager, ExchangeRate, FractionalRate, Price, PriceProvider, Rate, Ratio, SpecificJointsSwap, +}; +use orml_traits::parameter_type_with_key; +use primitives::{ + evm::{convert_decimals_to_evm, EvmAddress}, + Balance, Moment, ReserveIdentifier, TokenSymbol, +}; +use sp_core::crypto::AccountId32; +use sp_runtime::{ + testing::TestXt, + traits::{AccountIdConversion, IdentityLookup, One as OneT}, + BuildStorage, FixedPointNumber, +}; +use sp_std::{cell::RefCell, str::FromStr}; + +mod ecdp { + pub use super::super::*; +} + +pub type AccountId = AccountId32; +pub type BlockNumber = u64; +pub type AuctionId = u32; + +pub const ALICE: AccountId = AccountId32::new([1u8; 32]); +pub const BOB: AccountId = AccountId32::new([2u8; 32]); +pub const CAROL: AccountId = AccountId32::new([3u8; 32]); +pub const SEE: CurrencyId = CurrencyId::Token(TokenSymbol::SEE); +pub const USSD: CurrencyId = CurrencyId::Token(TokenSymbol::USSD); +pub const BTC: CurrencyId = CurrencyId::ForeignAsset(255); +pub const EDF: CurrencyId = CurrencyId::Token(TokenSymbol::EDF); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = ReserveIdentifier; + type DustRemovalWhitelist = Nothing; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = SEE; +} + +impl orml_currencies::Config for Runtime { + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} + +parameter_types! { + pub const LoansPalletId: PalletId = PalletId(*b"aca/loan"); +} + +impl module_ecdp_ussd_loans::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Tokens; + type RiskManager = EcdpUssdEngineModule; + type EcdpUssdTreasury = EcdpUssdTreasuryModule; + type PalletId = LoansPalletId; + type OnUpdateLoan = (); +} + +pub struct MockPriceSource; +impl PriceProvider for MockPriceSource { + fn get_relative_price(_base: CurrencyId, _quote: CurrencyId) -> Option { + Some(Price::one()) + } + + fn get_price(_currency_id: CurrencyId) -> Option { + Some(Price::one()) + } +} + +pub struct MockEcdpAuctionsManager; +impl EcdpAuctionsManager for MockEcdpAuctionsManager { + type Balance = Balance; + type CurrencyId = CurrencyId; + type AuctionId = AuctionId; + + fn new_collateral_auction( + _refund_recipient: &AccountId, + _currency_id: Self::CurrencyId, + _amount: Self::Balance, + _target: Self::Balance, + ) -> DispatchResult { + Ok(()) + } + + fn cancel_auction(_id: Self::AuctionId) -> DispatchResult { + Ok(()) + } + + fn get_total_target_in_auction() -> Self::Balance { + Default::default() + } + + fn get_total_collateral_in_auction(_id: Self::CurrencyId) -> Self::Balance { + Default::default() + } +} + +thread_local! { + static IS_SHUTDOWN: RefCell = RefCell::new(false); +} + +pub fn mock_shutdown() { + IS_SHUTDOWN.with(|v| *v.borrow_mut() = true) +} + +pub struct MockEcdpEmergencyShutdown; +impl EcdpEmergencyShutdown for MockEcdpEmergencyShutdown { + fn is_shutdown() -> bool { + IS_SHUTDOWN.with(|v| *v.borrow_mut()) + } +} + +ord_parameter_types! { + pub const One: AccountId = AccountId32::new([1u8; 32]); +} + +parameter_types! { + pub const GetUSSDCurrencyId: CurrencyId = USSD; + pub const EcdpUssdTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); + pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account_truncating(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![USSD], + ]; +} + +impl module_ecdp_ussd_treasury::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Currencies; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type EcdpAuctionsManagerHandler = MockEcdpAuctionsManager; + type UpdateOrigin = EnsureSignedBy; + type EdfisSwapHandler = (); + type Swap = SpecificJointsSwap<(), AlternativeSwapPathJointList>; + type MaxAuctionsCount = ConstU32<10_000>; + type PalletId = EcdpUssdTreasuryPalletId; + type TreasuryAccount = TreasuryAccount; + type WeightInfo = (); +} + +impl pallet_timestamp::Config for Runtime { + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1000>; + type WeightInfo = (); +} + +impl module_evm_accounts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = PalletBalances; + type ChainId = (); + type AddressMapping = module_evm_accounts::EvmAddressMapping; + type TransferAll = Currencies; + type WeightInfo = (); +} + +parameter_types! { + pub NetworkContractSource: EvmAddress = EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap(); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId = AccountId::from([1u8; 32]); + pub const NetworkContractAccount: AccountId = AccountId::from([0u8; 32]); + pub const StorageDepositPerByte: u128 = convert_decimals_to_evm(10); +} + +impl module_evm::Config for Runtime { + type AddressMapping = module_evm_accounts::EvmAddressMapping; + type Currency = PalletBalances; + type TransferAll = (); + type NewContractExtraBytes = ConstU32<1>; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = ConstU128<10>; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = (); + type PrecompilesValue = (); + type GasToWeight = (); + type ChargeTransactionPayment = module_support::mocks::MockReservedTransactionPayment; + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + + type DeveloperDeposit = ConstU128<1000>; + type PublicationFee = ConstU128<200>; + type TreasuryAccount = TreasuryAccount; + type FreePublicationOrigin = EnsureSignedBy; + + type Runner = module_evm::runner::stack::Runner; + type FindAuthor = (); + type Task = (); + type IdleScheduler = (); + type WeightInfo = (); +} + +impl module_evm_bridge::Config for Runtime { + type EVM = EVM; +} + +parameter_type_with_key! { + pub MinimumCollateralAmount: |_currency_id: CurrencyId| -> Balance { + 10 + }; +} + +parameter_types! { + pub DefaultLiquidationRatio: Ratio = Ratio::saturating_from_rational(3, 2); + pub DefaultDebitExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); + pub DefaultLiquidationPenalty: FractionalRate = FractionalRate::try_from(Rate::saturating_from_rational(10, 100)).unwrap(); + pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::saturating_from_rational(50, 100); + pub MaxLiquidationContractSlippage: Ratio = Ratio::saturating_from_rational(80, 100); + pub const EcdpUssdEnginePalletId: PalletId = PalletId(*b"aca/cdpe"); + pub const SettleErc20EvmOrigin: AccountId = AccountId32::new([255u8; 32]); +} + +impl module_cdp_engine::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PriceSource = MockPriceSource; + type DefaultLiquidationRatio = DefaultLiquidationRatio; + type DefaultDebitExchangeRate = DefaultDebitExchangeRate; + type DefaultLiquidationPenalty = DefaultLiquidationPenalty; + type MinimumDebitValue = ConstU128<2>; + type MinimumCollateralAmount = MinimumCollateralAmount; + type GetUSSDCurrencyId = GetUSSDCurrencyId; + type EcdpUssdTreasury = EcdpUssdTreasuryModule; + type UpdateOrigin = EnsureSignedBy; + type MaxSwapSlippageCompareToOracle = MaxSwapSlippageCompareToOracle; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 + type EcdpEmergencyShutdown = MockEcdpEmergencyShutdown; + type UnixTime = Timestamp; + type Currency = Currencies; + type EdfisSwapHandler = (); + type LiquidationContractsUpdateOrigin = EnsureSignedBy; + type MaxLiquidationContractSlippage = MaxLiquidationContractSlippage; + type MaxLiquidationContracts = ConstU32<10>; + type LiquidationEvmBridge = (); + type PalletId = EcdpUssdEnginePalletId; + type EvmAddressMapping = module_evm_accounts::EvmAddressMapping; + type Swap = SpecificJointsSwap<(), AlternativeSwapPathJointList>; + type EVMBridge = module_evm_bridge::EVMBridge; + type SettleErc20EvmOrigin = SettleErc20EvmOrigin; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = PalletBalances; + type DepositPerAuthorization = ConstU128<100>; + type CollateralCurrencyIds = CollateralCurrencyIds; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + EcdpModule: ecdp, + Tokens: orml_tokens, + PalletBalances: pallet_balances, + Currencies: orml_currencies, + EcdpUssdLoansModule: module_ecdp_ussd_loans, + EcdpUssdTreasuryModule: module_ecdp_ussd_treasury, + EcdpUssdEngineModule: module_cdp_engine, + Timestamp: pallet_timestamp, + EvmAccounts: module_evm_accounts, + EVM: module_evm, + EVMBridge: module_evm_bridge, + } +); + +/// An extrinsic type used for tests. +pub type Extrinsic = TestXt; + +impl SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +pub struct ExtBuilder { + endowed_native: Vec<(AccountId, Balance)>, + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + endowed_native: vec![(ALICE, 1000)], + balances: vec![ + (ALICE, BTC, 1000), + (BOB, BTC, 1000), + (ALICE, EDF, 1000), + (BOB, EDF, 1000), + ], + } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self.endowed_native, + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/blockchain/modules/ecdp/src/tests.rs b/blockchain/modules/ecdp/src/tests.rs new file mode 100644 index 00000000..f3c86ef0 --- /dev/null +++ b/blockchain/modules/ecdp/src/tests.rs @@ -0,0 +1,345 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Unit tests for the ECDP module. + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{RuntimeEvent, *}; +use module_support::{Rate, Ratio}; +use orml_traits::{Change, MultiCurrency}; +use sp_runtime::FixedPointNumber; + +#[test] +fn authorize_should_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_eq!(PalletBalances::reserved_balance(ALICE), 0); + assert_ok!(EcdpModule::authorize(RuntimeOrigin::signed(ALICE), BTC, BOB)); + assert_eq!( + PalletBalances::reserved_balance(ALICE), + <::DepositPerAuthorization as sp_runtime::traits::Get>::get() + ); + System::assert_last_event(RuntimeEvent::EcdpModule(crate::Event::Authorization { + authorizer: ALICE, + authorizee: BOB, + collateral_type: BTC, + })); + assert_ok!(EcdpModule::check_authorization(&ALICE, &BOB, BTC)); + assert_noop!( + EcdpModule::authorize(RuntimeOrigin::signed(ALICE), BTC, BOB), + Error::::AlreadyAuthorized + ); + }); +} + +#[test] +fn unauthorize_should_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpModule::authorize(RuntimeOrigin::signed(ALICE), BTC, BOB)); + assert_eq!( + PalletBalances::reserved_balance(ALICE), + <::DepositPerAuthorization as sp_runtime::traits::Get>::get() + ); + assert_ok!(EcdpModule::check_authorization(&ALICE, &BOB, BTC)); + + assert_ok!(EcdpModule::unauthorize(RuntimeOrigin::signed(ALICE), BTC, BOB)); + assert_eq!(PalletBalances::reserved_balance(ALICE), 0); + System::assert_last_event(RuntimeEvent::EcdpModule(crate::Event::UnAuthorization { + authorizer: ALICE, + authorizee: BOB, + collateral_type: BTC, + })); + assert_noop!( + EcdpModule::check_authorization(&ALICE, &BOB, BTC), + Error::::NoPermission + ); + assert_noop!( + EcdpModule::unauthorize(RuntimeOrigin::signed(ALICE), BTC, BOB), + Error::::AuthorizationNotExists + ); + }); +} + +#[test] +fn unauthorize_all_should_work() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpModule::authorize(RuntimeOrigin::signed(ALICE), BTC, BOB)); + assert_ok!(EcdpModule::authorize(RuntimeOrigin::signed(ALICE), EDF, CAROL)); + assert_eq!(PalletBalances::reserved_balance(ALICE), 200); + assert_ok!(EcdpModule::unauthorize_all(RuntimeOrigin::signed(ALICE))); + assert_eq!(PalletBalances::reserved_balance(ALICE), 0); + System::assert_last_event(RuntimeEvent::EcdpModule(crate::Event::UnAuthorizationAll { + authorizer: ALICE, + })); + + assert_noop!( + EcdpModule::check_authorization(&ALICE, &BOB, BTC), + Error::::NoPermission + ); + assert_noop!( + EcdpModule::check_authorization(&ALICE, &BOB, EDF), + Error::::NoPermission + ); + }); +} + +#[test] +fn transfer_loan_from_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), BTC, 100, 50)); + assert_ok!(EcdpModule::authorize(RuntimeOrigin::signed(ALICE), BTC, BOB)); + assert_ok!(EcdpModule::transfer_loan_from(RuntimeOrigin::signed(BOB), BTC, ALICE)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, BOB).debit, 50); + }); +} + +#[test] +fn transfer_unauthorization_loans_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + EcdpModule::transfer_loan_from(RuntimeOrigin::signed(ALICE), BTC, BOB), + Error::::NoPermission, + ); + }); +} + +#[test] +fn adjust_loan_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), BTC, 100, 50)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 50); + }); +} + +#[test] +fn adjust_loan_by_debit_value_should_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + assert_ok!(EcdpModule::adjust_loan_by_debit_value( + RuntimeOrigin::signed(ALICE), + BTC, + 100, + 50 + )); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + + assert_ok!(EcdpModule::adjust_loan_by_debit_value( + RuntimeOrigin::signed(ALICE), + BTC, + -10, + -5 + )); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 90); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 450); + }); +} + +#[test] +fn on_emergency_shutdown_should_work() { + ExtBuilder::default().build().execute_with(|| { + mock_shutdown(); + assert_noop!( + EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), BTC, 100, 50), + Error::::AlreadyShutdown, + ); + assert_noop!( + EcdpModule::transfer_loan_from(RuntimeOrigin::signed(ALICE), BTC, BOB), + Error::::AlreadyShutdown, + ); + assert_noop!( + EcdpModule::close_loan_has_debit_by_dex(RuntimeOrigin::signed(ALICE), BTC, 100), + Error::::AlreadyShutdown, + ); + }); +} + +#[test] +fn close_loan_has_debit_by_dex_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), BTC, 100, 50)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 50); + + assert_ok!(EcdpModule::close_loan_has_debit_by_dex( + RuntimeOrigin::signed(ALICE), + BTC, + 100, + )); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 0); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 0); + }); +} + +#[test] +fn transfer_debit_works() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + // set up two loans + assert_ok!(EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), BTC, 100, 500)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + + assert_ok!(EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), EDF, 100, 500)); + assert_eq!(EcdpUssdLoansModule::positions(EDF, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(EDF, ALICE).debit, 500); + + // Will not work for account with no open CDP + assert_noop!( + EcdpModule::transfer_debit(RuntimeOrigin::signed(BOB), BTC, EDF, 1000), + ArithmeticError::Underflow + ); + // Won't work when transfering more debit than is present + assert_noop!( + EcdpModule::transfer_debit(RuntimeOrigin::signed(ALICE), BTC, EDF, 10_000), + ArithmeticError::Underflow + ); + // Below minimum collateral threshold for the BTC CDP + assert_noop!( + EcdpModule::transfer_debit(RuntimeOrigin::signed(ALICE), BTC, EDF, 500), + module_cdp_engine::Error::::BelowRequiredCollateralRatio + ); + // Too large of a transfer + assert_noop!( + EcdpModule::transfer_debit(RuntimeOrigin::signed(ALICE), BTC, EDF, u128::MAX), + ArithmeticError::Overflow + ); + // Won't work for currency that is not collateral + assert_noop!( + EcdpModule::transfer_debit(RuntimeOrigin::signed(ALICE), BTC, SEE, 50), + module_cdp_engine::Error::::InvalidCollateralType + ); + + assert_ok!(EcdpModule::transfer_debit(RuntimeOrigin::signed(ALICE), BTC, EDF, 50)); + System::assert_last_event(RuntimeEvent::EcdpModule(crate::Event::::TransferDebit { + from_currency: BTC, + to_currency: EDF, + amount: 50, + })); + + assert_eq!(EcdpUssdLoansModule::positions(EDF, ALICE).debit, 550); + assert_eq!(EcdpUssdLoansModule::positions(EDF, ALICE).collateral, 100); + + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 450); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + }); +} + +#[test] +fn transfer_debit_no_ausd() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + assert_ok!(EcdpUssdEngineModule::set_collateral_params( + RuntimeOrigin::signed(ALICE), + EDF, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100000))), + Change::NewValue(Some(Ratio::saturating_from_rational(3, 2))), + Change::NewValue(Some(Rate::saturating_from_rational(2, 10))), + Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), + Change::NewValue(10000), + )); + + // set up two loans + assert_ok!(EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), BTC, 100, 500)); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(BTC, ALICE).debit, 500); + + assert_ok!(EcdpModule::adjust_loan(RuntimeOrigin::signed(ALICE), EDF, 100, 500)); + assert_eq!(EcdpUssdLoansModule::positions(EDF, ALICE).collateral, 100); + assert_eq!(EcdpUssdLoansModule::positions(EDF, ALICE).debit, 500); + + assert_eq!(Currencies::free_balance(USSD, &ALICE), 100); + assert_ok!(Currencies::transfer(RuntimeOrigin::signed(ALICE), BOB, USSD, 100)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + assert_ok!(EcdpModule::transfer_debit(RuntimeOrigin::signed(ALICE), BTC, EDF, 5)); + assert_eq!(Currencies::free_balance(USSD, &ALICE), 0); + }); +} diff --git a/blockchain/modules/ecdp/src/weights.rs b/blockchain/modules/ecdp/src/weights.rs new file mode 100644 index 00000000..a643a992 --- /dev/null +++ b/blockchain/modules/ecdp/src/weights.rs @@ -0,0 +1,284 @@ +// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم + +// This file is part of Setheum. + +// Copyright (C) 2019-Present Setheum Labs. +// 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 . + +//! Autogenerated weights for module_ecdp +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-05-27, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/setheum-node +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_ecdp +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./blockchains/modules/ecdp/src/weights.rs +// --template=.maintain/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_ecdp. +pub trait WeightInfo { + fn authorize() -> Weight; + fn unauthorize() -> Weight; + fn unauthorize_all(c: u32, ) -> Weight; + fn adjust_loan() -> Weight; + fn transfer_loan_from() -> Weight; + fn close_loan_has_debit_by_dex() -> Weight; + fn expand_position_collateral() -> Weight; + fn shrink_position_debit() -> Weight; + fn transfer_debit() -> Weight; + fn precompile_get_current_collateral_ratio() -> Weight; +} + +/// Weights for module_ecdp using the Setheum node and recommended hardware. +pub struct SetheumWeight(PhantomData); +impl WeightInfo for SetheumWeight { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: ECDP Authorization (r:1 w:1) + // Storage: Balances Reserves (r:1 w:1) + fn authorize() -> Weight { + Weight::from_parts(45_674_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: ECDP Authorization (r:1 w:1) + // Storage: Balances Reserves (r:1 w:1) + fn unauthorize() -> Weight { + Weight::from_parts(91_834_000, 0) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Balances Reserves (r:1 w:0) + // Storage: ECDP Authorization (r:0 w:1) + fn unauthorize_all(c: u32, ) -> Weight { + Weight::from_parts(51_744_000, 0) + // Standard Error: 866_000 + .saturating_add(Weight::from_parts(652_000, 0).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(c as u64))) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: EcdpEmergencyShutdown IsShutdown (r:1 w:0) + // Storage: CdpEngine CollateralParams (r:1 w:0) + // Storage: Loans EcdpPositions (r:1 w:1) + // Storage: Rewards PoolInfos (r:1 w:1) + // Storage: Rewards SharesAndWithdrawnRewards (r:1 w:1) + // Storage: Loans TotalEcdpPositions (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: CdpEngine DebitExchangeRate (r:1 w:0) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Storage: Prices LockedPrice (r:2 w:0) + // Storage: SetheumOracle Values (r:1 w:0) + // Storage: AssetRegistry AssetMetadatas (r:2 w:0) + fn adjust_loan() -> Weight { + Weight::from_parts(142_855_000, 0) + .saturating_add(T::DbWeight::get().reads(16 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: EcdpEmergencyShutdown IsShutdown (r:1 w:0) + // Storage: ECDP Authorization (r:1 w:0) + // Storage: Loans EcdpPositions (r:2 w:2) + // Storage: CdpEngine DebitExchangeRate (r:1 w:0) + // Storage: Prices LockedPrice (r:2 w:0) + // Storage: Oracle Values (r:1 w:0) + // Storage: AssetRegistry AssetMetadatas (r:2 w:0) + // Storage: CdpEngine CollateralParams (r:1 w:0) + // Storage: Rewards SharesAndWithdrawnRewards (r:2 w:2) + // Storage: Rewards PoolInfos (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Loans TotalEcdpPositions (r:1 w:1) + fn transfer_loan_from() -> Weight { + Weight::from_parts(120_478_000, 0) + .saturating_add(T::DbWeight::get().reads(17 as u64)) + .saturating_add(T::DbWeight::get().writes(8 as u64)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: EcdpEmergencyShutdown IsShutdown (r:1 w:0) + // Storage: Loans EcdpPositions (r:1 w:1) + // Storage: Prices LockedPrice (r:2 w:0) + // Storage: SetheumOracle Values (r:1 w:0) + // Storage: AssetRegistry AssetMetadatas (r:2 w:0) + // Storage: Homa StakingLedgers (r:1 w:0) + // Storage: Homa ToBondPool (r:1 w:0) + // Storage: Tokens TotalIssuance (r:1 w:0) + // Storage: Homa TotalVoidLiquid (r:1 w:0) + // Storage: CdpEngine DebitExchangeRate (r:1 w:0) + // Storage: CdpEngine CollateralParams (r:1 w:0) + // Storage: Tokens Accounts (r:6 w:6) + // Storage: System Account (r:3 w:2) + // Storage: CdpTreasury DebitPool (r:1 w:1) + // Storage: Rewards SharesAndWithdrawnRewards (r:1 w:1) + // Storage: Rewards PoolInfos (r:1 w:1) + // Storage: Loans TotalEcdpPositions (r:1 w:1) + // Storage: EcdpAuctionsManager TotalCollateralInAuction (r:1 w:0) + // Storage: Dex TradingPairStatuses (r:3 w:0) + // Storage: Dex LiquidityPool (r:2 w:2) + // Storage: AggregatedDex AggregatedSwapPaths (r:1 w:0) + fn close_loan_has_debit_by_dex() -> Weight { + Weight::from_parts(349_743_000, 0) + .saturating_add(T::DbWeight::get().reads(35 as u64)) + .saturating_add(T::DbWeight::get().writes(16 as u64)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: CdpEngine CollateralParams (r:1 w:0) + // Storage: Tokens Accounts (r:4 w:4) + // Storage: Tokens TotalIssuance (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: Dex TradingPairStatuses (r:1 w:0) + // Storage: Dex LiquidityPool (r:1 w:1) + // Storage: AggregatedDex AggregatedSwapPaths (r:1 w:0) + // Storage: CdpEngine DebitExchangeRate (r:1 w:0) + // Storage: Loans EcdpPositions (r:1 w:1) + // Storage: Rewards PoolInfos (r:1 w:1) + // Storage: Rewards SharesAndWithdrawnRewards (r:1 w:1) + // Storage: Loans TotalEcdpPositions (r:1 w:1) + // Storage: Prices LockedPrice (r:2 w:0) + // Storage: SetheumOracle Values (r:1 w:0) + // Storage: AssetRegistry AssetMetadatas (r:2 w:0) + fn expand_position_collateral() -> Weight { + Weight::from_parts(227_393_000, 0) + .saturating_add(T::DbWeight::get().reads(23 as u64)) + .saturating_add(T::DbWeight::get().writes(12 as u64)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: CdpEngine CollateralParams (r:1 w:0) + // Storage: Loans EcdpPositions (r:1 w:1) + // Storage: Dex TradingPairStatuses (r:1 w:0) + // Storage: Dex LiquidityPool (r:1 w:1) + // Storage: AggregatedDex AggregatedSwapPaths (r:1 w:0) + // Storage: Tokens Accounts (r:5 w:5) + // Storage: System Account (r:2 w:1) + // Storage: CdpEngine DebitExchangeRate (r:1 w:0) + // Storage: Rewards SharesAndWithdrawnRewards (r:1 w:1) + // Storage: Rewards PoolInfos (r:1 w:1) + // Storage: Loans TotalEcdpPositions (r:1 w:1) + // Storage: Tokens TotalIssuance (r:1 w:1) + fn shrink_position_debit() -> Weight { + Weight::from_parts(230_779_000, 0) + .saturating_add(T::DbWeight::get().reads(19 as u64)) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: Tokens TotalIssuance (r:2 w:1) + // Storage: CdpEngine CollateralParams (r:2 w:0) + // Storage: Loans EcdpPositions (r:2 w:2) + // Storage: Loans TotalEcdpPositions (r:2 w:2) + // Storage: CdpEngine DebitExchangeRate (r:2 w:0) + // Storage: Prices LockedPrice (r:3 w:0) + // Storage: SetheumOracle Values (r:1 w:0) + // Storage: AssetRegistry AssetMetadatas (r:2 w:0) + // Storage: Homa StakingLedgers (r:1 w:0) + // Storage: Homa ToBondPool (r:1 w:0) + // Storage: Homa TotalVoidLiquid (r:1 w:0) + fn transfer_debit() -> Weight { + Weight::from_parts(196_453_000, 0) + .saturating_add(T::DbWeight::get().reads(21 as u64)) + .saturating_add(T::DbWeight::get().writes(7 as u64)) + } + // Storage: Loans EcdpPositions (r:1 w:0) + // Storage: Prices LockedPrice (r:2 w:0) + // Storage: SetheumOracle Values (r:1 w:0) + // Storage: AssetRegistry AssetMetadatas (r:2 w:0) + // Storage: Homa StakingLedgers (r:1 w:0) + // Storage: Homa ToBondPool (r:1 w:0) + // Storage: Tokens TotalIssuance (r:1 w:0) + // Storage: Homa TotalVoidLiquid (r:1 w:0) + // Storage: CdpEngine DebitExchangeRate (r:1 w:0) + fn precompile_get_current_collateral_ratio() -> Weight { + Weight::from_parts(44_244_000, 0) + .saturating_add(T::DbWeight::get().reads(11 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn authorize() -> Weight { + Weight::from_parts(45_674_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn unauthorize() -> Weight { + Weight::from_parts(91_834_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn unauthorize_all(c: u32, ) -> Weight { + Weight::from_parts(51_744_000, 0) + // Standard Error: 866_000 + .saturating_add(Weight::from_parts(652_000, 0).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(c as u64))) + } + fn adjust_loan() -> Weight { + Weight::from_parts(142_855_000, 0) + .saturating_add(RocksDbWeight::get().reads(16 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } + fn transfer_loan_from() -> Weight { + Weight::from_parts(120_478_000, 0) + .saturating_add(RocksDbWeight::get().reads(17 as u64)) + .saturating_add(RocksDbWeight::get().writes(8 as u64)) + } + fn close_loan_has_debit_by_dex() -> Weight { + Weight::from_parts(349_743_000, 0) + .saturating_add(RocksDbWeight::get().reads(35 as u64)) + .saturating_add(RocksDbWeight::get().writes(16 as u64)) + } + fn expand_position_collateral() -> Weight { + Weight::from_parts(227_393_000, 0) + .saturating_add(RocksDbWeight::get().reads(23 as u64)) + .saturating_add(RocksDbWeight::get().writes(12 as u64)) + } + fn shrink_position_debit() -> Weight { + Weight::from_parts(230_779_000, 0) + .saturating_add(RocksDbWeight::get().reads(19 as u64)) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + } + fn transfer_debit() -> Weight { + Weight::from_parts(196_453_000, 0) + .saturating_add(RocksDbWeight::get().reads(21 as u64)) + .saturating_add(RocksDbWeight::get().writes(7 as u64)) + } + fn precompile_get_current_collateral_ratio() -> Weight { + Weight::from_parts(44_244_000, 0) + .saturating_add(RocksDbWeight::get().reads(11 as u64)) + } +} diff --git a/blockchain/modules/edfis-swap-legacy/src/lib.rs b/blockchain/modules/edfis-swap-legacy/src/lib.rs index 6f4cd675..691cac57 100644 --- a/blockchain/modules/edfis-swap-legacy/src/lib.rs +++ b/blockchain/modules/edfis-swap-legacy/src/lib.rs @@ -463,7 +463,7 @@ pub mod module { /// Remove liquidity from specific liquidity pool in the form of burning /// shares, and withdrawing currencies in trading pairs from liquidity - /// pool in proportion, and withdraw liquidity incentive interest. + /// pool in proportion, and withdraw liquidity incentive. /// /// - `currency_id_a`: currency id A. /// - `currency_id_b`: currency id B. diff --git a/blockchain/modules/edfis-swap-legacy/src/mock.rs b/blockchain/modules/edfis-swap-legacy/src/mock.rs index 0498bc8e..c1c67061 100644 --- a/blockchain/modules/edfis-swap-legacy/src/mock.rs +++ b/blockchain/modules/edfis-swap-legacy/src/mock.rs @@ -101,7 +101,7 @@ ord_parameter_types! { parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); - pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); + pub const EdfisSwapPalletId: PalletId = PalletId(*b"set/edfis"); pub AlternativeSwapPathJointList: Vec> = vec![ vec![EDF], ]; @@ -126,7 +126,7 @@ impl Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<3>; - type PalletId = DEXPalletId; + type PalletId = EdfisSwapPalletId; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type SwapDexIncentives = MockSwapDexIncentives; diff --git a/blockchain/modules/edfis-swap/src/lib.rs b/blockchain/modules/edfis-swap/src/lib.rs index 6c9850e7..b2c52cd4 100644 --- a/blockchain/modules/edfis-swap/src/lib.rs +++ b/blockchain/modules/edfis-swap/src/lib.rs @@ -457,7 +457,7 @@ pub mod module { /// Remove liquidity from specific liquidity pool in the form of burning /// shares, and withdrawing currencies in trading pairs from liquidity - /// pool in proportion, and withdraw liquidity incentive interest. + /// pool in proportion, and withdraw liquidity incentive. /// /// - `currency_id_a`: currency id A. /// - `currency_id_b`: currency id B. diff --git a/blockchain/modules/edfis-swap/src/mock.rs b/blockchain/modules/edfis-swap/src/mock.rs index eaf475ac..d078455f 100644 --- a/blockchain/modules/edfis-swap/src/mock.rs +++ b/blockchain/modules/edfis-swap/src/mock.rs @@ -102,7 +102,7 @@ ord_parameter_types! { parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); - pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); + pub const EdfisSwapPalletId: PalletId = PalletId(*b"set/edfis"); pub AlternativeSwapPathJointList: Vec> = vec![ vec![EDF], ]; @@ -127,7 +127,7 @@ impl Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<3>; - type PalletId = DEXPalletId; + type PalletId = EdfisSwapPalletId; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type SwapDexIncentives = MockSwapDexIncentives; diff --git a/blockchain/modules/evm/Cargo.toml b/blockchain/modules/evm/Cargo.toml index 1d1ed9cd..bf862b74 100644 --- a/blockchain/modules/evm/Cargo.toml +++ b/blockchain/modules/evm/Cargo.toml @@ -43,7 +43,7 @@ module-evm-utility = { workspace = true } primitives = { workspace = true } module-idle-scheduler = { workspace = true, optional = true } module-transaction-payment = { workspace = true } -module-dex = { workspace = true, optional = true } +module-edfis_swap_legacy = { workspace = true, optional = true } xcm-builder = { workspace = true } @@ -64,7 +64,7 @@ std = [ "parity-scale-codec/std", "frame-support/std", "frame-system/std", - "module-dex/std", + "module-edfis_swap_legacy/std", "module-evm-utility/std", "module-idle-scheduler/std", "module-support/std", @@ -92,7 +92,7 @@ with-ethereum-compatibility = [] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "module-dex/try-runtime", + "module-edfis_swap_legacy/try-runtime", "module-idle-scheduler/try-runtime", "module-transaction-payment/try-runtime", "orml-currencies/try-runtime", @@ -104,7 +104,7 @@ tracing = ["module-evm-utility/tracing"] wasm-bench = [ "wasm-bencher/wasm-bench", "hex", - "module-dex", + "module-edfis_swap_legacy", "module-idle-scheduler", "orml-currencies", "orml-tokens", diff --git a/blockchain/modules/evm/src/bench/mock.rs b/blockchain/modules/evm/src/bench/mock.rs index 5ff7aa77..0237698d 100644 --- a/blockchain/modules/evm/src/bench/mock.rs +++ b/blockchain/modules/evm/src/bench/mock.rs @@ -238,7 +238,7 @@ impl SwapDexIncentives for MockSwapDexIncentiv parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); - pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); + pub const EdfisSwapPalletId: PalletId = PalletId(*b"set/edfis"); } impl edfis_swap_legacy_module::Config for Runtime { @@ -246,7 +246,7 @@ impl edfis_swap_legacy_module::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type PalletId = DEXPalletId; + type PalletId = EdfisSwapPalletId; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type SwapDexIncentives = MockSwapDexIncentives; diff --git a/blockchain/modules/lockdrop/Cargo.toml b/blockchain/modules/lockdrop/Cargo.toml index ed905e12..7aba9a4d 100644 --- a/blockchain/modules/lockdrop/Cargo.toml +++ b/blockchain/modules/lockdrop/Cargo.toml @@ -37,5 +37,5 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "module-dex/try-runtime", + "module-edfis_swap_legacy/try-runtime", ] diff --git a/blockchain/modules/support/src/ecdp.rs b/blockchain/modules/support/src/ecdp.rs index cbef19cb..c34672d8 100644 --- a/blockchain/modules/support/src/ecdp.rs +++ b/blockchain/modules/support/src/ecdp.rs @@ -19,7 +19,7 @@ // along with this program. If not, see . use parity_scale_codec::FullCodec; -use primitives::ECDPPosition; +use primitives::EcdpEcdpPosition; use sp_core::U256; use sp_runtime::{DispatchError, DispatchResult}; use sp_std::{ @@ -30,11 +30,11 @@ use sp_std::{ use crate::{dex::*, ExchangeRate, Ratio}; -pub trait EmergencyShutdown { +pub trait EcdpEmergencyShutdown { fn is_shutdown() -> bool; } -pub trait AuctionsManager { +pub trait EcdpAuctionsManager { type CurrencyId; type Balance; type AuctionId: FullCodec + Debug + Clone + Eq + PartialEq; @@ -50,7 +50,7 @@ pub trait AuctionsManager { fn get_total_target_in_auction() -> Self::Balance; } -pub trait SlickUsdRiskManager { +pub trait EcdpUssdRiskManager { fn get_debit_value(currency_id: CurrencyId, debit_balance: DebitBalance) -> Balance; fn check_position_valid( @@ -64,7 +64,7 @@ pub trait SlickUsdRiskManager { } #[cfg(feature = "std")] -impl SlickUsdRiskManager +impl EcdpUssdRiskManager for () { fn get_debit_value(_currency_id: CurrencyId, _debit_balance: DebitBalance) -> Balance { @@ -86,7 +86,7 @@ impl SlickUsdRiskManager< } /// An abstraction of cdp treasury for SlickUSD ECDP Protocol. -pub trait SlickUsdEcdpTreasury { +pub trait EcdpUssdTreasury { type Balance; type CurrencyId; @@ -129,7 +129,7 @@ pub trait SlickUsdEcdpTreasury { fn withdraw_collateral(to: &AccountId, currency_id: Self::CurrencyId, amount: Self::Balance) -> DispatchResult; } -pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { +pub trait EcdpUssdTreasuryExtended: SlickUsdTreasury { fn swap_collateral_to_ussd( currency_id: Self::CurrencyId, limit: SwapLimit, @@ -153,7 +153,7 @@ pub trait SlickUsdEcdpTreasuryExtended: SlickUsdTreasury { } /// Functionality of SlickUSD ECDP Protocol to be exposed to EVM. -pub trait SlickUsdEcdpManager { +pub trait EcdpUssdManager { /// Adjust ECDP loan fn adjust_loan( who: &AccountId, @@ -164,7 +164,7 @@ pub trait SlickUsdEcdpManager { /// Close ECDP loan using DEX fn close_loan_by_dex(who: AccountId, currency_id: CurrencyId, max_collateral_amount: Balance) -> DispatchResult; /// Get open ECDP corresponding to an account and collateral `CurrencyId` - fn get_position(who: &AccountId, currency_id: CurrencyId) -> ECDPPosition; + fn get_position(who: &AccountId, currency_id: CurrencyId) -> EcdpEcdpPosition; /// Get liquidation ratio for collateral `CurrencyId` fn get_collateral_parameters(currency_id: CurrencyId) -> Vec; /// Get current ratio of collateral to debit of open ECDP @@ -174,7 +174,7 @@ pub trait SlickUsdEcdpManager { } /// Functionality of Setter ECDP Protocol to be exposed to EVM. -pub trait SetterEcdpManager { +pub trait EcdpSetrManager { /// Adjust ECDP loan fn adjust_loan( who: &AccountId, @@ -184,7 +184,7 @@ pub trait SetterEcdpManager { /// Close ECDP loan using DEX fn close_loan_by_dex(who: AccountId, max_collateral_amount: Balance) -> DispatchResult; /// Get open ECDP corresponding to an account and collateral - fn get_position(who: &AccountId) -> ECDPPosition; + fn get_position(who: &AccountId) -> EcdpEcdpPosition; /// Get liquidation ratio for collateral fn get_collateral_parameters() -> Vec; /// Get current ratio of collateral to debit of open ECDP diff --git a/blockchain/modules/transaction-payment/Cargo.toml b/blockchain/modules/transaction-payment/Cargo.toml index a21a3770..f3a10fe6 100644 --- a/blockchain/modules/transaction-payment/Cargo.toml +++ b/blockchain/modules/transaction-payment/Cargo.toml @@ -25,7 +25,7 @@ sp-core = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } orml-tokens = { workspace = true, features = ["std"] } module-currencies = { workspace = true, features = ["std"] } -module-dex = { workspace = true, features = ["std"] } +module-edfis_swap_legacy = { workspace = true, features = ["std"] } smallvec = { workspace = true } [features] diff --git a/blockchain/modules/transaction-payment/src/mock.rs b/blockchain/modules/transaction-payment/src/mock.rs index d8c4cd69..e390bb9d 100644 --- a/blockchain/modules/transaction-payment/src/mock.rs +++ b/blockchain/modules/transaction-payment/src/mock.rs @@ -158,7 +158,7 @@ ord_parameter_types! { } parameter_types! { - pub const DEXPalletId: PalletId = PalletId(*b"set/edfis"); + pub const EdfisSwapPalletId: PalletId = PalletId(*b"set/edfis"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![ TradingPair::from_currency_ids(USSD, SEE).unwrap(), @@ -175,7 +175,7 @@ impl edfis_swap_legacy_module::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type PalletId = DEXPalletId; + type PalletId = EdfisSwapPalletId; type Erc20InfoMapping = (); type SwapDexIncentives = (); type WeightInfo = (); diff --git a/blockchain/primitives/src/lib.rs b/blockchain/primitives/src/lib.rs index 14a973b1..10bc0096 100644 --- a/blockchain/primitives/src/lib.rs +++ b/blockchain/primitives/src/lib.rs @@ -182,7 +182,7 @@ impl Decode for TradingPair { } #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)] -pub struct ECDPPosition { +pub struct EcdpPosition { /// The amount of collateral. pub collateral: Balance, /// The amount of debit. @@ -195,8 +195,7 @@ pub enum ReserveIdentifier { CollatorSelection, EvmStorageDeposit, EvmDeveloperDeposit, - SetterEcdp, - SlickUsdEcdp, + Ecdp, Nft, TransactionPayment, TransactionPaymentDeposit,