diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index f8df9ddc..202f45a8 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -1,58 +1,22 @@ -name: Builds and tests +name: CI on: push: - branches: - - master + branches: + - main pull_request: + branches: [ main ] + +permissions: + checks: write + pull-requests: write jobs: - wasm_test: - name: Wasm tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - default: true - toolchain: nightly - - name: Install erdpy and arwentools - run : | - source .github/workflows/env - pip3 install erdpy - mkdir $HOME/elrondsdk - erdpy config set dependencies.arwentools.tag v1.3.3 - erdpy deps install arwentools - - name: Build the wasm contracts - run: | - source .github/workflows/env - ./build-wasm.sh - - name: Run Arwen tests - run: | - source .github/workflows/env - cargo test --features elrond-wasm-debug/arwen-tests - rust_test: - name: Rust tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - default: true - toolchain: nightly - - name: Run rust tests - run: cargo test - clippy_check: - name: Clippy linter check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - components: clippy - default: true - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: + contracts: + name: Contracts + uses: multiversx/mx-sc-actions/.github/workflows/contracts.yml@v2.3.0 + with: + rust-toolchain: nightly-2023-04-24 + vmtools-version: v1.4.60 + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/env b/.github/workflows/env index 9a9272b9..28a99a67 100644 --- a/.github/workflows/env +++ b/.github/workflows/env @@ -1 +1 @@ -export PATH=$HOME/.local/bin:$HOME/elrondsdk/arwentools:$PATH +export PATH=$HOME/.local/bin:$HOME/elrondsdk/vmtools:$PATH diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..afe4d97a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,16 @@ +name: On release, build contracts + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + build: + uses: multiversx/mx-sc-actions/.github/workflows/reproducible-build.yml@v2.2.6 + with: + image_tag: v4.1.4 + attach_to_existing_release: true + skip_preliminary_checks: true diff --git a/.gitignore b/.gitignore index fa71ba8d..736952ce 100644 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,12 @@ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock - +!**/wasm*/Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # Local testnet files **/deploy-testnet.interaction.json -**/erdpy.data-storage.json +**/mxpy.data-storage.json **/testnet/ **/deploy-child-sc-spam.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 079294cf..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "request": "launch", - "type": "node", - "name": "Dump env", - "args": ["-e", "console.log(JSON.stringify(process.env, null, 4))"], - "env": {"PATH":"${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/arwentools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}","VIRTUAL_ENV":"${env:HOME}/elrondsdk/erdpy-venv","RUSTUP_HOME":"${env:HOME}/elrondsdk/vendor-rust","CARGO_HOME":"${env:HOME}/elrondsdk/vendor-rust"} - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 34e99577..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "terminal.integrated.env.linux": { - "PATH": "${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/arwentools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}", - "VIRTUAL_ENV": "${env:HOME}/elrondsdk/erdpy-venv", - "RUSTUP_HOME": "${env:HOME}/elrondsdk/vendor-rust", - "CARGO_HOME": "${env:HOME}/elrondsdk/vendor-rust" - }, - "terminal.integrated.env.osx": { - "PATH": "${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/arwentools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}", - "VIRTUAL_ENV": "${env:HOME}/elrondsdk/erdpy-venv", - "RUSTUP_HOME": "${env:HOME}/elrondsdk/vendor-rust", - "CARGO_HOME": "${env:HOME}/elrondsdk/vendor-rust" - }, - "terminal.integrated.environmentChangesIndicator": "on", - "terminal.integrated.inheritEnv": true, - "workbench.dialogs.customEnabled": true, - "rust-client.rustupPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rustup", - "rust-client.rlsPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rls", - "rust-client.disableRustup": true, - "rust-client.autoStartRls": false -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index b8f235b0..00000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Dump env", - "command": "node", - "args": ["-e", "console.log(JSON.stringify(process.env, null, 4))"], - "type": "process", - "options": { - "env": {"PATH":"${env:HOME}/elrondsdk/vendor-rust/bin:${env:HOME}/elrondsdk/erdpy-venv/bin:${env:HOME}/elrondsdk/arwentools:${env:HOME}/elrondsdk/nodejs/latest/bin:${env:PATH}","VIRTUAL_ENV":"${env:HOME}/elrondsdk/erdpy-venv","RUSTUP_HOME":"${env:HOME}/elrondsdk/vendor-rust","CARGO_HOME":"${env:HOME}/elrondsdk/vendor-rust"} - } - } - ] -} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 9a49cc01..f9432301 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [workspace] members = [ - "egld-esdt-swap", - "egld-esdt-swap/abi", "esdt-safe", - "esdt-safe/abi", + "esdt-safe/meta", "multi-transfer-esdt", - "multi-transfer-esdt/abi", + "multi-transfer-esdt/meta", "multisig", - "multisig/abi" + "multisig/meta", + "bridged-tokens-wrapper", + "bridged-tokens-wrapper/meta" ] diff --git a/README.md b/README.md index 39883771..7d5cb173 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,10 @@ As said above, we're going to use ESDT to implement wrapped tokens, but how is t One important thing to note is you can never unwrap your WrappedETH while on the MultiversX blockchain, as that is not a native MultiversX token. You will only be able to unwrap them by transferring them to one of your Ethereum accounts and then unwrapping them there. + ## MultiversX -> Ethereum transaction -To be able to send eGLD to an Ethereum account, we first have to wrap the tokens through the `EgldEsdtSwap` contract. +To be able to send EGLD to an Ethereum account, we first have to wrap the tokens through the `EgldEsdtSwap` contract. Then you can create a transaction by making a smart contract call to the `EsdtSafe` SC with the tokens you want to transfer and the receiver's address. The tokens will be locked in the contract until the transaction is processed. If the transaction is successful, the tokens on the MultiversX side will be burned. If the transaction fails for whatever reason, you will get your tokens back. @@ -34,3 +35,4 @@ To be able to transfer your tokens back, you will likely have to use an ERC20 co ## Conclusion And that sums up the MultiversX-Ethereum bridge. It's open source, so if you're interested in the details, you can always check out the implementation. In the future, it will likely be implemented in xPortal, so it will be very straightforward to move your tokens around :) + diff --git a/bridged-tokens-wrapper/.gitignore b/bridged-tokens-wrapper/.gitignore new file mode 100644 index 00000000..ced1b927 --- /dev/null +++ b/bridged-tokens-wrapper/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +*/target/ + +# The mxpy output +output* diff --git a/bridged-tokens-wrapper/Cargo.toml b/bridged-tokens-wrapper/Cargo.toml new file mode 100644 index 00000000..dc1bbfd7 --- /dev/null +++ b/bridged-tokens-wrapper/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bridged-tokens-wrapper" +version = "0.0.0" +authors = ["Alin Cruceat "] +edition = "2018" +publish = false + +[lib] +path = "src/lib.rs" +[dependencies.transaction] +path = "../common/transaction" + +[dependencies.multiversx-sc] +version = "0.41.3" + +[dependencies.multiversx-sc-modules] +version = "0.41.3" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/bridged-tokens-wrapper/mandos/add_wrapped_token.scen.json b/bridged-tokens-wrapper/mandos/add_wrapped_token.scen.json new file mode 100644 index 00000000..0a0d8c44 --- /dev/null +++ b/bridged-tokens-wrapper/mandos/add_wrapped_token.scen.json @@ -0,0 +1,131 @@ +{ + "name": "add wrapped token", + "gasSchedule": "v4", + "steps": [ + { + "step": "setState", + "accounts": { + "address:owner": { + "nonce": "0", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "0", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "500000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "500000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "500" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + } + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + }, + { + "step": "scCall", + "txId": "add-wrapped-token", + "tx": { + "from": "address:owner", + "to": "sc:bridged_tokens_wrapper", + "function": "addWrappedToken", + "arguments": [ + "str:WUSDC-abcdef", + "u32:6" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "1", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "0", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "500000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "500000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "500" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + } + }, + "storage": { + "str:universalBridgedTokenIds.len": "1", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:token_decimals_num|nested:str:WUSDC-abcdef": "6" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/bridged-tokens-wrapper/mandos/blacklist_token.scen.json b/bridged-tokens-wrapper/mandos/blacklist_token.scen.json new file mode 100644 index 00000000..7f36f67d --- /dev/null +++ b/bridged-tokens-wrapper/mandos/blacklist_token.scen.json @@ -0,0 +1,123 @@ +{ + "name": "blacklist token", + "gasSchedule": "v4", + "steps": [ + { + "step": "externalSteps", + "path": "wrap_token.scen.json" + }, + { + "step": "scCall", + "txId": "blacklist-token", + "tx": { + "from": "address:owner", + "to": "sc:bridged_tokens_wrapper", + "function": "blacklistToken", + "arguments": [ + "str:USDC-aaaaaa" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "unwrap-token", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "value": "0", + "esdt": { + "tokenIdentifier": "str:WUSDC-abcdef", + "value": "100" + }, + "function": "unwrapToken", + "arguments": [ + "str:USDC-aaaaaa" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Esdt token unavailable", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "4", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "5", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "200000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "400000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "900" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:USDC-aaaaaa": { + "balance": "300000000000000" + }, + "str:USDC-cccccc": { + "balance": "100000000000000" + } + }, + "storage": { + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-cccccc", + "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:universalBridgedTokenIds.len": "1", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", + "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", + "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", + "str:token_decimals_num|nested:str:USDC-cccccc": "18" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/bridged-tokens-wrapper/mandos/remove_wrapped_token.scen.json b/bridged-tokens-wrapper/mandos/remove_wrapped_token.scen.json new file mode 100644 index 00000000..79970914 --- /dev/null +++ b/bridged-tokens-wrapper/mandos/remove_wrapped_token.scen.json @@ -0,0 +1,114 @@ +{ + "name": "remove wrapped token", + "gasSchedule": "v4", + "steps": [ + { + "step": "externalSteps", + "path": "blacklist_token.scen.json" + }, + { + "step": "scCall", + "txId": "add-wrapped-token", + "tx": { + "from": "address:owner", + "to": "sc:bridged_tokens_wrapper", + "function": "addWrappedToken", + "arguments": [ + "str:WUSDC-uvwxyz", + "u32:18" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "remove-wrapped-token", + "tx": { + "from": "address:owner", + "to": "sc:bridged_tokens_wrapper", + "function": "removeWrappedToken", + "arguments": [ + "str:WUSDC-abcdef" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "6", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "5", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "200000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "400000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "900" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:USDC-aaaaaa": { + "balance": "300000000000000" + }, + "str:USDC-cccccc": { + "balance": "100000000000000" + } + }, + "storage": { + "str:universalBridgedTokenIds.len": "1", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-uvwxyz": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-uvwxyz", + "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", + "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", + "str:token_decimals_num|nested:str:WUSDC-uvwxyz": "18" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/bridged-tokens-wrapper/mandos/setup.scen.json b/bridged-tokens-wrapper/mandos/setup.scen.json new file mode 100644 index 00000000..6a11bc37 --- /dev/null +++ b/bridged-tokens-wrapper/mandos/setup.scen.json @@ -0,0 +1,232 @@ +{ + "name": "add wrapped token", + "gasSchedule": "v4", + "steps": [ + { + "step": "setState", + "accounts": { + "address:owner": { + "nonce": "3", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "7", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "300000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "400000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "900" + }, + "str:WUSDC-ghijkl": { + "balance": "900" + }, + "str:WUSDC-mnopqr": { + "balance": "900" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-ghijkl": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-mnopqr": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:USDC-aaaaaa": { + "balance": "300000000000000" + }, + "str:USDC-cccccc": { + "balance": "100000000000000" + }, + "str:USDC-ffffff": { + "balance": "400000000000000" + }, + "str:USDC-eeeeee": { + "balance": "400000000000000" + } + }, + "storage": { + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-aaaaaa": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-aaaaaa", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:2": "str:USDC-cccccc", + "str:chainSpecificToUniversalMapping|nested:str:USDC-aaaaaa": "str:WUSDC-abcdef", + "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.len": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.index|nested:str:USDC-ffffff": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.item|u32:1": "str:USDC-ffffff", + "str:chainSpecificToUniversalMapping|nested:str:USDC-ffffff": "str:WUSDC-ghijkl", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.len": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.item|u32:1": "str:USDC-eeeeee", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.index|nested:str:USDC-eeeeee": "1", + "str:chainSpecificToUniversalMapping|nested:str:USDC-eeeeee": "str:WUSDC-mnopqr", + "str:universalBridgedTokenIds.len": "3", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-ghijkl": "2", + "str:universalBridgedTokenIds.item|u32:2": "str:WUSDC-ghijkl", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-mnopqr": "3", + "str:universalBridgedTokenIds.item|u32:3": "str:WUSDC-mnopqr", + "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", + "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", + "str:tokenLiquidity|nested:str:USDC-eeeeee": "400000000000000", + "str:tokenLiquidity|nested:str:USDC-ffffff": "400000000000000", + "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", + "str:token_decimals_num|nested:str:WUSDC-ghijkl": "6", + "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", + "str:token_decimals_num|nested:str:USDC-cccccc": "18" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "3", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "7", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "300000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "400000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "900" + }, + "str:WUSDC-ghijkl": { + "balance": "900" + }, + "str:WUSDC-mnopqr": { + "balance": "900" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-ghijkl": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-mnopqr": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:USDC-aaaaaa": { + "balance": "300000000000000" + }, + "str:USDC-cccccc": { + "balance": "100000000000000" + }, + "str:USDC-eeeeee": { + "balance": "400000000000000" + }, + "str:USDC-ffffff": { + "balance": "400000000000000" + } + }, + "storage": { + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-aaaaaa": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-aaaaaa", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:2": "str:USDC-cccccc", + "str:chainSpecificToUniversalMapping|nested:str:USDC-aaaaaa": "str:WUSDC-abcdef", + "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.len": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.index|nested:str:USDC-ffffff": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.item|u32:1": "str:USDC-ffffff", + "str:chainSpecificToUniversalMapping|nested:str:USDC-ffffff": "str:WUSDC-ghijkl", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.len": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.item|u32:1": "str:USDC-eeeeee", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.index|nested:str:USDC-eeeeee": "1", + "str:chainSpecificToUniversalMapping|nested:str:USDC-eeeeee": "str:WUSDC-mnopqr", + "str:universalBridgedTokenIds.len": "3", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-ghijkl": "2", + "str:universalBridgedTokenIds.item|u32:2": "str:WUSDC-ghijkl", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-mnopqr": "3", + "str:universalBridgedTokenIds.item|u32:3": "str:WUSDC-mnopqr", + "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", + "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", + "str:tokenLiquidity|nested:str:USDC-eeeeee": "400000000000000", + "str:tokenLiquidity|nested:str:USDC-ffffff": "400000000000000", + "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", + "str:token_decimals_num|nested:str:WUSDC-ghijkl": "6", + "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", + "str:token_decimals_num|nested:str:USDC-cccccc": "18" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/bridged-tokens-wrapper/mandos/unwrap_token.scen.json b/bridged-tokens-wrapper/mandos/unwrap_token.scen.json new file mode 100644 index 00000000..819c3ecd --- /dev/null +++ b/bridged-tokens-wrapper/mandos/unwrap_token.scen.json @@ -0,0 +1,248 @@ +{ + "name": "unwrap token", + "gasSchedule": "v4", + "steps": [ + { + "step": "externalSteps", + "path": "setup.scen.json" + }, + { + "step": "scCall", + "txId": "unwrap-token-unavailable", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "value": "0", + "esdt": { + "tokenIdentifier": "str:WUSDC-abcdef", + "value": "300" + }, + "function": "unwrapToken", + "arguments": [ + "str:USDC-bbbbbb" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Esdt token unavailable", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "unwrap-token-not-enough-funds", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "value": "0", + "esdt": { + "tokenIdentifier": "str:WUSDC-abcdef", + "value": "500" + }, + "function": "unwrapToken", + "arguments": [ + "str:USDC-aaaaaa" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Contract does not have enough funds", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "unwrap-token-requires_updating", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "value": "0", + "esdt": { + "tokenIdentifier": "str:WUSDC-mnopqr", + "value": "500" + }, + "function": "unwrapToken", + "arguments": [ + "str:USDC-eeeeee" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Universal token requires updating", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "chain-token-requires_updating", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "value": "0", + "esdt": { + "tokenIdentifier": "str:WUSDC-ghijkl", + "value": "500" + }, + "function": "unwrapToken", + "arguments": [ + "str:USDC-ffffff" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Chain-specific token requires updating", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "unwrap-token", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "value": "0", + "esdt": { + "tokenIdentifier": "str:WUSDC-abcdef", + "value": "100" + }, + "function": "unwrapToken", + "arguments": [ + "str:USDC-aaaaaa" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "3", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "12", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "400000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "400000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "800" + }, + "str:WUSDC-ghijkl": { + "balance": "900" + }, + "str:WUSDC-mnopqr": { + "balance": "900" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-ghijkl": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-mnopqr": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:USDC-aaaaaa": { + "balance": "200000000000000" + }, + "str:USDC-cccccc": { + "balance": "100000000000000" + }, + "str:USDC-ffffff": { + "balance": "400000000000000" + }, + "str:USDC-eeeeee": { + "balance": "400000000000000" + } + }, + "storage": { + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-aaaaaa": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-aaaaaa", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:2": "str:USDC-cccccc", + "str:chainSpecificToUniversalMapping|nested:str:USDC-aaaaaa": "str:WUSDC-abcdef", + "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.len": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.index|nested:str:USDC-ffffff": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-ghijkl|str:.item|u32:1": "str:USDC-ffffff", + "str:chainSpecificToUniversalMapping|nested:str:USDC-ffffff": "str:WUSDC-ghijkl", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.len": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.item|u32:1": "str:USDC-eeeeee", + "str:chainSpecificTokenIds|nested:str:WUSDC-mnopqr|str:.index|nested:str:USDC-eeeeee": "1", + "str:chainSpecificToUniversalMapping|nested:str:USDC-eeeeee": "str:WUSDC-mnopqr", + "str:universalBridgedTokenIds.len": "3", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-ghijkl": "2", + "str:universalBridgedTokenIds.item|u32:2": "str:WUSDC-ghijkl", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-mnopqr": "3", + "str:universalBridgedTokenIds.item|u32:3": "str:WUSDC-mnopqr", + "str:tokenLiquidity|nested:str:USDC-aaaaaa": "200000000000000", + "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", + "str:tokenLiquidity|nested:str:USDC-eeeeee": "400000000000000", + "str:tokenLiquidity|nested:str:USDC-ffffff": "400000000000000", + "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", + "str:token_decimals_num|nested:str:WUSDC-ghijkl": "6", + "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", + "str:token_decimals_num|nested:str:USDC-cccccc": "18" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/bridged-tokens-wrapper/mandos/whitelist_token.scen.json b/bridged-tokens-wrapper/mandos/whitelist_token.scen.json new file mode 100644 index 00000000..5a05ecaa --- /dev/null +++ b/bridged-tokens-wrapper/mandos/whitelist_token.scen.json @@ -0,0 +1,140 @@ +{ + "name": "whitelist token", + "gasSchedule": "v4", + "steps": [ + { + "step": "externalSteps", + "path": "add_wrapped_token.scen.json" + }, + { + "step": "scCall", + "txId": "whitelist-token-fail", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "function": "whitelistToken", + "arguments": [ + "str:USDC-bbbbbb", + "u32:18", + "str:WUSDC-abcdef" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Endpoint can only be called by owner", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "whitelist-token-a", + "tx": { + "from": "address:owner", + "to": "sc:bridged_tokens_wrapper", + "function": "whitelistToken", + "arguments": [ + "str:USDC-aaaaaa", + "u32:18", + "str:WUSDC-abcdef" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "whitelist-token-c", + "tx": { + "from": "address:owner", + "to": "sc:bridged_tokens_wrapper", + "function": "whitelistToken", + "arguments": [ + "str:USDC-cccccc", + "u32:18", + "str:WUSDC-abcdef" + ], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "3", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "1", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "500000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "500000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "500" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + } + }, + "storage": { + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-aaaaaa": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-aaaaaa", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:2": "str:USDC-cccccc", + "str:chainSpecificToUniversalMapping|nested:str:USDC-aaaaaa": "str:WUSDC-abcdef", + "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:universalBridgedTokenIds.len": "1", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", + "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", + "str:token_decimals_num|nested:str:USDC-cccccc": "18" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/bridged-tokens-wrapper/mandos/wrap_token.scen.json b/bridged-tokens-wrapper/mandos/wrap_token.scen.json new file mode 100644 index 00000000..65a5dfba --- /dev/null +++ b/bridged-tokens-wrapper/mandos/wrap_token.scen.json @@ -0,0 +1,157 @@ +{ + "name": "wrap token", + "gasSchedule": "v4", + "steps": [ + { + "step": "externalSteps", + "path": "whitelist_token.scen.json" + }, + { + "step": "scCall", + "txId": "wrap-token-fail", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "function": "wrapTokens", + "esdt": { + "tokenIdentifier": "str:USDC-bbbbbb", + "value": "300000000000000" + }, + "arguments": [], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*", + "out": [ + "nested:str:USDC-bbbbbb|u64:0|biguint:300000000000000" + ] + } + }, + { + "step": "scCall", + "txId": "wrap-token-a", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "function": "wrapTokens", + "esdt": { + "tokenIdentifier": "str:USDC-aaaaaa", + "value": "300000000000000" + }, + "arguments": [], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*", + "out": [ + "nested:str:WUSDC-abcdef|u64:0|biguint:300" + ] + } + }, + { + "step": "scCall", + "txId": "wrap-token-c", + "tx": { + "from": "address:user", + "to": "sc:bridged_tokens_wrapper", + "function": "wrapTokens", + "esdt": { + "tokenIdentifier": "str:USDC-cccccc", + "value": "100000000000000" + }, + "arguments": [], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*", + "out": [ + "nested:str:WUSDC-abcdef|u64:0|biguint:100" + ] + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "3", + "balance": "0", + "storage": {} + }, + "address:user": { + "nonce": "4", + "esdt": { + "str:USDC-aaaaaa": { + "balance": "200000000000000" + }, + "str:USDC-bbbbbb": { + "balance": "500000000000000" + }, + "str:USDC-cccccc": { + "balance": "400000000000000" + }, + "str:WUSDC-abcdef": { + "balance": "900" + } + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:USDC-aaaaaa": { + "balance": "300000000000000" + }, + "str:USDC-cccccc": { + "balance": "100000000000000" + } + }, + "storage": { + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.len": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-aaaaaa": "1", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.index|nested:str:USDC-cccccc": "2", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:1": "str:USDC-aaaaaa", + "str:chainSpecificTokenIds|nested:str:WUSDC-abcdef|str:.item|u32:2": "str:USDC-cccccc", + "str:chainSpecificToUniversalMapping|nested:str:USDC-aaaaaa": "str:WUSDC-abcdef", + "str:chainSpecificToUniversalMapping|nested:str:USDC-cccccc": "str:WUSDC-abcdef", + "str:universalBridgedTokenIds.len": "1", + "str:universalBridgedTokenIds.index|nested:str:WUSDC-abcdef": "1", + "str:universalBridgedTokenIds.item|u32:1": "str:WUSDC-abcdef", + "str:tokenLiquidity|nested:str:USDC-aaaaaa": "300000000000000", + "str:tokenLiquidity|nested:str:USDC-cccccc": "100000000000000", + "str:token_decimals_num|nested:str:WUSDC-abcdef": "6", + "str:token_decimals_num|nested:str:USDC-aaaaaa": "18", + "str:token_decimals_num|nested:str:USDC-cccccc": "18" + }, + "code": "file:../output/bridged-tokens-wrapper.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/bridged-tokens-wrapper/meta/Cargo.toml b/bridged-tokens-wrapper/meta/Cargo.toml new file mode 100644 index 00000000..55bf3058 --- /dev/null +++ b/bridged-tokens-wrapper/meta/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bridged-tokens-wrapper-meta" +version = "0.0.0" +edition = "2018" +publish = false +[dependencies.bridged-tokens-wrapper] +path = ".." + +[dependencies.multiversx-sc-meta] +version = "0.41.3" diff --git a/bridged-tokens-wrapper/meta/src/main.rs b/bridged-tokens-wrapper/meta/src/main.rs new file mode 100644 index 00000000..ac93d248 --- /dev/null +++ b/bridged-tokens-wrapper/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::(); +} diff --git a/egld-esdt-swap/elrond.json b/bridged-tokens-wrapper/multiversx.json similarity index 100% rename from egld-esdt-swap/elrond.json rename to bridged-tokens-wrapper/multiversx.json diff --git a/bridged-tokens-wrapper/src/dfp_big_uint.rs b/bridged-tokens-wrapper/src/dfp_big_uint.rs new file mode 100644 index 00000000..a70839f5 --- /dev/null +++ b/bridged-tokens-wrapper/src/dfp_big_uint.rs @@ -0,0 +1,44 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[derive(Clone, PartialEq, Eq)] +pub struct DFPBigUint { + bu: BigUint, + num_decimals: u32, +} + +impl DFPBigUint { + pub fn from_raw(bu: BigUint, num_decimals: u32) -> Self { + DFPBigUint { bu, num_decimals } + } + + pub fn convert(self, decimals: u32) -> Self { + if self.num_decimals == decimals { + return self; + } + + let new_bu = if self.num_decimals > decimals { + let diff_decimals = self.num_decimals - decimals; + self.bu / 10u64.pow(diff_decimals) + } else { + let diff_decimals = decimals - self.num_decimals; + self.bu * 10u64.pow(diff_decimals) + }; + + DFPBigUint { + bu: new_bu, + num_decimals: decimals, + } + } + + pub fn trunc(&self) -> Self { + DFPBigUint { + bu: self.bu.clone() / 10u64.pow(self.num_decimals), + num_decimals: 1, + } + } + + pub fn to_raw(&self) -> BigUint { + self.bu.clone() + } +} diff --git a/bridged-tokens-wrapper/src/lib.rs b/bridged-tokens-wrapper/src/lib.rs new file mode 100644 index 00000000..f18f28a9 --- /dev/null +++ b/bridged-tokens-wrapper/src/lib.rs @@ -0,0 +1,286 @@ +#![no_std] + +mod dfp_big_uint; +use core::ops::Deref; + +pub use dfp_big_uint::DFPBigUint; +use transaction::PaymentsVec; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +impl DFPBigUint {} + +#[multiversx_sc::contract] +pub trait BridgedTokensWrapper: multiversx_sc_modules::pause::PauseModule { + #[init] + fn init(&self) { + self.set_paused(true); + } + + #[only_owner] + #[endpoint(addWrappedToken)] + fn add_wrapped_token(&self, universal_bridged_token_ids: TokenIdentifier, num_decimals: u32) { + self.require_mint_and_burn_roles(&universal_bridged_token_ids); + self.token_decimals_num(&universal_bridged_token_ids) + .set(num_decimals); + self.universal_bridged_token_ids() + .insert(universal_bridged_token_ids); + } + + #[only_owner] + #[endpoint(updateWrappedToken)] + fn update_wrapped_token( + &self, + universal_bridged_token_ids: TokenIdentifier, + num_decimals: u32, + ) { + require!( + self.universal_bridged_token_ids() + .contains(&universal_bridged_token_ids), + "Universal token was not added yet" + ); + self.token_decimals_num(&universal_bridged_token_ids) + .set(num_decimals); + } + + #[only_owner] + #[endpoint(removeWrappedToken)] + fn remove_wrapped_token(&self, universal_bridged_token_ids: TokenIdentifier) { + let _ = self + .universal_bridged_token_ids() + .swap_remove(&universal_bridged_token_ids); + + let mut chain_specific_tokens = self.chain_specific_token_ids(&universal_bridged_token_ids); + for token in chain_specific_tokens.iter() { + self.chain_specific_to_universal_mapping(&token).clear(); + self.token_decimals_num(&token).clear(); + } + + chain_specific_tokens.clear(); + self.token_decimals_num(&universal_bridged_token_ids) + .clear(); + } + + #[only_owner] + #[endpoint(whitelistToken)] + fn whitelist_token( + &self, + chain_specific_token_id: TokenIdentifier, + chain_specific_token_decimals: u32, + universal_bridged_token_ids: TokenIdentifier, + ) { + self.require_mint_and_burn_roles(&universal_bridged_token_ids); + + let chain_to_universal_mapper = + self.chain_specific_to_universal_mapping(&chain_specific_token_id); + require!( + chain_to_universal_mapper.is_empty(), + "Chain-specific token is already mapped to another universal token" + ); + + self.token_decimals_num(&chain_specific_token_id) + .set(chain_specific_token_decimals); + + chain_to_universal_mapper.set(&universal_bridged_token_ids); + + let _ = self + .chain_specific_token_ids(&universal_bridged_token_ids) + .insert(chain_specific_token_id); + + self.universal_bridged_token_ids() + .insert(universal_bridged_token_ids); + } + + #[only_owner] + #[endpoint(updateWhitelistedToken)] + fn update_whitelisted_token( + &self, + chain_specific_token_id: TokenIdentifier, + chain_specific_token_decimals: u32, + ) { + let chain_to_universal_mapper = + self.chain_specific_to_universal_mapping(&chain_specific_token_id); + require!( + !chain_to_universal_mapper.is_empty(), + "Chain-specific token was not whitelisted yet" + ); + + self.token_decimals_num(&chain_specific_token_id) + .set(chain_specific_token_decimals); + } + + #[only_owner] + #[endpoint(blacklistToken)] + fn blacklist_token(&self, chain_specific_token_id: TokenIdentifier) { + let chain_to_universal_mapper = + self.chain_specific_to_universal_mapping(&chain_specific_token_id); + + let universal_bridged_token_ids = chain_to_universal_mapper.get(); + + let _ = self + .chain_specific_token_ids(&universal_bridged_token_ids) + .swap_remove(&chain_specific_token_id); + + chain_to_universal_mapper.clear(); + self.token_decimals_num(&chain_specific_token_id).clear(); + } + + #[payable("*")] + #[endpoint(depositLiquidity)] + fn deposit_liquidity(&self) { + let (payment_token, payment_amount) = self.call_value().single_fungible_esdt(); + self.token_liquidity(&payment_token) + .update(|liq| *liq += payment_amount); + } + + /// Will wrap what it can, and send back the rest unchanged + #[payable("*")] + #[endpoint(wrapTokens)] + fn wrap_tokens(&self) -> PaymentsVec { + require!(self.not_paused(), "Contract is paused"); + let original_payments = self.call_value().all_esdt_transfers().deref().clone(); + if original_payments.is_empty() { + return original_payments; + } + + let mut new_payments = ManagedVec::new(); + + for payment in &original_payments { + let universal_token_id_mapper = + self.chain_specific_to_universal_mapping(&payment.token_identifier); + + // if there is chain specific -> universal mapping, then the token is whitelisted + if universal_token_id_mapper.is_empty() { + new_payments.push(payment); + continue; + } + let universal_token_id = universal_token_id_mapper.get(); + self.require_tokens_have_set_decimals_num( + &universal_token_id, + &payment.token_identifier, + ); + self.token_liquidity(&payment.token_identifier) + .update(|value| *value += &payment.amount); + let converted_amount = self.get_converted_amount( + &payment.token_identifier, + &universal_token_id, + payment.amount, + ); + + self.send() + .esdt_local_mint(&universal_token_id, 0, &converted_amount); + new_payments.push(EsdtTokenPayment::new( + universal_token_id, + 0, + converted_amount, + )); + } + + let caller = self.blockchain().get_caller(); + self.send().direct_multi(&caller, &new_payments); + + new_payments + } + + #[payable("*")] + #[endpoint(unwrapToken)] + fn unwrap_token(&self, requested_token: TokenIdentifier) { + require!(self.not_paused(), "Contract is paused"); + let (payment_token, payment_amount) = self.call_value().single_fungible_esdt(); + require!(payment_amount > 0u32, "Must pay more than 0 tokens!"); + + let universal_bridged_token_ids = self + .chain_specific_to_universal_mapping(&requested_token) + .get(); + require!( + payment_token == universal_bridged_token_ids, + "Esdt token unavailable" + ); + self.require_tokens_have_set_decimals_num(&payment_token, &requested_token); + + let chain_specific_token_id = &requested_token; + let converted_amount = self.get_converted_amount( + &payment_token, + chain_specific_token_id, + payment_amount.clone(), + ); + + self.token_liquidity(chain_specific_token_id).update(|liq| { + require!( + converted_amount <= *liq, + "Contract does not have enough funds" + ); + + *liq -= &converted_amount; + }); + + self.send() + .esdt_local_burn(&universal_bridged_token_ids, 0, &payment_amount); + + let caller = self.blockchain().get_caller(); + self.send() + .direct_esdt(&caller, chain_specific_token_id, 0, &converted_amount); + } + + fn get_converted_amount( + &self, + from: &TokenIdentifier, + to: &TokenIdentifier, + amount: BigUint, + ) -> BigUint { + let from_decimals = self.token_decimals_num(from).get(); + let to_decimals = self.token_decimals_num(to).get(); + let converted_amount = DFPBigUint::from_raw(amount, from_decimals); + converted_amount.convert(to_decimals).to_raw() + } + + fn require_mint_and_burn_roles(&self, token_id: &TokenIdentifier) { + let roles = self.blockchain().get_esdt_local_roles(token_id); + + require!( + roles.has_role(&EsdtLocalRole::Mint) && roles.has_role(&EsdtLocalRole::Burn), + "Must set local role first" + ); + } + + fn require_tokens_have_set_decimals_num( + &self, + universal_token: &TokenIdentifier, + chain_token: &TokenIdentifier, + ) { + require!( + !self.token_decimals_num(universal_token).is_empty(), + "Universal token requires updating" + ); + require!( + !self.token_decimals_num(chain_token).is_empty(), + "Chain-specific token requires updating" + ); + } + + #[view(getUniversalBridgedTokenIds)] + #[storage_mapper("universalBridgedTokenIds")] + fn universal_bridged_token_ids(&self) -> UnorderedSetMapper; + + #[view(getTokenLiquidity)] + #[storage_mapper("tokenLiquidity")] + fn token_liquidity(&self, token: &TokenIdentifier) -> SingleValueMapper; + + #[view(getChainSpecificToUniversalMapping)] + #[storage_mapper("chainSpecificToUniversalMapping")] + fn chain_specific_to_universal_mapping( + &self, + token: &TokenIdentifier, + ) -> SingleValueMapper; + + #[view(getchainSpecificTokenIds)] + #[storage_mapper("chainSpecificTokenIds")] + fn chain_specific_token_ids( + &self, + universal_token_id: &TokenIdentifier, + ) -> UnorderedSetMapper; + + #[storage_mapper("token_decimals_num")] + fn token_decimals_num(&self, token: &TokenIdentifier) -> SingleValueMapper; +} diff --git a/bridged-tokens-wrapper/testnet.toml b/bridged-tokens-wrapper/testnet.toml new file mode 100644 index 00000000..e69de29b diff --git a/bridged-tokens-wrapper/tests/dfp_big_uint_test.rs b/bridged-tokens-wrapper/tests/dfp_big_uint_test.rs new file mode 100644 index 00000000..31a2c9b5 --- /dev/null +++ b/bridged-tokens-wrapper/tests/dfp_big_uint_test.rs @@ -0,0 +1,24 @@ +#![feature(associated_type_bounds)] +use bridged_tokens_wrapper::DFPBigUint; +use multiversx_sc_scenario::DebugApi; + +#[test] +fn test_biguint() { + let _ = DebugApi::dummy(); + let raw = 123456u64; + let dfp = DFPBigUint::::from_raw(raw.into(), 6); + let converted = dfp.clone().convert(9); + assert!(dfp.trunc() == converted.trunc()); + assert!(converted.clone().convert(9).to_raw() == 123456000u64); + assert!(converted.clone().convert(1).to_raw() == 1u64); + assert!(converted.clone().convert(3).to_raw() == 123u64); + assert!(converted.clone().convert(5).to_raw() == 12345u64); +} + +#[test] +fn test_mandos_scenario_values() { + let _ = DebugApi::dummy(); + let raw = 300000000000000u64; + let dfp = DFPBigUint::::from_raw(raw.into(), 18); + assert!(dfp.convert(6).to_raw() == 300u64); +} diff --git a/bridged-tokens-wrapper/tests/scenario_go_test.rs b/bridged-tokens-wrapper/tests/scenario_go_test.rs new file mode 100644 index 00000000..4bbc4b02 --- /dev/null +++ b/bridged-tokens-wrapper/tests/scenario_go_test.rs @@ -0,0 +1,29 @@ +#[test] +fn unwrap_token_go() { + multiversx_sc_scenario::run_go("mandos/unwrap_token.scen.json"); +} + +#[test] +fn wrap_token_go() { + multiversx_sc_scenario::run_go("mandos/wrap_token.scen.json"); +} + +#[test] +fn whitelist_token_go() { + multiversx_sc_scenario::run_go("mandos/whitelist_token.scen.json"); +} + +#[test] +fn blacklist_token_go() { + multiversx_sc_scenario::run_go("mandos/blacklist_token.scen.json"); +} + +#[test] +fn add_wrapped_token_go() { + multiversx_sc_scenario::run_go("mandos/add_wrapped_token.scen.json"); +} + +#[test] +fn remove_wrapped_token_go() { + multiversx_sc_scenario::run_go("mandos/remove_wrapped_token.scen.json"); +} diff --git a/bridged-tokens-wrapper/wasm/Cargo.lock b/bridged-tokens-wrapper/wasm/Cargo.lock new file mode 100644 index 00000000..f3a582a2 --- /dev/null +++ b/bridged-tokens-wrapper/wasm/Cargo.lock @@ -0,0 +1,299 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bridged-tokens-wrapper" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "transaction", +] + +[[package]] +name = "bridged-tokens-wrapper-wasm" +version = "0.0.0" +dependencies = [ + "bridged-tokens-wrapper", + "multiversx-sc-wasm-adapter", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "eth-address" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "multiversx-sc" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31561133b64837f1a7835ae4ba96383ab07d9ce401e703ad2a37aef45789a1e7" +dependencies = [ + "bitflags", + "hashbrown", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7638cb46a0e99c636fd55443ac534ff0a5fad0bd772e1037fbac9a75e04c3c9" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", + "wee_alloc", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e976002d51367f16140929c10ee695f95dd8d34c150a45db60d3fcd1328a267a" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23187464277575f8055c92cffbd664592825591f5cebb8dae038d2af04c5e639" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-modules" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb0b06f37396484f01d89295091db401b8b515ab781e3580a6bb333e912591" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af5fee54f1498ec8181593cd54a96ef7436543ad78848e2e805b9b62c27c4095" +dependencies = [ + "multiversx-sc", + "wee_alloc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "transaction" +version = "0.0.0" +dependencies = [ + "eth-address", + "multiversx-sc", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/bridged-tokens-wrapper/wasm/Cargo.toml b/bridged-tokens-wrapper/wasm/Cargo.toml new file mode 100644 index 00000000..05ea6ac7 --- /dev/null +++ b/bridged-tokens-wrapper/wasm/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bridged-tokens-wrapper-wasm" +version = "0.0.0" +authors = ["Alin Cruceat "] +edition = "2018" +publish = false + +[lib] +crate-type = ["cdylib"] +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" +[dependencies.bridged-tokens-wrapper] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.41.3" + +[workspace] +members = ["."] diff --git a/bridged-tokens-wrapper/wasm/src/lib.rs b/bridged-tokens-wrapper/wasm/src/lib.rs new file mode 100644 index 00000000..ca27291f --- /dev/null +++ b/bridged-tokens-wrapper/wasm/src/lib.rs @@ -0,0 +1,40 @@ +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 16 +// Async Callback (empty): 1 +// Total number of exported functions: 18 + +#![no_std] +#![feature(alloc_error_handler, lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + bridged_tokens_wrapper + ( + addWrappedToken + updateWrappedToken + removeWrappedToken + whitelistToken + updateWhitelistedToken + blacklistToken + depositLiquidity + wrapTokens + unwrapToken + getUniversalBridgedTokenIds + getTokenLiquidity + getChainSpecificToUniversalMapping + getchainSpecificTokenIds + pause + unpause + isPaused + ) +} + +multiversx_sc_wasm_adapter::empty_callback! {} diff --git a/build-wasm.sh b/build-wasm.sh index 6e42697c..9bad3006 100755 --- a/build-wasm.sh +++ b/build-wasm.sh @@ -8,5 +8,5 @@ for smart_contract_json in $SMART_CONTRACT_JSONS do smart_contract_folder=$(dirname $smart_contract_json) echo "" - (set -x; erdpy --verbose contract build $smart_contract_folder) + (set -x; mxpy --verbose contract build $smart_contract_folder --ignore-eei-checks) done diff --git a/clean-wasm.sh b/clean-wasm.sh index b812802f..b022bc99 100755 --- a/clean-wasm.sh +++ b/clean-wasm.sh @@ -8,7 +8,7 @@ for smart_contract_json in $SMART_CONTRACT_JSONS do smart_contract_folder=$(dirname $smart_contract_json) echo "" - (set -x; erdpy --verbose contract clean $smart_contract_folder) + (set -x; mxpy --verbose contract clean $smart_contract_folder) done # not wasm, but worth cleaning from time to time diff --git a/common/eth-address/Cargo.toml b/common/eth-address/Cargo.toml index beff399d..b0f24f38 100644 --- a/common/eth-address/Cargo.toml +++ b/common/eth-address/Cargo.toml @@ -1,15 +1,10 @@ [package] name = "eth-address" version = "0.0.0" -authors = [ "dorin-iancu ",] +authors = ["dorin-iancu "] edition = "2018" [lib] path = "src/lib.rs" - -[dependencies.elrond-wasm] -version = "0.18" -features = ["derive"] - -[dependencies.elrond-wasm-derive] -version = "0.18" +[dependencies.multiversx-sc] +version = "0.41.3" diff --git a/common/eth-address/src/lib.rs b/common/eth-address/src/lib.rs index 933a57c4..0e4e6a53 100644 --- a/common/eth-address/src/lib.rs +++ b/common/eth-address/src/lib.rs @@ -1,42 +1,27 @@ #![no_std] -elrond_wasm::derive_imports!(); -use elrond_wasm::types::Box; +multiversx_sc::derive_imports!(); +use multiversx_sc::{ + api::ManagedTypeApi, + types::{ManagedBuffer, ManagedByteArray}, +}; pub const ETH_ADDRESS_LEN: usize = 20; -#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode, Clone)] -pub struct EthAddress(Box<[u8; ETH_ADDRESS_LEN]>); - -impl EthAddress { - pub fn as_slice(&self) -> &[u8] { - &(*self.0)[..] - } - - pub fn is_zero(&self) -> bool { - self.0.eq(&Self::zero().0) - } +/// Wrapper over a 20-byte array +#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode, Clone, ManagedVecItem)] +pub struct EthAddress { + pub raw_addr: ManagedByteArray, } -impl EthAddress { +impl EthAddress { pub fn zero() -> Self { - EthAddress(Box::from([0u8; ETH_ADDRESS_LEN])) - } -} - -impl<'a> From<&'a [u8]> for EthAddress { - fn from(slice: &'a [u8]) -> Self { - let mut zero = Self::zero(); - if slice.len() == ETH_ADDRESS_LEN { - (*zero.0).copy_from_slice(slice) + Self { + raw_addr: ManagedByteArray::new_from_bytes(&[0u8; ETH_ADDRESS_LEN]), } - - zero } -} -impl From<[u8; ETH_ADDRESS_LEN]> for EthAddress { - fn from(array: [u8; ETH_ADDRESS_LEN]) -> Self { - Self::from(&array[..]) + pub fn as_managed_buffer(&self) -> &ManagedBuffer { + self.raw_addr.as_managed_buffer() } } diff --git a/common/fee-estimator-module/Cargo.toml b/common/fee-estimator-module/Cargo.toml index 3df5260e..5ae1347c 100644 --- a/common/fee-estimator-module/Cargo.toml +++ b/common/fee-estimator-module/Cargo.toml @@ -1,21 +1,9 @@ [package] name = "fee-estimator-module" version = "0.0.0" -authors = [ "dorin-iancu ",] +authors = ["dorin-iancu "] edition = "2018" - -[features] -wasm-output-mode = ["elrond-wasm-node"] - -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-derive] -version = "0.18" - -[dependencies.elrond-wasm-node] -version = "0.18" -optional = true - -[dev-dependencies.elrond-wasm-debug] -version = "0.18" +[dependencies.multiversx-sc] +version = "0.41.3" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/common/fee-estimator-module/src/aggregator_proxy.rs b/common/fee-estimator-module/src/aggregator_proxy.rs index 0aa79849..123254ae 100644 --- a/common/fee-estimator-module/src/aggregator_proxy.rs +++ b/common/fee-estimator-module/src/aggregator_proxy.rs @@ -1,32 +1,30 @@ -elrond_wasm::imports!(); +multiversx_sc::imports!(); pub const GWEI_STRING: &[u8] = b"GWEI"; -pub type AggregatorResultAsMultiResult = - MultiResult5; +pub type AggregatorResultAsMultiValue = + MultiValue5, ManagedBuffer, BigUint, u8>; -#[elrond_wasm_derive::proxy] +#[multiversx_sc::proxy] pub trait Aggregator { #[view(latestPriceFeedOptional)] fn latest_price_feed_optional( &self, - from: BoxedBytes, - to: BoxedBytes, - ) -> OptionalResult>; + from: ManagedBuffer, + to: ManagedBuffer, + ) -> OptionalValue>; } -pub struct AggregatorResult { +pub struct AggregatorResult { pub round_id: u32, - pub from_token_name: BoxedBytes, - pub to_token_name: BoxedBytes, - pub price: BigUint, + pub from_token_name: ManagedBuffer, + pub to_token_name: ManagedBuffer, + pub price: BigUint, pub decimals: u8, } -impl From> - for AggregatorResult -{ - fn from(multi_result: AggregatorResultAsMultiResult) -> Self { +impl From> for AggregatorResult { + fn from(multi_result: AggregatorResultAsMultiValue) -> Self { let (round_id, from_token_name, to_token_name, price, decimals) = multi_result.into_tuple(); AggregatorResult { diff --git a/common/fee-estimator-module/src/lib.rs b/common/fee-estimator-module/src/lib.rs index fe5cb88a..3504543a 100644 --- a/common/fee-estimator-module/src/lib.rs +++ b/common/fee-estimator-module/src/lib.rs @@ -1,62 +1,73 @@ #![no_std] -elrond_wasm::imports!(); +multiversx_sc::imports!(); mod aggregator_proxy; pub use aggregator_proxy::*; -const TICKER_SEPARATOR: u8 = b'-'; - -#[elrond_wasm_derive::module] +#[multiversx_sc::module] pub trait FeeEstimatorModule { #[only_owner] - #[endpoint(setDefaultPricePerGwei)] - fn set_default_price_per_gwei( - &self, - token_id: TokenIdentifier, - default_gwei_price: Self::BigUint, - ) -> SCResult<()> { - self.default_price_per_gwei(&token_id).set(&default_gwei_price); + #[endpoint(setFeeEstimatorContractAddress)] + fn set_fee_estimator_contract_address(&self, new_address: ManagedAddress) { + self.fee_estimator_contract_address().set(&new_address); + } - Ok(()) + #[only_owner] + #[endpoint(setEthTxGasLimit)] + fn set_eth_tx_gas_limit(&self, new_limit: BigUint) { + self.eth_tx_gas_limit().set(&new_limit); } + /// Default price being used if the aggregator lacks a mapping for this token + /// or the aggregator address is not set #[only_owner] - #[endpoint(setFeeEstimatorContractAddress)] - fn set_fee_estimator_contract_address(&self, new_address: Address) -> SCResult<()> { - self.fee_estimator_contract_address().set(&new_address); + #[endpoint(setDefaultPricePerGasUnit)] + fn set_default_price_per_gas_unit( + &self, + token_id: TokenIdentifier, + default_price_per_gas_unit: BigUint, + ) { + self.default_price_per_gas_unit(&token_id) + .set(&default_price_per_gas_unit); + } - Ok(()) + /// Token ticker being used when querying the aggregator for GWEI prices + #[only_owner] + #[endpoint(setTokenTicker)] + fn set_token_ticker(&self, token_id: TokenIdentifier, ticker: ManagedBuffer) { + self.token_ticker(&token_id).set(&ticker); } + /// Returns the fee for the given token ID (the fee amount is in the given token) #[view(calculateRequiredFee)] - fn calculate_required_fee(&self, token_id: &TokenIdentifier) -> Self::BigUint { - let price_per_gwei = self.get_price_per_gwei(token_id); + fn calculate_required_fee(&self, token_id: &TokenIdentifier) -> BigUint { + let price_per_gas_unit = self.get_price_per_gas_unit(token_id); let gas_limit = self.eth_tx_gas_limit().get(); - price_per_gwei * gas_limit + price_per_gas_unit * gas_limit } - fn get_price_per_gwei(&self, token_id: &TokenIdentifier) -> Self::BigUint { - let opt_price = self.get_aggregator_mapping(GWEI_STRING.into(), token_id.clone()); + fn get_price_per_gas_unit(&self, token_id: &TokenIdentifier) -> BigUint { + let opt_price = self.get_aggregator_mapping(&TokenIdentifier::from(GWEI_STRING), token_id); - opt_price.unwrap_or_else(|| self.default_price_per_gwei(token_id).get()) + opt_price.unwrap_or_else(|| self.default_price_per_gas_unit(token_id).get()) } fn get_aggregator_mapping( &self, - from: TokenIdentifier, - to: TokenIdentifier, - ) -> Option { + from: &TokenIdentifier, + to: &TokenIdentifier, + ) -> Option { let fee_estimator_sc_address = self.fee_estimator_contract_address().get(); if fee_estimator_sc_address.is_zero() { return None; } - let from_ticker = self.get_token_ticker(from); - let to_ticker = self.get_token_ticker(to); + let from_ticker = self.token_ticker(from).get(); + let to_ticker = self.token_ticker(to).get(); - let result: OptionalResult> = self + let result: OptionalValue> = self .aggregator_proxy(fee_estimator_sc_address) .latest_price_feed_optional(from_ticker, to_ticker) .execute_on_dest_context(); @@ -66,35 +77,25 @@ pub trait FeeEstimatorModule { .map(|multi_result| AggregatorResult::from(multi_result).price) } - fn get_token_ticker(&self, token_id: TokenIdentifier) -> BoxedBytes { - for (i, char) in token_id.as_esdt_identifier().iter().enumerate() { - if *char == TICKER_SEPARATOR { - return token_id.as_esdt_identifier()[..i].into(); - } - } - - token_id.into_boxed_bytes() - } - // proxies #[proxy] - fn aggregator_proxy(&self, sc_address: Address) -> aggregator_proxy::Proxy; + fn aggregator_proxy(&self, sc_address: ManagedAddress) -> aggregator_proxy::Proxy; // storage #[view(getFeeEstimatorContractAddress)] #[storage_mapper("feeEstimatorContractAddress")] - fn fee_estimator_contract_address(&self) -> SingleValueMapper; + fn fee_estimator_contract_address(&self) -> SingleValueMapper; - #[view(getDefaultPricePerGwei)] - #[storage_mapper("defaultPricePerGwei")] - fn default_price_per_gwei( - &self, - token_id: &TokenIdentifier, - ) -> SingleValueMapper; + #[view(getDefaultPricePerGasUnit)] + #[storage_mapper("defaultPricePerGasUnit")] + fn default_price_per_gas_unit(&self, token_id: &TokenIdentifier) -> SingleValueMapper; + + #[storage_mapper("tokenTicker")] + fn token_ticker(&self, token_id: &TokenIdentifier) -> SingleValueMapper; #[view(getEthTxGasLimit)] #[storage_mapper("ethTxGasLimit")] - fn eth_tx_gas_limit(&self) -> SingleValueMapper; + fn eth_tx_gas_limit(&self) -> SingleValueMapper; } diff --git a/common/max-bridged-amount-module/Cargo.toml b/common/max-bridged-amount-module/Cargo.toml new file mode 100644 index 00000000..8c9527b9 --- /dev/null +++ b/common/max-bridged-amount-module/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "max-bridged-amount-module" +version = "0.0.0" +authors = ["dorin-iancu "] +edition = "2018" +[dependencies.multiversx-sc] +version = "0.41.3" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/common/max-bridged-amount-module/src/lib.rs b/common/max-bridged-amount-module/src/lib.rs new file mode 100644 index 00000000..89ca7afb --- /dev/null +++ b/common/max-bridged-amount-module/src/lib.rs @@ -0,0 +1,32 @@ +#![no_std] + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait MaxBridgedAmountModule { + #[only_owner] + #[endpoint(setMaxBridgedAmount)] + fn set_max_bridged_amount(&self, token_id: TokenIdentifier, max_amount: BigUint) { + self.max_bridged_amount(&token_id).set(&max_amount); + } + + fn is_above_max_amount(&self, token_id: &TokenIdentifier, amount: &BigUint) -> bool { + let max_amount = self.max_bridged_amount(token_id).get(); + if max_amount > 0 { + amount > &max_amount + } else { + false + } + } + + fn require_below_max_amount(&self, token_id: &TokenIdentifier, amount: &BigUint) { + require!( + !self.is_above_max_amount(token_id, amount), + "Deposit over max amount" + ); + } + + #[view(getMaxBridgedAmount)] + #[storage_mapper("maxBridgedAmount")] + fn max_bridged_amount(&self, token_id: &TokenIdentifier) -> SingleValueMapper; +} diff --git a/common/token-module/Cargo.toml b/common/token-module/Cargo.toml index f4267315..04386c99 100644 --- a/common/token-module/Cargo.toml +++ b/common/token-module/Cargo.toml @@ -1,24 +1,12 @@ [package] name = "token-module" version = "0.0.0" -authors = [ "dorin-iancu ",] +authors = ["dorin-iancu "] edition = "2018" - -[features] -wasm-output-mode = ["elrond-wasm-node"] - [dependencies.fee-estimator-module] path = "../fee-estimator-module" -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-derive] -version = "0.18" - -[dependencies.elrond-wasm-node] -version = "0.18" -optional = true - -[dev-dependencies.elrond-wasm-debug] -version = "0.18" +[dependencies.multiversx-sc] +version = "0.41.3" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/common/token-module/src/lib.rs b/common/token-module/src/lib.rs index 8c16cd79..b1b5e3f3 100644 --- a/common/token-module/src/lib.rs +++ b/common/token-module/src/lib.rs @@ -1,41 +1,64 @@ #![no_std] -elrond_wasm::imports!(); +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); -pub const PERCENTAGE_TOTAL: u64 = 10_000; // precision of 2 decimals +pub const PERCENTAGE_TOTAL: u32 = 10_000; // precision of 2 decimals +pub static INVALID_PERCENTAGE_SUM_OVER_ERR_MSG: &[u8] = b"Percentages do not add up to 100%"; -#[elrond_wasm_derive::module] +#[derive(NestedEncode, NestedDecode, TypeAbi, ManagedVecItem, Clone)] +pub struct AddressPercentagePair { + pub address: ManagedAddress, + pub percentage: u32, +} + +#[multiversx_sc::module] pub trait TokenModule: fee_estimator_module::FeeEstimatorModule { // endpoints - owner-only + /// Distributes the accumulated fees to the given addresses. + /// Expected arguments are pairs of (address, percentage), + /// where percentages must add up to the PERCENTAGE_TOTAL constant #[only_owner] #[endpoint(distributeFees)] - fn distribute_fees(&self, address_percentage_pairs: Vec<(Address, u64)>) -> SCResult<()> { - let percentage_total = Self::BigUint::from(PERCENTAGE_TOTAL); + fn distribute_fees( + &self, + address_percentage_pairs: ManagedVec>, + ) { + let percentage_total = BigUint::from(PERCENTAGE_TOTAL); + + let mut percentage_sum = 0u64; + for pair in &address_percentage_pairs { + percentage_sum += pair.percentage as u64; + } + require!( + percentage_sum == PERCENTAGE_TOTAL as u64, + INVALID_PERCENTAGE_SUM_OVER_ERR_MSG + ); for token_id in self.token_whitelist().iter() { let accumulated_fees = self.accumulated_transaction_fees(&token_id).get(); - if accumulated_fees == 0 { + if accumulated_fees == 0u32 { continue; } let mut remaining_fees = accumulated_fees.clone(); - for (dest_address, percentage) in &address_percentage_pairs { + for pair in &address_percentage_pairs { let amount_to_send = - &(&accumulated_fees * &Self::BigUint::from(*percentage)) / &percentage_total; + &(&accumulated_fees * &BigUint::from(pair.percentage)) / &percentage_total; - remaining_fees -= &amount_to_send; + if amount_to_send > 0 { + remaining_fees -= &amount_to_send; - self.send() - .direct(dest_address, &token_id, 0, &amount_to_send, &[]); + self.send() + .direct_esdt(&pair.address, &token_id, 0, &amount_to_send); + } } self.accumulated_transaction_fees(&token_id) .set(&remaining_fees); } - - Ok(()) } #[only_owner] @@ -43,68 +66,60 @@ pub trait TokenModule: fee_estimator_module::FeeEstimatorModule { fn add_token_to_whitelist( &self, token_id: TokenIdentifier, - #[var_args] opt_default_price_per_gwei: OptionalArg, - ) -> SCResult<()> { - let default_price_per_gwei = opt_default_price_per_gwei.into_option().unwrap_or_default(); - - self.default_price_per_gwei(&token_id) - .set(&default_price_per_gwei); - self.token_whitelist().insert(token_id); + ticker: ManagedBuffer, + opt_default_price_per_gas_unit: OptionalValue, + ) { + self.token_ticker(&token_id).set(&ticker); + + if let OptionalValue::Some(default_price_per_gas_unit) = opt_default_price_per_gas_unit { + self.default_price_per_gas_unit(&token_id) + .set(&default_price_per_gas_unit); + } - Ok(()) + let _ = self.token_whitelist().insert(token_id); } #[only_owner] #[endpoint(removeTokenFromWhitelist)] - fn remove_token_from_whitelist(&self, token_id: TokenIdentifier) -> SCResult<()> { - self.token_whitelist().remove(&token_id); - self.default_price_per_gwei(&token_id).clear(); + fn remove_token_from_whitelist(&self, token_id: TokenIdentifier) { + self.token_ticker(&token_id).clear(); + self.default_price_per_gas_unit(&token_id).clear(); - Ok(()) + let _ = self.token_whitelist().swap_remove(&token_id); } - // views - - #[view(getAllKnownTokens)] - fn get_all_known_tokens(&self) -> MultiResultVec { - let mut all_tokens = Vec::new(); - - for token_id in self.token_whitelist().iter() { - all_tokens.push(token_id); - } + // private - all_tokens.into() + fn require_token_in_whitelist(&self, token_id: &TokenIdentifier) { + require!( + self.token_whitelist().contains(token_id), + "Token not in whitelist" + ); } - // private - - fn require_local_role_set( - &self, - token_id: &TokenIdentifier, - role: &EsdtLocalRole, - ) -> SCResult<()> { + fn require_local_role_set(&self, token_id: &TokenIdentifier, role: &EsdtLocalRole) { require!( self.is_local_role_set(token_id, role), "Must set local role first" ); - - Ok(()) } fn is_local_role_set(&self, token_id: &TokenIdentifier, role: &EsdtLocalRole) -> bool { let roles = self.blockchain().get_esdt_local_roles(token_id); - roles.contains(role) + roles.has_role(role) } // storage + #[view(getAllKnownTokens)] #[storage_mapper("tokenWhitelist")] - fn token_whitelist(&self) -> SetMapper; + fn token_whitelist(&self) -> UnorderedSetMapper; + #[view(getAccumulatedTransactionFees)] #[storage_mapper("accumulatedTransactionFees")] fn accumulated_transaction_fees( &self, token_id: &TokenIdentifier, - ) -> SingleValueMapper; + ) -> SingleValueMapper; } diff --git a/common/transaction/Cargo.toml b/common/transaction/Cargo.toml index 98a23dab..a3c78a10 100644 --- a/common/transaction/Cargo.toml +++ b/common/transaction/Cargo.toml @@ -1,17 +1,13 @@ [package] name = "transaction" version = "0.0.0" -authors = [ "dorin-iancu ",] +authors = ["dorin-iancu "] edition = "2018" [lib] path = "src/lib.rs" - [dependencies.eth-address] path = "../eth-address" -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-derive] -version = "0.18" +[dependencies.multiversx-sc] +version = "0.41.3" diff --git a/common/transaction/src/esdt_safe_batch.rs b/common/transaction/src/esdt_safe_batch.rs deleted file mode 100644 index 15ef576f..00000000 --- a/common/transaction/src/esdt_safe_batch.rs +++ /dev/null @@ -1,27 +0,0 @@ -elrond_wasm::derive_imports!(); - -use elrond_wasm::{ - api::BigUintApi, - types::{MultiResult2, MultiResultVec}, - Vec, -}; - -use crate::{Transaction, TxAsMultiResult}; - -#[derive(TypeAbi, TopEncode, TopDecode)] -pub struct EsdtSafeTxBatch { - pub batch_id: u64, - pub transactions: Vec>, -} - -impl Default for EsdtSafeTxBatch { - fn default() -> Self { - EsdtSafeTxBatch { - batch_id: 0, - transactions: Vec::new(), - } - } -} - -pub type EsdtSafeTxBatchSplitInFields = - MultiResult2>>; diff --git a/common/transaction/src/lib.rs b/common/transaction/src/lib.rs index 79a91331..c2050a82 100644 --- a/common/transaction/src/lib.rs +++ b/common/transaction/src/lib.rs @@ -1,30 +1,56 @@ #![no_std] -use elrond_wasm::api::BigUintApi; -use elrond_wasm::types::{Address, MultiResult6, TokenIdentifier}; +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + use eth_address::EthAddress; -pub mod esdt_safe_batch; +pub mod transaction_status; -elrond_wasm::derive_imports!(); +// revert protection +pub const MIN_BLOCKS_FOR_FINALITY: u64 = 10; +pub const TX_MULTIRESULT_NR_FIELDS: usize = 6; pub type TxNonce = u64; pub type BlockNonce = u64; -pub type TxAsMultiResult = - MultiResult6; +pub type SenderAddressRaw = ManagedBuffer; +pub type ReceiverAddressRaw = ManagedBuffer; +pub type TxAsMultiValue = MultiValue6< + BlockNonce, + TxNonce, + SenderAddressRaw, + ReceiverAddressRaw, + TokenIdentifier, + BigUint, +>; +pub type PaymentsVec = ManagedVec>; +pub type TxBatchSplitInFields = MultiValue2>>; + +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, ManagedVecItem, Clone)] +pub struct EthTransaction { + pub from: EthAddress, + pub to: ManagedAddress, + pub token_id: TokenIdentifier, + pub amount: BigUint, + pub tx_nonce: TxNonce, +} -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi)] -pub struct Transaction { +pub type EthTxAsMultiValue = + MultiValue5, ManagedAddress, TokenIdentifier, BigUint, TxNonce>; + +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, ManagedVecItem, Clone)] +pub struct Transaction { pub block_nonce: BlockNonce, pub nonce: TxNonce, - pub from: Address, - pub to: EthAddress, - pub token_identifier: TokenIdentifier, - pub amount: BigUint, + pub from: ManagedBuffer, + pub to: ManagedBuffer, + pub token_identifier: TokenIdentifier, + pub amount: BigUint, + pub is_refund_tx: bool, } -impl From> for Transaction { - fn from(tx_as_multiresult: TxAsMultiResult) -> Self { +impl From> for Transaction { + fn from(tx_as_multiresult: TxAsMultiValue) -> Self { let (block_nonce, nonce, from, to, token_identifier, amount) = tx_as_multiresult.into_tuple(); @@ -35,12 +61,13 @@ impl From> for Transaction Transaction { - pub fn into_multiresult(self) -> TxAsMultiResult { +impl Transaction { + pub fn into_multiresult(self) -> TxAsMultiValue { ( self.block_nonce, self.nonce, @@ -52,12 +79,3 @@ impl Transaction { .into() } } - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, TypeAbi, PartialEq, Clone, Copy)] -pub enum TransactionStatus { - None, - Pending, - InProgress, - Executed, - Rejected, -} diff --git a/common/transaction/src/transaction_status.rs b/common/transaction/src/transaction_status.rs new file mode 100644 index 00000000..467477dc --- /dev/null +++ b/common/transaction/src/transaction_status.rs @@ -0,0 +1,21 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[derive( + TopEncode, + TopDecode, + NestedEncode, + NestedDecode, + TypeAbi, + PartialEq, + Clone, + Copy, + ManagedVecItem, +)] +pub enum TransactionStatus { + None, + Pending, + InProgress, + Executed, + Rejected, +} diff --git a/common/tx-batch-module/Cargo.toml b/common/tx-batch-module/Cargo.toml new file mode 100644 index 00000000..45b50869 --- /dev/null +++ b/common/tx-batch-module/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tx-batch-module" +version = "0.0.0" +authors = ["dorin-iancu "] +edition = "2018" +[dependencies.multiversx-sc] +version = "0.41.3" + +[dependencies.transaction] +path = "../transaction" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/common/tx-batch-module/src/batch_status.rs b/common/tx-batch-module/src/batch_status.rs new file mode 100644 index 00000000..cdf6f902 --- /dev/null +++ b/common/tx-batch-module/src/batch_status.rs @@ -0,0 +1,16 @@ +multiversx_sc::derive_imports!(); + +use multiversx_sc::{api::ManagedTypeApi, types::ManagedVec}; +use transaction::{BlockNonce, TxNonce}; + +#[derive(TopEncode, TopDecode, TypeAbi)] +pub enum BatchStatus { + AlreadyProcessed, + Empty, + PartiallyFull { + end_block_nonce: BlockNonce, + tx_ids: ManagedVec, + }, + Full, + WaitingForSignatures, +} diff --git a/common/tx-batch-module/src/lib.rs b/common/tx-batch-module/src/lib.rs new file mode 100644 index 00000000..545a4cca --- /dev/null +++ b/common/tx-batch-module/src/lib.rs @@ -0,0 +1,275 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub use batch_status::BatchStatus; +use transaction::{Transaction, TxBatchSplitInFields, MIN_BLOCKS_FOR_FINALITY}; +use tx_batch_mapper::TxBatchMapper; + +pub mod batch_status; +pub mod tx_batch_mapper; + +#[multiversx_sc::module] +pub trait TxBatchModule { + // endpoints - owner-only + + #[only_owner] + #[endpoint(setMaxTxBatchSize)] + fn set_max_tx_batch_size(&self, new_max_tx_batch_size: usize) { + require!( + new_max_tx_batch_size > 0, + "Max tx batch size must be more than 0" + ); + + self.max_tx_batch_size().set(new_max_tx_batch_size); + } + + #[only_owner] + #[endpoint(setMaxTxBatchBlockDuration)] + fn set_max_tx_batch_block_duration(&self, new_max_tx_batch_block_duration: u64) { + require!( + new_max_tx_batch_block_duration > 0, + "Max tx batch block duration must be more than 0" + ); + + self.max_tx_batch_block_duration() + .set(new_max_tx_batch_block_duration); + } + + // views + + #[view(getCurrentTxBatch)] + fn get_current_tx_batch(&self) -> OptionalValue> { + let first_batch_id = self.first_batch_id().get(); + let first_batch = self.pending_batches(first_batch_id); + + if self.is_batch_full(&first_batch, first_batch_id, first_batch_id) + && self.is_batch_final(&first_batch) + { + let mut result_vec = MultiValueEncoded::new(); + for tx in first_batch.iter() { + result_vec.push(tx.into_multiresult()); + } + + return OptionalValue::Some((first_batch_id, result_vec).into()); + } + + OptionalValue::None + } + + #[view(getFirstBatchAnyStatus)] + fn get_first_batch_any_status(&self) -> OptionalValue> { + let first_batch_id = self.first_batch_id().get(); + self.get_batch(first_batch_id) + } + + #[view(getBatch)] + fn get_batch(&self, batch_id: u64) -> OptionalValue> { + let tx_batch = self.pending_batches(batch_id); + if tx_batch.is_empty() { + return OptionalValue::None; + } + + let mut result_vec = MultiValueEncoded::new(); + for tx in tx_batch.iter() { + result_vec.push(tx.into_multiresult()); + } + + OptionalValue::Some((batch_id, result_vec).into()) + } + + #[view(getBatchStatus)] + fn get_batch_status(&self, batch_id: u64) -> BatchStatus { + let first_batch_id = self.first_batch_id().get(); + if batch_id < first_batch_id { + return BatchStatus::AlreadyProcessed; + } + + let tx_batch = self.pending_batches(batch_id); + if tx_batch.is_empty() { + return BatchStatus::Empty; + } + + if self.is_batch_full(&tx_batch, batch_id, first_batch_id) { + if batch_id == first_batch_id { + return BatchStatus::WaitingForSignatures; + } else { + return BatchStatus::Full; + } + } + + let mut tx_ids = ManagedVec::new(); + for tx in tx_batch.iter() { + tx_ids.push(tx.nonce); + } + + let max_tx_batch_block_duration = self.max_tx_batch_block_duration().get(); + let first_tx_in_batch_block_nonce = tx_batch.get_first_tx().block_nonce; + + BatchStatus::PartiallyFull { + end_block_nonce: first_tx_in_batch_block_nonce + max_tx_batch_block_duration, + tx_ids, + } + } + + // private + + fn add_to_batch(&self, transaction: Transaction) -> u64 { + let first_batch_id = self.first_batch_id().get(); + let last_batch_id = self.last_batch_id().get(); + let mut last_batch = self.pending_batches(last_batch_id); + + if self.is_batch_full(&last_batch, last_batch_id, first_batch_id) { + let (new_batch_id, _) = self.create_new_batch(transaction); + + new_batch_id + } else { + last_batch.push(transaction); + + last_batch_id + } + } + + // optimized to prevent reading/storing the batch over and over + fn add_multiple_tx_to_batch( + &self, + transactions: &ManagedVec>, + ) -> ManagedVec { + if transactions.is_empty() { + return ManagedVec::new(); + } + + let first_batch_id = self.first_batch_id().get(); + let mut last_batch_id = self.last_batch_id().get(); + let mut last_batch = self.pending_batches(last_batch_id); + let mut batch_ids = ManagedVec::new(); + + for tx in transactions { + if self.is_batch_full(&last_batch, last_batch_id, first_batch_id) { + (last_batch_id, last_batch) = self.create_new_batch(tx); + } else { + last_batch.push(tx); + } + + batch_ids.push(last_batch_id); + } + + batch_ids + } + + fn create_new_batch( + &self, + transaction: Transaction, + ) -> (u64, TxBatchMapper) { + let last_batch_id = self.last_batch_id().get(); + let new_batch_id = last_batch_id + 1; + + let mut new_batch = self.pending_batches(new_batch_id); + new_batch.push(transaction); + + self.last_batch_id().set(new_batch_id); + + (new_batch_id, new_batch) + } + + fn is_batch_full( + &self, + tx_batch: &TxBatchMapper, + batch_id: u64, + first_batch_id: u64, + ) -> bool { + if tx_batch.is_empty() { + return false; + } + + let max_batch_size = self.max_tx_batch_size().get(); + if tx_batch.len() == max_batch_size { + return true; + } + + // if this is not the first batch, we ignore the timestamp checks + // we only check for max len + if batch_id > first_batch_id { + return false; + } + + let current_block_nonce = self.blockchain().get_block_nonce(); + let first_tx_in_batch_block_nonce = tx_batch.get_first_tx().block_nonce; + + // reorg protection + if current_block_nonce < first_tx_in_batch_block_nonce { + return false; + } + + let block_diff = current_block_nonce - first_tx_in_batch_block_nonce; + let max_tx_batch_block_duration = self.max_tx_batch_block_duration().get(); + + block_diff >= max_tx_batch_block_duration + } + + fn is_batch_final(&self, tx_batch: &TxBatchMapper) -> bool { + if tx_batch.is_empty() { + return false; + } + + let last_tx_in_batch = tx_batch.get_last_tx(); + let current_block = self.blockchain().get_block_nonce(); + + // reorg protection + if current_block < last_tx_in_batch.block_nonce { + return false; + } + + let block_diff = current_block - last_tx_in_batch.block_nonce; + + block_diff > MIN_BLOCKS_FOR_FINALITY + } + + fn clear_first_batch(&self, mapper: &mut TxBatchMapper) { + let first_batch_id = self.first_batch_id().get(); + let new_first_batch_id = first_batch_id + 1; + + // for the case when the last existing batch was processed + // otherwise, we'd create a batch with the same ID again + self.last_batch_id().update(|last_batch_id| { + if *last_batch_id == first_batch_id { + *last_batch_id = new_first_batch_id; + } + }); + self.first_batch_id().set(new_first_batch_id); + + mapper.clear(); + } + + fn get_and_save_next_tx_id(&self) -> u64 { + self.last_tx_nonce().update(|last_tx_nonce| { + *last_tx_nonce += 1; + *last_tx_nonce + }) + } + + // storage + + #[view(getFirstBatchId)] + #[storage_mapper("firstBatchId")] + fn first_batch_id(&self) -> SingleValueMapper; + + #[view(getLastBatchId)] + #[storage_mapper("lastBatchId")] + fn last_batch_id(&self) -> SingleValueMapper; + + #[storage_mapper("pendingBatches")] + fn pending_batches(&self, batch_id: u64) -> TxBatchMapper; + + #[storage_mapper("lastTxNonce")] + fn last_tx_nonce(&self) -> SingleValueMapper; + + // configurable + + #[storage_mapper("maxTxBatchSize")] + fn max_tx_batch_size(&self) -> SingleValueMapper; + + #[storage_mapper("maxTxBatchBlockDuration")] + fn max_tx_batch_block_duration(&self) -> SingleValueMapper; +} diff --git a/common/tx-batch-module/src/tx_batch_mapper.rs b/common/tx-batch-module/src/tx_batch_mapper.rs new file mode 100644 index 00000000..589d7c08 --- /dev/null +++ b/common/tx-batch-module/src/tx_batch_mapper.rs @@ -0,0 +1,141 @@ +use multiversx_sc::{ + api::{ErrorApiImpl, StorageMapperApi}, + storage::{ + mappers::{StorageClearable, StorageMapper, VecMapper}, + StorageKey, + }, +}; +use transaction::Transaction; + +static EMPTY_VEC_ERR_MSG: &[u8] = b"Empty vec"; + +pub struct TxBatchMapper +where + SA: StorageMapperApi, +{ + vec_mapper: VecMapper>, + vec_len: usize, + first_tx: Option>, + last_tx: Option>, +} + +impl StorageMapper for TxBatchMapper +where + SA: StorageMapperApi, +{ + fn new(base_key: StorageKey) -> Self { + let vec_mapper = VecMapper::new(base_key); + let vec_len = vec_mapper.len(); + + let (first_tx, last_tx) = if vec_len > 0 { + (Some(vec_mapper.get(1)), Some(vec_mapper.get(vec_len))) + } else { + (None, None) + }; + + TxBatchMapper { + vec_mapper, + vec_len, + first_tx, + last_tx, + } + } +} + +impl StorageClearable for TxBatchMapper +where + SA: StorageMapperApi, +{ + fn clear(&mut self) { + self.vec_mapper.clear(); + } +} + +impl TxBatchMapper +where + SA: StorageMapperApi, +{ + #[inline] + pub fn len(&self) -> usize { + self.vec_len + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn get_first_tx(&self) -> &Transaction { + if self.is_empty() { + SA::error_api_impl().signal_error(EMPTY_VEC_ERR_MSG); + } + + unsafe { self.first_tx.as_ref().unwrap_unchecked() } + } + + pub fn get_last_tx(&self) -> &Transaction { + if self.is_empty() { + SA::error_api_impl().signal_error(EMPTY_VEC_ERR_MSG); + } + + unsafe { self.last_tx.as_ref().unwrap_unchecked() } + } + + pub fn push(&mut self, tx: Transaction) { + if self.is_empty() { + self.first_tx = Some(tx.clone()); + } + + self.vec_mapper.push(&tx); + self.vec_len += 1; + self.last_tx = Some(tx); + } + + /// Provides a forward iterator. + pub fn iter(&self) -> Iter<'_, SA> { + Iter::new(self) + } +} + +pub struct Iter<'a, SA> +where + SA: StorageMapperApi, +{ + index: usize, + mapper: &'a TxBatchMapper, +} + +impl<'a, SA> Iter<'a, SA> +where + SA: StorageMapperApi, +{ + fn new(mapper: &'a TxBatchMapper) -> Iter<'a, SA> { + Iter { index: 1, mapper } + } +} + +impl<'a, SA> Iterator for Iter<'a, SA> +where + SA: StorageMapperApi, +{ + type Item = Transaction; + + #[inline] + fn next(&mut self) -> Option { + let current_index = self.index; + if current_index > self.mapper.len() { + return None; + } + + self.index += 1; + + if current_index == 1 { + return Some(self.mapper.get_first_tx().clone()); + } + if current_index == self.mapper.len() { + return Some(self.mapper.get_last_tx().clone()); + } + + Some(self.mapper.vec_mapper.get_unchecked(current_index)) + } +} diff --git a/docs/gas_costs.md b/docs/gas_costs.md index 7e048a9a..ccd297b5 100644 --- a/docs/gas_costs.md +++ b/docs/gas_costs.md @@ -20,23 +20,23 @@ generic `propose` with 1-2 arguments (like changeQuorum, for example): 40M `proposeEsdtSafeSetCurrentTransactionBatchStatus`: Since arguments are statuses (which are all 1 byte), this function is quite cheap regardless of how big the batch is. 40M should cover all cases, but 50M can be used for extra-safety. -`performAction(Ethereum to Elrond batch)`: If all transactions in the batch use a different token, this will cost around 60M base + 20M * nr_transactions. The reason these are so expensive is because the multiTransfer SC has to perform calls to a price-aggregator to estimate the fees. Either way, there is an optimization in place to cache costs if multiple transfers use the same token. Hence, most of the time, the cost per transaction won't be as high. Even so, this estimate should cover for most, if not all, cases. +`performAction(Ethereum to MultiversX batch)`: If all transactions in the batch use a different token, this will cost around 60M base + 20M * nr_transactions. The reason these are so expensive is because the multiTransfer SC has to perform calls to a price-aggregator to estimate the fees. Either way, there is an optimization in place to cache costs if multiple transfers use the same token. Hence, most of the time, the cost per transaction won't be as high. Even so, this estimate should cover for most, if not all, cases. `fetchNextTransactionBatch`: Base cost seems to be around 40M, with ~10M extra per transaction fetched. This is a bit tricky to estimate, since the relayer has no way of knowing how many transactions will be in the batch before actually fetching it. Having said that, the max number of transactions per batch is currently 10, so around 40M + 10 * 20M = 240M should cover for most cases, so let's say 250M hard-coded gas limit. There is no risk of assigning too much gas here, as even with no transactions, 250M gas limit with actual 40M gas used does not exceed the 10x limitation. -`performAction(Elrond to Ethereum batch, i.e. SetStatus)`: Same ~35M base cost, with approximately 10M more per transaction. Cheaper than the multiTransfer, as fees are calculated when the user creates a deposit. Still, the contract requires minting tokens for each transaction, so to be safe, I suggest 35M + 15M * nr_transactions. +`performAction(MultiversX to Ethereum batch, i.e. SetStatus)`: Same ~35M base cost, with approximately 10M more per transaction. Cheaper than the multiTransfer, as fees are calculated when the user creates a deposit. Still, the contract requires minting tokens for each transaction, so to be safe, I suggest 35M + 15M * nr_transactions. ## Summary -| Action | Recommended Gas limit | -| ----------- | ----------- | -| EsdtSafe CreateTransaction | 75M | -| stake/unstake | 35M | -| simple propose | 40M | -| sign | 35M | -| proposeMultiTransferEsdtBatch | 35M + 15M * nr_transactions_in_batch | -| proposeEsdtSafeSetCurrentTransactionBatchStatus | 50M | -| performAction(Ethereum to Elrond batch) | 60M + 20M * nr_transactions | -| fetchNextTransactionBatch | 250 M | -| performAction(Elrond to Ethereum batch) | 35M + 15M * nr_transactions | +| Action | Recommended Gas limit | +| ----------------------------------------------- | ------------------------------------ | +| EsdtSafe CreateTransaction | 75M | +| stake/unstake | 35M | +| simple propose | 40M | +| sign | 35M | +| proposeMultiTransferEsdtBatch | 35M + 15M * nr_transactions_in_batch | +| proposeEsdtSafeSetCurrentTransactionBatchStatus | 50M | +| performAction(Ethereum to MultiversX batch) | 60M + 20M * nr_transactions | +| fetchNextTransactionBatch | 250 M | +| performAction(MultiversX to Ethereum batch) | 35M + 15M * nr_transactions | diff --git a/docs/relayer.md b/docs/relayer.md index d1e9a6f5..66e2fd82 100644 --- a/docs/relayer.md +++ b/docs/relayer.md @@ -6,7 +6,7 @@ In this document, you will find all the possible actions, scenarios and what is We're assuming all the smart contracts are already setup as described [here](setup.md). -For details about multisig smart contract design, check here: https://github.com/ElrondNetwork/elrond-wasm-rs/blob/master/contracts/examples/multisig/README.md. +For details about multisig smart contract design, check here: https://github.com/multiversx/mx-sdk-rs/blob/master/contracts/examples/multisig/README.md. If you're interested in a more abstract explanation, check the [readme](../README.md). @@ -16,13 +16,13 @@ As a relayer, you will interact directly only with the multisig-staking smart co The first and most important prerequisite is being recognized as a board member by the multisig smart contract. Only owner may add board members. -But that is only the first step. You will not be able to perform any board-member exclusive action until you've staked a certain amount of eGLD in the multisig contract. Once staked, you cannot unstake until your role has been revoked. This can also happen in two ways: +But that is only the first step. You will not be able to perform any board-member exclusive action until you've staked a certain amount of EGLD in the multisig contract. Once staked, you cannot unstake until your role has been revoked. This can also happen in two ways: - The owner removes you from the board member list, in which case you will then be able to unstake your full stake - The owner "slashes" your stake, you lose your board member role and part of your stake and can unstake the rest. Stake "slashing" will only happen if you're actively being malicious. So play nice! -## Elrond -> Ethereum transaction +## MultiversX -> Ethereum transaction For this kind of transaction, we'll be using the `EsdtSafe` contract. The user will have to submit the transaction through the `EsdtSafe` SC. @@ -34,15 +34,15 @@ The transaction data (block nonce, transaction nonce, sender, receiver, token_id Note: Transaction nonce is not the account nonce of the sender account, and rather an internal nonce used by the `EsdtSafe` smart contract. It can safely be ignored by relayers. -Once the transaction has been executed, a `SetCurrentTransactionBatchStatus` action will be proposed (through the `proposeEsdtSafeSetCurrentTransactionBatchStatus` endpoint), which, for each transaction in the batch, will set the status to "Executed" if it was executed successfully, or "Rejected" if it failed for any reason. Success will burn the tokens on the Elrond side, while a failure will return the tokens to the user. Endpoint signature is as follows: +Once the transaction has been executed, a `SetCurrentTransactionBatchStatus` action will be proposed (through the `proposeEsdtSafeSetCurrentTransactionBatchStatus` endpoint), which, for each transaction in the batch, will set the status to "Executed" if it was executed successfully, or "Rejected" if it failed for any reason. Success will burn the tokens on the MultiversX side, while a failure will return the tokens to the user. Endpoint signature is as follows: ``` #[endpoint(proposeEsdtSafeSetCurrentTransactionBatchStatus)] fn propose_esdt_safe_set_current_transaction_batch_status( &self, esdt_safe_batch_id: usize, - #[var_args] tx_batch_status: VarArgs, -) -> SCResult + tx_batch_status: VarArgs, +) -> usize ``` `esdt_safe_batch_id` is the ID of the current batch. Each batch will have an assigned ID to make sure relayers always sign the expected batch. @@ -70,13 +70,13 @@ The endpoint returns the assigned Action ID. Other relayers can get this ID by u fn get_action_id_for_set_current_transaction_batch_status( &self, esdt_safe_batch_id: usize, - #[var_args] expected_tx_batch_status: VarArgs, + expected_tx_batch_status: VarArgs, ) -> usize ``` -And that's about it for Elrond -> Ethereum transactions. The only thing you'll have to figure out yourself is how to decide which relayer executes the transaction and the steps required on the Ethereum side. +And that's about it for MutiversX -> Ethereum transactions. The only thing you'll have to figure out yourself is how to decide which relayer executes the transaction and the steps required on the Ethereum side. -## Ethereum -> Elrond transaction +## Ethereum -> MutiversX transaction In this case the process is very simple, as most of the processing will happen on the Ethereum side. Once all that is complete, all the relayers have to do is propose a `BatchTransferEsdtToken` to the `MultiTransferEsdt` SC, with the appropriate receiver, token identifier and amount. When this action is executed, the user will receive their tokens. @@ -87,8 +87,8 @@ This is done through the `proposeMultiTransferEsdtBatch` endpoint: fn propose_multi_transfer_esdt_batch( &self, batch_id: u64, - #[var_args] transfers: MultiArgVec>, -) -> SCResult { + transfers: MultiValueVec>, +) -> usize { ``` `batch_id` is an id provided by the relayers. It is used internally to know if an action was proposed for that specific batch. @@ -102,43 +102,43 @@ The endpoint returns the assigned Action ID. Other relayers can get this ID by u fn get_action_id_for_transfer_batch( &self, batch_id: u64, - #[var_args] transfers: MultiArgVec>, + transfers: MultiValueVec>, ) -> usize ``` ## Miscellaneous view functions ``` -/// Mapping between ERC20 Ethereum address and Elrond ESDT Token Identifiers +/// Mapping between ERC20 Ethereum address and MutiversX ESDT Token Identifiers #[view(getErc20AddressForTokenId)] #[storage_mapper("erc20AddressForTokenId")] fn erc20_address_for_token_id( &self, token_id: &TokenIdentifier, -) -> SingleValueMapper; +) -> SingleValueMapper; #[view(getTokenIdForErc20Address)] #[storage_mapper("tokenIdForErc20Address")] fn token_id_for_erc20_address( &self, erc20_address: &EthAddress, -) -> SingleValueMapper; +) -> SingleValueMapper; ``` -Returns the mapping between Elrond ESDT token identifier and Ethereum ERC20 contract address. Note that if the mapping returns "EGLD", that means "empty" (Internally, the TokenIdentifier is serialized as "empty" for "EGLD", and as such, "empty" storage is deserialized as "EGLD"). +Returns the mapping between MutiversX ESDT token identifier and Ethereum ERC20 contract address. Note that if the mapping returns "EGLD", that means "empty" (Internally, the TokenIdentifier is serialized as "empty" for "EGLD", and as such, "empty" storage is deserialized as "EGLD"). ``` #[view(getCurrentTxBatch)] -fn get_current_tx_batch(&self) -> EsdtSafeTxBatchSplitInFields +fn get_current_tx_batch(&self) -> EsdtSafeTxBatchSplitInFields ``` Returns the current transaction batch, each field of each transaction separated by '@'. The result type is defined as follows: ``` -pub type EsdtSafeTxBatchSplitInFields = MultiResult2>>; +pub type EsdtSafeTxBatchSplitInFields = MultiValue2>>; -pub type TxAsMultiResult = -MultiResult6; +pub type TxAsMultiValue = +MultiValue6; ``` The first result is the batch ID, followed by pairs of (block nonce, tx nonce, sender address, receiver address, token type, amount), each as a separate result, i.e. delimited by `@`. diff --git a/docs/setup.md b/docs/setup.md index 14469220..01186a22 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -10,7 +10,7 @@ IMPORTANT: Ticker should always be chosen so that the aggregator has a mapping f You can find more about how to issue an ESDT token here: https://docs.elrond.com/developers/esdt-tokens/#issuance-of-fungible-esdt-tokens -Next, we're going to setup the main "controller" contract, which will be a multisig-style SC. You can find more details about this type of smart contract here: https://github.com/ElrondNetwork/elrond-wasm-rs/blob/master/contracts/examples/multisig/README.md +Next, we're going to setup the main "controller" contract, which will be a multisig-style SC. You can find more details about this type of smart contract here: https://github.com/multiversx/mx-sdk-rs/blob/master/contracts/examples/multisig/README.md Basically, we will have a certain number of board members (in this case we will call them "relayers") which will validate transactions and take the appropriate actions. As this is a multisig contract, at least a certain number of members must agree to the action, otherwise it cannot be performed. @@ -20,20 +20,20 @@ Once the multisig contract is deployed and setup properly, the owner must call t #[endpoint(deployChildContracts)] fn deploy_child_contracts( &self, - egld_esdt_swap_code: BoxedBytes, - multi_transfer_esdt_code: BoxedBytes, - esdt_safe_code: BoxedBytes, - price_aggregator_contract_address: Address, - esdt_safe_eth_tx_gas_limit: Self::BigUint, - multi_transfer_esdt_eth_tx_gas_limit: Self::BigUint, + egld_esdt_swap_code: ManagedBuffer, + multi_transfer_esdt_code: ManagedBuffer, + esdt_safe_code: ManagedBuffer, + price_aggregator_contract_address: ManagedAddress, + esdt_safe_eth_tx_gas_limit: BigUint, + multi_transfer_esdt_eth_tx_gas_limit: BigUint, wrapped_egld_token_id: TokenIdentifier, - #[var_args] token_whitelist: VarArgs, + token_whitelist: VarArgs, ) ``` The `_code` arguments are the compiled wasm bytecode of the respective contracts. `price_aggregator_contract_address` is the aggregator described in the intro (has to be deployed already). -`esdt_safe_eth_tx_gas_limit` and `multi_transfer_esdt_eth_tx_gas_limit` are gas limits used for Elrond -> Ethereum tx, and Ethereum -> Elrond tx respectively. This is the gas limit used for processing on the Ethereum side (briding over to Elrond or from Elrond). This cost is used to calculate the fees taken from the bridged token, to be then used as payment/incentive for the relayers. +`esdt_safe_eth_tx_gas_limit` and `multi_transfer_esdt_eth_tx_gas_limit` are gas limits used for MultiversX -> Ethereum tx, and Ethereum -> MultiversX tx respectively. This is the gas limit used for processing on the Ethereum side (briding over to MultiversX or from MultiversX). This cost is used to calculate the fees taken from the bridged token, to be then used as payment/incentive for the relayers. `wrapped_egld_token_id` is the token identifier of the previously issued "WrappedEgld" token (Note: identifier format is ticker + '-' + 6 random characters). For WrappedEgld, it might look something like "EGLD-123456". @@ -43,7 +43,7 @@ Once those are setup, the individual contracts will need a bit more setup themse # EgldEsdtSwap -Requires having local MINT and local BURN roles for the WrappedEgld token. More info about how to set local roles can be found here: https://docs.elrond.com/developers/esdt-tokens/#setting-and-unsetting-special-roles +Requires having local MINT and local BURN roles for the WrappedEgld token. More info about how to set local roles can be found here: https://docs.multiversx.com/tokens/esdt-tokens/#setting-and-unsetting-special-roles # MultiTransferEsdt @@ -55,7 +55,7 @@ Requires local BURN role set for every token added to the whitelist. # Erc20 to TokenIdentifier mapping -The relayers will need to know the mapping between Erc20 tokens on Ethereum and their respective representation as ESDT on Elrond. This mapping can be added by using the following function: +The relayers will need to know the mapping between Erc20 tokens on Ethereum and their respective representation as ESDT on MultiversX. This mapping can be added by using the following function: ``` #[endpoint(addMapping)] diff --git a/egld-esdt-swap/.gitignore b/egld-esdt-swap/.gitignore deleted file mode 100644 index bdcc2329..00000000 --- a/egld-esdt-swap/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -*/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# The erdpy output -output diff --git a/egld-esdt-swap/Cargo.toml b/egld-esdt-swap/Cargo.toml deleted file mode 100644 index 2dde6208..00000000 --- a/egld-esdt-swap/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "egld-esdt-swap" -version = "0.0.0" -authors = [ "you",] -edition = "2018" -publish = false - -[lib] -path = "src/lib.rs" - -[features] -wasm-output-mode = [ "elrond-wasm-node",] - -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-derive] -version = "0.18" - -[dependencies.elrond-wasm-node] -version = "0.18" -optional = true - -[dev-dependencies.elrond-wasm-debug] -version = "0.18" diff --git a/egld-esdt-swap/abi/Cargo.toml b/egld-esdt-swap/abi/Cargo.toml deleted file mode 100644 index 6d265941..00000000 --- a/egld-esdt-swap/abi/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "egld-esdt-swap-abi" -version = "0.0.0" -authors = [ "you",] -edition = "2018" -publish = false - -[dev-dependencies] - -[dependencies.egld-esdt-swap] -path = ".." - -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-debug] -version = "0.18" diff --git a/egld-esdt-swap/abi/src/main.rs b/egld-esdt-swap/abi/src/main.rs deleted file mode 100644 index a67116da..00000000 --- a/egld-esdt-swap/abi/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - elrond_wasm_debug::abi_json::print_abi::(); -} diff --git a/egld-esdt-swap/interaction/snippets.sh b/egld-esdt-swap/interaction/snippets.sh deleted file mode 100644 index d5c981c5..00000000 --- a/egld-esdt-swap/interaction/snippets.sh +++ /dev/null @@ -1,89 +0,0 @@ -ALICE="/home/elrond/elrond-sdk/erdpy/testnet/wallets/users/alice.pem" -BOB="/home/elrond/elrond-sdk/erdpy/testnet/wallets/users/bob.pem" -ADDRESS=$(erdpy data load --key=address-testnet-egld-esdt-swap) -DEPLOY_TRANSACTION=$(erdpy data load --key=deployTransaction-testnet) -PROXY=https://testnet-gateway.elrond.com -CHAIN_ID=T - -ESDT_SYSTEM_SC_ADDRESS=erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u - -deploy() { - ###################################################################### - ############################ Update after issue ###################### - ###################################################################### - local WRAPPED_EGLD_TOKEN_ID=0x - - erdpy --verbose contract deploy --project=${PROJECT} --recall-nonce --pem=${ALICE} \ - --gas-limit=100000000 \ - --arguments ${WRAPPED_EGLD_TOKEN_ID} \ - --send --outfile="deploy-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return - - TRANSACTION=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['hash']") - ADDRESS=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['address']") - - erdpy data store --key=address-testnet --value=${ADDRESS} - erdpy data store --key=deployTransaction-testnet-egld-esdt-swap --value=${TRANSACTION} - - echo "" - echo "Smart contract address: ${ADDRESS}" -} - -upgrade() { - erdpy --verbose contract upgrade ${ADDRESS} --project=${PROJECT} --recall-nonce --pem=${ALICE} \ - --arguments ${WRAPPED_EGLD_TOKEN_ID} --gas-limit=100000000 --outfile="upgrade.json" \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} || return -} - -issueWrappedEgld() { - local TOKEN_DISPLAY_NAME=0x5772617070656445676c64 # "WrappedEgld" - local TOKEN_TICKER=0x5745474c44 # "WEGLD" - local INITIAL_SUPPLY=0x01 # 1 - local NR_DECIMALS=0x12 # 18 - local CAN_ADD_SPECIAL_ROLES=0x63616e4164645370656369616c526f6c6573 # "canAddSpecialRoles" - local TRUE=0x74727565 # "true" - - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --value=5000000000000000000 --function="issue" \ - --arguments ${TOKEN_DISPLAY_NAME} ${TOKEN_TICKER} ${INITIAL_SUPPLY} ${NR_DECIMALS} ${CAN_ADD_SPECIAL_ROLES} ${TRUE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -setLocalRoles() { - local LOCAL_MINT_ROLE=0x45534454526f6c654c6f63616c4d696e74 # "ESDTRoleLocalMint" - local LOCAL_BURN_ROLE=0x45534454526f6c654c6f63616c4275726e # "ESDTRoleLocalBurn" - local ADDRESS_HEX = $(erdpy wallet bech32 --decode ${ADDRESS}) - - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --function="setSpecialRole" \ - --arguments ${WRAPPED_EGLD_TOKEN_ID} ${ADDRESS_HEX} ${LOCAL_MINT_ROLE} ${LOCAL_BURN_ROLE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -wrapEgldBob() { - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=10000000 --value=1000 --function="wrapEgld" \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -unwrapEgldBob() { - local UNWRAP_EGLD_ENDPOINT=0x756e7772617045676c64 # "unwrapEgld" - local UNWRAP_AMOUNT=0x05 - - getWrappedEgldTokenIdentifier - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=10000000 --function="ESDTTransfer" \ - --arguments ${TOKEN_IDENTIFIER} ${UNWRAP_AMOUNT} ${UNWRAP_EGLD_ENDPOINT} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -# views - -getWrappedEgldTokenIdentifier() { - local QUERY_OUTPUT=$(erdpy --verbose contract query ${ADDRESS} --function="getWrappedEgldTokenId" --proxy=${PROXY}) - TOKEN_IDENTIFIER=0x$(jq -r '.[0] .hex' <<< "${QUERY_OUTPUT}") - echo "Wrapped eGLD token identifier: ${TOKEN_IDENTIFIER}" -} - -getLockedEgldBalance() { - erdpy --verbose contract query ${ADDRESS} --function="getLockedEgldBalance" --proxy=${PROXY} -} diff --git a/egld-esdt-swap/mandos/unwrap_egld.scen.json b/egld-esdt-swap/mandos/unwrap_egld.scen.json deleted file mode 100644 index 5aad864a..00000000 --- a/egld-esdt-swap/mandos/unwrap_egld.scen.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "unwrap egld", - "steps": [ - { - "step": "externalSteps", - "path": "wrap_egld.scen.json" - }, - { - "step": "scCall", - "txId": "unwrap-egld", - "tx": { - "from": "address:user", - "to": "sc:egld_esdt_swap", - "value": "0", - "esdt": { - "tokenIdentifier": "str:EGLD-abcdef", - "value": "300" - }, - "function": "unwrapEgld", - "arguments": [], - "gasLimit": "8,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:user": { - "nonce": "2", - "balance": "800", - "esdt": { - "str:EGLD-abcdef": "200" - }, - "storage": {} - }, - "sc:egld_esdt_swap": { - "nonce": "0", - "balance": "200", - "esdt": { - "str:EGLD-abcdef": { - "balance": "1", - "roles": [ - "ESDTRoleLocalMint", - "ESDTRoleLocalBurn" - ] - } - }, - "storage": { - "str:wrappedEgldTokenId": "str:EGLD-abcdef" - }, - "code": "file:../output/egld-esdt-swap.wasm" - } - } - } - ] -} diff --git a/egld-esdt-swap/mandos/wrap_egld.scen.json b/egld-esdt-swap/mandos/wrap_egld.scen.json deleted file mode 100644 index 35f2bc93..00000000 --- a/egld-esdt-swap/mandos/wrap_egld.scen.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "name": "wrap egld", - "steps": [ - { - "step": "setState", - "accounts": { - "address:user": { - "nonce": "0", - "balance": "1000", - "storage": {} - }, - "sc:egld_esdt_swap": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:EGLD-abcdef": { - "balance": "1", - "roles": [ - "ESDTRoleLocalMint", - "ESDTRoleLocalBurn" - ] - } - }, - "storage": { - "str:wrappedEgldTokenId": "str:EGLD-abcdef" - }, - "code": "file:../output/egld-esdt-swap.wasm" - } - } - }, - { - "step": "scCall", - "txId": "wrap-egld", - "tx": { - "from": "address:user", - "to": "sc:egld_esdt_swap", - "value": "500", - "function": "wrapEgld", - "arguments": [], - "gasLimit": "7,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:user": { - "nonce": "1", - "balance": "500", - "esdt": { - "str:EGLD-abcdef": "500" - }, - "storage": {} - }, - "sc:egld_esdt_swap": { - "nonce": "0", - "balance": "500", - "esdt": { - "str:EGLD-abcdef": { - "balance": "1", - "roles": [ - "ESDTRoleLocalMint", - "ESDTRoleLocalBurn" - ] - } - }, - "storage": { - "str:wrappedEgldTokenId": "str:EGLD-abcdef" - }, - "code": "file:../output/egld-esdt-swap.wasm" - } - } - } - ] -} diff --git a/egld-esdt-swap/src/lib.rs b/egld-esdt-swap/src/lib.rs deleted file mode 100644 index 8d86e105..00000000 --- a/egld-esdt-swap/src/lib.rs +++ /dev/null @@ -1,207 +0,0 @@ -#![no_std] - -elrond_wasm::imports!(); - -const GAS_FOR_CALLBACK: u64 = 500_000u64; - -#[elrond_wasm_derive::contract] -pub trait EgldEsdtSwap { - #[init] - fn init(&self, wrapped_egld_token_id: TokenIdentifier) -> SCResult<()> { - require!( - wrapped_egld_token_id.is_valid_esdt_identifier(), - "Invalid token id" - ); - - self.wrapped_egld_token_id().set(&wrapped_egld_token_id); - - Ok(()) - } - - // endpoints - - #[payable("EGLD")] - #[endpoint(wrapEgld)] - fn wrap_egld( - &self, - #[payment] payment: Self::BigUint, - #[var_args] accept_funds_endpoint_name: OptionalArg, - ) -> SCResult>> { - require!(payment > 0, "Payment must be more than 0"); - - let wrapped_egld_token_id = self.wrapped_egld_token_id().get(); - - self.require_local_role_set(&wrapped_egld_token_id, &EsdtLocalRole::Mint)?; - self.send() - .esdt_local_mint(&wrapped_egld_token_id, 0, &payment); - - let caller = self.blockchain().get_caller(); - let function = accept_funds_endpoint_name - .into_option() - .unwrap_or_else(BoxedBytes::empty); - - if self.needs_execution(&caller, &function) { - self.require_has_enough_gas_for_transfer_via_async()?; - Ok(OptionalResult::Some(self.transfer_via_async( - caller, - wrapped_egld_token_id, - payment, - function, - ))) - } else { - self.send() - .direct(&caller, &wrapped_egld_token_id, 0, &payment, b"wrapping"); - Ok(OptionalResult::None) - } - } - - #[payable("*")] - #[endpoint(unwrapEgld)] - fn unwrap_egld( - &self, - #[payment] payment: Self::BigUint, - #[payment_token] token_id: TokenIdentifier, - #[var_args] accept_funds_endpoint_name: OptionalArg, - ) -> SCResult>> { - let wrapped_egld_token_id = self.wrapped_egld_token_id().get(); - - require!(token_id.is_esdt(), "Only ESDT tokens accepted"); - require!(token_id == wrapped_egld_token_id, "Wrong esdt token"); - require!(payment > 0, "Must pay more than 0 tokens!"); - // this should never happen, but we'll check anyway - require!( - payment <= self.get_locked_egld_balance(), - "Contract does not have enough funds" - ); - - self.require_local_role_set(&wrapped_egld_token_id, &EsdtLocalRole::Burn)?; - self.send() - .esdt_local_burn(&wrapped_egld_token_id, 0, &payment); - - // 1 wrapped eGLD = 1 eGLD, so we pay back the same amount - let caller = self.blockchain().get_caller(); - let function = accept_funds_endpoint_name - .into_option() - .unwrap_or_else(BoxedBytes::empty); - let egld_token_id = TokenIdentifier::egld(); - - if self.needs_execution(&caller, &function) { - self.require_has_enough_gas_for_transfer_via_async()?; - Ok(OptionalResult::Some(self.transfer_via_async( - caller, - egld_token_id, - payment, - function, - ))) - } else { - self.send() - .direct(&caller, &egld_token_id, 0, &payment, b"unwrapping"); - Ok(OptionalResult::None) - } - } - - // views - - #[view(getLockedEgldBalance)] - fn get_locked_egld_balance(&self) -> Self::BigUint { - self.blockchain() - .get_sc_balance(&TokenIdentifier::egld(), 0) - } - - // private - - fn needs_execution(&self, caller: &Address, function: &BoxedBytes) -> bool { - self.blockchain().is_smart_contract(caller) && !function.is_empty() - } - - fn require_has_enough_gas_for_transfer_via_async(&self) -> SCResult<()> { - let gas_needed_for_callback = GAS_FOR_CALLBACK; - let gas_leftover_required = gas_needed_for_callback; - let total_gas_needed = gas_needed_for_callback + gas_leftover_required; - - require!( - self.blockchain().get_gas_left() > total_gas_needed, - "Not enough gas" - ); - Ok(()) - } - - fn transfer_via_async( - &self, - caller: Address, - token_id: TokenIdentifier, - amount: Self::BigUint, - function: BoxedBytes, - ) -> AsyncCall { - let gas_limit = self.blockchain().get_gas_left() - GAS_FOR_CALLBACK; - - let contract_call: ContractCall = - ContractCall::new(self.send(), caller.clone(), function) - .with_token_transfer(token_id, amount) - .with_gas_limit(gas_limit); - - contract_call - .async_call() - .with_callback(self.callbacks().transfer_via_async_callback(caller)) - } - - fn require_local_role_set( - &self, - token_id: &TokenIdentifier, - role: &EsdtLocalRole, - ) -> SCResult<()> { - let roles = self.blockchain().get_esdt_local_roles(token_id); - require!(roles.contains(role), "Must set local role first"); - - Ok(()) - } - - fn revert_operation_and_send( - &self, - address: Address, - returned_token_id: TokenIdentifier, - returned_amount: Self::BigUint, - ) { - let egld_token_id = TokenIdentifier::egld(); - let wrapped_egld_token_id = self.wrapped_egld_token_id().get(); - - if returned_token_id == egld_token_id { - self.send() - .esdt_local_mint(&wrapped_egld_token_id, 0, &returned_amount); - self.send() - .direct(&address, &wrapped_egld_token_id, 0, &returned_amount, &[]); - } else { - self.send() - .esdt_local_burn(&wrapped_egld_token_id, 0, &returned_amount); - self.send() - .direct(&address, &egld_token_id, 0, &returned_amount, &[]); - } - } - - // callbacks - - #[callback] - fn transfer_via_async_callback( - &self, - caller: Address, - #[call_result] result: AsyncCallResult<()>, - ) { - match result { - AsyncCallResult::Ok(_) => {} - AsyncCallResult::Err(_) => { - let (returned_tokens, token_identifier) = self.call_value().payment_token_pair(); - if returned_tokens != 0 { - self.revert_operation_and_send(caller, token_identifier, returned_tokens); - } - } - } - } - - // storage - - // 1 eGLD = 1 wrapped eGLD, and they are interchangeable through this contract - - #[view(getWrappedEgldTokenId)] - #[storage_mapper("wrappedEgldTokenId")] - fn wrapped_egld_token_id(&self) -> SingleValueMapper; -} diff --git a/egld-esdt-swap/testnet.toml b/egld-esdt-swap/testnet.toml deleted file mode 100644 index 3020c585..00000000 --- a/egld-esdt-swap/testnet.toml +++ /dev/null @@ -1,2 +0,0 @@ -[folders] -elrond_go = "/home/elrond/elrond-go" \ No newline at end of file diff --git a/egld-esdt-swap/tests/mandos_go_test.rs b/egld-esdt-swap/tests/mandos_go_test.rs deleted file mode 100644 index 4451befc..00000000 --- a/egld-esdt-swap/tests/mandos_go_test.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[test] -fn unwrap_egld_go() { - elrond_wasm_debug::mandos_go("mandos/unwrap_egld.scen.json"); -} - -#[test] -fn wrap_egld_go() { - elrond_wasm_debug::mandos_go("mandos/wrap_egld.scen.json"); -} diff --git a/egld-esdt-swap/wasm/Cargo.toml b/egld-esdt-swap/wasm/Cargo.toml deleted file mode 100644 index 92a55119..00000000 --- a/egld-esdt-swap/wasm/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "egld-esdt-swap-wasm" -version = "0.0.0" -authors = [ "you",] -edition = "2018" -publish = false - -[lib] -crate-type = [ "cdylib",] - -[workspace] -members = [ ".",] - -[dev-dependencies] - -[profile.release] -codegen-units = 1 -opt-level = "z" -lto = true -debug = false -panic = "abort" - -[dependencies.egld-esdt-swap] -features = [ "wasm-output-mode",] -default-features = false -path = ".." - -[dependencies.elrond-wasm-output] -version = "0.18" -features = [ "wasm-output-mode",] diff --git a/egld-esdt-swap/wasm/src/lib.rs b/egld-esdt-swap/wasm/src/lib.rs deleted file mode 100644 index e497b385..00000000 --- a/egld-esdt-swap/wasm/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![no_std] - -pub use egld_esdt_swap::*; -pub use elrond_wasm_output::*; diff --git a/elrond.workspace.json b/elrond.workspace.json deleted file mode 100644 index 9e26dfee..00000000 --- a/elrond.workspace.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/esdt-safe/.gitignore b/esdt-safe/.gitignore index bdcc2329..f00d026a 100644 --- a/esdt-safe/.gitignore +++ b/esdt-safe/.gitignore @@ -7,5 +7,5 @@ # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock -# The erdpy output +# The mxpy output output diff --git a/esdt-safe/Cargo.toml b/esdt-safe/Cargo.toml index 0390e883..d3c4062d 100644 --- a/esdt-safe/Cargo.toml +++ b/esdt-safe/Cargo.toml @@ -1,20 +1,12 @@ [package] name = "esdt-safe" version = "0.0.0" -authors = [ "you",] +authors = ["you"] edition = "2018" publish = false [lib] path = "src/lib.rs" - -[features] -wasm-output-mode = [ - "elrond-wasm-node", - "fee-estimator-module/wasm-output-mode", - "token-module/wasm-output-mode", -] - [dependencies.transaction] path = "../common/transaction" @@ -27,15 +19,16 @@ path = "../common/fee-estimator-module" [dependencies.token-module] path = "../common/token-module" -[dependencies.elrond-wasm] -version = "0.18" +[dependencies.tx-batch-module] +path = "../common/tx-batch-module" -[dependencies.elrond-wasm-derive] -version = "0.18" +[dependencies.max-bridged-amount-module] +path = "../common/max-bridged-amount-module" -[dependencies.elrond-wasm-node] -version = "0.18" -optional = true +[dependencies.multiversx-sc] +version = "0.41.3" -[dev-dependencies.elrond-wasm-debug] -version = "0.18" +[dependencies.multiversx-sc-modules] +version = "0.41.3" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/esdt-safe/abi/Cargo.toml b/esdt-safe/abi/Cargo.toml deleted file mode 100644 index d4abef5f..00000000 --- a/esdt-safe/abi/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "esdt-safe-abi" -version = "0.0.0" -authors = [ "you",] -edition = "2018" -publish = false - -[dev-dependencies] - -[dependencies.esdt-safe] -path = ".." - -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-debug] -version = "0.18" diff --git a/esdt-safe/abi/src/main.rs b/esdt-safe/abi/src/main.rs deleted file mode 100644 index 8516e430..00000000 --- a/esdt-safe/abi/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - elrond_wasm_debug::abi_json::print_abi::(); -} diff --git a/esdt-safe/interaction/snippets.sh b/esdt-safe/interaction/snippets.sh index 23313c87..8a46ca3a 100644 --- a/esdt-safe/interaction/snippets.sh +++ b/esdt-safe/interaction/snippets.sh @@ -1,7 +1,7 @@ -ALICE="/home/elrond/elrond-sdk/erdpy/testnet/wallets/users/alice.pem" -BOB="/home/elrond/elrond-sdk/erdpy/testnet/wallets/users/bob.pem" -ADDRESS=$(erdpy data load --key=address-testnet-esdt-safe) -DEPLOY_TRANSACTION=$(erdpy data load --key=deployTransaction-testnet) +ALICE="/home/elrond/elrond-sdk/mxpy/testnet/wallets/users/alice.pem" +BOB="/home/elrond/elrond-sdk/mxpy/testnet/wallets/users/bob.pem" +ADDRESS=$(mxpy data load --key=address-testnet-esdt-safe) +DEPLOY_TRANSACTION=$(mxpy data load --key=deployTransaction-testnet) PROXY=https://testnet-gateway.elrond.com CHAIN_ID=T @@ -26,23 +26,23 @@ deploy() { ####################################################################### local ETHEREUM_FEE_PREPAY_SC_ADDRESS=0x - erdpy --verbose contract deploy --project=${PROJECT} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract deploy --project=${PROJECT} --recall-nonce --pem=${ALICE} \ --gas-limit=100000000 \ --arguments ${ETHEREUM_FEE_PREPAY_SC_ADDRESS} ${WRAPPED_EGLD_TOKEN_ID} ${WRAPPED_ETH_TOKEN_ID} \ --send --outfile="deploy-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return - TRANSACTION=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['hash']") - ADDRESS=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['address']") + TRANSACTION=$(mxpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['hash']") + ADDRESS=$(mxpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['address']") - erdpy data store --key=address-testnet-esdt-safe --value=${ADDRESS} - erdpy data store --key=deployTransaction-testnet --value=${TRANSACTION} + mxpy data store --key=address-testnet-esdt-safe --value=${ADDRESS} + mxpy data store --key=deployTransaction-testnet --value=${TRANSACTION} echo "" echo "Smart contract address: ${ADDRESS}" } upgrade() { - erdpy --verbose contract upgrade ${ADDRESS} --project=${PROJECT} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract upgrade ${ADDRESS} --project=${PROJECT} --recall-nonce --pem=${ALICE} \ --gas-limit=100000000 --send --outfile="upgrade.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return } @@ -54,7 +54,7 @@ issueWrappedEth() { local CAN_ADD_SPECIAL_ROLES=0x63616e4164645370656369616c526f6c6573 # "canAddSpecialRoles" local TRUE=0x74727565 # "true" - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=60000000 --value=5000000000000000000 --function="issue" \ --arguments ${TOKEN_DISPLAY_NAME} ${TOKEN_TICKER} ${INITIAL_SUPPLY} ${NR_DECIMALS} ${CAN_ADD_SPECIAL_ROLES} ${TRUE} \ --send --proxy=${PROXY} --chain=${CHAIN_ID} @@ -62,9 +62,9 @@ issueWrappedEth() { setLocalRolesWrappedEgld() { local LOCAL_BURN_ROLE=0x45534454526f6c654c6f63616c4275726e # "ESDTRoleLocalBurn" - local ADDRESS_HEX = $(erdpy wallet bech32 --decode ${ADDRESS}) + local ADDRESS_HEX = $(mxpy wallet bech32 --decode ${ADDRESS}) - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=60000000 --function="setSpecialRole" \ --arguments ${WRAPPED_EGLD_TOKEN_ID} ${ADDRESS_HEX} ${LOCAL_BURN_ROLE} \ --send --proxy=${PROXY} --chain=${CHAIN_ID} @@ -72,16 +72,16 @@ setLocalRolesWrappedEgld() { setLocalRolesWrappedEth() { local LOCAL_BURN_ROLE=0x45534454526f6c654c6f63616c4275726e # "ESDTRoleLocalBurn" - local ADDRESS_HEX = $(erdpy wallet bech32 --decode ${ADDRESS}) + local ADDRESS_HEX = $(mxpy wallet bech32 --decode ${ADDRESS}) - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=60000000 --function="setSpecialRole" \ --arguments ${WRAPPED_ETH_TOKEN_ID} ${ADDRESS_HEX} ${LOCAL_BURN_ROLE} \ --send --proxy=${PROXY} --chain=${CHAIN_ID} } getNextPendingTransaction() { - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=25000000 --function="getNextPendingTransaction" \ --send --proxy=${PROXY} --chain=${CHAIN_ID} } @@ -92,7 +92,7 @@ setTransactionExecuted() { local TX_NONCE = 0x01 local TX_STATUS = ${TX_STATUS_EXECUTED} - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=90000000 --function="setTransactionStatus" \ --arguments ${RELAYER_REWARD_ADDRESS} ${ORIGINAL_TX_SENDER} ${TX_NONCE} ${TX_STATUS} \ --send --proxy=${PROXY} --chain=${CHAIN_ID} @@ -104,7 +104,7 @@ setTransactionRejected() { local TX_NONCE = 0x02 local TX_STATUS = ${TX_STATUS_REJECTED} - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=90000000 --function="setTransactionStatus" \ --arguments ${RELAYER_REWARD_ADDRESS} ${ORIGINAL_TX_SENDER} ${TX_NONCE} ${TX_STATUS} \ --send --proxy=${PROXY} --chain=${CHAIN_ID}D} @@ -115,7 +115,7 @@ createTransaction() { local DEST_ADDRESS = ${ALICE_ETH_ADDRESS} local TOKEN_USED_FOR_TX_FEES = 0x45474c44 # "EGLD" - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ + mxpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ --gas-limit=50000000 --function="ESDTTransfer" \ --arguments ${WRAPPED_EGLD_TOKEN_IDENTIFIER} 0x64 ${CREATE_TRANSACTION_ENDPOINT} ${DEST_ADDRESS} ${TOKEN_USED_FOR_TX_FEES} \ --send --proxy=${PROXY} --chain=${CHAIN_ID} @@ -124,7 +124,7 @@ createTransaction() { # views getTransactionStatus() { - erdpy --verbose contract query ${ADDRESS} --function="getTransactionStatus" \ + mxpy --verbose contract query ${ADDRESS} --function="getTransactionStatus" \ --arguments ${BOB_ADDRESS} 0x01 \ --proxy=${PROXY} } diff --git a/esdt-safe/mandos/add_refund_batch.scen.json b/esdt-safe/mandos/add_refund_batch.scen.json new file mode 100644 index 00000000..c88b0013 --- /dev/null +++ b/esdt-safe/mandos/add_refund_batch.scen.json @@ -0,0 +1,116 @@ +{ + "name": "add refund batch", + "steps": [ + { + "step": "externalSteps", + "path": "create_another_tx_ok.scen.json" + }, + { + "step": "setState", + "currentBlockInfo": { + "blockNonce": "5" + } + }, + { + "step": "scCall", + "txId": "add-refund-batch", + "tx": { + "from": "address:owner", + "to": "sc:esdt_safe", + "function": "addRefundBatch", + "arguments": [ + { + "01-block_nonce": "u64:2", + "02-nonce": "u64:1", + "03-from": "u32:20|0x0102030405060708091011121314151617181920", + "04-to": "u32:32|address:user1", + "05-token_identifier": "nested:str:BRIDGE-123456", + "06-amount": "biguint:2,000,000", + "07-is_refund_tx": "u8:1", + "11-block_nonce": "u64:3", + "12-nonce": "u64:2", + "13-from": "u32:20|0x0102030405060708091011121314151617181920", + "14-to": "u32:32|address:user2", + "15-token_identifier": "nested:str:BRIDGE-123456", + "16-amount": "biguint:3,000,000", + "17-is_refund_tx": "u8:1" + } + ], + "gasLimit": "100,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "out": [ + + ], + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:esdt_safe": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": { + "balance": "3,001,300", + "roles": [ + "ESDTRoleLocalBurn" + ] + } + }, + "storage": { + "str:pendingBatches|u64:1|str:.item|u32:1": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:1", + "3-from": "u32:32|address:user1", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:400", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:1|str:.item|u32:2": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:2", + "3-from": "u32:32|address:user2", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:900", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:1|str:.item|u32:3": { + "1-block_nonce": "u64:5", + "2-nonce": "u64:3", + "3-from": "u32:32|address:user1", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:500,000", + "7-is_refund_tx": "u8:1" + }, + "str:pendingBatches|u64:1|str:.item|u32:4": { + "1-block_nonce": "u64:5", + "2-nonce": "u64:4", + "3-from": "u32:32|address:user2", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:1,500,000", + "7-is_refund_tx": "u8:1" + }, + "str:firstBatchId": "1", + "str:lastBatchId": "1", + + "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "3,000,000", + + "+": "" + }, + "code": "file:../output/esdt-safe.wasm" + }, + "+": {} + } + } + ] +} diff --git a/esdt-safe/mandos/create_another_tx_ok.scen.json b/esdt-safe/mandos/create_another_tx_ok.scen.json index 1fcf115f..224c9e7a 100644 --- a/esdt-safe/mandos/create_another_tx_ok.scen.json +++ b/esdt-safe/mandos/create_another_tx_ok.scen.json @@ -25,17 +25,34 @@ "expect": { "status": "0", "out": [ - "1", - "str:GWEI", - "str:BRIDGE", - "10", - "0" + ], "message": "", "gas": "*", "refund": "*" } }, + { + "step": "scQuery", + "txId": "get-batch-status-after-second-add", + "tx": { + "to": "sc:esdt_safe", + "function": "getBatchStatus", + "arguments": [ + "1" + ] + }, + "expect": { + "out": [ + { + "1-enum-variant": "u8:2", + "2-end_block_nonce": "u64:100", + "3-vec_len": "u32:2", + "4-tx_ids": "u64:1|u64:2" + } + ] + } + }, { "step": "checkState", "accounts": { @@ -59,29 +76,27 @@ } }, "storage": { - "str:pendingBatches|u64:0": [ - { - "1-block_nonce": "u64:0", - "2-nonce": "u64:1", - "3-from": "address:user1", - "4-to": "0x0102030405060708091011121314151617181920", - "5-token_identifier": "nested:str:BRIDGE-123456", - "6-amount": "biguint:400" - }, - { - "1-block_nonce": "u64:0", - "2-nonce": "u64:2", - "3-from": "address:user2", - "4-to": "0x0102030405060708091011121314151617181920", - "5-token_identifier": "nested:str:BRIDGE-123456", - "6-amount": "biguint:900" - } - ], - "str:firstBatchId": "0", - "str:lastBatchId": "0", - + "str:pendingBatches|u64:1|str:.item|u32:1": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:1", + "3-from": "u32:32|address:user1", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:400", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:1|str:.item|u32:2": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:2", + "3-from": "u32:32|address:user2", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:900", + "7-is_refund_tx": "u8:0" + }, + "str:firstBatchId": "1", + "str:lastBatchId": "1", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "3,000,000", - "+": "" }, "code": "file:../output/esdt-safe.wasm" diff --git a/esdt-safe/mandos/create_another_tx_too_late_for_batch.scen.json b/esdt-safe/mandos/create_another_tx_too_late_for_batch.scen.json index fc4a19bb..c9584de9 100644 --- a/esdt-safe/mandos/create_another_tx_too_late_for_batch.scen.json +++ b/esdt-safe/mandos/create_another_tx_too_late_for_batch.scen.json @@ -21,6 +21,24 @@ } } }, + { + "step": "scQuery", + "txId": "get-batch-status-final", + "tx": { + "to": "sc:esdt_safe", + "function": "getBatchStatus", + "arguments": [ + "1" + ] + }, + "expect": { + "out": [ + { + "1-enum-variant": "u8:4" + } + ] + } + }, { "step": "scCall", "txId": "user2-create-transaction-too-late-for-batch", @@ -41,11 +59,7 @@ "expect": { "status": "0", "out": [ - "1", - "str:GWEI", - "str:BRIDGE", - "10", - "0" + ], "message": "", "gas": "*", @@ -75,44 +89,204 @@ } }, "storage": { - "str:pendingBatches|u64:0": [ - { - "1-block_nonce": "u64:0", - "2-nonce": "u64:1", - "3-from": "address:user1", - "4-to": "0x0102030405060708091011121314151617181920", - "5-token_identifier": "nested:str:BRIDGE-123456", - "6-amount": "biguint:400" - }, - { - "1-block_nonce": "u64:0", - "2-nonce": "u64:2", - "3-from": "address:user2", - "4-to": "0x0102030405060708091011121314151617181920", - "5-token_identifier": "nested:str:BRIDGE-123456", - "6-amount": "biguint:900" - } - ], - "str:pendingBatches|u64:1": { + "str:pendingBatches|u64:1|str:.item|u32:1": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:1", + "3-from": "u32:32|address:user1", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:400", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:1|str:.item|u32:2": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:2", + "3-from": "u32:32|address:user2", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:900", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:2|str:.item|u32:1": { "1-block_nonce": "u64:101", "2-nonce": "u64:3", - "3-from": "address:user2", - "4-to": "0x0102030405060708091011121314151617181920", + "3-from": "u32:32|address:user2", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", "5-token_identifier": "nested:str:BRIDGE-123456", - "6-amount": "biguint:100" + "6-amount": "biguint:100", + "7-is_refund_tx": "u8:0" }, - - "str:firstBatchId": "0", - "str:lastBatchId": "1", - + "str:firstBatchId": "1", + "str:lastBatchId": "2", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "4,500,000", - "+": "" }, "code": "file:../output/esdt-safe.wasm" }, "+": {} } + }, + { + "step": "scQuery", + "txId": "query-first-batch-id", + "tx": { + "to": "sc:esdt_safe", + "function": "getFirstBatchId", + "arguments": [] + }, + "expect": { + "out": [ + "1" + ] + } + }, + { + "step": "scQuery", + "txId": "query-last-batch-id", + "tx": { + "to": "sc:esdt_safe", + "function": "getLastBatchId", + "arguments": [] + }, + "expect": { + "out": [ + "2" + ] + } + }, + { + "step": "setState", + "currentBlockInfo": { + "blockNonce": "500" + }, + "accounts": { + "address:user3": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": "2,000,000" + }, + "storage": {} + } + } + }, + { + "step": "scCall", + "txId": "add another tx, too late, but still added to current last batch", + "tx": { + "from": "address:user3", + "to": "sc:esdt_safe", + "esdt": { + "tokenIdentifier": "str:BRIDGE-123456", + "value": "1,500,100" + }, + "function": "createTransaction", + "arguments": [ + "0x0102030405060708091011121314151617181920" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "out": [ + + ], + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:user2": { + "nonce": "*", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": "499,900" + }, + "storage": {} + }, + "sc:esdt_safe": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": { + "balance": "6,001,500", + "roles": [ + "ESDTRoleLocalBurn" + ] + } + }, + "storage": { + "str:pendingBatches|u64:1|str:.item|u32:1": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:1", + "3-from": "u32:32|address:user1", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:400", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:1|str:.item|u32:2": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:2", + "3-from": "u32:32|address:user2", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:900", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:2|str:.item|u32:1": { + "1-block_nonce": "u64:101", + "2-nonce": "u64:3", + "3-from": "u32:32|address:user2", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:100", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:2|str:.item|u32:2": { + "1-block_nonce": "u64:500", + "2-nonce": "u64:4", + "3-from": "u32:32|address:user3", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:BRIDGE-123456", + "6-amount": "biguint:100", + "7-is_refund_tx": "u8:0" + }, + "str:firstBatchId": "1", + "str:lastBatchId": "2", + "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "6,000,000", + "+": "" + }, + "code": "file:../output/esdt-safe.wasm" + }, + "+": {} + } + }, + { + "step": "scQuery", + "txId": "query-is-batch-2-final", + "tx": { + "to": "sc:esdt_safe", + "function": "getBatchStatus", + "arguments": [ + "2" + ] + }, + "expect": { + "out": [ + { + "1-enum_identifier": "u8:2", + "2-end_block_nonce": "u64:201", + "3-vec_len": "u32:2", + "4-first_tx_nonce": "u64:3", + "5-second_tx_nonce": "u64:4" + } + ] + } } ] } diff --git a/esdt-safe/mandos/create_transaction_ok.scen.json b/esdt-safe/mandos/create_transaction_ok.scen.json index 91e2bd93..d3e159cb 100644 --- a/esdt-safe/mandos/create_transaction_ok.scen.json +++ b/esdt-safe/mandos/create_transaction_ok.scen.json @@ -5,6 +5,24 @@ "step": "externalSteps", "path": "setup_accounts.scen.json" }, + { + "step": "scQuery", + "txId": "get-batch-status-before", + "tx": { + "to": "sc:esdt_safe", + "function": "getBatchStatus", + "arguments": [ + "1" + ] + }, + "expect": { + "out": [ + { + "1-enum-variant": "u8:1" + } + ] + } + }, { "step": "scCall", "txId": "create-transaction-ok", @@ -24,18 +42,33 @@ }, "expect": { "status": "0", - "out": [ - "1", - "str:GWEI", - "str:BRIDGE", - "10", - "0" - ], + "out": [], "message": "", "gas": "*", "refund": "*" } }, + { + "step": "scQuery", + "txId": "get-batch-status-after", + "tx": { + "to": "sc:esdt_safe", + "function": "getBatchStatus", + "arguments": [ + "1" + ] + }, + "expect": { + "out": [ + { + "1-enum-variant": "u8:2", + "2-end_block_nonce": "u64:100", + "3-vec_len": "u32:1", + "4-tx_ids": "u64:1" + } + ] + } + }, { "step": "checkState", "accounts": { @@ -59,19 +92,18 @@ } }, "storage": { - "str:pendingBatches|u64:0": { + "str:pendingBatches|u64:1|str:.item|u32:1": { "1-block_nonce": "u64:0", "2-nonce": "u64:1", - "3-from": "address:user1", - "4-to": "0x0102030405060708091011121314151617181920", + "3-from": "u32:32|address:user1", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", "5-token_identifier": "nested:str:BRIDGE-123456", - "6-amount": "biguint:400" + "6-amount": "biguint:400", + "7-is_refund_tx": "u8:0" }, - "str:firstBatchId": "0", - "str:lastBatchId": "0", - + "str:firstBatchId": "1", + "str:lastBatchId": "1", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "1,500,000", - "+": "" }, "code": "file:../output/esdt-safe.wasm" diff --git a/esdt-safe/mandos/create_transaction_over_max_amount.scen.json b/esdt-safe/mandos/create_transaction_over_max_amount.scen.json new file mode 100644 index 00000000..c76faa7f --- /dev/null +++ b/esdt-safe/mandos/create_transaction_over_max_amount.scen.json @@ -0,0 +1,82 @@ +{ + "name": "create transaction ok", + "steps": [ + { + "step": "externalSteps", + "path": "setup_accounts.scen.json" + }, + { + "step": "scCall", + "txId": "create-transaction-ok", + "tx": { + "from": "address:owner", + "to": "sc:esdt_safe", + "function": "setMaxBridgedAmount", + "arguments": [ + "str:BRIDGE-123456", + "1,600,000" + ], + "gasLimit": "60,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "out": [], + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "create-transaction-over-max", + "tx": { + "from": "address:user1", + "to": "sc:esdt_safe", + "esdt": { + "tokenIdentifier": "str:BRIDGE-123456", + "value": "1,800,000" + }, + "function": "createTransaction", + "arguments": [ + "0x0102030405060708091011121314151617181920" + ], + "gasLimit": "60,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Deposit over max amount", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "create-transaction-ok", + "tx": { + "from": "address:user1", + "to": "sc:esdt_safe", + "esdt": { + "tokenIdentifier": "str:BRIDGE-123456", + "value": "1,600,000" + }, + "function": "createTransaction", + "arguments": [ + "0x0102030405060708091011121314151617181920" + ], + "gasLimit": "60,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "out": [ + + ], + "message": "", + "gas": "*", + "refund": "*" + } + } + ] +} diff --git a/esdt-safe/mandos/distribute_fees.scen.json b/esdt-safe/mandos/distribute_fees.scen.json index 4bb06f84..30469112 100644 --- a/esdt-safe/mandos/distribute_fees.scen.json +++ b/esdt-safe/mandos/distribute_fees.scen.json @@ -14,7 +14,7 @@ "value": "0", "function": "distributeFees", "arguments": [ - "address:owner|u64:10000" + "address:owner|u32:10000" ], "gasLimit": "30,000,000", "gasPrice": "0" diff --git a/esdt-safe/mandos/execute_batch_both_rejected.scen.json b/esdt-safe/mandos/execute_batch_both_rejected.scen.json index eabe6cc5..55d42cc2 100644 --- a/esdt-safe/mandos/execute_batch_both_rejected.scen.json +++ b/esdt-safe/mandos/execute_batch_both_rejected.scen.json @@ -14,7 +14,7 @@ "value": "0", "function": "setTransactionBatchStatus", "arguments": [ - "0", + "1", "4", "4" ], "gasLimit": "50,000,000", @@ -27,6 +27,50 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "user1-claim-refund", + "tx": { + "from": "address:user1", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "user2-claim-refund", + "tx": { + "from": "address:user2", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": "*", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { @@ -58,9 +102,9 @@ } }, "storage": { - "str:pendingBatches|u64:0": "", - "str:firstBatchId": "1", - "str:lastBatchId": "1", + "str:pendingBatches|u64:1": "", + "str:firstBatchId": "2", + "str:lastBatchId": "2", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "3,000,000", diff --git a/esdt-safe/mandos/execute_batch_both_success.scen.json b/esdt-safe/mandos/execute_batch_both_success.scen.json index b9d2b746..d8d3bdcb 100644 --- a/esdt-safe/mandos/execute_batch_both_success.scen.json +++ b/esdt-safe/mandos/execute_batch_both_success.scen.json @@ -14,7 +14,7 @@ "value": "0", "function": "setTransactionBatchStatus", "arguments": [ - "0", + "1", "3", "3" ], "gasLimit": "50,000,000", @@ -27,6 +27,48 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "user1-claim-refund", + "tx": { + "from": "address:user1", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Nothing to refund", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "user2-claim-refund", + "tx": { + "from": "address:user2", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Nothing to refund", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { @@ -58,9 +100,9 @@ } }, "storage": { - "str:pendingBatches|u64:0": "", - "str:firstBatchId": "1", - "str:lastBatchId": "1", + "str:pendingBatches|u64:1": "", + "str:firstBatchId": "2", + "str:lastBatchId": "2", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "3,000,000", diff --git a/esdt-safe/mandos/execute_batch_one_success_one_rejected.scen.json b/esdt-safe/mandos/execute_batch_one_success_one_rejected.scen.json index 537d14c6..757f5141 100644 --- a/esdt-safe/mandos/execute_batch_one_success_one_rejected.scen.json +++ b/esdt-safe/mandos/execute_batch_one_success_one_rejected.scen.json @@ -14,7 +14,7 @@ "value": "0", "function": "setTransactionBatchStatus", "arguments": [ - "0", + "1", "3", "4" ], "gasLimit": "50,000,000", @@ -27,6 +27,49 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "user1-claim-refund", + "tx": { + "from": "address:user1", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Nothing to refund", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "user2-claim-refund", + "tx": { + "from": "address:user2", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": "*", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { @@ -58,9 +101,9 @@ } }, "storage": { - "str:pendingBatches|u64:0": "", - "str:firstBatchId": "1", - "str:lastBatchId": "1", + "str:pendingBatches|u64:1": "", + "str:firstBatchId": "2", + "str:lastBatchId": "2", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "3,000,000", diff --git a/esdt-safe/mandos/execute_transaction_rejected.scen.json b/esdt-safe/mandos/execute_transaction_rejected.scen.json index 9076914a..52a289ac 100644 --- a/esdt-safe/mandos/execute_transaction_rejected.scen.json +++ b/esdt-safe/mandos/execute_transaction_rejected.scen.json @@ -14,7 +14,7 @@ "value": "0", "function": "setTransactionBatchStatus", "arguments": [ - "0", + "1", "4" ], "gasLimit": "50,000,000", @@ -27,6 +27,28 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "user1-claim-refund", + "tx": { + "from": "address:user1", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": "*", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { @@ -50,9 +72,9 @@ } }, "storage": { - "str:pendingBatches|u64:0": "", - "str:firstBatchId": "1", - "str:lastBatchId": "1", + "str:pendingBatches|u64:1": "", + "str:firstBatchId": "2", + "str:lastBatchId": "2", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "1,500,000", diff --git a/esdt-safe/mandos/execute_transaction_success.scen.json b/esdt-safe/mandos/execute_transaction_success.scen.json index 49a9e5dd..71fbb4c3 100644 --- a/esdt-safe/mandos/execute_transaction_success.scen.json +++ b/esdt-safe/mandos/execute_transaction_success.scen.json @@ -14,7 +14,7 @@ "value": "0", "function": "setTransactionBatchStatus", "arguments": [ - "0", + "1", "3" ], "gasLimit": "50,000,000", @@ -27,6 +27,27 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "user1-claim-refund", + "tx": { + "from": "address:user1", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:BRIDGE-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Nothing to refund", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { @@ -50,9 +71,9 @@ } }, "storage": { - "str:pendingBatches|u64:0": "", - "str:firstBatchId": "1", - "str:lastBatchId": "1", + "str:pendingBatches|u64:1": "", + "str:firstBatchId": "2", + "str:lastBatchId": "2", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "1,500,000", diff --git a/esdt-safe/mandos/get_next_pending_tx.scen.json b/esdt-safe/mandos/get_next_pending_tx.scen.json index b383ca28..00b61c4f 100644 --- a/esdt-safe/mandos/get_next_pending_tx.scen.json +++ b/esdt-safe/mandos/get_next_pending_tx.scen.json @@ -21,7 +21,7 @@ }, "expect": { "out": [ - "0", + "1", "0", "1", diff --git a/esdt-safe/mandos/get_next_tx_batch.scen.json b/esdt-safe/mandos/get_next_tx_batch.scen.json index cebf3244..8a22bcda 100644 --- a/esdt-safe/mandos/get_next_tx_batch.scen.json +++ b/esdt-safe/mandos/get_next_tx_batch.scen.json @@ -21,7 +21,7 @@ }, "expect": { "out": [ - "0", + "1", "0", "1", diff --git a/esdt-safe/mandos/get_next_tx_batch_too_early.scen.json b/esdt-safe/mandos/get_next_tx_batch_too_early.scen.json index 9db89e45..bff60c09 100644 --- a/esdt-safe/mandos/get_next_tx_batch_too_early.scen.json +++ b/esdt-safe/mandos/get_next_tx_batch_too_early.scen.json @@ -22,6 +22,50 @@ "expect": { "out": [] } + }, + { + "step": "scQuery", + "txId": "get-first-tx-batch", + "tx": { + "to": "sc:esdt_safe", + "function": "getBatch", + "arguments": [ + "1" + ] + }, + "expect": { + "out": [ + "1", + + "0", + "1", + "address:user1", + "0x0102030405060708091011121314151617181920", + "str:BRIDGE-123456", + "400", + + "0", + "2", + "address:user2", + "0x0102030405060708091011121314151617181920", + "str:BRIDGE-123456", + "900" + ] + } + }, + { + "step": "scQuery", + "txId": "get-batch-invalid-id", + "tx": { + "to": "sc:esdt_safe", + "function": "getBatch", + "arguments": [ + "2" + ] + }, + "expect": { + "out": [] + } } ] } diff --git a/esdt-safe/mandos/setup_accounts.scen.json b/esdt-safe/mandos/setup_accounts.scen.json index 2e804138..c6e77b27 100644 --- a/esdt-safe/mandos/setup_accounts.scen.json +++ b/esdt-safe/mandos/setup_accounts.scen.json @@ -47,8 +47,7 @@ "value": "0", "arguments": [ "sc:price_aggregator", - "150,000", - "str:BRIDGE-123456" + "150,000" ], "gasLimit": "20,000,000", "gasPrice": "0" @@ -60,6 +59,28 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "add-token-1", + "tx": { + "from": "address:owner", + "to": "sc:esdt_safe", + "value": "0", + "function": "addTokenToWhitelist", + "arguments": [ + "str:BRIDGE-123456", + "str:BRIDGE" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { @@ -70,8 +91,13 @@ "str:feeEstimatorContractAddress": "sc:price_aggregator", "str:maxTxBatchSize": "10", "str:maxTxBatchBlockDuration": "100", + "str:firstBatchId": "1", + "str:lastBatchId": "1", "str:ethTxGasLimit": "150,000", - "str:tokenWhitelist.value|u32:1": "str:BRIDGE-123456", + "str:tokenTicker|nested:str:BRIDGE-123456": "str:BRIDGE", + "str:tokenTicker|nested:str:GWEI": "str:GWEI", + "str:tokenWhitelist.index|nested:str:BRIDGE-123456": "1", + "str:pause_module:paused": "true", "+": "" }, "code": "file:../output/esdt-safe.wasm" @@ -79,6 +105,25 @@ "+": {} } }, + { + "step": "scCall", + "txId": "unpause", + "tx": { + "from": "address:owner", + "to": "sc:esdt_safe", + "function": "unpause", + "arguments": [], + "gasLimit": "100,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "out": [], + "message": "", + "gas": "*", + "refund": "*" + } + }, { "step": "setState", "comment": "setting local burn role", @@ -98,17 +143,32 @@ "str:feeEstimatorContractAddress": "sc:price_aggregator", "str:maxTxBatchSize": "10", "str:maxTxBatchBlockDuration": "100", + "str:firstBatchId": "1", + "str:lastBatchId": "1", "str:ethTxGasLimit": "150,000", - "str:tokenWhitelist.value|u32:1": "str:BRIDGE-123456", - - "str:tokenWhitelist.info": "0x00000001000000010000000100000001", - "str:tokenWhitelist.node_idBRIDGE-123456": "0x01", - "str:tokenWhitelist.node_links|u32:1": "0x0000000000000000" + "str:tokenTicker|nested:str:BRIDGE-123456": "str:BRIDGE", + "str:tokenTicker|nested:str:GWEI": "str:GWEI", + "str:tokenWhitelist.index|nested:str:BRIDGE-123456": "1", + "str:tokenWhitelist.item|u32:1": "str:BRIDGE-123456", + "str:tokenWhitelist.len": "1" }, "code": "file:../output/esdt-safe.wasm", "owner": "address:owner" } } + }, + { + "step": "scQuery", + "txId": "get-all-known-tokens", + "tx": { + "to": "sc:esdt_safe", + "function": "getAllKnownTokens" + }, + "expect": { + "out": [ + "str:BRIDGE-123456" + ] + } } ] -} +} \ No newline at end of file diff --git a/esdt-safe/mandos/zero_fees.scen.json b/esdt-safe/mandos/zero_fees.scen.json index d4c030a9..f5d6d36d 100644 --- a/esdt-safe/mandos/zero_fees.scen.json +++ b/esdt-safe/mandos/zero_fees.scen.json @@ -98,19 +98,18 @@ } }, "storage": { - "str:pendingBatches|u64:0": { + "str:pendingBatches|u64:1|str:.item|u32:1": { "1-block_nonce": "u64:0", "2-nonce": "u64:1", - "3-from": "address:user1", - "4-to": "0x0102030405060708091011121314151617181920", + "3-from": "u32:32|address:user1", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", "5-token_identifier": "nested:str:BRIDGE-123456", - "6-amount": "biguint:1,500,400" + "6-amount": "biguint:1,500,400", + "7-is_refund_tx": "u8:0" }, - "str:firstBatchId": "0", - "str:lastBatchId": "0", - + "str:firstBatchId": "1", + "str:lastBatchId": "1", "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "0", - "+": "" }, "code": "file:../output/esdt-safe.wasm" @@ -120,4 +119,3 @@ } ] } - diff --git a/esdt-safe/meta/Cargo.toml b/esdt-safe/meta/Cargo.toml new file mode 100644 index 00000000..a16eeefd --- /dev/null +++ b/esdt-safe/meta/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "esdt-safe-meta" +version = "0.0.0" +authors = ["you"] +edition = "2018" +publish = false + +[dev-dependencies] +[dependencies.esdt-safe] +path = ".." + +[dependencies.multiversx-sc-meta] +version = "0.41.3" diff --git a/esdt-safe/meta/src/main.rs b/esdt-safe/meta/src/main.rs new file mode 100644 index 00000000..232dc09e --- /dev/null +++ b/esdt-safe/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::(); +} diff --git a/esdt-safe/elrond.json b/esdt-safe/multiversx.json similarity index 100% rename from esdt-safe/elrond.json rename to esdt-safe/multiversx.json diff --git a/esdt-safe/src/lib.rs b/esdt-safe/src/lib.rs index b71e6640..30269c30 100644 --- a/esdt-safe/src/lib.rs +++ b/esdt-safe/src/lib.rs @@ -1,95 +1,85 @@ #![no_std] #![allow(non_snake_case)] -elrond_wasm::imports!(); -elrond_wasm::derive_imports!(); +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use core::convert::TryFrom; use eth_address::*; -use transaction::esdt_safe_batch::EsdtSafeTxBatchSplitInFields; -use transaction::*; +use fee_estimator_module::GWEI_STRING; +use transaction::{transaction_status::TransactionStatus, Transaction}; const DEFAULT_MAX_TX_BATCH_SIZE: usize = 10; -const DEFAULT_MAX_TX_BATCH_BLOCK_DURATION: u64 = 100; - -#[elrond_wasm_derive::contract] -pub trait EsdtSafe: fee_estimator_module::FeeEstimatorModule + token_module::TokenModule { +const DEFAULT_MAX_TX_BATCH_BLOCK_DURATION: u64 = 100; // ~10 minutes + +#[multiversx_sc::contract] +pub trait EsdtSafe: + fee_estimator_module::FeeEstimatorModule + + token_module::TokenModule + + tx_batch_module::TxBatchModule + + max_bridged_amount_module::MaxBridgedAmountModule + + multiversx_sc_modules::pause::PauseModule +{ + /// fee_estimator_contract_address - The address of a Price Aggregator contract, + /// which will get the price of token A in token B + /// + /// eth_tx_gas_limit - The gas limit that will be used for transactions on the ETH side. + /// Will be used to compute the fees for the transfer #[init] - fn init( - &self, - fee_estimator_contract_address: Address, - eth_tx_gas_limit: Self::BigUint, - #[var_args] token_whitelist: VarArgs, - ) -> SCResult<()> { + fn init(&self, fee_estimator_contract_address: ManagedAddress, eth_tx_gas_limit: BigUint) { self.fee_estimator_contract_address() .set(&fee_estimator_contract_address); self.eth_tx_gas_limit().set(ð_tx_gas_limit); - for token in token_whitelist.into_vec() { - require!(token.is_valid_esdt_identifier(), "Invalid token ID"); - let _ = self.token_whitelist().insert(token); - } - self.max_tx_batch_size() - .set_if_empty(&DEFAULT_MAX_TX_BATCH_SIZE); + .set_if_empty(DEFAULT_MAX_TX_BATCH_SIZE); self.max_tx_batch_block_duration() - .set_if_empty(&DEFAULT_MAX_TX_BATCH_BLOCK_DURATION); - - Ok(()) - } - - // endpoints - owner-only - - #[only_owner] - #[endpoint(setMaxTxBatchSize)] - fn set_max_tx_batch_size(&self, new_max_tx_batch_size: usize) -> SCResult<()> { - require!( - new_max_tx_batch_size > 0, - "Max tx batch size must be more than 0" - ); + .set_if_empty(DEFAULT_MAX_TX_BATCH_BLOCK_DURATION); - self.max_tx_batch_size().set(&new_max_tx_batch_size); - - Ok(()) - } - - #[only_owner] - #[endpoint(setMaxTxBatchBlockDuration)] - fn set_max_tx_batch_block_duration( - &self, - new_max_tx_batch_block_duration: u64, - ) -> SCResult<()> { - require!( - new_max_tx_batch_block_duration > 0, - "Max tx batch block duration must be more than 0" - ); + // batch ID 0 is considered invalid + self.first_batch_id().set_if_empty(1); + self.last_batch_id().set_if_empty(1); - self.max_tx_batch_block_duration() - .set(&new_max_tx_batch_block_duration); + // set ticker for "GWEI" + let gwei_token_id = TokenIdentifier::from(GWEI_STRING); + self.token_ticker(&gwei_token_id) + .set(gwei_token_id.as_managed_buffer()); - Ok(()) + self.set_paused(true); } + /// Sets the statuses for the transactions, after they were executed on the Ethereum side. + /// + /// Only TransactionStatus::Executed (3) and TransactionStatus::Rejected (4) values are allowed. + /// Number of provided statuses must be equal to number of transactions in the batch. #[only_owner] #[endpoint(setTransactionBatchStatus)] fn set_transaction_batch_status( &self, batch_id: u64, - #[var_args] tx_statuses: VarArgs, - ) -> SCResult<()> { + tx_statuses: MultiValueEncoded, + ) { let first_batch_id = self.first_batch_id().get(); require!( batch_id == first_batch_id, "Batches must be processed in order" ); - let tx_batch = self.pending_batches(batch_id).get(); + let mut tx_batch = self.pending_batches(batch_id); require!( tx_batch.len() == tx_statuses.len(), "Invalid number of statuses provided" ); - for (tx, tx_status) in tx_batch.iter().zip(tx_statuses.into_vec().iter()) { - match *tx_status { + for (tx, tx_status) in tx_batch.iter().zip(tx_statuses.to_vec().iter()) { + // Since tokens don't exist in the EsdtSafe in the case of a refund transaction + // we have no tokens to burn, nor to refund + if tx.is_refund_tx { + continue; + } + + match tx_status { TransactionStatus::Executed => { // local burn role might be removed while tx is executed // tokens will remain locked forever in that case @@ -99,48 +89,95 @@ pub trait EsdtSafe: fee_estimator_module::FeeEstimatorModule + token_module::Tok } } TransactionStatus::Rejected => { - self.refund_esdt_token(&tx.from, &tx.token_identifier, &tx.amount); + let addr = ManagedAddress::try_from(tx.from).unwrap(); + self.mark_refund(&addr, &tx.token_identifier, &tx.amount); } _ => { - return sc_error!("Transaction status may only be set to Executed or Rejected") + sc_panic!("Transaction status may only be set to Executed or Rejected"); } } + + self.set_status_event(batch_id, tx.nonce, tx_status); } - let new_first_batch_id = first_batch_id + 1; + self.clear_first_batch(&mut tx_batch); + } + + /// Converts failed Ethereum -> Elrond transactions to Elrond -> Ethereum transaction. + /// This is done every now and then to refund the tokens. + /// + /// As with normal Elrond -> Ethereum transactions, a part of the tokens will be + /// subtracted to pay for the fees + #[only_owner] + #[endpoint(addRefundBatch)] + fn add_refund_batch(&self, refund_transactions: ManagedVec>) { + let block_nonce = self.blockchain().get_block_nonce(); + let mut cached_token_ids = ManagedVec::::new(); + let mut cached_prices = ManagedVec::::new(); + let mut new_transactions = ManagedVec::new(); + let mut original_tx_nonces = ManagedVec::::new(); + + for refund_tx in &refund_transactions { + let required_fee = match cached_token_ids + .iter() + .position(|id| *id == refund_tx.token_identifier) + { + Some(index) => (*cached_prices.get(index)).clone(), + None => { + let queried_fee = self.calculate_required_fee(&refund_tx.token_identifier); + cached_token_ids.push(refund_tx.token_identifier.clone()); + cached_prices.push(queried_fee.clone()); + + queried_fee + } + }; - // for the case when the last existing batch was processed - // otherwise, we'd create a batch with the same ID again - self.last_batch_id().update(|last_batch_id| { - if *last_batch_id == first_batch_id { - *last_batch_id = new_first_batch_id; + if refund_tx.amount <= required_fee { + continue; } - }); - self.first_batch_id().set(&new_first_batch_id); - self.pending_batches(batch_id).clear(); - Ok(()) + let actual_bridged_amount = refund_tx.amount - required_fee; + let tx_nonce = self.get_and_save_next_tx_id(); + + // "from" and "to" are inverted, since this was initially an Ethereum -> Elrond tx + let new_tx = Transaction { + block_nonce, + nonce: tx_nonce, + from: refund_tx.to, + to: refund_tx.from, + token_identifier: refund_tx.token_identifier, + amount: actual_bridged_amount, + is_refund_tx: true, + }; + new_transactions.push(new_tx); + original_tx_nonces.push(refund_tx.nonce); + } + + let batch_ids = self.add_multiple_tx_to_batch(&new_transactions); + for (i, tx) in new_transactions.iter().enumerate() { + let batch_id = batch_ids.get(i); + let original_tx_nonce = original_tx_nonces.get(i); + + self.add_refund_transaction_event(batch_id, tx.nonce, original_tx_nonce); + } } // endpoints + /// Create an Elrond -> Ethereum transaction. Only fungible tokens are accepted. + /// + /// Every transfer will have a part of the tokens subtracted as fees. + /// The fee amount depends on the global eth_tx_gas_limit + /// and the current GWEI price, respective to the bridged token + /// + /// fee_amount = price_per_gas_unit * eth_tx_gas_limit #[payable("*")] #[endpoint(createTransaction)] - fn create_transaction( - &self, - #[payment_token] payment_token: TokenIdentifier, - #[payment_amount] payment_amount: Self::BigUint, - to: EthAddress, - ) -> SCResult<()> { - require!( - self.call_value().esdt_token_nonce() == 0, - "Only fungible ESDT tokens accepted" - ); - require!( - self.token_whitelist().contains(&payment_token), - "Payment token is not on whitelist" - ); - require!(!to.is_zero(), "Can't transfer to address zero"); + fn create_transaction(&self, to: EthAddress) { + require!(self.not_paused(), "Cannot create transaction while paused"); + + let (payment_token, payment_amount) = self.call_value().single_fungible_esdt(); + self.require_token_in_whitelist(&payment_token); let required_fee = self.calculate_required_fee(&payment_token); require!( @@ -148,132 +185,100 @@ pub trait EsdtSafe: fee_estimator_module::FeeEstimatorModule + token_module::Tok "Transaction fees cost more than the entire bridged amount" ); + self.require_below_max_amount(&payment_token, &payment_amount); + self.accumulated_transaction_fees(&payment_token) .update(|fees| *fees += &required_fee); let actual_bridged_amount = payment_amount - required_fee; let caller = self.blockchain().get_caller(); - let tx_nonce = self.last_tx_nonce().update(|last_tx_nonce| { - *last_tx_nonce += 1; - *last_tx_nonce - }); + let tx_nonce = self.get_and_save_next_tx_id(); let tx = Transaction { block_nonce: self.blockchain().get_block_nonce(), nonce: tx_nonce, - from: caller, - to, + from: caller.as_managed_buffer().clone(), + to: to.as_managed_buffer().clone(), token_identifier: payment_token, amount: actual_bridged_amount, + is_refund_tx: false, }; - self.add_to_batch(tx); - - Ok(()) + let batch_id = self.add_to_batch(tx); + self.create_transaction_event(batch_id, tx_nonce); } - // views - - #[view(getCurrentTxBatch)] - fn get_current_tx_batch(&self) -> OptionalResult> { - let first_batch_id = self.first_batch_id().get(); - let first_batch = self.pending_batches(first_batch_id).get(); - - if self.is_batch_full(&first_batch) { - let batch_len = first_batch.len(); - let mut result_vec = Vec::with_capacity(batch_len); - for tx in first_batch { - result_vec.push(tx.into_multiresult()); - } + /// Claim funds for failed Elrond -> Ethereum transactions. + /// These are not sent automatically to prevent the contract getting stuck. + /// For example, if the receiver is a SC, a frozen account, etc. + #[endpoint(claimRefund)] + fn claim_refund(&self, token_id: TokenIdentifier) -> EsdtTokenPayment { + let caller = self.blockchain().get_caller(); + let refund_amount = self.refund_amount(&caller, &token_id).get(); + require!(refund_amount > 0, "Nothing to refund"); - return OptionalResult::Some((first_batch_id, result_vec.into()).into()); - } + self.refund_amount(&caller, &token_id).clear(); + self.send() + .direct_esdt(&caller, &token_id, 0, &refund_amount); - OptionalResult::None + EsdtTokenPayment::new(token_id, 0, refund_amount) } - // private - - fn add_to_batch(&self, transaction: Transaction) { - let last_batch_id = self.last_batch_id().get(); - let mut last_batch = self.pending_batches(last_batch_id).get(); - - if self.is_batch_full(&last_batch) { - self.create_new_batch(transaction); - } else { - last_batch.push(transaction); - self.pending_batches(last_batch_id).set(&last_batch); + /// Query function that lists all refund amounts for a user. + /// Useful for knowing which token IDs to pass to the claimRefund endpoint. + #[view(getRefundAmounts)] + fn get_refund_amounts( + &self, + address: ManagedAddress, + ) -> MultiValueEncoded> { + let mut refund_amounts = MultiValueEncoded::new(); + for token_id in self.token_whitelist().iter() { + let amount = self.refund_amount(&address, &token_id).get(); + if amount > 0u32 { + refund_amounts.push((token_id, amount).into()); + } } - } - - #[allow(clippy::vec_init_then_push)] - fn create_new_batch(&self, transaction: Transaction) { - let last_batch_id = self.last_batch_id().get(); - let new_batch_id = last_batch_id + 1; - - let mut new_batch = Vec::with_capacity(1); - new_batch.push(transaction); - self.pending_batches(new_batch_id).set(&new_batch); - self.last_batch_id().set(&new_batch_id); + refund_amounts } - fn is_batch_full(&self, tx_batch: &[Transaction]) -> bool { - if tx_batch.is_empty() { - return false; - } - - let max_batch_size = self.max_tx_batch_size().get(); - if tx_batch.len() == max_batch_size { - return true; - } - - let current_block_nonce = self.blockchain().get_block_nonce(); - let first_tx_in_batch_block_nonce = tx_batch[0].block_nonce; - let block_diff = current_block_nonce - first_tx_in_batch_block_nonce; - let max_tx_batch_block_duration = self.max_tx_batch_block_duration().get(); - - block_diff > max_tx_batch_block_duration - } + // private - fn burn_esdt_token(&self, token_id: &TokenIdentifier, amount: &Self::BigUint) { + fn burn_esdt_token(&self, token_id: &TokenIdentifier, amount: &BigUint) { self.send().esdt_local_burn(token_id, 0, amount); } - fn refund_esdt_token(&self, to: &Address, token_id: &TokenIdentifier, amount: &Self::BigUint) { - self.send() - .direct(to, token_id, 0, amount, self.data_or_empty(to, b"refund")); - } - - fn data_or_empty(&self, to: &Address, data: &'static [u8]) -> &[u8] { - if self.blockchain().is_smart_contract(to) { - &[] - } else { - data - } + fn mark_refund(&self, to: &ManagedAddress, token_id: &TokenIdentifier, amount: &BigUint) { + self.refund_amount(to, token_id) + .update(|refund| *refund += amount); } - // storage + // events - #[storage_mapper("firstBatchId")] - fn first_batch_id(&self) -> SingleValueMapper; + #[event("createTransactionEvent")] + fn create_transaction_event(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); - #[storage_mapper("lastBatchId")] - fn last_batch_id(&self) -> SingleValueMapper; - - #[storage_mapper("pendingBatches")] - fn pending_batches( + #[event("addRefundTransactionEvent")] + fn add_refund_transaction_event( &self, - batch_id: u64, - ) -> SingleValueMapper>>; + #[indexed] batch_id: u64, + #[indexed] tx_id: u64, + #[indexed] original_tx_id: u64, + ); - #[storage_mapper("lastTxNonce")] - fn last_tx_nonce(&self) -> SingleValueMapper; - - // configurable + #[event("setStatusEvent")] + fn set_status_event( + &self, + #[indexed] batch_id: u64, + #[indexed] tx_id: u64, + #[indexed] tx_status: TransactionStatus, + ); - #[storage_mapper("maxTxBatchSize")] - fn max_tx_batch_size(&self) -> SingleValueMapper; + // storage - #[storage_mapper("maxTxBatchBlockDuration")] - fn max_tx_batch_block_duration(&self) -> SingleValueMapper; + #[storage_mapper("refundAmount")] + fn refund_amount( + &self, + address: &ManagedAddress, + token_id: &TokenIdentifier, + ) -> SingleValueMapper; } diff --git a/esdt-safe/tests/mandos_go_test.rs b/esdt-safe/tests/mandos_go_test.rs deleted file mode 100644 index cdb3cd13..00000000 --- a/esdt-safe/tests/mandos_go_test.rs +++ /dev/null @@ -1,69 +0,0 @@ -#[test] -fn claim_fees_go() { - elrond_wasm_debug::mandos_go("mandos/distribute_fees.scen.json"); -} - -#[test] -fn create_another_tx_ok_go() { - elrond_wasm_debug::mandos_go("mandos/create_another_tx_ok.scen.json"); -} - -#[test] -fn create_another_tx_too_late_for_batch_go() { - elrond_wasm_debug::mandos_go("mandos/create_another_tx_too_late_for_batch.scen.json"); -} - -#[test] -fn create_transaction_ok_go() { - elrond_wasm_debug::mandos_go("mandos/create_transaction_ok.scen.json"); -} - -#[test] -fn execute_batch_both_rejected_go() { - elrond_wasm_debug::mandos_go("mandos/execute_batch_both_rejected.scen.json"); -} - -#[test] -fn execute_batch_both_success_go() { - elrond_wasm_debug::mandos_go("mandos/execute_batch_both_success.scen.json"); -} - -#[test] -fn execute_batch_one_success_one_rejected_go() { - elrond_wasm_debug::mandos_go("mandos/execute_batch_one_success_one_rejected.scen.json"); -} - -#[test] -fn execute_transaction_rejected_go() { - elrond_wasm_debug::mandos_go("mandos/execute_transaction_rejected.scen.json"); -} - -#[test] -fn execute_transaction_success_go() { - elrond_wasm_debug::mandos_go("mandos/execute_transaction_success.scen.json"); -} - -#[test] -fn get_next_pending_tx_go() { - elrond_wasm_debug::mandos_go("mandos/get_next_pending_tx.scen.json"); -} - -#[test] -fn get_next_tx_batch_go() { - elrond_wasm_debug::mandos_go("mandos/get_next_tx_batch.scen.json"); -} - -#[test] -fn get_next_tx_batch_too_early_go() { - elrond_wasm_debug::mandos_go("mandos/get_next_tx_batch_too_early.scen.json"); -} - -#[test] -fn setup_accounts_go() { - elrond_wasm_debug::mandos_go("mandos/setup_accounts.scen.json"); -} - -#[test] -fn zero_fees_go() { - elrond_wasm_debug::mandos_go("mandos/zero_fees.scen.json"); -} diff --git a/esdt-safe/tests/scenario_go_test.rs b/esdt-safe/tests/scenario_go_test.rs new file mode 100644 index 00000000..205306a5 --- /dev/null +++ b/esdt-safe/tests/scenario_go_test.rs @@ -0,0 +1,69 @@ +#[test] +fn claim_fees_go() { + multiversx_sc_scenario::run_go("mandos/distribute_fees.scen.json"); +} + +#[test] +fn create_another_tx_ok_go() { + multiversx_sc_scenario::run_go("mandos/create_another_tx_ok.scen.json"); +} + +#[test] +fn create_another_tx_too_late_for_batch_go() { + multiversx_sc_scenario::run_go("mandos/create_another_tx_too_late_for_batch.scen.json"); +} + +#[test] +fn create_transaction_ok_go() { + multiversx_sc_scenario::run_go("mandos/create_transaction_ok.scen.json"); +} + +#[test] +fn execute_batch_both_rejected_go() { + multiversx_sc_scenario::run_go("mandos/execute_batch_both_rejected.scen.json"); +} + +#[test] +fn execute_batch_both_success_go() { + multiversx_sc_scenario::run_go("mandos/execute_batch_both_success.scen.json"); +} + +#[test] +fn execute_batch_one_success_one_rejected_go() { + multiversx_sc_scenario::run_go("mandos/execute_batch_one_success_one_rejected.scen.json"); +} + +#[test] +fn execute_transaction_rejected_go() { + multiversx_sc_scenario::run_go("mandos/execute_transaction_rejected.scen.json"); +} + +#[test] +fn execute_transaction_success_go() { + multiversx_sc_scenario::run_go("mandos/execute_transaction_success.scen.json"); +} + +#[test] +fn get_next_pending_tx_go() { + multiversx_sc_scenario::run_go("mandos/get_next_pending_tx.scen.json"); +} + +#[test] +fn get_next_tx_batch_go() { + multiversx_sc_scenario::run_go("mandos/get_next_tx_batch.scen.json"); +} + +#[test] +fn get_next_tx_batch_too_early_go() { + multiversx_sc_scenario::run_go("mandos/get_next_tx_batch_too_early.scen.json"); +} + +#[test] +fn setup_accounts_go() { + multiversx_sc_scenario::run_go("mandos/setup_accounts.scen.json"); +} + +#[test] +fn zero_fees_go() { + multiversx_sc_scenario::run_go("mandos/zero_fees.scen.json"); +} diff --git a/esdt-safe/wasm/Cargo.toml b/esdt-safe/wasm/Cargo.toml index 158c09f7..d49dabc8 100644 --- a/esdt-safe/wasm/Cargo.toml +++ b/esdt-safe/wasm/Cargo.toml @@ -1,30 +1,25 @@ [package] name = "esdt-safe-wasm" version = "0.0.0" -authors = [ "you",] +authors = ["you"] edition = "2018" publish = false [lib] -crate-type = [ "cdylib",] +crate-type = ["cdylib"] [workspace] -members = [ ".",] +members = ["."] [dev-dependencies] - [profile.release] codegen-units = 1 opt-level = "z" lto = true debug = false panic = "abort" - [dependencies.esdt-safe] -features = [ "wasm-output-mode",] -default-features = false path = ".." -[dependencies.elrond-wasm-output] -version = "0.18" -features = [ "wasm-output-mode",] +[dependencies.multiversx-sc-wasm-adapter] +version = "0.41.3" diff --git a/esdt-safe/wasm/src/lib.rs b/esdt-safe/wasm/src/lib.rs index 89f41dec..bb0d544d 100644 --- a/esdt-safe/wasm/src/lib.rs +++ b/esdt-safe/wasm/src/lib.rs @@ -1,4 +1,55 @@ +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 31 +// Async Callback (empty): 1 +// Total number of exported functions: 33 + #![no_std] +#![feature(alloc_error_handler, lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + esdt_safe + ( + setTransactionBatchStatus + addRefundBatch + createTransaction + claimRefund + getRefundAmounts + setFeeEstimatorContractAddress + setEthTxGasLimit + setDefaultPricePerGasUnit + setTokenTicker + calculateRequiredFee + getFeeEstimatorContractAddress + getDefaultPricePerGasUnit + getEthTxGasLimit + distributeFees + addTokenToWhitelist + removeTokenFromWhitelist + getAllKnownTokens + getAccumulatedTransactionFees + setMaxTxBatchSize + setMaxTxBatchBlockDuration + getCurrentTxBatch + getFirstBatchAnyStatus + getBatch + getBatchStatus + getFirstBatchId + getLastBatchId + setMaxBridgedAmount + getMaxBridgedAmount + pause + unpause + isPaused + ) +} -pub use esdt_safe::*; -pub use elrond_wasm_output::*; +multiversx_sc_wasm_adapter::empty_callback! {} diff --git a/multi-transfer-esdt/.gitignore b/multi-transfer-esdt/.gitignore index eaf5915b..9494cb14 100644 --- a/multi-transfer-esdt/.gitignore +++ b/multi-transfer-esdt/.gitignore @@ -3,5 +3,5 @@ /target/ */target/ -# The erdpy output +# The mxpy output output diff --git a/multi-transfer-esdt/Cargo.toml b/multi-transfer-esdt/Cargo.toml index e08586eb..18c9455e 100644 --- a/multi-transfer-esdt/Cargo.toml +++ b/multi-transfer-esdt/Cargo.toml @@ -1,38 +1,25 @@ [package] name = "multi-transfer-esdt" version = "0.0.0" -authors = [ "you",] +authors = ["you"] edition = "2018" publish = false [lib] path = "src/lib.rs" - -[features] -wasm-output-mode = [ - "elrond-wasm-node", - "fee-estimator-module/wasm-output-mode", - "token-module/wasm-output-mode", -] - [dependencies.transaction] path = "../common/transaction" -[dependencies.fee-estimator-module] -path = "../common/fee-estimator-module" - -[dependencies.token-module] -path = "../common/token-module" - -[dependencies.elrond-wasm] -version = "0.18" +[dependencies.tx-batch-module] +path = "../common/tx-batch-module" -[dependencies.elrond-wasm-derive] -version = "0.18" +[dependencies.max-bridged-amount-module] +path = "../common/max-bridged-amount-module" -[dependencies.elrond-wasm-node] -version = "0.18" -optional = true +[dependencies.bridged-tokens-wrapper] +path = "../bridged-tokens-wrapper" -[dev-dependencies.elrond-wasm-debug] -version = "0.18" +[dependencies.multiversx-sc] +version = "0.41.3" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/multi-transfer-esdt/abi/Cargo.toml b/multi-transfer-esdt/abi/Cargo.toml deleted file mode 100644 index 0ed12be8..00000000 --- a/multi-transfer-esdt/abi/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "multi-transfer-esdt-abi" -version = "0.0.0" -authors = [ "you",] -edition = "2018" -publish = false - -[dev-dependencies] - -[dependencies.multi-transfer-esdt] -path = ".." - -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-debug] -version = "0.18" diff --git a/multi-transfer-esdt/abi/src/main.rs b/multi-transfer-esdt/abi/src/main.rs deleted file mode 100644 index aea94ec8..00000000 --- a/multi-transfer-esdt/abi/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - elrond_wasm_debug::abi_json::print_abi::(); -} diff --git a/multi-transfer-esdt/interaction/snippets.sh b/multi-transfer-esdt/interaction/snippets.sh index 2742f77f..32996d89 100644 --- a/multi-transfer-esdt/interaction/snippets.sh +++ b/multi-transfer-esdt/interaction/snippets.sh @@ -1,7 +1,7 @@ -ALICE="/home/elrond/elrond-sdk/erdpy/testnet/wallets/users/alice.pem" -BOB="/home/elrond/elrond-sdk/erdpy/testnet/wallets/users/bob.pem" -ADDRESS=$(erdpy data load --key=address-testnet-multi-transfer-esdt) -DEPLOY_TRANSACTION=$(erdpy data load --key=deployTransaction-testnet) +ALICE="/home/elrond/elrond-sdk/mxpy/testnet/wallets/users/alice.pem" +BOB="/home/elrond/elrond-sdk/mxpy/testnet/wallets/users/bob.pem" +ADDRESS=$(mxpy data load --key=address-testnet-multi-transfer-esdt) +DEPLOY_TRANSACTION=$(mxpy data load --key=deployTransaction-testnet) PROXY=https://testnet-gateway.elrond.com CHAIN_ID=T @@ -15,31 +15,31 @@ WRAPPED_EGLD_TOKEN_ID=0x WRAPPED_ETH_TOKEN_ID=0x deploy() { - erdpy --verbose contract deploy --project=${PROJECT} \ + mxpy --verbose contract deploy --project=${PROJECT} \ --arguments ${WRAPPED_EGLD_TOKEN_ID} ${WRAPPED_ETH_TOKEN_ID} \ --recall-nonce --pem=${ALICE} --gas-limit=100000000 --send \ --outfile="deploy-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return - TRANSACTION=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['hash']") - ADDRESS=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['address']") + TRANSACTION=$(mxpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['hash']") + ADDRESS=$(mxpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['address']") - erdpy data store --key=address-testnet --value=${ADDRESS} - erdpy data store --key=deployTransaction-testnet-multi-transfer-esdt --value=${TRANSACTION} + mxpy data store --key=address-testnet --value=${ADDRESS} + mxpy data store --key=deployTransaction-testnet-multi-transfer-esdt --value=${TRANSACTION} echo "" echo "Smart contract address: ${ADDRESS}" } upgrade() { - erdpy --verbose contract upgrade ${ADDRESS} --project=${PROJECT} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract upgrade ${ADDRESS} --project=${PROJECT} --recall-nonce --pem=${ALICE} \ --gas-limit=100000000 --send --outfile="upgrade.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return } setLocalRolesWrappedEgld() { local LOCAL_MINT_ROLE=0x45534454526f6c654c6f63616c4d696e74 # "ESDTRoleLocalMint" - local ADDRESS_HEX = $(erdpy wallet bech32 --decode ${ADDRESS}) + local ADDRESS_HEX = $(mxpy wallet bech32 --decode ${ADDRESS}) - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=60000000 --function="setSpecialRole" \ --arguments ${WRAPPED_EGLD_TOKEN_ID} ${ADDRESS_HEX} ${LOCAL_MINT_ROLE} \ --send --proxy=${PROXY} --chain=${CHAIN_ID} @@ -47,9 +47,9 @@ setLocalRolesWrappedEgld() { setLocalRolesWrappedEth() { local LOCAL_MINT_ROLE=0x45534454526f6c654c6f63616c4d696e74 # "ESDTRoleLocalMint" - local ADDRESS_HEX = $(erdpy wallet bech32 --decode ${ADDRESS}) + local ADDRESS_HEX = $(mxpy wallet bech32 --decode ${ADDRESS}) - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=60000000 --function="setSpecialRole" \ --arguments ${WRAPPED_ETH_TOKEN_ID} ${ADDRESS} ${LOCAL_MINT_ROLE} \ --send --proxy=${PROXY} --chain=${CHAIN_ID} @@ -60,7 +60,7 @@ transferEsdtToken() { local TOKEN_ID = WRAPPED_ETH_TOKEN_ID local AMOUNT = 0x05 - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ + mxpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ --gas-limit=10000000 --function="transferEsdtToken" \ --arguments ${DEST_ADDRESS} ${TOKEN_ID} ${AMOUNT} --send --proxy=${PROXY} --chain=${CHAIN_ID} } diff --git a/multi-transfer-esdt/mandos/batch_transfer_both_executed.scen.json b/multi-transfer-esdt/mandos/batch_transfer_both_executed.scen.json index dfac8a5d..e64ad857 100644 --- a/multi-transfer-esdt/mandos/batch_transfer_both_executed.scen.json +++ b/multi-transfer-esdt/mandos/batch_transfer_both_executed.scen.json @@ -14,8 +14,9 @@ "value": "0", "function": "batchTransferEsdtToken", "arguments": [ - "address:user1|nested:str:BRIDGE-123456|biguint:100,200", - "address:user2|nested:str:WRAPPED-123456|biguint:500" + "1", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:BRIDGE-123456|biguint:100,200|u64:1", + "0x0102030405060708091011121314151617181920|address:user2|nested:str:WRAPPED-123456|biguint:500|u64:2" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -23,15 +24,7 @@ "expect": { "status": "0", "message": "", - "out": [ - "1", - "str:GWEI", - "str:BRIDGE", - "10", - "0", - "3", - "3" - ], + "out": [], "gas": "*", "refund": "*" } @@ -43,7 +36,7 @@ "nonce": "0", "balance": "0", "esdt": { - "str:BRIDGE-123456": "200" + "str:BRIDGE-123456": "100,200" }, "storage": {} }, @@ -55,29 +48,6 @@ }, "storage": {} }, - "sc:multi_transfer_esdt": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:BRIDGE-123456": { - "balance": "100,000", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:WRAPPED-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "100,000", - "+": "" - }, - "code": "file:../output/multi-transfer-esdt.wasm" - }, "+": {} } } diff --git a/multi-transfer-esdt/mandos/batch_transfer_both_failed.scen.json b/multi-transfer-esdt/mandos/batch_transfer_both_failed.scen.json index d379fc49..a9b34ac0 100644 --- a/multi-transfer-esdt/mandos/batch_transfer_both_failed.scen.json +++ b/multi-transfer-esdt/mandos/batch_transfer_both_failed.scen.json @@ -14,8 +14,9 @@ "value": "0", "function": "batchTransferEsdtToken", "arguments": [ - "sc:multi_transfer_esdt|nested:str:BRIDGE-123456|biguint:100,200", - "sc:multi_transfer_esdt|nested:str:WRAPPED-123456|biguint:100,500" + "1", + "0x0102030405060708091011121314151617181920|sc:multi_transfer_esdt|nested:str:BRIDGE-123456|biguint:100,200|u64:1", + "0x0102030405060708091011121314151617181920|sc:multi_transfer_esdt|nested:str:WRAPPED-123456|biguint:100,500|u64:2" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -23,13 +24,86 @@ "expect": { "status": "0", "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scQuery", + "txId": "get-current-refund-tx-batch", + "tx": { + "to": "sc:multi_transfer_esdt", + "function": "getFirstBatchAnyStatus", + "arguments": [] + }, + "expect": { "out": [ - "4", - "4" + "1", + + "0", + "1", + "0x0102030405060708091011121314151617181920", + "sc:multi_transfer_esdt", + "str:BRIDGE-123456", + "100,200", + + "0", + "2", + "0x0102030405060708091011121314151617181920", + "sc:multi_transfer_esdt", + "str:WRAPPED-123456", + "100,500" + ] + } + }, + { + "step": "scCall", + "txId": "clear-refund-batch", + "tx": { + "from": "address:owner", + "to": "sc:multi_transfer_esdt", + "value": "0", + "function": "getAndClearFirstRefundBatch", + "arguments": [], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [ + "1", + + "0", + "1", + "0x0102030405060708091011121314151617181920", + "sc:multi_transfer_esdt", + "str:BRIDGE-123456", + "100,200", + + "0", + "2", + "0x0102030405060708091011121314151617181920", + "sc:multi_transfer_esdt", + "str:WRAPPED-123456", + "100,500" ], "gas": "*", "refund": "*" } + }, + { + "step": "scQuery", + "txId": "get-current-refund-tx-batch-after-clear", + "tx": { + "to": "sc:multi_transfer_esdt", + "function": "getFirstBatchAnyStatus", + "arguments": [] + }, + "expect": { + "out": [] + } } ] } diff --git a/multi-transfer-esdt/mandos/batch_transfer_one_executed_one_failed.scen.json b/multi-transfer-esdt/mandos/batch_transfer_one_executed_one_failed.scen.json index b1e00ae5..7ee4cb31 100644 --- a/multi-transfer-esdt/mandos/batch_transfer_one_executed_one_failed.scen.json +++ b/multi-transfer-esdt/mandos/batch_transfer_one_executed_one_failed.scen.json @@ -14,8 +14,9 @@ "value": "0", "function": "batchTransferEsdtToken", "arguments": [ - "address:user1|nested:str:BRIDGE-123456|biguint:100,200", - "sc:multi_transfer_esdt|nested:str:WRAPPED-123456|biguint:500" + "1", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:BRIDGE-123456|biguint:100,200|u64:1", + "0x0102030405060708091011121314151617181920|sc:multi_transfer_esdt|nested:str:WRAPPED-123456|biguint:500|u64:2" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -23,15 +24,7 @@ "expect": { "status": "0", "message": "", - "out": [ - "1", - "str:GWEI", - "str:BRIDGE", - "10", - "0", - "3", - "4" - ], + "out": [], "gas": "*", "refund": "*" } @@ -43,12 +36,33 @@ "nonce": "0", "balance": "0", "esdt": { - "str:BRIDGE-123456": "200" + "str:BRIDGE-123456": "100,200" }, "storage": {} }, "+": {} } + }, + { + "step": "scQuery", + "txId": "get-current-refund-tx-batch", + "tx": { + "to": "sc:multi_transfer_esdt", + "function": "getFirstBatchAnyStatus", + "arguments": [] + }, + "expect": { + "out": [ + "1", + + "0", + "2", + "0x0102030405060708091011121314151617181920", + "sc:multi_transfer_esdt", + "str:WRAPPED-123456", + "500" + ] + } } ] } diff --git a/multi-transfer-esdt/mandos/batch_transfer_to_frozen_account.scen.json b/multi-transfer-esdt/mandos/batch_transfer_to_frozen_account.scen.json new file mode 100644 index 00000000..53ffbc12 --- /dev/null +++ b/multi-transfer-esdt/mandos/batch_transfer_to_frozen_account.scen.json @@ -0,0 +1,87 @@ +{ + "name": "batch transfer one executed, one failed", + "steps": [ + { + "step": "externalSteps", + "path": "setup_accounts.scen.json" + }, + { + "step": "setState", + "accounts": { + "address:frozen_user": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": { + "instances": [ + { + "nonce": "0", + "balance": "200" + } + ], + "frozen": "true" + } + } + } + } + }, + { + "step": "scCall", + "txId": "batch-transfer-to-frozen-account", + "tx": { + "from": "address:owner", + "to": "sc:multi_transfer_esdt", + "value": "0", + "function": "batchTransferEsdtToken", + "arguments": [ + "1", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:BRIDGE-123456|biguint:100,200|u64:1", + "0x0102030405060708091011121314151617181920|address:frozen_user|nested:str:BRIDGE-123456|biguint:500|u64:2" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:user1": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": "100,200" + }, + "storage": {} + }, + "+": {} + } + }, + { + "step": "scQuery", + "txId": "get-current-refund-tx-batch", + "tx": { + "to": "sc:multi_transfer_esdt", + "function": "getFirstBatchAnyStatus", + "arguments": [] + }, + "expect": { + "out": [ + "1", + "0", + "2", + "0x0102030405060708091011121314151617181920", + "address:frozen_user", + "str:BRIDGE-123456", + "500" + ] + } + } + ] +} \ No newline at end of file diff --git a/multi-transfer-esdt/mandos/batch_transfer_with_wrapping.scen.json b/multi-transfer-esdt/mandos/batch_transfer_with_wrapping.scen.json new file mode 100644 index 00000000..07bb451c --- /dev/null +++ b/multi-transfer-esdt/mandos/batch_transfer_with_wrapping.scen.json @@ -0,0 +1,164 @@ +{ + "name": "batch transfer both executed", + "steps": [ + { + "step": "externalSteps", + "path": "../../bridged-tokens-wrapper/mandos/whitelist_token.scen.json" + }, + { + "step": "setState", + "accounts": { + "address:owner": { + "nonce": "0", + "balance": "0", + "storage": {} + }, + "address:user1": { + "nonce": "0", + "balance": "0", + "storage": {} + }, + "address:user2": { + "nonce": "0", + "balance": "0", + "storage": {} + } + }, + "newAddresses": [ + { + "creatorAddress": "address:owner", + "creatorNonce": "0", + "newAddress": "sc:multi_transfer_esdt" + } + ] + }, + { + "step": "scDeploy", + "txId": "deploy", + "tx": { + "from": "address:owner", + "contractCode": "file:../output/multi-transfer-esdt.wasm", + "value": "0", + "arguments": [], + "gasLimit": "20,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "setState", + "comment": "setting local mint role", + "accounts": { + "sc:multi_transfer_esdt": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": { + "balance": "0", + "roles": [ + "ESDTRoleLocalMint" + ] + }, + "str:USDC-aaaaaa": { + "balance": "0", + "roles": [ + "ESDTRoleLocalMint" + ] + }, + "str:USDC-cccccc": { + "balance": "0", + "roles": [ + "ESDTRoleLocalMint" + ] + } + }, + "storage": { + "str:maxTxBatchSize": "10", + "str:maxTxBatchBlockDuration": "3,600", + "str:firstBatchId": "1", + "str:lastBatchId": "1", + "str:wrappingContractAddress": "sc:bridged_tokens_wrapper" + }, + "code": "file:../output/multi-transfer-esdt.wasm", + "owner": "address:owner" + } + } + }, + { + "step": "scCall", + "txId": "batch-transfer-both-executed", + "tx": { + "from": "address:owner", + "to": "sc:multi_transfer_esdt", + "value": "0", + "function": "batchTransferEsdtToken", + "arguments": [ + "1", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:BRIDGE-123456|biguint:100,200|u64:1", + "0x0102030405060708091011121314151617181920|address:user2|nested:str:USDC-aaaaaa|biguint:500|u64:2", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:USDC-cccccc|biguint:1000|u64:3" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:user1": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:BRIDGE-123456": "100,200", + "str:WUSDC-abcdef": "1,000" + }, + "storage": {} + }, + "address:user2": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:WUSDC-abcdef": "500" + }, + "storage": {} + }, + "sc:bridged_tokens_wrapper": { + "nonce": "0", + "esdt": { + "str:WUSDC-abcdef": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:WUSDC-uvwxyz": { + "balance": "1", + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn" + ] + }, + "str:USDC-aaaaaa": "500", + "str:USDC-cccccc": "1,000" + }, + "storage": "*", + "code": "*" + }, + "+": {} + } + } + ] +} diff --git a/multi-transfer-esdt/mandos/distribute_fees.scen.json b/multi-transfer-esdt/mandos/distribute_fees.scen.json deleted file mode 100644 index 70b90241..00000000 --- a/multi-transfer-esdt/mandos/distribute_fees.scen.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "owner claim fees", - "steps": [ - { - "step": "externalSteps", - "path": "batch_transfer_both_executed.scen.json" - }, - { - "step": "scCall", - "txId": "owner-distribute-fees", - "tx": { - "from": "address:owner", - "to": "sc:multi_transfer_esdt", - "value": "0", - "function": "distributeFees", - "arguments": [ - "address:owner|u64:10000" - ], - "gasLimit": "30,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "out": [], - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:owner": { - "nonce": "*", - "balance": "0", - "esdt": { - "str:BRIDGE-123456": "100,000" - }, - "storage": {} - }, - "+": {} - } - } - ] -} diff --git a/multi-transfer-esdt/mandos/setup_accounts.scen.json b/multi-transfer-esdt/mandos/setup_accounts.scen.json index 26e6f2e2..37240de8 100644 --- a/multi-transfer-esdt/mandos/setup_accounts.scen.json +++ b/multi-transfer-esdt/mandos/setup_accounts.scen.json @@ -1,10 +1,6 @@ { "name": "setup accounts", "steps": [ - { - "step": "externalSteps", - "path": "../../price-aggregator/mandos/get_latest_price_feed.scen.json" - }, { "step": "setState", "accounts": { @@ -39,10 +35,7 @@ "from": "address:owner", "contractCode": "file:../output/multi-transfer-esdt.wasm", "value": "0", - "arguments": [ - "sc:price_aggregator", - "10,000" - ], + "arguments": [], "gasLimit": "20,000,000", "gasPrice": "0" }, @@ -75,202 +68,15 @@ } }, "storage": { - "str:feeEstimatorContractAddress": "sc:price_aggregator", - "str:ethTxGasLimit": "10,000" + "str:maxTxBatchSize": "10", + "str:maxTxBatchBlockDuration": "3,600", + "str:firstBatchId": "1", + "str:lastBatchId": "1" }, "code": "file:../output/multi-transfer-esdt.wasm", "owner": "address:owner" } } - }, - { - "step": "scCall", - "txId": "add-first-token-to-whitelist", - "tx": { - "from": "address:owner", - "to": "sc:multi_transfer_esdt", - "value": "0", - "function": "addTokenToWhitelist", - "arguments": [ - "str:BRIDGE-123456" - ], - "gasLimit": "15,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "scCall", - "txId": "add-second-token-to-whitelist", - "tx": { - "from": "address:owner", - "to": "sc:multi_transfer_esdt", - "value": "0", - "function": "addTokenToWhitelist", - "arguments": [ - "str:WRAPPED-123456" - ], - "gasLimit": "15,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:owner": { - "nonce": "3", - "balance": "0", - "storage": {} - }, - "sc:multi_transfer_esdt": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:BRIDGE-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:WRAPPED-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:tokenWhitelist.value|u32:1": "str:BRIDGE-123456", - "str:tokenWhitelist.value|u32:2": "str:WRAPPED-123456", - "+": "" - }, - "code": "file:../output/multi-transfer-esdt.wasm" - }, - "+": {} - } - }, - { - "step": "scCall", - "txId": "remove-second-token-from-whitelist", - "tx": { - "from": "address:owner", - "to": "sc:multi_transfer_esdt", - "value": "0", - "function": "removeTokenFromWhitelist", - "arguments": [ - "str:WRAPPED-123456" - ], - "gasLimit": "15,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:owner": { - "nonce": "4", - "balance": "0", - "storage": {} - }, - "sc:multi_transfer_esdt": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:BRIDGE-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:WRAPPED-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:tokenWhitelist.value|u32:1": "str:BRIDGE-123456", - "str:tokenWhitelist.value|u32:2": "", - "+": "" - }, - "code": "file:../output/multi-transfer-esdt.wasm" - }, - "+": {} - } - }, - { - "step": "scCall", - "txId": "add-second-token-back-to-whitelist", - "tx": { - "from": "address:owner", - "to": "sc:multi_transfer_esdt", - "value": "0", - "function": "addTokenToWhitelist", - "arguments": [ - "str:WRAPPED-123456" - ], - "gasLimit": "15,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:owner": { - "nonce": "5", - "balance": "0", - "storage": {} - }, - "sc:multi_transfer_esdt": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:BRIDGE-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:WRAPPED-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:tokenWhitelist.value|u32:1": "str:BRIDGE-123456", - "str:tokenWhitelist.value|u32:3": "str:WRAPPED-123456", - "+": "" - }, - "code": "file:../output/multi-transfer-esdt.wasm" - }, - "+": {} - } } ] } diff --git a/multi-transfer-esdt/mandos/transfer_ok.scen.json b/multi-transfer-esdt/mandos/transfer_ok.scen.json index d4c80ecf..85911b69 100644 --- a/multi-transfer-esdt/mandos/transfer_ok.scen.json +++ b/multi-transfer-esdt/mandos/transfer_ok.scen.json @@ -14,7 +14,8 @@ "value": "0", "function": "batchTransferEsdtToken", "arguments": [ - "address:user1|nested:str:BRIDGE-123456|biguint:100,200" + "1", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:BRIDGE-123456|biguint:100,200|u64:1" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -22,14 +23,7 @@ "expect": { "status": "0", "message": "", - "out": [ - "1", - "str:GWEI", - "str:BRIDGE", - "10", - "0", - "3" - ], + "out": [], "gas": "*", "refund": "*" } @@ -41,34 +35,11 @@ "nonce": "0", "balance": "0", "esdt": { - "str:BRIDGE-123456": "200" + "str:BRIDGE-123456": "100,200" }, "storage": {} }, - "sc:multi_transfer_esdt": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:BRIDGE-123456": { - "balance": "100,000", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:WRAPPED-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "100,000", - "+": "" - }, - "code": "file:../output/multi-transfer-esdt.wasm" - }, - "+": {} + "+": "" } } ] diff --git a/multi-transfer-esdt/mandos/two_transfers_same_token.scen.json b/multi-transfer-esdt/mandos/two_transfers_same_token.scen.json index 787e7ca2..4799f41f 100644 --- a/multi-transfer-esdt/mandos/two_transfers_same_token.scen.json +++ b/multi-transfer-esdt/mandos/two_transfers_same_token.scen.json @@ -14,8 +14,9 @@ "value": "0", "function": "batchTransferEsdtToken", "arguments": [ - "address:user1|nested:str:BRIDGE-123456|biguint:100,200", - "address:user1|nested:str:BRIDGE-123456|biguint:100,200" + "1", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:BRIDGE-123456|biguint:100,200|u64:1", + "0x0102030405060708091011121314151617181920|address:user1|nested:str:BRIDGE-123456|biguint:100,200|u64:2" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -23,15 +24,7 @@ "expect": { "status": "0", "message": "", - "out": [ - "1", - "str:GWEI", - "str:BRIDGE", - "10", - "0", - "3", - "3" - ], + "out": [], "gas": "*", "refund": "*" } @@ -43,33 +36,10 @@ "nonce": "0", "balance": "0", "esdt": { - "str:BRIDGE-123456": "400" + "str:BRIDGE-123456": "200,400" }, "storage": {} }, - "sc:multi_transfer_esdt": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:BRIDGE-123456": { - "balance": "200,000", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:WRAPPED-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:accumulatedTransactionFees|nested:str:BRIDGE-123456": "200,000", - "+": "" - }, - "code": "file:../output/multi-transfer-esdt.wasm" - }, "+": {} } } diff --git a/multi-transfer-esdt/meta/Cargo.toml b/multi-transfer-esdt/meta/Cargo.toml new file mode 100644 index 00000000..9a489dd5 --- /dev/null +++ b/multi-transfer-esdt/meta/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "multi-transfer-esdt-meta" +version = "0.0.0" +authors = ["you"] +edition = "2018" +publish = false + +[dev-dependencies] +[dependencies.multi-transfer-esdt] +path = ".." + +[dependencies.multiversx-sc-meta] +version = "0.41.3" diff --git a/multi-transfer-esdt/meta/src/main.rs b/multi-transfer-esdt/meta/src/main.rs new file mode 100644 index 00000000..224a78da --- /dev/null +++ b/multi-transfer-esdt/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::(); +} diff --git a/multi-transfer-esdt/elrond.json b/multi-transfer-esdt/multiversx.json similarity index 100% rename from multi-transfer-esdt/elrond.json rename to multi-transfer-esdt/multiversx.json diff --git a/multi-transfer-esdt/src/lib.rs b/multi-transfer-esdt/src/lib.rs index b95191e3..015d9e42 100644 --- a/multi-transfer-esdt/src/lib.rs +++ b/multi-transfer-esdt/src/lib.rs @@ -1,86 +1,208 @@ #![no_std] -use transaction::TransactionStatus; +multiversx_sc::imports!(); -elrond_wasm::imports!(); +use transaction::{EthTransaction, PaymentsVec, Transaction, TxBatchSplitInFields}; -pub type SingleTransferTuple = (Address, TokenIdentifier, BigUint); +const DEFAULT_MAX_TX_BATCH_SIZE: usize = 10; +const DEFAULT_MAX_TX_BATCH_BLOCK_DURATION: u64 = u64::MAX; -#[elrond_wasm_derive::contract] +#[multiversx_sc::contract] pub trait MultiTransferEsdt: - fee_estimator_module::FeeEstimatorModule + token_module::TokenModule + tx_batch_module::TxBatchModule + max_bridged_amount_module::MaxBridgedAmountModule { #[init] - fn init( - &self, - fee_estimator_contract_address: Address, - eth_tx_gas_limit: Self::BigUint, - #[var_args] token_whitelist: VarArgs, - ) -> SCResult<()> { - self.fee_estimator_contract_address() - .set(&fee_estimator_contract_address); - - for token in token_whitelist.into_vec() { - require!(token.is_valid_esdt_identifier(), "Invalid token ID"); - self.token_whitelist().insert(token); - } + fn init(&self, opt_wrapping_contract_address: OptionalValue) { + self.max_tx_batch_size() + .set_if_empty(DEFAULT_MAX_TX_BATCH_SIZE); + self.max_tx_batch_block_duration() + .set_if_empty(DEFAULT_MAX_TX_BATCH_BLOCK_DURATION); - self.eth_tx_gas_limit().set(ð_tx_gas_limit); + self.set_wrapping_contract_address(opt_wrapping_contract_address); - Ok(()) + // batch ID 0 is considered invalid + self.first_batch_id().set_if_empty(1); + self.last_batch_id().set_if_empty(1); } #[only_owner] #[endpoint(batchTransferEsdtToken)] fn batch_transfer_esdt_token( &self, - #[var_args] transfers: VarArgs>, - ) -> SCResult> { - let mut tx_statuses = Vec::new(); - let mut cached_token_ids = Vec::new(); - let mut cached_prices = Vec::new(); - - for (i, (to, token_id, amount)) in transfers.into_vec().iter().enumerate() { - if to.is_zero() || self.blockchain().is_smart_contract(to) { - tx_statuses.push(TransactionStatus::Rejected); - continue; + batch_id: u64, + transfers: MultiValueEncoded>, + ) { + let mut valid_payments_list = ManagedVec::new(); + let mut valid_dest_addresses_list = ManagedVec::new(); + let mut refund_tx_list = ManagedVec::new(); + + let own_sc_address = self.blockchain().get_sc_address(); + let sc_shard = self.blockchain().get_shard_of_address(&own_sc_address); + + for eth_tx in transfers { + let mut must_refund = false; + if eth_tx.to.is_zero() || self.blockchain().is_smart_contract(ð_tx.to) { + self.transfer_failed_invalid_destination(batch_id, eth_tx.tx_nonce); + must_refund = true; + } else if !self.is_local_role_set(ð_tx.token_id, &EsdtLocalRole::Mint) { + self.transfer_failed_invalid_token(batch_id, eth_tx.tx_nonce); + must_refund = true; + } else if self.is_above_max_amount(ð_tx.token_id, ð_tx.amount) { + self.transfer_over_max_amount(batch_id, eth_tx.tx_nonce); + must_refund = true; + } else if self.is_account_same_shard_frozen(sc_shard, ð_tx.to, ð_tx.token_id) { + self.transfer_failed_frozen_destination_account(batch_id, eth_tx.tx_nonce); + must_refund = true; } - if !self.token_whitelist().contains(token_id) - || !self.is_local_role_set(token_id, &EsdtLocalRole::Mint) - { - tx_statuses.push(TransactionStatus::Rejected); + + if must_refund { + let refund_tx = self.convert_to_refund_tx(eth_tx); + refund_tx_list.push(refund_tx); + continue; } - let queried_fee: Self::BigUint; - let required_fee = match cached_token_ids.iter().position(|&id| id == token_id) { - Some(index) => &cached_prices[index], - None => { - queried_fee = self.calculate_required_fee(token_id); - cached_token_ids.push(token_id); - cached_prices.push(queried_fee.clone()); + self.send() + .esdt_local_mint(ð_tx.token_id, 0, ð_tx.amount); + + // emit event before the actual transfer so we don't have to save the tx_nonces as well + self.transfer_performed_event(batch_id, eth_tx.tx_nonce); - &queried_fee - } - }; + valid_dest_addresses_list.push(eth_tx.to); + valid_payments_list.push(EsdtTokenPayment::new(eth_tx.token_id, 0, eth_tx.amount)); + } - if required_fee >= amount { - tx_statuses.push(TransactionStatus::Rejected); - continue; + let payments_after_wrapping = self.wrap_tokens(valid_payments_list); + self.distribute_payments(valid_dest_addresses_list, payments_after_wrapping); + + self.add_multiple_tx_to_batch(&refund_tx_list); + } + + #[only_owner] + #[endpoint(getAndClearFirstRefundBatch)] + fn get_and_clear_first_refund_batch(&self) -> OptionalValue> { + let opt_current_batch = self.get_first_batch_any_status(); + if matches!(opt_current_batch, OptionalValue::Some(_)) { + let first_batch_id = self.first_batch_id().get(); + let mut first_batch = self.pending_batches(first_batch_id); + + self.clear_first_batch(&mut first_batch); + } + + opt_current_batch + } + + #[only_owner] + #[endpoint(setWrappingContractAddress)] + fn set_wrapping_contract_address(&self, opt_new_address: OptionalValue) { + match opt_new_address { + OptionalValue::Some(sc_addr) => { + require!( + self.blockchain().is_smart_contract(&sc_addr), + "Invalid unwrapping contract address" + ); + + self.wrapping_contract_address().set(&sc_addr); } + OptionalValue::None => self.wrapping_contract_address().clear(), + } + } - let amount_to_send = amount - required_fee; + // private + + fn convert_to_refund_tx(&self, eth_tx: EthTransaction) -> Transaction { + Transaction { + block_nonce: self.blockchain().get_block_nonce(), + nonce: eth_tx.tx_nonce, + from: eth_tx.from.as_managed_buffer().clone(), + to: eth_tx.to.as_managed_buffer().clone(), + token_identifier: eth_tx.token_id, + amount: eth_tx.amount, + is_refund_tx: true, + } + } - self.accumulated_transaction_fees(token_id) - .update(|fees| *fees += required_fee); + fn is_local_role_set(&self, token_id: &TokenIdentifier, role: &EsdtLocalRole) -> bool { + let roles = self.blockchain().get_esdt_local_roles(token_id); - self.send().esdt_local_mint(token_id, 0, amount); - self.send() - .direct(to, token_id, 0, &amount_to_send, &[i as u8]); + roles.has_role(role) + } - tx_statuses.push(TransactionStatus::Executed); + fn is_account_same_shard_frozen( + &self, + sc_shard: u32, + dest_address: &ManagedAddress, + token_id: &TokenIdentifier, + ) -> bool { + let dest_shard = self.blockchain().get_shard_of_address(dest_address); + if sc_shard != dest_shard { + return false; + } + + let token_data = self + .blockchain() + .get_esdt_token_data(dest_address, token_id, 0); + token_data.frozen + } + + fn wrap_tokens(&self, payments: PaymentsVec) -> PaymentsVec { + if self.wrapping_contract_address().is_empty() { + return payments; + } + + self.get_wrapping_contract_proxy_instance() + .wrap_tokens() + .with_multi_token_transfer(payments) + .execute_on_dest_context() + } + + fn distribute_payments( + &self, + dest_addresses: ManagedVec, + payments: PaymentsVec, + ) { + for (dest, p) in dest_addresses.iter().zip(payments.iter()) { + self.send() + .direct_esdt(&dest, &p.token_identifier, 0, &p.amount); } + } - Ok(tx_statuses.into()) + // proxies + + #[proxy] + fn wrapping_contract_proxy( + &self, + sc_address: ManagedAddress, + ) -> bridged_tokens_wrapper::Proxy; + + fn get_wrapping_contract_proxy_instance(&self) -> bridged_tokens_wrapper::Proxy { + self.wrapping_contract_proxy(self.wrapping_contract_address().get()) } + + // storage + + #[view(getWrappingContractAddress)] + #[storage_mapper("wrappingContractAddress")] + fn wrapping_contract_address(&self) -> SingleValueMapper; + + // events + + #[event("transferPerformedEvent")] + fn transfer_performed_event(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + + #[event("transferFailedInvalidDestination")] + fn transfer_failed_invalid_destination(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + + #[event("transferFailedInvalidToken")] + fn transfer_failed_invalid_token(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); + + #[event("transferFailedFrozenDestinationAccount")] + fn transfer_failed_frozen_destination_account( + &self, + #[indexed] batch_id: u64, + #[indexed] tx_id: u64, + ); + + #[event("transferOverMaxAmount")] + fn transfer_over_max_amount(&self, #[indexed] batch_id: u64, #[indexed] tx_id: u64); } diff --git a/multi-transfer-esdt/tests/mandos_go_test.rs b/multi-transfer-esdt/tests/mandos_go_test.rs deleted file mode 100644 index ca927dd7..00000000 --- a/multi-transfer-esdt/tests/mandos_go_test.rs +++ /dev/null @@ -1,34 +0,0 @@ -#[test] -fn batch_transfer_both_executed_go() { - elrond_wasm_debug::mandos_go("mandos/batch_transfer_both_executed.scen.json"); -} - -#[test] -fn batch_transfer_both_failed_go() { - elrond_wasm_debug::mandos_go("mandos/batch_transfer_both_failed.scen.json"); -} - -#[test] -fn batch_transfer_one_executed_one_failed_go() { - elrond_wasm_debug::mandos_go("mandos/batch_transfer_one_executed_one_failed.scen.json"); -} - -#[test] -fn claim_fees_go() { - elrond_wasm_debug::mandos_go("mandos/distribute_fees.scen.json"); -} - -#[test] -fn setup_accounts_go() { - elrond_wasm_debug::mandos_go("mandos/setup_accounts.scen.json"); -} - -#[test] -fn transfer_ok_go() { - elrond_wasm_debug::mandos_go("mandos/transfer_ok.scen.json"); -} - -#[test] -fn two_transfers_same_token_go() { - elrond_wasm_debug::mandos_go("mandos/two_transfers_same_token.scen.json"); -} diff --git a/multi-transfer-esdt/tests/scenario_go_test.rs b/multi-transfer-esdt/tests/scenario_go_test.rs new file mode 100644 index 00000000..cdda9c2b --- /dev/null +++ b/multi-transfer-esdt/tests/scenario_go_test.rs @@ -0,0 +1,34 @@ +#[test] +fn batch_transfer_both_executed_go() { + multiversx_sc_scenario::run_go("mandos/batch_transfer_both_executed.scen.json"); +} + +#[test] +fn batch_transfer_both_failed_go() { + multiversx_sc_scenario::run_go("mandos/batch_transfer_both_failed.scen.json"); +} + +#[test] +fn batch_transfer_one_executed_one_failed_go() { + multiversx_sc_scenario::run_go("mandos/batch_transfer_one_executed_one_failed.scen.json"); +} + +#[test] +fn batch_transfer_to_frozen_account_go() { + multiversx_sc_scenario::run_go("mandos/batch_transfer_to_frozen_account.scen.json"); +} + +#[test] +fn setup_accounts_go() { + multiversx_sc_scenario::run_go("mandos/setup_accounts.scen.json"); +} + +#[test] +fn transfer_ok_go() { + multiversx_sc_scenario::run_go("mandos/transfer_ok.scen.json"); +} + +#[test] +fn two_transfers_same_token_go() { + multiversx_sc_scenario::run_go("mandos/two_transfers_same_token.scen.json"); +} diff --git a/multi-transfer-esdt/wasm/Cargo.lock b/multi-transfer-esdt/wasm/Cargo.lock new file mode 100644 index 00000000..fa0324f9 --- /dev/null +++ b/multi-transfer-esdt/wasm/Cargo.lock @@ -0,0 +1,325 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bridged-tokens-wrapper" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "transaction", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "eth-address" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "max-bridged-amount-module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "multi-transfer-esdt" +version = "0.0.0" +dependencies = [ + "bridged-tokens-wrapper", + "max-bridged-amount-module", + "multiversx-sc", + "transaction", + "tx-batch-module", +] + +[[package]] +name = "multi-transfer-esdt-wasm" +version = "0.0.0" +dependencies = [ + "multi-transfer-esdt", + "multiversx-sc-wasm-adapter", +] + +[[package]] +name = "multiversx-sc" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31561133b64837f1a7835ae4ba96383ab07d9ce401e703ad2a37aef45789a1e7" +dependencies = [ + "bitflags", + "hashbrown", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7638cb46a0e99c636fd55443ac534ff0a5fad0bd772e1037fbac9a75e04c3c9" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", + "wee_alloc", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e976002d51367f16140929c10ee695f95dd8d34c150a45db60d3fcd1328a267a" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23187464277575f8055c92cffbd664592825591f5cebb8dae038d2af04c5e639" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-modules" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb0b06f37396484f01d89295091db401b8b515ab781e3580a6bb333e912591" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af5fee54f1498ec8181593cd54a96ef7436543ad78848e2e805b9b62c27c4095" +dependencies = [ + "multiversx-sc", + "wee_alloc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "transaction" +version = "0.0.0" +dependencies = [ + "eth-address", + "multiversx-sc", +] + +[[package]] +name = "tx-batch-module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "transaction", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/multi-transfer-esdt/wasm/Cargo.toml b/multi-transfer-esdt/wasm/Cargo.toml index 7b55c9ed..8dcc8c0f 100644 --- a/multi-transfer-esdt/wasm/Cargo.toml +++ b/multi-transfer-esdt/wasm/Cargo.toml @@ -1,30 +1,25 @@ [package] name = "multi-transfer-esdt-wasm" version = "0.0.0" -authors = [ "you",] +authors = ["you"] edition = "2018" publish = false [lib] -crate-type = [ "cdylib",] +crate-type = ["cdylib"] [workspace] -members = [ ".",] +members = ["."] [dev-dependencies] - [profile.release] codegen-units = 1 opt-level = "z" lto = true debug = false panic = "abort" - [dependencies.multi-transfer-esdt] -features = [ "wasm-output-mode",] -default-features = false path = ".." -[dependencies.elrond-wasm-output] -version = "0.18" -features = [ "wasm-output-mode",] +[dependencies.multiversx-sc-wasm-adapter] +version = "0.41.3" diff --git a/multi-transfer-esdt/wasm/src/lib.rs b/multi-transfer-esdt/wasm/src/lib.rs index 1e167229..86ea70f1 100644 --- a/multi-transfer-esdt/wasm/src/lib.rs +++ b/multi-transfer-esdt/wasm/src/lib.rs @@ -1,4 +1,38 @@ +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 14 +// Async Callback (empty): 1 +// Total number of exported functions: 16 + #![no_std] +#![feature(alloc_error_handler, lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + multi_transfer_esdt + ( + batchTransferEsdtToken + getAndClearFirstRefundBatch + setWrappingContractAddress + getWrappingContractAddress + setMaxTxBatchSize + setMaxTxBatchBlockDuration + getCurrentTxBatch + getFirstBatchAnyStatus + getBatch + getBatchStatus + getFirstBatchId + getLastBatchId + setMaxBridgedAmount + getMaxBridgedAmount + ) +} -pub use multi_transfer_esdt::*; -pub use elrond_wasm_output::*; +multiversx_sc_wasm_adapter::empty_callback! {} diff --git a/multisig/.gitignore b/multisig/.gitignore index eaf5915b..9494cb14 100644 --- a/multisig/.gitignore +++ b/multisig/.gitignore @@ -3,5 +3,5 @@ /target/ */target/ -# The erdpy output +# The mxpy output output diff --git a/multisig/Cargo.toml b/multisig/Cargo.toml index 63fc25fe..6a8ff88d 100644 --- a/multisig/Cargo.toml +++ b/multisig/Cargo.toml @@ -7,14 +7,6 @@ publish = false [lib] path = "src/lib.rs" - -[features] -wasm-output-mode = [ - "elrond-wasm-node", - "fee-estimator-module/wasm-output-mode", - "token-module/wasm-output-mode", -] - [dependencies.transaction] path = "../common/transaction" @@ -27,8 +19,11 @@ path = "../common/fee-estimator-module" [dependencies.token-module] path = "../common/token-module" -[dependencies.egld-esdt-swap] -path = "../egld-esdt-swap" +[dependencies.tx-batch-module] +path = "../common/tx-batch-module" + +[dependencies.max-bridged-amount-module] +path = "../common/max-bridged-amount-module" [dependencies.esdt-safe] path = "../esdt-safe" @@ -36,15 +31,10 @@ path = "../esdt-safe" [dependencies.multi-transfer-esdt] path = "../multi-transfer-esdt" -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-derive] -version = "0.18" - -[dependencies.elrond-wasm-node] -version = "0.18" -optional = true +[dependencies.multiversx-sc] +version = "0.41.3" -[dev-dependencies.elrond-wasm-debug] -version = "0.18" +[dependencies.multiversx-sc-modules] +version = "0.41.3" +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.3" diff --git a/multisig/abi/src/main.rs b/multisig/abi/src/main.rs deleted file mode 100644 index ad339807..00000000 --- a/multisig/abi/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - elrond_wasm_debug::abi_json::print_abi::(); -} diff --git a/multisig/interaction/README.md b/multisig/interaction/README.md new file mode 100644 index 00000000..54d72848 --- /dev/null +++ b/multisig/interaction/README.md @@ -0,0 +1,22 @@ +## Using snippets script for the bridge SCs + +### Step 1: Update [configs.cfg](config/configs.cfg) +- Update Alice(owner) and initial relayers' pem location. +- Update any other SC setting that you would like to change. + +### Step 2: Run script file: +Run script.sh with a given command. +Available commands are: +- deploy-aggregator +- deploy-wrapper +- deploy-bridge-contracts +- add-relayer +- remove-relayer +- whitelist-token +- set-safe-max-tx +- set-safe-batch-block-duration +- change-quorum +- pause-contracts +- unpause-contracts + +All the commands that are changing any SC settings will automatically update also [configs.cfg](config/configs.cfg). However, there are some points (like token issueing) when the admin will be ask to first update the configs before proceeding with next steps. \ No newline at end of file diff --git a/multisig/interaction/config/aggregator-snippets.sh b/multisig/interaction/config/aggregator-snippets.sh new file mode 100644 index 00000000..3c12fa92 --- /dev/null +++ b/multisig/interaction/config/aggregator-snippets.sh @@ -0,0 +1,43 @@ +deployAggregator() { + CHECK_VARIABLES AGGREGATOR_WASM CHAIN_SPECIFIC_TOKEN ALICE_ADDRESS + + mxpy --verbose contract deploy --bytecode=${AGGREGATOR_WASM} --recall-nonce --pem=${ALICE} \ + --gas-limit=100000000 --arguments 1 0 ${ALICE_ADDRESS} \ + --send --outfile=deploy-price-agregator-testnet.interaction.json --proxy=${PROXY} --chain=${CHAIN_ID} || return + + TRANSACTION=$(mxpy data parse --file="./deploy-price-agregator-testnet.interaction.json" --expression="data['emittedTransactionHash']") + ADDRESS=$(mxpy data parse --file="./deploy-price-agregator-testnet.interaction.json" --expression="data['contractAddress']") + + mxpy data store --key=address-testnet-safe --value=${ADDRESS} + mxpy data store --key=deployTransaction-testnet --value=${TRANSACTION} + + echo "" + echo "Price agregator: ${ADDRESS}" +} + +submitAggregatorBatch() { + CHECK_VARIABLES AGGREGATOR CHAIN_SPECIFIC_TOKEN FEE_AMOUNT NR_DECIMALS_CHAIN_SPECIFIC + + FEE=$(echo "scale=0; $FEE_AMOUNT*10^$NR_DECIMALS_CHAIN_SPECIFIC/1" | bc) + + mxpy --verbose contract call ${AGGREGATOR} --recall-nonce --pem=${ALICE} \ + --gas-limit=15000000 --function="submitBatch" \ + --arguments str:GWEI str:${CHAIN_SPECIFIC_TOKEN_TICKER} ${FEE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} || return +} + +pauseAggregator() { + CHECK_VARIABLES AGGREGATOR + + mxpy --verbose contract call ${AGGREGATOR} --recall-nonce --pem=${ALICE} \ + --gas-limit=5000000 --function="pause" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} || return +} + +unpauseAggregator() { + CHECK_VARIABLES AGGREGATOR + + mxpy --verbose contract call ${AGGREGATOR} --recall-nonce --pem=${ALICE} \ + --gas-limit=5000000 --function="unpause" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} || return +} diff --git a/multisig/interaction/config/configs.cfg b/multisig/interaction/config/configs.cfg new file mode 100644 index 00000000..9b58c555 --- /dev/null +++ b/multisig/interaction/config/configs.cfg @@ -0,0 +1,79 @@ +ESDT_SYSTEM_SC_ADDRESS=erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u + +#============CHAIN============== + +PROXY=https://testnet-gateway.multiversx.com +CHAIN_ID=T + +#============OWNER============== + +ALICE="./wallets/erd1zsha9cvx7gwraytgp740dcjzwy9v5xwnmas77d33uve6sk0rs0vqel4ln5.pem" +ALICE_ADDRESS=erd1zsha9cvx7gwraytgp740dcjzwy9v5xwnmas77d33uve6sk0rs0vqel4ln5 + +#============WASM FILES============== + +AGGREGATOR_WASM="./price-aggregator.wasm" +SAFE_WASM="./esdt-safe.wasm" +BRIDGED_TOKENS_WRAPPER_WASM="./bridged-tokens-wrapper.wasm" +MULTI_TRANSFER_WASM="./multi-transfer-esdt.wasm" +MULTISIG_WASM="./multisig.wasm" + +#============CONTRACT ADDRESSES============== + +AGGREGATOR=erd1qqqqqqqqqqqqqpgqnlxmcs47kunkewcllqh0d72gv5pjhvfvs0vqa57fhf +SAFE=erd1qqqqqqqqqqqqqpgqkhx64czgpc84eftg6q25lrfyv95ndwtcs0vqwusfuh +BRIDGED_TOKENS_WRAPPER=erd1qqqqqqqqqqqqqpgqlgjwk8mpfycxpdf9q2sgzcndtdhdxr5ss0vqgygjmn +MULTI_TRANSFER=erd1qqqqqqqqqqqqqpgqjsh8kss3w67xks7ths5d795q3nz8y52as0vqu0ujzg +MULTISIG=erd1qqqqqqqqqqqqqpgqdcat402y6c62hv07gt04rynjg4668z9fs0vq3qxepp + +#============TOKENS SETTINGS============== +NR_DECIMALS_CHAIN_SPECIFIC=6 +NR_DECIMALS_UNIVERSAL=6 + +UNIVERSAL_TOKEN_TICKER=USDC +UNIVERSAL_TOKEN_DISPLAY_NAME=WrappedUSDC + +CHAIN_SPECIFIC_TOKEN_TICKER=ETHUSDC +CHAIN_SPECIFIC_TOKEN_DISPLAY_NAME=EthereumWrappedUSDC +UNIVERSAL_TOKENS_ALREADY_MINTED=0 + +#============TOKENS TO BE WHITELISTED============== +UNIVERSAL_TOKEN=USDT-92ae4e +CHAIN_SPECIFIC_TOKEN=ETHUSDT-41b065 +ERC20_TOKEN=0xbe388e5276035575e672ce795a87d16976ecaacb + +#============BRIDGE SETTINGS============== + +FEE_AMOUNT=50 # value without decimals +MAX_AMOUNT=100000 # value without decimals + +RELAYER_REQUIRED_STAKE=0 #1000eGLD +SLASH_AMOUNT=0 +QUORUM=3 + +MAX_TX_PER_BATCH=70 +MAX_TX_BLOCK_DURATION_PER_BATCH=100 #10minutes + +#============RELAYERS ADDRESSES============== +RELAYER_ADDR_0=erd1l9hpewk3dd6mz5j0yytjnzqtydukslmsms2d0fn0pg8pc42chqgqk8pm5u +RELAYER_ADDR_1=erd1tfj6fpr5hd5fw8h6efycw42mk3c4wynausntvg6x98kcyn5npkkqmq7t9g +RELAYER_ADDR_2=erd1689ky73s93aptw8ek0wrrzujtus365ygrqgj0hgj4cn09n5k3kuqyhr0j7 +RELAYER_ADDR_3=erd1uae36y2qvrslexcv3mwjqy9lwrlspvkdyjsar2xcj5ctrq7th26q8twvyc +RELAYER_ADDR_4=erd1pkwhtl85ze02ckaf0t6qgycgx863kxyefxyqxumcth0nwtu3a9yqzg2yn2 +RELAYER_ADDR_5=erd197rmyux8ttzuwssh9thsewlzsc766tvpnussqkpew5vm9x63k0kqw56hr7 +RELAYER_ADDR_6=erd1yhqfhsf237uyz4uzzg0duy9hhg8t3j4wtt020c5cy59w2v2jvkgq6nxf0c +RELAYER_ADDR_7=erd1pgvt06tyhlwegkdkzknyhcxfl3wz69wseqeqashm3d83mxjaewzqnug90w +RELAYER_ADDR_8=erd1ujfl0yxff6ey008ef89au4dn486dj549kpjuuu4xxtn9q550x3gqcu6wed +RELAYER_ADDR_9=erd1zsha9cvx7gwraytgp740dcjzwy9v5xwnmas77d33uve6sk0rs0vqel4ln5 + +RELAYER_WALLET0="./walletsRelay/${RELAYER_ADDR_0}.pem" +RELAYER_WALLET1="./walletsRelay/${RELAYER_ADDR_1}.pem" +RELAYER_WALLET2="./walletsRelay/${RELAYER_ADDR_2}.pem" +RELAYER_WALLET3="./walletsRelay/${RELAYER_ADDR_3}.pem" +RELAYER_WALLET4="./walletsRelay/${RELAYER_ADDR_4}.pem" +RELAYER_WALLET5="./walletsRelay/${RELAYER_ADDR_5}.pem" +RELAYER_WALLET6="./walletsRelay/${RELAYER_ADDR_6}.pem" +RELAYER_WALLET7="./walletsRelay/${RELAYER_ADDR_7}.pem" +RELAYER_WALLET8="./walletsRelay/${RELAYER_ADDR_8}.pem" +RELAYER_WALLET9="./walletsRelay/${RELAYER_ADDR_9}.pem" + diff --git a/multisig/interaction/config/helper.cfg b/multisig/interaction/config/helper.cfg new file mode 100644 index 00000000..0d8d105e --- /dev/null +++ b/multisig/interaction/config/helper.cfg @@ -0,0 +1,74 @@ +CONFIG_FILE=$SCRIPTPATH/config/configs.cfg +function confirmation { + FUNC="$1" + echo -e + read -p "Do you want to go on with ${GREEN}"${FUNC}"${NC} (Default No) ? (Yy/Nn)" yn + echo -e + + case $yn in + [Yy]* ) + + echo -e "${GREEN}Proceeding with "${FUNC}"!${NC}" + ${FUNC} + ;; + [Nn]* ) + echo -e "${GREEN}Exiting...${NC}" + ;; + + * ) + echo -e "${GREEN}I'll take that as a no then... ${NC}" + ;; + esac +} + +function confirmation-with-skip { + FUNC="$1" + echo -e + read -p "Do you want to go on with "${FUNC}" (Default No) ? (Yy/Nn)" yn + echo -e + + case $yn in + [Yy]* ) + + echo -e "${GREEN}Proceeding with "${FUNC}"!${NC}" + ${FUNC} + ;; + [Nn]* ) + ;; + + * ) + echo -e "${GREEN}I'll take that as a no then... ${NC}" + ;; + esac +} + +function continue-confirmation { + FUNC="$1" + echo -e + read -p "Enter any key to continue with "${FUNC}"" yn + echo -e + + ${FUNC} +} + +function manual-update-config-file { + source $SCRIPTPATH/config/configs.cfg +} + +function update-config { + TARGET_KEY=$1 + REPLACEMENT_VALUE=$2 + sed "s/^$TARGET_KEY=.*/$TARGET_KEY=$REPLACEMENT_VALUE/" $CONFIG_FILE > $SCRIPTPATH/config/temp.x + mv $SCRIPTPATH/config/temp.x "$CONFIG_FILE" + source $CONFIG_FILE +} + +CHECK_VARIABLES() +{ + VARIABLE_NAMES=("$@") + for VARIABLE_NAME in "${VARIABLE_NAMES[@]}"; do + [ -z "${!VARIABLE_NAME}" ] && echo "$VARIABLE_NAME variable is unset." && VAR_UNSET=true + done + [ -n "$VAR_UNSET" ] && return 1 + return 0 +} \ No newline at end of file diff --git a/multisig/interaction/config/issue-tokens-snippets.sh b/multisig/interaction/config/issue-tokens-snippets.sh new file mode 100644 index 00000000..b800bcde --- /dev/null +++ b/multisig/interaction/config/issue-tokens-snippets.sh @@ -0,0 +1,60 @@ +ESDT_ISSUE_COST=50000000000000000 + +issueUniversalToken() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS ESDT_ISSUE_COST UNIVERSAL_TOKEN_DISPLAY_NAME \ + UNIVERSAL_TOKEN_TICKER NR_DECIMALS_UNIVERSAL + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --value=${ESDT_ISSUE_COST} --function="issue" \ + --arguments str:${UNIVERSAL_TOKEN_DISPLAY_NAME} str:${UNIVERSAL_TOKEN_TICKER} \ + 0 ${NR_DECIMALS_UNIVERSAL} str:canAddSpecialRoles str:true \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +issueChainSpecificToken() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS ESDT_ISSUE_COST CHAIN_SPECIFIC_TOKEN_DISPLAY_NAME \ + CHAIN_SPECIFIC_TOKEN_TICKER NR_DECIMALS_CHAIN_SPECIFIC UNIVERSAL_TOKENS_ALREADY_MINTED + + VALUE_TO_MINT=$(echo "scale=0; $UNIVERSAL_TOKENS_ALREADY_MINTED*10^$NR_DECIMALS_CHAIN_SPECIFIC/1" | bc) + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --value=${ESDT_ISSUE_COST} --function="issue" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN_DISPLAY_NAME} str:${CHAIN_SPECIFIC_TOKEN_TICKER} \ + ${VALUE_TO_MINT} ${NR_DECIMALS_CHAIN_SPECIFIC} str:canAddSpecialRoles str:true \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +transferToSC() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER CHAIN_SPECIFIC_TOKEN + + VALUE_TO_MINT=$(echo "scale=0; $UNIVERSAL_TOKENS_ALREADY_MINTED*10^$NR_DECIMALS_CHAIN_SPECIFIC/1" | bc) + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=5000000 --function="ESDTTransfer" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${VALUE_TO_MINT} str:depositLiquidity \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +setMintRole() { + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="setSpecialRole" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${ALICE_ADDRESS} str:ESDTRoleLocalMint \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +unSetMintRole() { + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="unSetSpecialRole" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${ALICE_ADDRESS} str:ESDTRoleLocalMint \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +mint() { + CHECK_VARIABLES NR_DECIMALS_CHAIN_SPECIFIC ALICE_ADDRESS CHAIN_SPECIFIC_TOKEN + read -p "Amount to mint(without decimals): " AMOUNT_TO_MINT + VALUE_TO_MINT=$(echo "scale=0; $AMOUNT_TO_MINT*10^$NR_DECIMALS_CHAIN_SPECIFIC/1" | bc) + mxpy --verbose contract call ${ALICE_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=300000 --function="ESDTLocalMint" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${VALUE_TO_MINT} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} \ No newline at end of file diff --git a/multisig/interaction/config/menu_functions.cfg b/multisig/interaction/config/menu_functions.cfg new file mode 100644 index 00000000..e7585ccf --- /dev/null +++ b/multisig/interaction/config/menu_functions.cfg @@ -0,0 +1,143 @@ +#!/bin/bash +set -e + +source $SCRIPTPATH/config/aggregator-snippets.sh +source $SCRIPTPATH/config/issue-tokens-snippets.sh +source $SCRIPTPATH/config/multisig-snippets.sh +source $SCRIPTPATH/config/multitransfer-snippets.sh +source $SCRIPTPATH/config/relayers-snippets.sh +source $SCRIPTPATH/config/upgrade-snippets.sh +source $SCRIPTPATH/config/wrapped-snippets.sh +source $SCRIPTPATH/config/safe-snippets.sh + +CHECK_VARIABLES ALICE PROXY CHAIN_ID +function deploy-aggregator { + deployAggregator + update-config AGGREGATOR ${ADDRESS} + confirmation-with-skip unpauseAggregator + + echo -e + echo "Aggregator deployed!" + echo -e +} + +function deploy-wrapper { + deployBridgedTokensWrapper + update-config BRIDGED_TOKENS_WRAPPER ${ADDRESS} +} + +function upgrade-wrapper { + wrapper-upgrade +} + +function upgrade-wrapper-universal-token { + wrapper-updateWrappedToken +} + +function upgrade-wrapper-chain-specific-token { + wrapper-updateWhitelistedToken +} + +function deploy-bridge-contracts { + deploySafe + update-config SAFE ${ADDRESS} + confirmation-with-skip deployMultiTransfer + update-config MULTI_TRANSFER ${ADDRESS} + confirmation-with-skip deployMultisig + update-config MULTISIG ${ADDRESS} + confirmation-with-skip changeChildContractsOwnershipSafe + confirmation-with-skip changeChildContractsOwnershipMultiTransfer +} + +function remove-whitelist-token { + echo -e + echo "Update TOKENS TO BE WHITELISTED section in configs.cfg with the SC" + echo -e + confirmation-with-skip manual-update-config-file + confirmation-with-skip unsetLocalRolesBridgedTokensWrapper + + confirmation-with-skip removeWrappedToken + confirmation-with-skip wrapper-blacklistToken + confirmation-with-skip unsetLocalRolesEsdtSafe + confirmation-with-skip unsetLocalRolesMultiTransferEsdt + confirmation-with-skip clearMapping + confirmation-with-skip removeTokenFromWhitelist +} + +function whitelist-token { + echo -e + echo "PREREQUIREMENTS: BRIDGED_TOKENS_WRAPPER needs to have MINT+BURN role for the UNIVERSAL TOKEN" + echo "Check and update TOKENS SETTINGS section in configs.cfg" + echo -e + confirmation-with-skip manual-update-config-file + + confirmation-with-skip issueUniversalToken + confirmation-with-skip issueChainSpecificToken + + echo -e + echo "Update TOKENS TO BE WHITELISTED section in configs.cfg with the SC" + echo -e + confirmation-with-skip manual-update-config-file + confirmation-with-skip setLocalRolesBridgedTokensWrapper + + confirmation-with-skip transferToSC + confirmation-with-skip addWrappedToken + confirmation-with-skip wrapper-whitelistToken + confirmation-with-skip setLocalRolesEsdtSafe + confirmation-with-skip setLocalRolesMultiTransferEsdt + confirmation-with-skip addMapping + confirmation-with-skip addTokenToWhitelist + echo -e + echo "Update FEE_AMOUNT and MAX_AMOUNT in BRIDGE SETTINGS section in configs.cfg" + echo -e + confirmation-with-skip manual-update-config-file + + confirmation-with-skip submitAggregatorBatch + + confirmation-with-skip esdtSafeSetMaxBridgedAmountForToken + confirmation-with-skip multiTransferEsdtSetMaxBridgedAmountForToken +} + +function change-quorum { + read -p "Quorum: " QUORUM + update-config QUORUM ${QUORUM} + changeQuorum +} + +function set-safe-max-tx { + read -p "New batch size: " BATCH_SIZE + update-config MAX_TX_PER_BATCH ${BATCH_SIZE} + esdtSafeSetMaxTxBatchSize +} + +function set-safe-batch-block-duration { + read -p "New batch block duration: " BLOCK_DURATION + update-config MAX_TX_BLOCK_DURATION_PER_BATCH ${BLOCK_DURATION} + esdtSafeSetMaxTxBatchBlockDuration +} + +function pause-contracts { + confirmation-with-skip pause + confirmation-with-skip pauseEsdtSafe + confirmation-with-skip pauseAggregator + confirmation-with-skip wrapper-pause +} + +function unpause-contracts { + confirmation-with-skip unpause + confirmation-with-skip unpauseEsdtSafe + confirmation-with-skip unpauseAggregator + confirmation-with-skip wrapper-unpause +} + +function set-fee { + confirmation-with-skip submitAggregatorBatch +} + +function mint-chain-specific { + confirmation-with-skip setMintRole + confirmation-with-skip mint + update-config UNIVERSAL_TOKENS_ALREADY_MINTED ${AMOUNT_TO_MINT} + confirmation-with-skip transferToSC + confirmation-with-skip unSetMintRole +} \ No newline at end of file diff --git a/multisig/interaction/config/multisig-snippets.sh b/multisig/interaction/config/multisig-snippets.sh new file mode 100644 index 00000000..2037bfc1 --- /dev/null +++ b/multisig/interaction/config/multisig-snippets.sh @@ -0,0 +1,164 @@ +deployMultisig() { + CHECK_VARIABLES RELAYER_ADDR_0 RELAYER_ADDR_1 RELAYER_ADDR_2 RELAYER_ADDR_3 \ + RELAYER_ADDR_4 RELAYER_ADDR_5 RELAYER_ADDR_6 RELAYER_ADDR_7 RELAYER_ADDR_8 \ + RELAYER_ADDR_9 SAFE MULTI_TRANSFER RELAYER_REQUIRED_STAKE SLASH_AMOUNT QUORUM MULTISIG_WASM + + MIN_STAKE=$(echo "$RELAYER_REQUIRED_STAKE*10^18" | bc) + mxpy --verbose contract deploy --bytecode=${MULTISIG_WASM} --recall-nonce --pem=${ALICE} \ + --gas-limit=200000000 \ + --arguments ${SAFE} ${MULTI_TRANSFER} \ + ${MIN_STAKE} ${SLASH_AMOUNT} ${QUORUM} \ + ${RELAYER_ADDR_0} ${RELAYER_ADDR_1} ${RELAYER_ADDR_2} ${RELAYER_ADDR_3} \ + --send --outfile="deploy-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return + + TRANSACTION=$(mxpy data parse --file="./deploy-testnet.interaction.json" --expression="data['emitted_tx']['hash']") + ADDRESS=$(mxpy data parse --file="./deploy-testnet.interaction.json" --expression="data['contractAddress']") + + mxpy data store --key=address-testnet-multisig --value=${ADDRESS} + mxpy data store --key=deployTransaction-testnet --value=${TRANSACTION} + + echo "" + echo "Multisig contract address: ${ADDRESS}" +} + +changeChildContractsOwnershipSafe() { + CHECK_VARIABLES SAFE MULTISIG + + mxpy --verbose contract call ${SAFE} --recall-nonce --pem=${ALICE} \ + --gas-limit=10000000 --function="ChangeOwnerAddress" \ + --arguments ${MULTISIG} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +changeChildContractsOwnershipMultiTransfer() { + CHECK_VARIABLES MULTI_TRANSFER MULTISIG + + mxpy --verbose contract call ${MULTI_TRANSFER} --recall-nonce --pem=${ALICE} \ + --gas-limit=10000000 --function="ChangeOwnerAddress" \ + --arguments ${MULTISIG} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +clearMapping() { + CHECK_VARIABLES ERC20_TOKEN CHAIN_SPECIFIC_TOKEN MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="clearMapping" \ + --arguments ${ERC20_TOKEN} str:${CHAIN_SPECIFIC_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +addMapping() { + CHECK_VARIABLES ERC20_TOKEN CHAIN_SPECIFIC_TOKEN MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="addMapping" \ + --arguments ${ERC20_TOKEN} str:${CHAIN_SPECIFIC_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +addTokenToWhitelist() { + CHECK_VARIABLES CHAIN_SPECIFIC_TOKEN CHAIN_SPECIFIC_TOKEN_TICKER MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="esdtSafeAddTokenToWhitelist" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} str:${CHAIN_SPECIFIC_TOKEN_TICKER} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +removeTokenFromWhitelist() { + CHECK_VARIABLES CHAIN_SPECIFIC_TOKEN CHAIN_SPECIFIC_TOKEN_TICKER MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="esdtSafeRemoveTokenFromWhitelist" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +esdtSafeSetMaxTxBatchSize() { + CHECK_VARIABLES MAX_TX_PER_BATCH MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=30000000 --function="esdtSafeSetMaxTxBatchSize" \ + --arguments ${MAX_TX_PER_BATCH} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +esdtSafeSetMaxTxBatchBlockDuration() { + CHECK_VARIABLES MAX_TX_BLOCK_DURATION_PER_BATCH MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=30000000 --function="esdtSafeSetMaxTxBatchBlockDuration" \ + --arguments ${MAX_TX_BLOCK_DURATION_PER_BATCH} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +clearMapping() { + CHECK_VARIABLES ERC20_TOKEN CHAIN_SPECIFIC_TOKEN MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="clearMapping" \ + --arguments ${ERC20_TOKEN} str:${CHAIN_SPECIFIC_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +changeQuorum() { + CHECK_VARIABLES QUORUM MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="changeQuorum" \ + --arguments ${QUORUM} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +pause() { + CHECK_VARIABLES MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="pause" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +pauseEsdtSafe() { + CHECK_VARIABLES MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="pauseEsdtSafe" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +unpause() { + CHECK_VARIABLES MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="unpause" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +unpauseEsdtSafe() { + CHECK_VARIABLES MULTISIG + + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="unpauseEsdtSafe" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +esdtSafeSetMaxBridgedAmountForToken() { + CHECK_VARIABLES MAX_AMOUNT NR_DECIMALS_CHAIN_SPECIFIC CHAIN_SPECIFIC_TOKEN MULTISIG + + MAX=$(echo "scale=0; $MAX_AMOUNT*10^$NR_DECIMALS_CHAIN_SPECIFIC/1" | bc) + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="esdtSafeSetMaxBridgedAmountForToken" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${MAX} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +multiTransferEsdtSetMaxBridgedAmountForToken() { + CHECK_VARIABLES MAX_AMOUNT NR_DECIMALS_CHAIN_SPECIFIC CHAIN_SPECIFIC_TOKEN MULTISIG + + MAX=$(echo "scale=0; $MAX_AMOUNT*10^$NR_DECIMALS_CHAIN_SPECIFIC/1" | bc) + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=40000000 --function="multiTransferEsdtSetMaxBridgedAmountForToken" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${MAX} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} diff --git a/multisig/interaction/config/multitransfer-snippets.sh b/multisig/interaction/config/multitransfer-snippets.sh new file mode 100644 index 00000000..38304735 --- /dev/null +++ b/multisig/interaction/config/multitransfer-snippets.sh @@ -0,0 +1,32 @@ +deployMultiTransfer() { + CHECK_VARIABLES MULTI_TRANSFER_WASM BRIDGED_TOKENS_WRAPPER + + mxpy --verbose contract deploy --bytecode=${MULTI_TRANSFER_WASM} --recall-nonce --pem=${ALICE} \ + --gas-limit=100000000 \ + --arguments ${BRIDGED_TOKENS_WRAPPER} --metadata-payable \ + --send --outfile="deploy-multitransfer-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return + + ADDRESS=$(mxpy data parse --file="./deploy-multitransfer-testnet.interaction.json" --expression="data['contractAddress']") + mxpy data store --key=address-testnet-multitransfer --value=${ADDRESS} + + echo "" + echo "Multi transfer contract address: ${ADDRESS}" +} + +setLocalRolesMultiTransferEsdt() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS CHAIN_SPECIFIC_TOKEN MULTI_TRANSFER + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="setSpecialRole" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${MULTI_TRANSFER} str:ESDTRoleLocalMint \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +unsetLocalRolesMultiTransferEsdt() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS CHAIN_SPECIFIC_TOKEN MULTI_TRANSFER + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="unSetSpecialRole" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${MULTI_TRANSFER} str:ESDTRoleLocalMint \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} \ No newline at end of file diff --git a/multisig/interaction/config/relayers-snippets.sh b/multisig/interaction/config/relayers-snippets.sh new file mode 100644 index 00000000..3528a641 --- /dev/null +++ b/multisig/interaction/config/relayers-snippets.sh @@ -0,0 +1,84 @@ +addBoardMember() { + CHECK_VARIABLES MULTISIG + + read -p "Relayer address: " RELAYER_ADDR + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=35000000 --function="addBoardMember" --arguments ${RELAYER_ADDR} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +removeBoardMember() { + CHECK_VARIABLES MULTISIG + + read -p "Relayer address: " RELAYER_ADDR + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${ALICE} \ + --gas-limit=35000000 --function="removeUser" --arguments ${RELAYER_ADDR} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +unstake() { + CHECK_VARIABLES MULTISIG RELAYER_REQUIRED_STAKE + + read -p "Relayer address: " RELAYER_ADDR + MIN_STAKE=$(echo "$RELAYER_REQUIRED_STAKE*10^18" | bc) + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem="./walletsRelay/${RELAYER_ADDR}.pem" \ + --gas-limit=35000000 --function="unstake" \ + --arguments ${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +stake() { + CHECK_VARIABLES MULTISIG RELAYER_REQUIRED_STAKE \ + RELAYER_WALLET0 RELAYER_WALLET1 RELAYER_WALLET2 RELAYER_WALLET3 RELAYER_WALLET4 \ + RELAYER_WALLET5 RELAYER_WALLET6 RELAYER_WALLET7 RELAYER_WALLET8 RELAYER_WALLET9 + + MIN_STAKE=$(echo "$RELAYER_REQUIRED_STAKE*10^18" | bc) + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET0} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET1} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET2} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET3} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET4} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET5} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET6} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET7} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET8} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} + echo "---------------------------------------------------------" + echo "---------------------------------------------------------" + mxpy --verbose contract call ${MULTISIG} --recall-nonce --pem=${RELAYER_WALLET9} \ + --gas-limit=35000000 --function="stake" --value=${MIN_STAKE} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} \ No newline at end of file diff --git a/multisig/interaction/config/safe-snippets.sh b/multisig/interaction/config/safe-snippets.sh new file mode 100644 index 00000000..636c5cbc --- /dev/null +++ b/multisig/interaction/config/safe-snippets.sh @@ -0,0 +1,35 @@ +deploySafe() { + CHECK_VARIABLES SAFE_WASM AGGREGATOR + + mxpy --verbose contract deploy --bytecode=${SAFE_WASM} --recall-nonce --pem=${ALICE} \ + --gas-limit=150000000 \ + --arguments ${AGGREGATOR} 1 \ + --send --outfile="deploy-safe-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return + + TRANSACTION=$(mxpy data parse --file="./deploy-safe-testnet.interaction.json" --expression="data['emittedTransactionHash']") + ADDRESS=$(mxpy data parse --file="./deploy-safe-testnet.interaction.json" --expression="data['contractAddress']") + + mxpy data store --key=address-testnet-safe --value=${ADDRESS} + mxpy data store --key=deployTransaction-testnet --value=${TRANSACTION} + + echo "" + echo "Safe contract address: ${ADDRESS}" +} + +setLocalRolesEsdtSafe() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS CHAIN_SPECIFIC_TOKEN SAFE + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="setSpecialRole" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${SAFE} str:ESDTRoleLocalBurn \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +unsetLocalRolesEsdtSafe() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS CHAIN_SPECIFIC_TOKEN SAFE + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="unSetSpecialRole" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${SAFE} str:ESDTRoleLocalBurn \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} \ No newline at end of file diff --git a/multisig/interaction/config/upgrade-snippets.sh b/multisig/interaction/config/upgrade-snippets.sh new file mode 100644 index 00000000..5eeb077f --- /dev/null +++ b/multisig/interaction/config/upgrade-snippets.sh @@ -0,0 +1,53 @@ +#TODO: check & updates upgrade snippets +deploySafeForUpgrade() { + getAggregatorAddressHex + + local ESDT_SAFE_ETH_TX_GAS_LIMIT=20000 # gives us 200$ for elrond->eth + + mxpy --verbose contract deploy --project=${PROJECT_SAFE} --recall-nonce --pem=${ALICE} \ + --gas-limit=150000000 \ + --arguments 0x${AGGREGATOR_ADDRESS_HEX} ${ESDT_SAFE_ETH_TX_GAS_LIMIT} \ + --send --outfile="deploy-safe-upgrade.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return + + ADDRESS=$(mxpy data parse --file="./deploy-safe-upgrade.interaction.json" --expression="data['contractAddress']") + + echo "" + echo "Safe contract address: ${ADDRESS}" +} + + +upgradeSafeContract() { + getEsdtSafeAddressHex + getAggregatorAddressHex + local ESDT_SAFE_ETH_TX_GAS_LIMIT=20000 + + local NEW_SAFE_BECH=$(mxpy data parse --file="./deploy-safe-upgrade.interaction.json" --expression="data['contractAddress']") + local NEW_SAFE_ADDR=$(mxpy wallet bech32 --decode $NEW_SAFE_BECH) + + + + mxpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=400000000 --function="upgradeChildContractFromSource" \ + --arguments 0x${ESDT_SAFE_ADDRESS_HEX} 0x${NEW_SAFE_ADDR} 0x00 \ + 0x${AGGREGATOR_ADDRESS_HEX} ${ESDT_SAFE_ETH_TX_GAS_LIMIT} \ + --send --outfile="upgradesafe-child-sc-spam.json" --proxy=${PROXY} --chain=${CHAIN_ID} +} + +upgrade() { + mxpy --verbose contract upgrade ${ADDRESS} --project=${PROJECT} --recall-nonce --pem=${ALICE} \ + --gas-limit=100000000 --send --outfile="upgrade.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return +} + +upgradeMultisig() { + getMultiTransferEsdtAddressHex + getEsdtSafeAddressHex + getMultiTransferEsdtAddressHex + + local SLASH_AMOUNT=0x00 # 0 + MIN_STAKE=$(echo "$RELAYER_REQUIRED_STAKE*10^18" | bc) + mxpy --verbose contract upgrade ${ADDRESS} --bytecode=../output/multisig.wasm --recall-nonce --pem=${ALICE} \ + --arguments 0x${ESDT_SAFE_ADDRESS_HEX} 0x${MULTI_TRANSFER_ESDT_ADDRESS_HEX} \ + ${local} ${SLASH_AMOUNT} 0x07 \ + --gas-limit=200000000 --send --outfile="upgrade-multisig.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return + +} \ No newline at end of file diff --git a/multisig/interaction/config/wrapped-snippets.sh b/multisig/interaction/config/wrapped-snippets.sh new file mode 100644 index 00000000..4985d888 --- /dev/null +++ b/multisig/interaction/config/wrapped-snippets.sh @@ -0,0 +1,129 @@ +# 1. deployBridgedTokensWrapper +# 3. setLocalRolesBridgedTokensWrapper # - keep in mind we need to do this with the token owner +# 4. addWrappedToken +# 5. whitelistToken +# If the SC already exists, skip the first step +# If we want to add another chain, do only the last step + +deployBridgedTokensWrapper() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER_WASM + + mxpy --verbose contract deploy --bytecode=${BRIDGED_TOKENS_WRAPPER_WASM} --recall-nonce --pem=${ALICE} \ + --gas-limit=30000000 \ + --send --outfile="deploy-bridged-tokens-wrapper-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return + + TRANSACTION=$(mxpy data parse --file="./deploy-bridged-tokens-wrapper-testnet.interaction.json" --expression="data['emittedTransactionHash']") + ADDRESS=$(mxpy data parse --file="./deploy-bridged-tokens-wrapper-testnet.interaction.json" --expression="data['contractAddress']") + + mxpy data store --key=address-testnet-bridged-tokens-wrapper --value=${ADDRESS} + mxpy data store --key=deployTransaction-testnet --value=${TRANSACTION} + + echo "" + echo "Bridged tokens wrapper SC: ${ADDRESS}" +} + +setLocalRolesBridgedTokensWrapper() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS UNIVERSAL_TOKEN BRIDGED_TOKENS_WRAPPER + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="setSpecialRole" \ + --arguments str:${UNIVERSAL_TOKEN} ${BRIDGED_TOKENS_WRAPPER} str:ESDTRoleLocalMint str:ESDTRoleLocalBurn\ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +unsetLocalRolesBridgedTokensWrapper() { + CHECK_VARIABLES ESDT_SYSTEM_SC_ADDRESS UNIVERSAL_TOKEN BRIDGED_TOKENS_WRAPPER + + mxpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ + --gas-limit=60000000 --function="unSetSpecialRole" \ + --arguments str:${UNIVERSAL_TOKEN} ${BRIDGED_TOKENS_WRAPPER} str:ESDTRoleLocalMint str:ESDTRoleLocalBurn\ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +addWrappedToken() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER UNIVERSAL_TOKEN NR_DECIMALS_UNIVERSAL + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=6000000 --function="addWrappedToken" \ + --arguments str:${UNIVERSAL_TOKEN} ${NR_DECIMALS_UNIVERSAL} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +removeWrappedToken() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER UNIVERSAL_TOKEN + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=6000000 --function="removeWrappedToken" \ + --arguments str:${UNIVERSAL_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +removeWrappedToken() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER UNIVERSAL_TOKEN + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=6000000 --function="removeWrappedToken" \ + --arguments str:${UNIVERSAL_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +wrapper-whitelistToken() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER CHAIN_SPECIFIC_TOKEN NR_DECIMALS_CHAIN_SPECIFIC UNIVERSAL_TOKEN + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=6000000 --function="whitelistToken" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${NR_DECIMALS_CHAIN_SPECIFIC} str:${UNIVERSAL_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +wrapper-blacklistToken() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER CHAIN_SPECIFIC_TOKEN UNIVERSAL_TOKEN + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=6000000 --function="blacklistToken" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +wrapper-updateWrappedToken() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER UNIVERSAL_TOKEN NR_DECIMALS_UNIVERSAL + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=6000000 --function="updateWrappedToken" \ + --arguments str:${UNIVERSAL_TOKEN} ${NR_DECIMALS_UNIVERSAL} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + +wrapper-updateWhitelistedToken() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER CHAIN_SPECIFIC_TOKEN NR_DECIMALS_CHAIN_SPECIFIC + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=6000000 --function="updateWhitelistedToken" \ + --arguments str:${CHAIN_SPECIFIC_TOKEN} ${NR_DECIMALS_CHAIN_SPECIFIC} \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} +} + + +wrapper-unpause() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=5000000 --function="unpause" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} || return +} + +wrapper-pause() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER + + mxpy --verbose contract call ${BRIDGED_TOKENS_WRAPPER} --recall-nonce --pem=${ALICE} \ + --gas-limit=5000000 --function="pause" \ + --send --proxy=${PROXY} --chain=${CHAIN_ID} || return +} + +wrapper-upgrade() { + CHECK_VARIABLES BRIDGED_TOKENS_WRAPPER BRIDGED_TOKENS_WRAPPER_WASM + + mxpy --verbose contract upgrade ${BRIDGED_TOKENS_WRAPPER} --bytecode=${BRIDGED_TOKENS_WRAPPER_WASM} --recall-nonce --pem=${ALICE} \ + --gas-limit=50000000 --send \ + --outfile="upgrade-bridged-tokens-wrapper.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return +} \ No newline at end of file diff --git a/multisig/interaction/script.sh b/multisig/interaction/script.sh new file mode 100755 index 00000000..ed684a2a --- /dev/null +++ b/multisig/interaction/script.sh @@ -0,0 +1,102 @@ +#!/bin/bash +set -e + +#Make script aware of its location +SCRIPTPATH="$( cd "$(dirname -- "$0")" ; pwd -P )" + +source $SCRIPTPATH/config/configs.cfg +source $SCRIPTPATH/config/helper.cfg +source $SCRIPTPATH/config/menu_functions.cfg + +case "$1" in +'deploy-aggregator') + confirmation deploy-aggregator + ;; + +'deploy-wrapper') + confirmation deploy-wrapper + ;; + +'upgrade-wrapper') + confirmation upgrade-wrapper + ;; + +'deploy-bridge-contracts') + echo -e + echo "PREREQUIREMENTS: AGGREGATOR & BRIDGED_TOKENS_WRAPPER deployed" + echo -e + confirmation deploy-bridge-contracts + ;; + +'add-relayer') + confirmation addBoardMember + ;; + +'remove-relayer') + confirmation removeBoardMember + ;; + +'whitelist-token') + echo -e + echo "PREREQUIREMENTS: BRIDGED_TOKENS_WRAPPER needs to have MINT+BURN role for the UNIVERSAL TOKEN" + echo "Check and update TOKENS SETTINGS section in configs.cfg" + source $SCRIPTPATH/config/configs.cfg + echo -e + confirmation whitelist-token + ;; + +'remove-whitelist-token') + echo -e + echo "PREREQUIREMENTS: BRIDGED_TOKENS_WRAPPER needs to have MINT+BURN role for the UNIVERSAL TOKEN" + echo "Check and update TOKENS SETTINGS section in configs.cfg" + source $SCRIPTPATH/config/configs.cfg + echo -e + confirmation remove-whitelist-token + ;; + +'set-safe-max-tx') + confirmation set-safe-max-tx + ;; + +'set-safe-batch-block-duration') + confirmation set-safe-batch-block-duration + ;; + +'change-quorum') + confirmation change-quorum + ;; + +'pause-contracts') + confirmation pause-contracts + ;; + +'unpause-contracts') + confirmation unpause-contracts + ;; + +'set-swap-fee') + confirmation set-fee + ;; + +'mint-chain-specific') + confirmation mint-chain-specific + ;; + +'upgrade-wrapper-universal-token') + confirmation upgrade-wrapper-universal-token + ;; + +'upgrade-wrapper-chain-specific-token') + confirmation upgrade-wrapper-chain-specific-token + ;; + +*) + echo "Usage: Invalid choice: '"$1"'" + echo -e + echo "Choose from:" + echo " { \"deploy-aggregator\", \"deploy-wrapper\", \"upgrade-wrapper\", \"deploy-bridge-contracts\", \"add-relayer\", \"remove-relayer\", \"whitelist-token\", " + echo " \"remove-whitelist-token\", \"set-safe-max-tx\", \"set-safe-batch-block-duration\", \"change-quorum\", \"pause-contracts\", \"unpause-contracts\", " + echo " \"set-swap-fee\", \"mint-chain-specific\", \"upgrade-wrapper-universal-token\", \"upgrade-wrapper-chain-specific-token\" }" + ;; + +esac \ No newline at end of file diff --git a/multisig/interaction/snippets.sh b/multisig/interaction/snippets.sh deleted file mode 100644 index e03037f8..00000000 --- a/multisig/interaction/snippets.sh +++ /dev/null @@ -1,377 +0,0 @@ -# Alice will be the owner of the multisig -# Bob will be the single board member -# Quorum size will be 1 - -# Path towards PEM files -ALICE="/home/elrond/Downloads/devnetWalletKey.pem" -BOB="" - -ADDRESS=erd1qqqqqqqqqqqqqpgq5300tayry6ardyw66azx3tljp3uhl8jq082sluzkm4 -DEPLOY_TRANSACTION=$(erdpy data load --key=deployTransaction-testnet) -PROXY=https://devnet-gateway.elrond.com -CHAIN_ID=D - -RELAYER_REQUIRED_STAKE=0x03e8 # 1000 -ESDT_ISSUE_COST=0xB1A2BC2EC50000 # 0.05 eGLD -ESDT_ISSUE_COST_DECIMAL=50000000000000000 - -# Addresses in Hex -BOB_ADDRESS=0x - -RELAYER_ADDR_1=0x1832cec1c79f80ba75e51d9f1e05a7691b802299d30ea803a5ece42395658f89 -RELAYER_ADDR_2=0xa02c0f0ec7cddf4cb6cd80a7a210e3c3b5905d692bbb6f5deea8c0f2c93b92ca -RELAYER_ADDR_3=0x5c0601a6949cca981207aaf1accb44d4b7ac1bef74f5d0e33a36c0efdff647ae - -ESDT_SYSTEM_SC_ADDRESS=erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u - -# Setup and aggregator first, then put its address hex-encoded in this variable -AGGREGATOR_ADDRESS=0x0000000000000000050081d0b65d6bd5bd7d5af6df1a26e89513c6f38cd5e3df - -######################################################################### -################## Update after issueing the tokens ##################### -######################################################################### -WRAPPED_EGLD_TOKEN_ID=0x45474c442d316138626639 -WRAPPED_ETH_TOKEN_ID=0x4554482d353063336133 - -deploy() { - local SLASH_AMOUNT=0x01f4 # 500 - - erdpy --verbose contract deploy --project=${PROJECT} --recall-nonce --pem=${ALICE} \ - --gas-limit=400000000 \ - --arguments ${RELAYER_REQUIRED_STAKE} ${SLASH_AMOUNT} 0x02 \ - ${RELAYER_ADDR_1} ${RELAYER_ADDR_2} ${RELAYER_ADDR_3} \ - --send --outfile="deploy-testnet.interaction.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return - - TRANSACTION=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['hash']") - ADDRESS=$(erdpy data parse --file="deploy-testnet.interaction.json" --expression="data['emitted_tx']['address']") - - erdpy data store --key=address-testnet-multisig --value=${ADDRESS} - erdpy data store --key=deployTransaction-testnet --value=${TRANSACTION} - - echo "" - echo "Smart contract address: ${ADDRESS}" -} - -upgrade() { - erdpy --verbose contract upgrade ${ADDRESS} --project=${PROJECT} --recall-nonce --pem=${ALICE} \ - --gas-limit=100000000 --send --outfile="upgrade.json" --proxy=${PROXY} --chain=${CHAIN_ID} || return -} - -deployChildContracts() { - local EGLD_ESDT_SWAP_CODE=0x"$(xxd -p ../egld-esdt-swap/output/egld-esdt-swap.wasm | tr -d '\n')" - local ESDT_SAFE_CODE=0x"$(xxd -p ../esdt-safe/output/esdt-safe.wasm | tr -d '\n')" - local MULTI_TRANSFER_ESDT_CODE=0x"$(xxd -p ../multi-transfer-esdt/output/multi-transfer-esdt.wasm | tr -d '\n')" - - local ESDT_SAFE_ETH_TX_GAS_LIMIT=150000 - local MULTI_TRANSFER_ESDT_TX_GAS_LIMIT=10000 - - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=400000000 --function="deployChildContracts" \ - --arguments ${EGLD_ESDT_SWAP_CODE} ${MULTI_TRANSFER_ESDT_CODE} ${ESDT_SAFE_CODE} \ - ${AGGREGATOR_ADDRESS} ${ESDT_SAFE_ETH_TX_GAS_LIMIT} ${MULTI_TRANSFER_ESDT_TX_GAS_LIMIT} \ - ${WRAPPED_EGLD_TOKEN_ID} ${WRAPPED_ETH_TOKEN_ID} \ - --send --outfile="deploy-child-sc-spam.json" --proxy=${PROXY} --chain=${CHAIN_ID} -} - -stake() { - local RELAYER_REQUIRED_STAKE_DECIMAL=1000 - - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=35000000 --function="stake" --value=${RELAYER_REQUIRED_STAKE_DECIMAL} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -unstake() { - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=35000000 --function="unstake" \ - --arguments ${RELAYER_REQUIRED_STAKE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -addMapping() { - local WRAPPED_EGLD_ERC20=0xC06606b0248F56aA93DB3236dB0bED97B9Ad1135 - local WRAPPED_ETH_ERC20=0x1F3ff2dA93DB23be6F73696950701F5cE471D7d4 - - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=40000000 --function="addMapping" \ - --arguments ${WRAPPED_EGLD_ERC20} ${WRAPPED_EGLD_TOKEN_ID} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} - - sleep 10 - - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=40000000 --function="addMapping" \ - --arguments ${WRAPPED_ETH_ERC20} ${WRAPPED_ETH_TOKEN_ID} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -changeQuorum() { - local NEW_QUORUM=0x02 - - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=40000000 --function="changeQuorum" \ - --arguments ${NEW_QUORUM} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -# Issue Tokens - -issueWrappedEgld() { - local TOKEN_DISPLAY_NAME=0x5772617070656445676c64 # "WrappedEgld" - local TOKEN_TICKER=0x45474c44 # "EGLD" - local INITIAL_SUPPLY=0x01 # 1 - local NR_DECIMALS=0x12 # 18 - local CAN_ADD_SPECIAL_ROLES=0x63616e4164645370656369616c526f6c6573 # "canAddSpecialRoles" - local TRUE=0x74727565 # "true" - - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --value=${ESDT_ISSUE_COST_DECIMAL} --function="issue" \ - --arguments ${TOKEN_DISPLAY_NAME} ${TOKEN_TICKER} ${INITIAL_SUPPLY} ${NR_DECIMALS} ${CAN_ADD_SPECIAL_ROLES} ${TRUE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -issueWrappedEth() { - local TOKEN_DISPLAY_NAME=0x57726170706564457468 # "WrappedEth" - local TOKEN_TICKER=0x455448 # "ETH" - local INITIAL_SUPPLY=0x01 # 1 - local NR_DECIMALS=0x12 # 18 - local CAN_ADD_SPECIAL_ROLES=0x63616e4164645370656369616c526f6c6573 # "canAddSpecialRoles" - local TRUE=0x74727565 # "true" - - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --value=${ESDT_ISSUE_COST_DECIMAL} --function="issue" \ - --arguments ${TOKEN_DISPLAY_NAME} ${TOKEN_TICKER} ${INITIAL_SUPPLY} ${NR_DECIMALS} ${CAN_ADD_SPECIAL_ROLES} ${TRUE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -# Set Local Roles - -setLocalRolesEgldEsdtSwap() { - getEgldEsdtSwapAddress - bech32ToHex ${EGLD_ESDT_SWAP_ADDRESS} - - local LOCAL_MINT_ROLE=0x45534454526f6c654c6f63616c4d696e74 # "ESDTRoleLocalMint" - local LOCAL_BURN_ROLE=0x45534454526f6c654c6f63616c4275726e # "ESDTRoleLocalBurn" - - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --function="setSpecialRole" \ - --arguments ${WRAPPED_EGLD_TOKEN_ID} 0x${ADDRESS_HEX} ${LOCAL_MINT_ROLE} ${LOCAL_BURN_ROLE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -# Note: increase sleep time if needed -setLocalRolesEsdtSafe() { - getEsdtSafeAddress - bech32ToHex ${ESDT_SAFE_ADDRESS} - - local LOCAL_BURN_ROLE=0x45534454526f6c654c6f63616c4275726e # "ESDTRoleLocalBurn" - - # set roles for WrappedEgld - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --function="setSpecialRole" \ - --arguments ${WRAPPED_EGLD_TOKEN_ID} 0x${ADDRESS_HEX} ${LOCAL_BURN_ROLE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} - - sleep 10 - - # set roles for WrappedEth - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --function="setSpecialRole" \ - --arguments ${WRAPPED_ETH_TOKEN_ID} 0x${ADDRESS_HEX} ${LOCAL_BURN_ROLE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -# Note: increase sleep time if needed -setLocalRolesMultiTransferEsdt() { - getMultiTransferEsdtAddress - bech32ToHex ${MULTI_TRANSFER_ESDT_ADDRESS} - - local LOCAL_MINT_ROLE=0x45534454526f6c654c6f63616c4d696e74 # "ESDTRoleLocalMint" - - # set roles for WrappedEgld - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --function="setSpecialRole" \ - --arguments ${WRAPPED_EGLD_TOKEN_ID} 0x${ADDRESS_HEX} ${LOCAL_MINT_ROLE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} - - sleep 10 - - # set roles for WrappedEth - erdpy --verbose contract call ${ESDT_SYSTEM_SC_ADDRESS} --recall-nonce --pem=${ALICE} \ - --gas-limit=60000000 --function="setSpecialRole" \ - --arguments ${WRAPPED_ETH_TOKEN_ID} 0x${ADDRESS_HEX} ${LOCAL_MINT_ROLE} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -# MultiTransferEsdtCalls - -transferEsdt() { - local BATCH_ID = 0x01 - local DEST = ${CAROL_ADDRESS} - local TOKEN_ID = ${WRAPPED_ETH_TOKEN_ID} - local AMOUNT = 0x0A - - # Bob proposes action - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=25000000 --function="proposeMultiTransferEsdtBatch" \ - --arguments ${BATCH_ID} ${DEST} ${TOKEN_ID} ${AMOUNT} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} - - sleep 10 - - # Bob signs the action - getActionLastIndex - bobSign - sleep 10 - - # Bob executes the action - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=100000000 --function="performAction" \ - --arguments ${ACTION_INDEX} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -fetchNextTransactionBatch() { - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=25000000 --function="fetchNextTransactionBatch" \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -setTransactionExecuted() { - local RELAYER_REWARD_ADDRESS = ${BOB_ADDRESS} - local TX_STATUS = 0x03 - - # Bob proposes action - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=25000000 --function="proposeEsdtSafeSetCurrentTransactionBatchStatus" \ - --arguments ${RELAYER_REWARD_ADDRESS} ${TX_STATUS} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} - - # Bob signs the action - getActionLastIndex - bobSign - sleep 10 - - # Bob executes the action - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=100000000 --function="performAction" \ - --arguments ${ACTION_INDEX} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -setTransactionRejected() { - local RELAYER_REWARD_ADDRESS = ${BOB_ADDRESS} - local TX_STATUS = 0x04 - - # Bob proposes action - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=25000000 --function="proposeEsdtSafeSetCurrentTransactionBatchStatus" \ - --arguments ${RELAYER_REWARD_ADDRESS} ${TX_STATUS} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} - - # Bob signs the action - getActionLastIndex - bobSign - sleep 10 - - # Bob executes the action - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=100000000 --function="performAction" \ - --arguments ${ACTION_INDEX} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -# views - -getEgldEsdtSwapAddress() { - local QUERY_OUTPUT=$(erdpy --verbose contract query ${ADDRESS} --function="getEgldEsdtSwapAddress" --proxy=${PROXY}) - parseQueryOutput - parsedAddressToBech32 - - EGLD_ESDT_SWAP_ADDRESS=${ADDRESS_BECH32} - echo "EgldEsdtSwap address: ${EGLD_ESDT_SWAP_ADDRESS}" -} - -getEsdtSafeAddress() { - local QUERY_OUTPUT=$(erdpy --verbose contract query ${ADDRESS} --function="getEsdtSafeAddress" --proxy=${PROXY}) - parseQueryOutput - parsedAddressToBech32 - - ESDT_SAFE_ADDRESS=${ADDRESS_BECH32} - echo "EsdtSafe address: ${ESDT_SAFE_ADDRESS}" -} - -getMultiTransferEsdtAddress() { - local QUERY_OUTPUT=$(erdpy --verbose contract query ${ADDRESS} --function="getMultiTransferEsdtAddress" --proxy=${PROXY}) - parseQueryOutput - parsedAddressToBech32 - - MULTI_TRANSFER_ESDT_ADDRESS=${ADDRESS_BECH32} - echo "MultiTransferEsdt address: ${MULTI_TRANSFER_ESDT_ADDRESS}" -} - -getActionLastIndex() { - local QUERY_OUTPUT=$(erdpy --verbose contract query ${ADDRESS} --function="getActionLastIndex" --proxy=${PROXY}) - parseQueryOutput - - ACTION_INDEX=0x${PARSED} - - echo "Last action index: ${ACTION_INDEX}" -} - -calculateTxCostInEgld() { - getEsdtSafeAddress - - erdpy --verbose contract call ${ESDT_SAFE_ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=50000000 --function="calculateRequiredFee" \ - --arguments ${WRAPPED_EGLD_TOKEN_ID} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} - - #local QUERY_OUTPUT=$(erdpy --verbose contract query ${ESDT_SAFE_ADDRESS} --function="calculateRequiredFee" --arguments ${WRAPPED_EGLD_TOKEN_ID} --proxy=${PROXY}) - #parseQueryOutput - - #COST=0x${PARSED} - - #echo "Last action index: ${COST}" -} - -calculateTxCostInEth() { - getEsdtSafeAddress - - erdpy --verbose contract call ${ESDT_SAFE_ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=50000000 --function="calculateRequiredFee" \ - --arguments ${WRAPPED_ETH_TOKEN_ID} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -manualQuery() { - erdpy --verbose contract call erd1qqqqqqqqqqqqqpgqs8gtvhtt6k7h6khkmudzd6y4z0r08rx4u00svnnxt2 --recall-nonce --pem=${BOB} \ - --gas-limit=50000000 --function="latestPriceFeedOptional" \ - --arguments 0x47574549 0x45474c44 \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -getCurrentBatch() { - erdpy --verbose contract query ${ADDRESS} --function="getCurrentTxBatch" --proxy=${PROXY} -} - -# helpers - -parseQueryOutput() { - PARSED=$(jq -r '.[0].hex' <<< "${QUERY_OUTPUT}") -} - -parsedAddressToBech32() { - ADDRESS_BECH32=$(erdpy wallet bech32 --encode ${PARSED}) -} - -bobSign() { - erdpy --verbose contract call ${ADDRESS} --recall-nonce --pem=${BOB} \ - --gas-limit=25000000 --function="sign" \ - --arguments ${ACTION_INDEX} \ - --send --proxy=${PROXY} --chain=${CHAIN_ID} -} - -bech32ToHex() { - ADDRESS_HEX=$(erdpy wallet bech32 --decode $1) -} diff --git a/multisig/mandos/change_token_config.scen.json b/multisig/mandos/change_token_config.scen.json new file mode 100644 index 00000000..8a9d0c73 --- /dev/null +++ b/multisig/mandos/change_token_config.scen.json @@ -0,0 +1,249 @@ +{ + "name": "change token config", + "steps": [ + { + "step": "externalSteps", + "path": "setup.scen.json" + }, + { + "step": "scCall", + "txId": "change-fee-estimator-sc-address", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "changeFeeEstimatorContractAddress", + "arguments": [ + "sc:egld_esdt_swap" + ], + "gasLimit": "55,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:esdt_safe": { + "nonce": "0", + "balance": "0", + "esdt": "*", + "storage": { + "str:feeEstimatorContractAddress": "sc:egld_esdt_swap", + "+": "" + }, + "code": "*" + }, + "+": {} + } + }, + { + "step": "scCall", + "txId": "change-default-price-per-gas-unit", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "changeDefaultPricePerGasUnit", + "arguments": [ + "str:ETH-123456", + "100" + ], + "gasLimit": "55,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:esdt_safe": { + "nonce": "0", + "balance": "0", + "esdt": "*", + "storage": { + "str:defaultPricePerGasUnit|nested:str:ETH-123456": "100", + "+": "" + }, + "code": "*" + }, + "+": {} + } + }, + { + "step": "scCall", + "txId": "change-token-ticker", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "changeTokenTicker", + "arguments": [ + "str:ETH-123456", + "str:WETH" + ], + "gasLimit": "55,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:esdt_safe": { + "nonce": "0", + "balance": "0", + "esdt": "*", + "storage": { + "str:tokenTicker|nested:str:ETH-123456": "str:WETH", + "+": "" + }, + "code": "*" + }, + "+": {} + } + }, + { + "step": "scCall", + "txId": "add-mapping-1", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "addMapping", + "arguments": [ + "0x0102030405060708091011121314151617181920", + "str:ETH-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "add-mapping-2", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "addMapping", + "arguments": [ + "0x0102030405060708091011121314151617181999", + "str:EGLD-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "add-mapping-invalid", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "addMapping", + "arguments": [ + "0x0000030405060708091011121314151617181999", + "str:EGLD-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Mapping already exists for token ID", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scQuery", + "txId": "query-token-id", + "tx": { + "to": "sc:multisig", + "function": "getTokenIdForErc20Address", + "arguments": [ + "0x0102030405060708091011121314151617181999" + ] + }, + "expect": { + "out": [ + "str:EGLD-123456" + ] + } + }, + { + "step": "scQuery", + "txId": "query-erc20-address", + "tx": { + "to": "sc:multisig", + "function": "getErc20AddressForTokenId", + "arguments": [ + "str:EGLD-123456" + ] + }, + "expect": { + "out": [ + "0x0102030405060708091011121314151617181999" + ] + } + }, + { + "step": "scCall", + "txId": "remove-mapping-invalid", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "clearMapping", + "arguments": [ + "0x0102030405060708091011121314151617181920", + "str:EGLD-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Invalid mapping", + "out": [], + "gas": "*", + "refund": "*" + } + } + ] +} diff --git a/multisig/mandos/create_elrond_to_ethereum_tx_batch.scen.json b/multisig/mandos/create_elrond_to_ethereum_tx_batch.scen.json index 420921ad..1cd5744f 100644 --- a/multisig/mandos/create_elrond_to_ethereum_tx_batch.scen.json +++ b/multisig/mandos/create_elrond_to_ethereum_tx_batch.scen.json @@ -5,58 +5,6 @@ "step": "externalSteps", "path": "setup.scen.json" }, - { - "step": "scCall", - "txId": "wrap-egld", - "tx": { - "from": "address:user", - "to": "sc:egld_esdt_swap", - "value": "2,000,000", - "function": "wrapEgld", - "arguments": [], - "gasLimit": "10,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "out": [], - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:user": { - "nonce": "*", - "balance": "0", - "esdt": { - "str:ETH-123456": "1,000,000", - "str:EGLD-123456": "2,000,000" - }, - "storage": {} - }, - "sc:egld_esdt_swap": { - "nonce": "0", - "balance": "2,000,000", - "esdt": { - "str:EGLD-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint", - "ESDTRoleLocalBurn" - ] - } - }, - "storage": { - "str:wrappedEgldTokenId": "str:EGLD-123456" - }, - "code": "file:../../egld-esdt-swap/output/egld-esdt-swap.wasm" - }, - "+": {} - } - }, { "step": "scCall", "txId": "first-create-transaction", @@ -76,13 +24,7 @@ }, "expect": { "status": "0", - "out": [ - "1", - "str:GWEI", - "str:EGLD", - "10", - "0" - ], + "out": [], "message": "", "gas": "*", "refund": "*" @@ -118,19 +60,18 @@ } }, "storage": { - "str:pendingBatches|u64:0": { + "str:pendingBatches|u64:1|str:.item|u32:1": { "1-block_nonce": "u64:0", "2-nonce": "u64:1", - "3-from": "address:user", - "4-to": "0x0102030405060708091011121314151617181920", + "3-from": "u32:32|address:user", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", "5-token_identifier": "nested:str:EGLD-123456", - "6-amount": "biguint:400" + "6-amount": "biguint:400", + "7-is_refund_tx": "u8:0" }, - "str:firstBatchId": "0", - "str:lastBatchId": "0", - + "str:firstBatchId": "1", + "str:lastBatchId": "1", "str:accumulatedTransactionFees|nested:str:EGLD-123456": "1,500,000", - "+": "" }, "code": "*" @@ -157,13 +98,7 @@ }, "expect": { "status": "0", - "out": [ - "1", - "str:GWEI", - "str:ETH", - "1", - "0" - ], + "out": [], "message": "", "gas": "*", "refund": "*" @@ -199,31 +134,28 @@ } }, "storage": { - "str:pendingBatches|u64:0": [ - { - "1-block_nonce": "u64:0", - "2-nonce": "u64:1", - "3-from": "address:user", - "4-to": "0x0102030405060708091011121314151617181920", - "5-token_identifier": "nested:str:EGLD-123456", - "6-amount": "biguint:400" - }, - { - "1-block_nonce": "u64:0", - "2-nonce": "u64:2", - "3-from": "address:user", - "4-to": "0x0102030405060708091011121314151617181920", - "5-token_identifier": "nested:str:ETH-123456", - "6-amount": "biguint:350,000" - } - ], - "str:firstBatchId": "0", - "str:lastBatchId": "0", - + "str:pendingBatches|u64:1|str:.item|u32:1": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:1", + "3-from": "u32:32|address:user", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:EGLD-123456", + "6-amount": "biguint:400", + "7-is_refund_tx": "u8:0" + }, + "str:pendingBatches|u64:1|str:.item|u32:2": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:2", + "3-from": "u32:32|address:user", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:ETH-123456", + "6-amount": "biguint:350,000", + "7-is_refund_tx": "u8:0" + }, + "str:firstBatchId": "1", + "str:lastBatchId": "1", "str:accumulatedTransactionFees|nested:str:EGLD-123456": "1,500,000", "str:accumulatedTransactionFees|nested:str:ETH-123456": "150,000", - - "+": "" }, "code": "*" @@ -247,15 +179,13 @@ }, "expect": { "out": [ - "0", - + "1", "0", "1", "address:user", "0x0102030405060708091011121314151617181920", "str:EGLD-123456", "400", - "0", "2", "address:user", @@ -267,4 +197,3 @@ } ] } - diff --git a/multisig/mandos/ethereum_to_elrond_tx_batch_ok.scen.json b/multisig/mandos/ethereum_to_elrond_tx_batch_ok.scen.json index 30f283cb..86fe3a03 100644 --- a/multisig/mandos/ethereum_to_elrond_tx_batch_ok.scen.json +++ b/multisig/mandos/ethereum_to_elrond_tx_batch_ok.scen.json @@ -15,8 +15,8 @@ "function": "proposeMultiTransferEsdtBatch", "arguments": [ "1", - "address:user", "str:EGLD-123456", "500,000", - "address:user", "str:ETH-123456", "500,000" + "0x0102030405060708091011121314151617181920", "address:user", "str:EGLD-123456", "500,000", "1", + "0x0102030405060708091011121314151617181920", "address:user", "str:ETH-123456", "500,000", "2" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -39,19 +39,23 @@ "balance": "*", "storage": { "str:action_data.item|u32:1": { - "1-action_type": "u8:7", + "1-action_type": "u8:2", "2-batch_id": "u64:1", "3-transfers_len": "u32:2", "4-transfers_vec": [ { - "1-dest": "address:user", - "2-token_id": "nested:str:EGLD-123456", - "3-amount": "biguint:500,000" + "1-from": "0x0102030405060708091011121314151617181920", + "2-to": "address:user", + "3-token_id": "nested:str:EGLD-123456", + "4-amount": "biguint:500,000", + "5-tx_nonce": "u64:1" }, { - "1-dest": "address:user", - "2-token_id": "nested:str:ETH-123456", - "3-amount": "biguint:500,000" + "1-from": "0x0102030405060708091011121314151617181920", + "2-to": "address:user", + "3-token_id": "nested:str:ETH-123456", + "4-amount": "biguint:500,000", + "5-tx_nonce": "u64:2" } ] }, @@ -84,24 +88,9 @@ "refund": "*" } }, - { - "step": "scQuery", - "txId": "try-query-statuses-before-execution", - "tx": { - "to": "sc:multisig", - "function": "getStatusesAfterExecution", - "arguments": [ - "1" - ] - }, - "expect": { - "out": [] - } - }, { "step": "scCall", "txId": "perform-action-transfer", - "comment": "output is from execute_on_dest_context results being propagated to the initial caller", "tx": { "from": "address:relayer1", "to": "sc:multisig", @@ -116,148 +105,23 @@ "expect": { "status": "0", "message": "", - "out": [ - "1", "str:GWEI", "str:EGLD", "10", "0", - "1", "str:GWEI", "str:ETH", "1", "0", - "3", "3" - ], + "out": [], "gas": "*", "refund": "*" } }, - { - "step": "scQuery", - "txId": "query-statuses", - "tx": { - "to": "sc:multisig", - "function": "getStatusesAfterExecution", - "arguments": [ - "1" - ] - }, - "expect": { - "out": [ - "3", - "3" - ] - } - }, { "step": "checkState", "accounts": { "address:user": { "nonce": "*", - "balance": "2,000,000", - "esdt": { - "str:EGLD-123456": "400,000", - "str:ETH-123456": "1,490,000" - }, - "storage": {} - }, - "sc:multi_transfer": { - "nonce": "0", "balance": "0", "esdt": { - "str:EGLD-123456": { - "balance": "100,000", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:ETH-123456": { - "balance": "10,000", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:accumulatedTransactionFees|nested:str:EGLD-123456": "100,000", - "str:accumulatedTransactionFees|nested:str:ETH-123456": "10,000", - - "+": "" - }, - "code": "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm" - }, - "+": {} - } - }, - { - "step": "scCall", - "txId": "owner-distribute-fees", - "tx": { - "from": "address:owner", - "to": "sc:multisig", - "value": "0", - "function": "distributeFeesFromChildContracts", - "arguments": [ - "address:relayer1", "4000", - "address:relayer2", "6000" - ], - "gasLimit": "200,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:relayer1": { - "nonce": "*", - "balance": "0", - "esdt": { - "str:EGLD-123456": { - "balance": "40,000" - }, - "str:ETH-123456": { - "balance": "4,000" - } - }, - "storage": {} - }, - "address:relayer2": { - "nonce": "*", - "balance": "0", - "esdt": { - "str:EGLD-123456": { - "balance": "60,000" - }, - "str:ETH-123456": { - "balance": "6,000" - } + "str:EGLD-123456": "2,500,000", + "str:ETH-123456": "1,500,000" }, "storage": {} }, - "sc:multi_transfer": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:EGLD-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:ETH-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:accumulatedTransactionFees|nested:str:EGLD-123456": "0", - "str:accumulatedTransactionFees|nested:str:ETH-123456": "0", - - "+": "" - }, - "code": "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm" - }, "+": {} } } diff --git a/multisig/mandos/ethereum_to_elrond_tx_batch_rejected.scen.json b/multisig/mandos/ethereum_to_elrond_tx_batch_rejected.scen.json index a51df539..f3d8f739 100644 --- a/multisig/mandos/ethereum_to_elrond_tx_batch_rejected.scen.json +++ b/multisig/mandos/ethereum_to_elrond_tx_batch_rejected.scen.json @@ -5,6 +5,70 @@ "step": "externalSteps", "path": "setup.scen.json" }, + { + "step": "scCall", + "txId": "try-propose-wrong-batch-id", + "tx": { + "from": "address:relayer1", + "to": "sc:multisig", + "value": "0", + "function": "proposeMultiTransferEsdtBatch", + "arguments": [ + "2", + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:EGLD-123456", + "2,000,000", + "u64:1", + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:ETH-123456", + "2,000,000", + "u64:2" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Can only propose for next batch ID", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "try-propose-wrong-tx-id", + "tx": { + "from": "address:relayer1", + "to": "sc:multisig", + "value": "0", + "function": "proposeMultiTransferEsdtBatch", + "arguments": [ + "1", + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:EGLD-123456", + "2,000,000", + "u64:2", + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:ETH-123456", + "2,000,000", + "u64:3" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Invalid Tx ID", + "out": [], + "gas": "*", + "refund": "*" + } + }, { "step": "scCall", "txId": "propose-transfer-to-smart-contract", @@ -15,8 +79,16 @@ "function": "proposeMultiTransferEsdtBatch", "arguments": [ "1", - "sc:egld_esdt_swap", "str:EGLD-123456", "500,000", - "sc:egld_esdt_swap", "str:ETH-123456", "500,000" + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:EGLD-123456", + "2,000,000", + "u64:1", + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:ETH-123456", + "2,000,000", + "u64:2" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -39,19 +111,23 @@ "balance": "*", "storage": { "str:action_data.item|u32:1": { - "1-action_type": "u8:7", + "1-action_type": "u8:2", "2-batch_id": "u64:1", "3-transfers_len": "u32:2", "4-transfers_vec": [ { - "1-dest": "sc:egld_esdt_swap", - "2-token_id": "nested:str:EGLD-123456", - "3-amount": "biguint:500,000" + "1-from": "0x0102030405060708091011121314151617181920", + "2-to": "sc:egld_esdt_swap", + "3-token_id": "nested:str:EGLD-123456", + "4-amount": "biguint:2,000,000", + "5-tx_id": "u64:1" }, { - "1-dest": "sc:egld_esdt_swap", - "2-token_id": "nested:str:ETH-123456", - "3-amount": "biguint:500,000" + "1-from": "0x0102030405060708091011121314151617181920", + "2-to": "sc:egld_esdt_swap", + "3-token_id": "nested:str:ETH-123456", + "4-amount": "biguint:2,000,000", + "5-tx_id": "u64:2" } ] }, @@ -96,35 +172,124 @@ "arguments": [ "1" ], - "gasLimit": "50,000,000", + "gasLimit": "100,000,000", "gasPrice": "0" }, "expect": { "status": "0", "message": "", - "out": [ - "4", "4" - ], + "out": [], "gas": "*", "refund": "*" } }, { "step": "scQuery", - "txId": "query-statuses", + "txId": "get-current-refund-tx-batch", "tx": { "to": "sc:multisig", - "function": "getStatusesAfterExecution", - "arguments": [ - "1" - ] + "function": "getCurrentRefundBatch", + "arguments": [] }, "expect": { "out": [ - "4", - "4" + "1", + "0", + "1", + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:EGLD-123456", + "2,000,000", + "0", + "2", + "0x0102030405060708091011121314151617181920", + "sc:egld_esdt_swap", + "str:ETH-123456", + "2,000,000" ] } + }, + { + "step": "scCall", + "txId": "move-refund-batch-to-safe", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "value": "0", + "function": "moveRefundBatchToSafe", + "arguments": [], + "gasLimit": "200,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "scQuery", + "txId": "get-current-refund-tx-batch-after-move", + "tx": { + "to": "sc:multisig", + "function": "getCurrentRefundBatch", + "arguments": [] + }, + "expect": { + "out": [] + } + }, + { + "step": "checkState", + "accounts": { + "sc:esdt_safe": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:EGLD-123456": { + "balance": "0", + "roles": [ + "ESDTRoleLocalBurn" + ] + }, + "str:ETH-123456": { + "balance": "0", + "roles": [ + "ESDTRoleLocalBurn" + ] + } + }, + "storage": { + "str:pendingBatches|u64:1|str:.item|u32:1": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:1", + "3-from": "u32:32|sc:egld_esdt_swap", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:EGLD-123456", + "6-amount": "biguint:500,000", + "7-is_refund_tx": "u8:1" + }, + "str:pendingBatches|u64:1|str:.item|u32:2": { + "1-block_nonce": "u64:0", + "2-nonce": "u64:2", + "3-from": "u32:32|sc:egld_esdt_swap", + "4-to": "u32:20|0x0102030405060708091011121314151617181920", + "5-token_identifier": "nested:str:ETH-123456", + "6-amount": "biguint:1,850,000", + "7-is_refund_tx": "u8:1" + }, + "str:firstBatchId": "1", + "str:lastBatchId": "1", + "str:accumulatedTransactionFees|nested:str:EGLD-123456": "0", + "str:accumulatedTransactionFees|nested:str:ETH-123456": "0", + "+": "" + }, + "code": "*" + }, + "+": {} + } } ] } diff --git a/multisig/mandos/execute_elrond_to_ethereum_tx_batch.scen.json b/multisig/mandos/execute_elrond_to_ethereum_tx_batch.scen.json index 68895fa1..77368000 100644 --- a/multisig/mandos/execute_elrond_to_ethereum_tx_batch.scen.json +++ b/multisig/mandos/execute_elrond_to_ethereum_tx_batch.scen.json @@ -14,8 +14,9 @@ "value": "0", "function": "proposeEsdtSafeSetCurrentTransactionBatchStatus", "arguments": [ - "0", - "3", "3" + "1", + "3", + "3" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -24,22 +25,6 @@ "status": "0", "message": "", "out": [ - "0", - - "0", - "1", - "address:user", - "0x0102030405060708091011121314151617181920", - "str:EGLD-123456", - "400", - - "0", - "2", - "address:user", - "0x0102030405060708091011121314151617181920", - "str:ETH-123456", - "350,000", - "1" ], "gas": "*", @@ -54,8 +39,8 @@ "balance": "*", "storage": { "str:action_data.item|u32:1": { - "1-action_type": "u8:6", - "2-batch_id": "u64:0", + "1-action_type": "u8:1", + "2-batch_id": "u64:1", "3-tx_batch_status_len": "u32:2", "4-tx_batch_status_vec": "u8:3|u8:3" }, @@ -109,6 +94,48 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "user-claim-refund-egld", + "tx": { + "from": "address:user", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:EGLD-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Nothing to refund", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "user-claim-refund-eth", + "tx": { + "from": "address:user", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:ETH-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "4", + "message": "str:Nothing to refund", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "comment": "the tokens are burned after successful tx execution", @@ -130,16 +157,13 @@ ] } }, - "storage": { + "storage": { "str:transactionStatus|address:user|u32:1": "", "str:transactionsByNonce|address:user|str:.item|u32:1": "", - "str:transactionStatus|address:user|u32:2": "", "str:transactionsByNonce|address:user|str:.item|u32:2": "", - "str:accumulatedTransactionFees|nested:str:EGLD-123456": "1,500,000", "str:accumulatedTransactionFees|nested:str:ETH-123456": "150,000", - "+": "" }, "code": "*" @@ -156,8 +180,10 @@ "value": "0", "function": "distributeFeesFromChildContracts", "arguments": [ - "address:relayer1", "4000", - "address:relayer2", "6000" + "address:relayer1", + "4000", + "address:relayer2", + "6000" ], "gasLimit": "200,000,000", "gasPrice": "0" @@ -215,10 +241,9 @@ ] } }, - "storage": { + "storage": { "str:accumulatedTransactionFees|nested:str:EGLD-123456": "0", "str:accumulatedTransactionFees|nested:str:ETH-123456": "0", - "+": "" }, "code": "*" diff --git a/multisig/mandos/reject_elrond_to_ethereum_tx_batch.scen.json b/multisig/mandos/reject_elrond_to_ethereum_tx_batch.scen.json index f327d524..1150e544 100644 --- a/multisig/mandos/reject_elrond_to_ethereum_tx_batch.scen.json +++ b/multisig/mandos/reject_elrond_to_ethereum_tx_batch.scen.json @@ -14,8 +14,9 @@ "value": "0", "function": "proposeEsdtSafeSetCurrentTransactionBatchStatus", "arguments": [ - "0", - "4", "4" + "1", + "4", + "4" ], "gasLimit": "50,000,000", "gasPrice": "0" @@ -24,22 +25,6 @@ "status": "0", "message": "", "out": [ - "0", - - "0", - "1", - "address:user", - "0x0102030405060708091011121314151617181920", - "str:EGLD-123456", - "400", - - "0", - "2", - "address:user", - "0x0102030405060708091011121314151617181920", - "str:ETH-123456", - "350,000", - "1" ], "gas": "*", @@ -54,8 +39,8 @@ "balance": "*", "storage": { "str:action_data.item|u32:1": { - "1-action_type": "u8:6", - "2-batch_id": "u64:0", + "1-action_type": "u8:1", + "2-batch_id": "u64:1", "3-tx_batch_status_len": "u32:2", "4-tx_batch_status_vec": "u8:4|u8:4" }, @@ -109,6 +94,69 @@ "refund": "*" } }, + { + "step": "scQuery", + "txId": "user-query-refund-amounts", + "tx": { + "to": "sc:esdt_safe", + "function": "getRefundAmounts", + "arguments": [ + "address:user" + ] + }, + "expect": { + "out": [ + "str:EGLD-123456", + "400", + "str:ETH-123456", + "350,000" + ] + } + }, + { + "step": "scCall", + "txId": "user-claim-refund-egld", + "tx": { + "from": "address:user", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:EGLD-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "txId": "user-claim-refund-eth", + "tx": { + "from": "address:user", + "to": "sc:esdt_safe", + "value": "0", + "function": "claimRefund", + "arguments": [ + "str:ETH-123456" + ], + "gasLimit": "50,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": "*", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "comment": "the tokens are returned to the user (except the tx fees)", @@ -139,16 +187,13 @@ ] } }, - "storage": { + "storage": { "str:transactionStatus|address:user|u32:1": "", "str:transactionsByNonce|address:user|str:.item|u32:1": "", - "str:transactionStatus|address:user|u32:2": "", "str:transactionsByNonce|address:user|str:.item|u32:2": "", - "str:accumulatedTransactionFees|nested:str:EGLD-123456": "1,500,000", "str:accumulatedTransactionFees|nested:str:ETH-123456": "150,000", - "+": "" }, "code": "*" @@ -165,8 +210,10 @@ "value": "0", "function": "distributeFeesFromChildContracts", "arguments": [ - "address:relayer1", "4000", - "address:relayer2", "6000" + "address:relayer1", + "4000", + "address:relayer2", + "6000" ], "gasLimit": "200,000,000", "gasPrice": "0" @@ -224,10 +271,9 @@ ] } }, - "storage": { + "storage": { "str:accumulatedTransactionFees|nested:str:EGLD-123456": "0", "str:accumulatedTransactionFees|nested:str:ETH-123456": "0", - "+": "" }, "code": "*" diff --git a/multisig/mandos/setup.scen.json b/multisig/mandos/setup.scen.json index 379e142a..9739684e 100644 --- a/multisig/mandos/setup.scen.json +++ b/multisig/mandos/setup.scen.json @@ -5,6 +5,78 @@ "step": "externalSteps", "path": "../../price-aggregator/mandos/oracle_gwei_in_eth_and_egld_submit.scen.json" }, + { + "step": "setState", + "comment": "simulate child contracts deploy + setting local roles + change ownership. Changing ownership will be done after the multisig deploy in the real setup, but this is way less verbose in mandos.", + "accounts": { + "sc:multi_transfer": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:EGLD-123456": { + "balance": "0", + "roles": [ + "ESDTRoleLocalMint" + ] + }, + "str:ETH-123456": { + "balance": "0", + "roles": [ + "ESDTRoleLocalMint" + ] + } + }, + "storage": { + "str:maxTxBatchSize": "10", + "str:maxTxBatchBlockDuration": "3,600", + + "str:firstBatchId": "1", + "str:lastBatchId": "1" + }, + "owner": "sc:multisig", + "code": "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm" + }, + "sc:esdt_safe": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:EGLD-123456": { + "balance": "0", + "roles": [ + "ESDTRoleLocalBurn" + ] + }, + "str:ETH-123456": { + "balance": "0", + "roles": [ + "ESDTRoleLocalBurn" + ] + } + }, + "storage": { + "str:feeEstimatorContractAddress": "sc:price_aggregator", + "str:maxTxBatchSize": "10", + "str:maxTxBatchBlockDuration": "100", + "str:ethTxGasLimit": "150,000", + + "str:firstBatchId": "1", + "str:lastBatchId": "1", + + "str:tokenTicker|nested:str:GWEI": "str:GWEI", + "str:tokenTicker|nested:str:EGLD-123456": "str:EGLD", + "str:tokenTicker|nested:str:ETH-123456": "str:ETH", + + "str:tokenWhitelist.index|nested:str:EGLD-123456": "1", + "str:tokenWhitelist.item|u32:1": "str:EGLD-123456", + "str:tokenWhitelist.index|nested:str:ETH-123456": "2", + "str:tokenWhitelist.item|u32:2": "str:ETH-123456", + "str:tokenWhitelist.len": "2" + }, + "owner": "sc:multisig", + "code": "file:../../esdt-safe/output/esdt-safe.wasm" + } + } + }, { "step": "setState", "accounts": { @@ -25,8 +97,9 @@ }, "address:user": { "nonce": "0", - "balance": "2,000,000", + "balance": "0", "esdt": { + "str:EGLD-123456": "2,000,000", "str:ETH-123456": "1,000,000" }, "storage": {} @@ -48,6 +121,8 @@ "contractCode": "file:../output/multisig.wasm", "value": "0", "arguments": [ + "sc:esdt_safe", + "sc:multi_transfer", "1000", "500", "2", @@ -64,137 +139,13 @@ "refund": "*" } }, - { - "step": "setState", - "newAddresses": [ - { - "creatorAddress": "sc:multisig", - "creatorNonce": "0", - "newAddress": "sc:egld_esdt_swap" - }, - { - "creatorAddress": "sc:multisig", - "creatorNonce": "1", - "newAddress": "sc:multi_transfer" - }, - { - "creatorAddress": "sc:multisig", - "creatorNonce": "2", - "newAddress": "sc:esdt_safe" - } - ] - }, { "step": "checkState", "accounts": { - "address:owner": { - "nonce": "*", - "balance": "0", - "storage": {} - }, - "sc:multisig": { - "nonce": "*", - "balance": "0", - "storage": { - "str:num_board_members": "2", - "str:quorum": "2", - "str:requiredStakeAmount": "1000", - "str:slashAmount": "500", - "str:user_id_to_address|u32:1": "address:relayer1", - "str:user_id_to_address|u32:2": "address:relayer2", - "+": "" - }, - "code": "file:../output/multisig.wasm" - }, - "+": {} - } - }, - { - "step": "scCall", - "txId": "deploy-child-contracts", - "tx": { - "from": "address:owner", - "to": "sc:multisig", - "value": "0", - "function": "deployChildContracts", - "arguments": [ - "file:../../egld-esdt-swap/output/egld-esdt-swap.wasm", - "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm", - "file:../../esdt-safe/output/esdt-safe.wasm", - "sc:price_aggregator", - "150,000", - "10,000", - "str:EGLD-123456", - "str:ETH-123456" - ], - "gasLimit": "100,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "out": [], - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "address:owner": { - "nonce": "*", - "balance": "0", - "storage": {} - }, - "sc:egld_esdt_swap": { - "nonce": "0", - "balance": "0", - "storage": { - "str:wrappedEgldTokenId": "str:EGLD-123456" - }, - "code": "file:../../egld-esdt-swap/output/egld-esdt-swap.wasm" - }, - "sc:multi_transfer": { - "nonce": "0", - "balance": "0", - "storage": { - "str:feeEstimatorContractAddress": "sc:price_aggregator", - "str:ethTxGasLimit": "10,000", - - "str:tokenWhitelist.info": "u32:2|u32:1|u32:2|u32:2", - "str:tokenWhitelist.node_idEGLD-123456": "2", - "str:tokenWhitelist.node_idETH-123456": "1", - "str:tokenWhitelist.node_links|u32:1": "u32:0|u32:2", - "str:tokenWhitelist.node_links|u32:2": "u32:1|u32:0", - "str:tokenWhitelist.value|u32:2": "str:EGLD-123456", - "str:tokenWhitelist.value|u32:1": "str:ETH-123456" - }, - "code": "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm" - }, - "sc:esdt_safe": { - "nonce": "0", - "balance": "0", - "storage": { - "str:feeEstimatorContractAddress": "sc:price_aggregator", - "str:maxTxBatchSize": "10", - "str:maxTxBatchBlockDuration": "100", - "str:ethTxGasLimit": "150,000", - - "str:tokenWhitelist.info": "u32:2|u32:1|u32:2|u32:2", - "str:tokenWhitelist.node_idEGLD-123456": "2", - "str:tokenWhitelist.node_idETH-123456": "1", - "str:tokenWhitelist.node_links|u32:1": "u32:0|u32:2", - "str:tokenWhitelist.node_links|u32:2": "u32:1|u32:0", - "str:tokenWhitelist.value|u32:2": "str:EGLD-123456", - "str:tokenWhitelist.value|u32:1": "str:ETH-123456" - }, - "code": "file:../../esdt-safe/output/esdt-safe.wasm" - }, "sc:multisig": { "nonce": "*", "balance": "0", "storage": { - "str:egldEsdtSwapAddress": "sc:egld_esdt_swap", "str:esdtSafeAddress": "sc:esdt_safe", "str:multiTransferEsdtAddress": "sc:multi_transfer", @@ -202,8 +153,8 @@ "str:quorum": "2", "str:requiredStakeAmount": "1000", "str:slashAmount": "500", - "str:user_role|u32:1": "2", - "str:user_role|u32:2": "2", + "str:user_role|u32:1": "1", + "str:user_role|u32:2": "1", "str:user_address_to_id|address:relayer1": "1", "str:user_address_to_id|address:relayer2": "2", @@ -211,103 +162,13 @@ "str:user_id_to_address|u32:1": "address:relayer1", "str:user_id_to_address|u32:2": "address:relayer2", - "str:statusesAfterExecution": "u64:0|u32:0" + "str:pause_module:paused": "true" }, "code": "file:../output/multisig.wasm" }, "+": {} } }, - { - "step": "setState", - "comment": "setting local roles", - "accounts": { - "sc:egld_esdt_swap": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:EGLD-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint", - "ESDTRoleLocalBurn" - ] - } - }, - "storage": { - "str:wrappedEgldTokenId": "str:EGLD-123456" - }, - "owner": "sc:multisig", - "code": "file:../../egld-esdt-swap/output/egld-esdt-swap.wasm" - }, - "sc:multi_transfer": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:EGLD-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - }, - "str:ETH-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint" - ] - } - }, - "storage": { - "str:feeEstimatorContractAddress": "sc:price_aggregator", - "str:ethTxGasLimit": "10,000", - - "str:tokenWhitelist.info": "u32:2|u32:1|u32:2|u32:2", - "str:tokenWhitelist.node_idEGLD-123456": "2", - "str:tokenWhitelist.node_idETH-123456": "1", - "str:tokenWhitelist.node_links|u32:1": "u32:0|u32:2", - "str:tokenWhitelist.node_links|u32:2": "u32:1|u32:0", - "str:tokenWhitelist.value|u32:2": "str:EGLD-123456", - "str:tokenWhitelist.value|u32:1": "str:ETH-123456" - }, - "owner": "sc:multisig", - "code": "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm" - }, - "sc:esdt_safe": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:EGLD-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalBurn" - ] - }, - "str:ETH-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalBurn" - ] - } - }, - "storage": { - "str:feeEstimatorContractAddress": "sc:price_aggregator", - "str:maxTxBatchSize": "10", - "str:maxTxBatchBlockDuration": "100", - "str:ethTxGasLimit": "150,000", - - "str:tokenWhitelist.info": "u32:2|u32:1|u32:2|u32:2", - "str:tokenWhitelist.node_idEGLD-123456": "2", - "str:tokenWhitelist.node_idETH-123456": "1", - "str:tokenWhitelist.node_links|u32:1": "u32:0|u32:2", - "str:tokenWhitelist.node_links|u32:2": "u32:1|u32:0", - "str:tokenWhitelist.value|u32:2": "str:EGLD-123456", - "str:tokenWhitelist.value|u32:1": "str:ETH-123456" - }, - "owner": "sc:multisig", - "code": "file:../../esdt-safe/output/esdt-safe.wasm" - } - } - }, { "step": "scCall", "txId": "first-relayer-stake", @@ -406,6 +267,39 @@ "address:relayer2" ] } + }, + { + "step": "scQuery", + "txId": "get-all-known-tokens", + "tx": { + "to": "sc:esdt_safe", + "function": "getAllKnownTokens" + }, + "expect": { + "out": [ + "str:EGLD-123456", + "str:ETH-123456" + ] + } + }, + { + "step": "scCall", + "txId": "unpause multisig", + "tx": { + "from": "address:owner", + "to": "sc:multisig", + "function": "unpause", + "arguments": [], + "gasLimit": "100,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "out": [], + "message": "", + "gas": "*", + "refund": "*" + } } ] } diff --git a/multisig/mandos/unstake.scen.json b/multisig/mandos/unstake.scen.json index 76c2582e..4ba80b64 100644 --- a/multisig/mandos/unstake.scen.json +++ b/multisig/mandos/unstake.scen.json @@ -95,7 +95,7 @@ "balance": "2000", "storage": { "str:quorum": "1", - "str:user_role|u32:1": "2", + "str:user_role|u32:1": "1", "str:user_role|u32:2": "0", "+": "" }, @@ -161,7 +161,7 @@ "balance": "1000", "storage": { "str:quorum": "1", - "str:user_role|u32:1": "2", + "str:user_role|u32:1": "1", "str:user_role|u32:2": "0", "+": "" }, @@ -179,14 +179,14 @@ "value": "0", "function": "proposeMultiTransferEsdtBatch", "arguments": [ - "2" + "1" ], "gasLimit": "40,000,000", "gasPrice": "0" }, "expect": { "status": "4", - "message": "str:only board members and proposers can propose", + "message": "str:only board members can propose", "out": [], "gas": "*", "refund": "*" diff --git a/multisig/mandos/upgrade_child_sc.scen.json b/multisig/mandos/upgrade_child_sc.scen.json deleted file mode 100644 index e526a234..00000000 --- a/multisig/mandos/upgrade_child_sc.scen.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "upgrade child sc", - "steps": [ - { - "step": "externalSteps", - "path": "setup.scen.json" - }, - { - "step": "scCall", - "txId": "upgrade-child-contract", - "tx": { - "from": "address:owner", - "to": "sc:multisig", - "value": "0", - "function": "upgradeChildContract", - "arguments": [ - "sc:egld_esdt_swap", - "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm", - "sc:price_aggregator", - "10,000" - ], - "gasLimit": "200,000,000", - "gasPrice": "0" - }, - "expect": { - "status": "0", - "message": "", - "out": [], - "gas": "*", - "refund": "*" - } - }, - { - "step": "checkState", - "accounts": { - "sc:egld_esdt_swap": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:EGLD-123456": { - "balance": "0", - "roles": [ - "ESDTRoleLocalMint", - "ESDTRoleLocalBurn" - ] - } - }, - "storage": { - "str:wrappedEgldTokenId": "str:EGLD-123456", - "str:feeEstimatorContractAddress": "sc:price_aggregator", - "str:ethTxGasLimit": "10,000" - }, - "code": "file:../../multi-transfer-esdt/output/multi-transfer-esdt.wasm" - }, - "+": {} - } - } - ] -} diff --git a/multisig/abi/Cargo.toml b/multisig/meta/Cargo.toml similarity index 62% rename from multisig/abi/Cargo.toml rename to multisig/meta/Cargo.toml index 55de6bc5..d57b4414 100644 --- a/multisig/abi/Cargo.toml +++ b/multisig/meta/Cargo.toml @@ -1,15 +1,11 @@ [package] -name = "multisig-abi" +name = "multisig-meta" version = "0.0.0" authors = ["Andrei Marinica , Dorin Iancu "] edition = "2018" publish = false - [dependencies.multisig] path = ".." -[dependencies.elrond-wasm] -version = "0.18" - -[dependencies.elrond-wasm-debug] -version = "0.18" +[dependencies.multiversx-sc-meta] +version = "0.41.3" diff --git a/multisig/meta/src/main.rs b/multisig/meta/src/main.rs new file mode 100644 index 00000000..69a0a648 --- /dev/null +++ b/multisig/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::(); +} diff --git a/multisig/elrond.json b/multisig/multiversx.json similarity index 100% rename from multisig/elrond.json rename to multisig/multiversx.json diff --git a/multisig/src/action.rs b/multisig/src/action.rs index 9167e02b..d2bf8850 100644 --- a/multisig/src/action.rs +++ b/multisig/src/action.rs @@ -1,30 +1,24 @@ -use elrond_wasm::api::BigUintApi; -use elrond_wasm::types::{Address, TokenIdentifier, Vec}; -use transaction::TransactionStatus; +use multiversx_sc::api::ManagedTypeApi; +use multiversx_sc::types::ManagedVec; +use transaction::transaction_status::TransactionStatus; +use transaction::EthTransaction; -elrond_wasm::derive_imports!(); +multiversx_sc::derive_imports!(); -// Actions with _ in front are not used -// Keeping the actions even if they're not used, for backwards compatibility of action type ID #[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, TypeAbi)] -pub enum Action { +pub enum Action { Nothing, - _AddBoardMember(Address), - _AddProposer(Address), - _RemoveUser(Address), - _SlashUser(Address), - _ChangeQuorum(usize), SetCurrentTransactionBatchStatus { esdt_safe_batch_id: u64, - tx_batch_status: Vec, + tx_batch_status: ManagedVec, }, BatchTransferEsdtToken { - batch_id: u64, - transfers: Vec<(Address, TokenIdentifier, BigUint)>, + eth_batch_id: u64, + transfers: ManagedVec>, }, } -impl Action { +impl Action { /// Only pending actions are kept in storage, /// both executed and discarded actions are removed (converted to `Nothing`). /// So this is equivalent to `action != Action::Nothing`. @@ -32,22 +26,3 @@ impl Action { !matches!(*self, Action::Nothing) } } - -/// Not used internally, just to retrieve results via endpoint. -#[derive(TopEncode, TypeAbi)] -pub struct ActionFullInfo { - pub action_id: usize, - pub action_data: Action, - pub signers: Vec
, -} - -#[cfg(test)] -mod test { - use super::Action; - use elrond_wasm_debug::api::RustBigUint; - - #[test] - fn test_is_pending() { - assert!(!Action::::Nothing.is_pending()); - } -} diff --git a/multisig/src/lib.rs b/multisig/src/lib.rs index 4c29149f..9b5903ca 100644 --- a/multisig/src/lib.rs +++ b/multisig/src/lib.rs @@ -1,43 +1,103 @@ #![no_std] -#![allow(non_snake_case)] #![allow(clippy::too_many_arguments)] mod action; +mod multisig_general; +mod queries; +mod setup; +mod storage; mod user_role; - -use token_module::{ProxyTrait as OtherProxyTrait, PERCENTAGE_TOTAL}; +mod util; use action::Action; -use transaction::esdt_safe_batch::EsdtSafeTxBatchSplitInFields; +use token_module::{AddressPercentagePair, INVALID_PERCENTAGE_SUM_OVER_ERR_MSG, PERCENTAGE_TOTAL}; +use transaction::transaction_status::TransactionStatus; +use transaction::TxBatchSplitInFields; use transaction::*; use user_role::UserRole; -mod multisig_general; -mod setup; -mod storage; -mod util; +use esdt_safe::ProxyTrait as _; +use multi_transfer_esdt::ProxyTrait as _; +use token_module::ProxyTrait as _; +use tx_batch_module::ProxyTrait as _; -elrond_wasm::imports!(); +multiversx_sc::imports!(); /// Multi-signature smart contract implementation. /// Acts like a wallet that needs multiple signers for any action performed. -#[elrond_wasm_derive::contract] +#[multiversx_sc::contract] pub trait Multisig: multisig_general::MultisigGeneralModule + setup::SetupModule + storage::StorageModule + util::UtilModule + + queries::QueriesModule + + multiversx_sc_modules::pause::PauseModule { + /// EsdtSafe and MultiTransferEsdt are expected to be deployed and configured separately, + /// and then having their ownership changed to this Multisig SC. + #[init] + fn init( + &self, + esdt_safe_sc_address: ManagedAddress, + multi_transfer_sc_address: ManagedAddress, + required_stake: BigUint, + slash_amount: BigUint, + quorum: usize, + board: MultiValueEncoded, + ) { + let mut duplicates = false; + let board_len = board.len(); + self.user_mapper() + .get_or_create_users(board.into_iter(), |user_id, new_user| { + if !new_user { + duplicates = true; + } + self.user_id_to_role(user_id).set(UserRole::BoardMember); + }); + require!(!duplicates, "duplicate board member"); + + self.num_board_members() + .update(|nr_board_members| *nr_board_members += board_len); + self.change_quorum(quorum); + + require!( + slash_amount <= required_stake, + "slash amount must be less than or equal to required stake" + ); + self.required_stake_amount().set(&required_stake); + self.slash_amount().set(&slash_amount); + + require!( + self.blockchain().is_smart_contract(&esdt_safe_sc_address), + "Esdt Safe address is not a Smart Contract address" + ); + self.esdt_safe_address().set(&esdt_safe_sc_address); + + require!( + self.blockchain() + .is_smart_contract(&multi_transfer_sc_address), + "Multi Transfer address is not a Smart Contract address" + ); + self.multi_transfer_esdt_address() + .set(&multi_transfer_sc_address); + + self.set_paused(true); + } + + /// Distributes the accumulated fees to the given addresses. + /// Expected arguments are pairs of (address, percentage), + /// where percentages must add up to the PERCENTAGE_TOTAL constant #[only_owner] #[endpoint(distributeFeesFromChildContracts)] fn distribute_fees_from_child_contracts( &self, - #[var_args] dest_address_percentage_pairs: VarArgs>, - ) -> SCResult<()> { - let mut args = Vec::new(); - let mut total_percentage = 0; + dest_address_percentage_pairs: MultiValueEncoded>, + ) { + let mut args = ManagedVec::new(); + let mut total_percentage = 0u64; - for pair in dest_address_percentage_pairs.into_vec() { + for pair in dest_address_percentage_pairs { let (dest_address, percentage) = pair.into_tuple(); require!( @@ -45,29 +105,29 @@ pub trait Multisig: "Cannot transfer to smart contract dest_address" ); - total_percentage += percentage; - args.push((dest_address, percentage)); + total_percentage += percentage as u64; + args.push(AddressPercentagePair { + address: dest_address, + percentage, + }); } require!( - total_percentage == PERCENTAGE_TOTAL, - "Percentages do not add up to 100%" + total_percentage == PERCENTAGE_TOTAL as u64, + INVALID_PERCENTAGE_SUM_OVER_ERR_MSG ); - self.esdt_safe_proxy(self.esdt_safe_address().get()) - .distribute_fees(args.clone()) - .execute_on_dest_context(); - - self.multi_transfer_esdt_proxy(self.multi_transfer_esdt_address().get()) + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() .distribute_fees(args) .execute_on_dest_context(); - - Ok(()) } + /// Board members have to stake a certain amount of EGLD + /// before being allowed to sign actions #[payable("EGLD")] #[endpoint] - fn stake(&self, #[payment] payment: Self::BigUint) -> SCResult<()> { + fn stake(&self, #[payment] payment: BigUint) { let caller = self.blockchain().get_caller(); let caller_role = self.user_role(&caller); require!( @@ -77,12 +137,10 @@ pub trait Multisig: self.amount_staked(&caller) .update(|amount_staked| *amount_staked += payment); - - Ok(()) } #[endpoint] - fn unstake(&self, amount: Self::BigUint) -> SCResult<()> { + fn unstake(&self, amount: BigUint) { let caller = self.blockchain().get_caller(); let amount_staked = self.amount_staked(&caller).get(); require!( @@ -100,37 +158,41 @@ pub trait Multisig: } self.amount_staked(&caller).set(&remaining_stake); - self.send().direct_egld(&caller, &amount, &[]); - - Ok(()) + self.send().direct_egld(&caller, &amount); } // ESDT Safe SC calls + /// After a batch is processed on the Ethereum side, + /// the EsdtSafe expects a list of statuses of said transactions (success or failure). + /// + /// This endpoint proposes an action to set the statuses to a certain list of values. + /// Nothing is changed in the EsdtSafe contract until the action is signed and executed. #[endpoint(proposeEsdtSafeSetCurrentTransactionBatchStatus)] fn propose_esdt_safe_set_current_transaction_batch_status( &self, esdt_safe_batch_id: u64, - #[var_args] tx_batch_status: VarArgs, - ) -> SCResult { - let call_result = self - .esdt_safe_proxy(self.esdt_safe_address().get()) + tx_batch_status: MultiValueEncoded, + ) -> usize { + let call_result: OptionalValue> = self + .get_esdt_safe_proxy_instance() .get_current_tx_batch() .execute_on_dest_context(); - let (current_batch_id, current_batch_transactions) = call_result - .into_option() - .ok_or("Current batch is empty")? - .into_tuple(); + let (current_batch_id, current_batch_transactions) = match call_result { + OptionalValue::Some(batch) => batch.into_tuple(), + OptionalValue::None => sc_panic!("Current batch is empty"), + }; + let statuses_vec = tx_batch_status.to_vec(); require!( self.action_id_for_set_current_transaction_batch_status(esdt_safe_batch_id) - .get(&tx_batch_status.0) - == None, + .get(&statuses_vec) + .is_none(), "Action already proposed" ); - let current_batch_len = current_batch_transactions.len(); - let status_batch_len = tx_batch_status.len(); + let current_batch_len = current_batch_transactions.raw_len() / TX_MULTIRESULT_NR_FIELDS; + let status_batch_len = statuses_vec.len(); require!( current_batch_len == status_batch_len, "Number of statuses provided must be equal to number of transactions in current batch" @@ -142,157 +204,104 @@ pub trait Multisig: let action_id = self.propose_action(Action::SetCurrentTransactionBatchStatus { esdt_safe_batch_id, - tx_batch_status: tx_batch_status.0.clone(), - })?; + tx_batch_status: statuses_vec.clone(), + }); self.action_id_for_set_current_transaction_batch_status(esdt_safe_batch_id) - .insert(tx_batch_status.into_vec(), action_id); + .insert(statuses_vec, action_id); - Ok(action_id) + action_id } // Multi-transfer ESDT SC calls + /// Proposes a batch of Ethereum -> Elrond transfers. + /// Transactions have to be separated by fields, in the following order: + /// Sender Address, Destination Address, Token ID, Amount, Tx Nonce #[endpoint(proposeMultiTransferEsdtBatch)] fn propose_multi_transfer_esdt_batch( &self, - batch_id: u64, - #[var_args] transfers: MultiArgVec>, - ) -> SCResult { - self.require_multi_transfer_esdt_deployed()?; + eth_batch_id: u64, + transfers: MultiValueEncoded>, + ) -> usize { + let next_eth_batch_id = self.last_executed_eth_batch_id().get() + 1; + require!( + eth_batch_id == next_eth_batch_id, + "Can only propose for next batch ID" + ); - let transfers_as_tuples = self.transfers_multiarg_to_tuples_vec(transfers); + let transfers_as_eth_tx = self.transfers_multi_value_to_eth_tx_vec(transfers); + self.require_valid_eth_tx_ids(&transfers_as_eth_tx); + let batch_hash = self.hash_eth_tx_batch(&transfers_as_eth_tx); require!( - self.batch_id_to_action_id_mapping(batch_id) - .get(&transfers_as_tuples) - == None, + self.batch_id_to_action_id_mapping(eth_batch_id) + .get(&batch_hash) + .is_none(), "This batch was already proposed" ); let action_id = self.propose_action(Action::BatchTransferEsdtToken { - batch_id, - transfers: transfers_as_tuples.clone(), - })?; + eth_batch_id, + transfers: transfers_as_eth_tx, + }); - self.batch_id_to_action_id_mapping(batch_id) - .insert(transfers_as_tuples, action_id); + self.batch_id_to_action_id_mapping(eth_batch_id) + .insert(batch_hash, action_id); - Ok(action_id) + action_id + } + + /// Failed Ethereum -> Elrond transactions are saved in the MultiTransfer SC + /// as "refund transactions", and stored in batches, using the same mechanism as EsdtSafe. + /// + /// This function moves the first refund batch into the EsdtSafe SC, + /// converting the transactions into Elrond -> Ethereum transactions + /// and adding them into EsdtSafe batches + #[only_owner] + #[endpoint(moveRefundBatchToSafe)] + fn move_refund_batch_to_safe(&self) { + let opt_refund_batch_fields: OptionalValue> = self + .get_multi_transfer_esdt_proxy_instance() + .get_and_clear_first_refund_batch() + .execute_on_dest_context(); + + if let OptionalValue::Some(refund_batch_fields) = opt_refund_batch_fields { + let (_batch_id, all_tx_fields) = refund_batch_fields.into_tuple(); + let mut refund_batch = ManagedVec::new(); + + for tx_fields in all_tx_fields { + refund_batch.push(Transaction::from(tx_fields)); + } + + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .add_refund_batch(refund_batch) + .execute_on_dest_context(); + } } /// Proposers and board members use this to launch signed actions. #[endpoint(performAction)] - fn perform_action_endpoint(&self, action_id: usize) -> SCResult<()> { + fn perform_action_endpoint(&self, action_id: usize) { + require!( + !self.action_mapper().item_is_empty(action_id), + "Action was already executed" + ); + let caller_address = self.blockchain().get_caller(); - let caller_id = self.user_mapper().get_user_id(&caller_address); - let caller_role = self.get_user_id_to_role(caller_id); + let caller_role = self.get_user_role(&caller_address); require!( - caller_role.can_perform_action(), - "only board members and proposers can perform actions" + caller_role.is_board_member(), + "only board members can perform actions" ); require!( self.quorum_reached(action_id), "quorum has not been reached" ); - require!( - !self.pause_status().get(), - "No actions may be executed while paused" - ); + require!(self.not_paused(), "No actions may be executed while paused"); self.perform_action(action_id); - - Ok(()) - } - - #[view(getCurrentTxBatch)] - fn get_current_tx_batch(&self) -> OptionalResult> { - let _ = self - .esdt_safe_proxy(self.esdt_safe_address().get()) - .get_current_tx_batch() - .execute_on_dest_context(); - - // result is already returned automatically from the EsdtSafe call, - // we only keep this signature for correct ABI generation - OptionalResult::None - } - - #[view(isValidActionId)] - fn is_valid_action_id(&self, action_id: usize) -> bool { - let min_id = 1; - let max_id = self.action_mapper().len(); - - action_id >= min_id && action_id <= max_id - } - - /// Actions are cleared after execution, so an empty entry means the action was executed already - /// Returns "false" if the action ID is invalid - #[view(wasActionExecuted)] - fn was_action_executed(&self, action_id: usize) -> bool { - if self.is_valid_action_id(action_id) { - self.action_mapper().item_is_empty(action_id) - } else { - false - } - } - - /// If the mapping was made, it means that the transfer action was proposed in the past - /// To check if it was executed as well, use the wasActionExecuted view - #[view(wasTransferActionProposed)] - fn was_transfer_action_proposed( - &self, - batch_id: u64, - #[var_args] transfers: MultiArgVec>, - ) -> bool { - let action_id = self.get_action_id_for_transfer_batch(batch_id, transfers); - - self.is_valid_action_id(action_id) - } - - #[view(getActionIdForTransferBatch)] - fn get_action_id_for_transfer_batch( - &self, - batch_id: u64, - #[var_args] transfers: MultiArgVec>, - ) -> usize { - let transfers_as_tuples = self.transfers_multiarg_to_tuples_vec(transfers); - - self.batch_id_to_action_id_mapping(batch_id) - .get(&transfers_as_tuples) - .unwrap_or(0) - } - - #[view(getStatusesAfterExecution)] - fn get_statuses_after_execution(&self, batch_id: u64) -> MultiResultVec { - let (actual_batch_id, statuses) = self.statuses_after_execution().get(); - if batch_id == actual_batch_id { - statuses.into() - } else { - MultiResultVec::new() - } - } - - #[view(wasSetCurrentTransactionBatchStatusActionProposed)] - fn was_set_current_transaction_batch_status_action_proposed( - &self, - esdt_safe_batch_id: u64, - #[var_args] expected_tx_batch_status: VarArgs, - ) -> bool { - self.is_valid_action_id(self.get_action_id_for_set_current_transaction_batch_status( - esdt_safe_batch_id, - expected_tx_batch_status, - )) - } - - #[view(getActionIdForSetCurrentTransactionBatchStatus)] - fn get_action_id_for_set_current_transaction_batch_status( - &self, - esdt_safe_batch_id: u64, - #[var_args] expected_tx_batch_status: VarArgs, - ) -> usize { - self.action_id_for_set_current_transaction_batch_status(esdt_safe_batch_id) - .get(&expected_tx_batch_status.0) - .unwrap_or(0) } // private @@ -320,18 +329,19 @@ pub trait Multisig: action_ids_mapper.clear(); - self.esdt_safe_proxy(self.esdt_safe_address().get()) + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() .set_transaction_batch_status( esdt_safe_batch_id, - VarArgs::from(tx_batch_status), + MultiValueEncoded::from(tx_batch_status), ) .execute_on_dest_context(); } Action::BatchTransferEsdtToken { - batch_id, + eth_batch_id, transfers, } => { - let mut action_ids_mapper = self.batch_id_to_action_id_mapping(batch_id); + let mut action_ids_mapper = self.batch_id_to_action_id_mapping(eth_batch_id); // if there's only one proposed action, // the action was already cleared at the beginning of this function @@ -342,33 +352,19 @@ pub trait Multisig: } action_ids_mapper.clear(); + self.last_executed_eth_batch_id().update(|id| *id += 1); - let transfers_len = transfers.len(); - let statuses = self - .multi_transfer_esdt_proxy(self.multi_transfer_esdt_address().get()) - .batch_transfer_esdt_token(transfers.into()) - .execute_on_dest_context_custom_range(|_, after| { - (after - transfers_len, after) - }); + let last_tx_index = transfers.len() - 1; + let last_tx = transfers.get(last_tx_index); + self.last_executed_eth_tx_id().set(last_tx.tx_nonce); - self.statuses_after_execution() - .set(&(batch_id, statuses.into_vec())); + let transfers_multi: MultiValueEncoded> = + transfers.into(); + let _: IgnoreValue = self + .get_multi_transfer_esdt_proxy_instance() + .batch_transfer_esdt_token(eth_batch_id, transfers_multi) + .execute_on_dest_context(); } - _ => {} } } - - // proxies - - #[proxy] - fn egld_esdt_swap_proxy(&self, sc_address: Address) -> egld_esdt_swap::Proxy; - - #[proxy] - fn esdt_safe_proxy(&self, sc_address: Address) -> esdt_safe::Proxy; - - #[proxy] - fn multi_transfer_esdt_proxy( - &self, - sc_address: Address, - ) -> multi_transfer_esdt::Proxy; } diff --git a/multisig/src/multisig_general.rs b/multisig/src/multisig_general.rs index 8487861c..c044e663 100644 --- a/multisig/src/multisig_general.rs +++ b/multisig/src/multisig_general.rs @@ -1,13 +1,15 @@ -elrond_wasm::imports!(); +multiversx_sc::imports!(); use crate::action::Action; use crate::user_role::UserRole; -#[elrond_wasm_derive::module] -pub trait MultisigGeneralModule: crate::util::UtilModule + crate::storage::StorageModule { +#[multiversx_sc::module] +pub trait MultisigGeneralModule: + crate::util::UtilModule + crate::storage::StorageModule + multiversx_sc_modules::pause::PauseModule +{ /// Used by board members to sign actions. #[endpoint] - fn sign(&self, action_id: usize) -> SCResult<()> { + fn sign(&self, action_id: usize) { require!( !self.action_mapper().item_is_empty_unchecked(action_id), "action does not exist" @@ -15,91 +17,30 @@ pub trait MultisigGeneralModule: crate::util::UtilModule + crate::storage::Stora let caller_address = self.blockchain().get_caller(); let caller_id = self.user_mapper().get_user_id(&caller_address); - let caller_role = self.get_user_id_to_role(caller_id); - require!(caller_role.can_sign(), "only board members can sign"); + let caller_role = self.user_id_to_role(caller_id).get(); + require!(caller_role.is_board_member(), "only board members can sign"); require!(self.has_enough_stake(&caller_address), "not enough stake"); - self.action_signer_ids(action_id).update(|signer_ids| { - if !signer_ids.contains(&caller_id) { - signer_ids.push(caller_id); - } - }); - - Ok(()) - } - - /// Board members can withdraw their signatures if they no longer desire for the action to be executed. - /// Actions that are left with no valid signatures can be then deleted to free up storage. - #[endpoint] - fn unsign(&self, action_id: usize) -> SCResult<()> { - require!( - !self.action_mapper().item_is_empty_unchecked(action_id), - "action does not exist" - ); - - let caller_address = self.blockchain().get_caller(); - let caller_id = self.user_mapper().get_user_id(&caller_address); - let caller_role = self.get_user_id_to_role(caller_id); - require!(caller_role.can_sign(), "only board members can un-sign"); - require!(self.has_enough_stake(&caller_address), "not enough stake"); - - self.action_signer_ids(action_id).update(|signer_ids| { - if let Some(signer_pos) = signer_ids - .iter() - .position(|&signer_id| signer_id == caller_id) - { - // since we don't care about the order, - // it is ok to call swap_remove, which is O(1) - signer_ids.swap_remove(signer_pos); - } - }); - - Ok(()) + let _ = self.action_signer_ids(action_id).insert(caller_id); } - /// Clears storage pertaining to an action that is no longer supposed to be executed. - /// Any signatures that the action received must first be removed, via `unsign`. - /// Otherwise this endpoint would be prone to abuse. - #[endpoint(discardAction)] - fn discard_action(&self, action_id: usize) -> SCResult<()> { + fn propose_action(&self, action: Action) -> usize { let caller_address = self.blockchain().get_caller(); let caller_id = self.user_mapper().get_user_id(&caller_address); - let caller_role = self.get_user_id_to_role(caller_id); + let caller_role = self.user_id_to_role(caller_id).get(); require!( - caller_role.can_discard_action(), - "only board members and proposers can discard actions" - ); - require!( - self.get_action_valid_signer_count(action_id) == 0, - "cannot discard action with valid signatures" - ); - - self.clear_action(action_id); - Ok(()) - } - - fn propose_action(&self, action: Action) -> SCResult { - let caller_address = self.blockchain().get_caller(); - let caller_id = self.user_mapper().get_user_id(&caller_address); - let caller_role = self.get_user_id_to_role(caller_id); - require!( - caller_role.can_propose(), - "only board members and proposers can propose" + caller_role.is_board_member(), + "only board members can propose" ); - require!( - !self.pause_status().get(), - "No actions may be proposed while paused" - ); + require!(self.not_paused(), "No actions may be proposed while paused"); let action_id = self.action_mapper().push(&action); - if caller_role.can_sign() { - // also sign - // since the action is newly created, the caller can be the only signer - self.action_signer_ids(action_id).set(&[caller_id].to_vec()); + if self.has_enough_stake(&caller_address) { + let _ = self.action_signer_ids(action_id).insert(caller_id); } - Ok(action_id) + action_id } fn clear_action(&self, action_id: usize) { @@ -107,43 +48,26 @@ pub trait MultisigGeneralModule: crate::util::UtilModule + crate::storage::Stora self.action_signer_ids(action_id).clear(); } - /// Can be used to: - /// - create new user (board member / proposer) - /// - remove user (board member / proposer) - /// - reactivate removed user - /// - convert between board member and proposer - /// Will keep the board size and proposer count in sync. - fn change_user_role(&self, user_address: Address, new_role: UserRole) { - let user_id = self.user_mapper().get_or_create_user(&user_address); - let old_role = if user_id == 0 { - UserRole::None - } else { - self.get_user_id_to_role(user_id) - }; - self.set_user_id_to_role(user_id, new_role); + fn add_board_member(&self, user_address: &ManagedAddress) { + let user_id = self.user_mapper().get_or_create_user(user_address); + let old_role = self.user_id_to_role(user_id).get(); + + if !old_role.is_board_member() { + self.num_board_members().update(|value| *value += 1); + self.user_id_to_role(user_id).set(UserRole::BoardMember); + } + } - // update board size - #[allow(clippy::collapsible_else_if)] - if old_role == UserRole::BoardMember { - if new_role != UserRole::BoardMember { - self.num_board_members().update(|value| *value -= 1); - } - } else { - if new_role == UserRole::BoardMember { - self.num_board_members().update(|value| *value += 1); - } + fn remove_board_member(&self, user_address: &ManagedAddress) { + let user_id = self.user_mapper().get_user_id(user_address); + if user_id == 0 { + return; } - // update num_proposers - #[allow(clippy::collapsible_else_if)] - if old_role == UserRole::Proposer { - if new_role != UserRole::Proposer { - self.num_proposers().update(|value| *value -= 1); - } - } else { - if new_role == UserRole::Proposer { - self.num_proposers().update(|value| *value += 1); - } + let old_role = self.user_id_to_role(user_id).get(); + if old_role.is_board_member() { + self.num_board_members().update(|value| *value -= 1); + self.user_id_to_role(user_id).set(UserRole::None); } } } diff --git a/multisig/src/queries.rs b/multisig/src/queries.rs new file mode 100644 index 00000000..dc5c15ad --- /dev/null +++ b/multisig/src/queries.rs @@ -0,0 +1,193 @@ +multiversx_sc::imports!(); + +use crate::{action::Action, user_role::UserRole}; +use transaction::{transaction_status::TransactionStatus, EthTxAsMultiValue, TxBatchSplitInFields}; + +use tx_batch_module::ProxyTrait as _; + +/// Note: Additional queries can be found in the Storage module +#[multiversx_sc::module] +pub trait QueriesModule: crate::storage::StorageModule + crate::util::UtilModule { + /// Returns the current EsdtSafe batch. + /// + /// First result is the batch ID, then pairs of 6 results, representing transactions + /// split by fields: + /// + /// Block Nonce, Tx Nonce, Sender Address, Receiver Address, Token ID, Amount + #[view(getCurrentTxBatch)] + fn get_current_tx_batch(&self) -> OptionalValue> { + self.get_esdt_safe_proxy_instance() + .get_current_tx_batch() + .execute_on_dest_context() + } + + /// Returns a batch of failed Ethereum -> Elrond transactions. + /// The result format is the same as getCurrentTxBatch + #[view(getCurrentRefundBatch)] + fn get_current_refund_batch(&self) -> OptionalValue> { + self.get_multi_transfer_esdt_proxy_instance() + .get_first_batch_any_status() + .execute_on_dest_context() + } + + /// Actions are cleared after execution, so an empty entry means the action was executed already + /// Returns "false" if the action ID is invalid + #[view(wasActionExecuted)] + fn was_action_executed(&self, action_id: usize) -> bool { + if self.is_valid_action_id(action_id) { + self.action_mapper().item_is_empty(action_id) + } else { + false + } + } + + /// Used for Ethereum -> Elrond batches. + /// If the mapping was made, it means that the transfer action was proposed in the past. + /// To check if it was executed as well, use the wasActionExecuted view + #[view(wasTransferActionProposed)] + fn was_transfer_action_proposed( + &self, + eth_batch_id: u64, + transfers: MultiValueEncoded>, + ) -> bool { + let action_id = self.get_action_id_for_transfer_batch(eth_batch_id, transfers); + + self.is_valid_action_id(action_id) + } + + /// Used for Ethereum -> Elrond batches. + /// If `wasActionExecuted` returns true, then this can be used to get the action ID. + /// Will return 0 if the transfers were not proposed + #[view(getActionIdForTransferBatch)] + fn get_action_id_for_transfer_batch( + &self, + eth_batch_id: u64, + transfers: MultiValueEncoded>, + ) -> usize { + let transfers_as_struct = self.transfers_multi_value_to_eth_tx_vec(transfers); + let batch_hash = self.hash_eth_tx_batch(&transfers_as_struct); + + self.batch_id_to_action_id_mapping(eth_batch_id) + .get(&batch_hash) + .unwrap_or(0) + } + + /// Used for Elrond -> Ethereum batches. + /// Returns "true" if an action was already proposed for the given batch, + /// with these exact transaction statuses, in this exact order + #[view(wasSetCurrentTransactionBatchStatusActionProposed)] + fn was_set_current_transaction_batch_status_action_proposed( + &self, + esdt_safe_batch_id: u64, + expected_tx_batch_status: MultiValueEncoded, + ) -> bool { + self.is_valid_action_id(self.get_action_id_for_set_current_transaction_batch_status( + esdt_safe_batch_id, + expected_tx_batch_status, + )) + } + + /// If `wasSetCurrentTransactionBatchStatusActionProposed` return true, + /// this can be used to get the action ID. + /// Will return 0 if the set status action was not proposed + #[view(getActionIdForSetCurrentTransactionBatchStatus)] + fn get_action_id_for_set_current_transaction_batch_status( + &self, + esdt_safe_batch_id: u64, + expected_tx_batch_status: MultiValueEncoded, + ) -> usize { + self.action_id_for_set_current_transaction_batch_status(esdt_safe_batch_id) + .get(&expected_tx_batch_status.to_vec()) + .unwrap_or(0) + } + + /// Returns `true` (`1`) if the user has signed the action. + /// Does not check whether or not the user is still a board member and the signature valid. + #[view] + fn signed(&self, user: ManagedAddress, action_id: usize) -> bool { + let user_id = self.user_mapper().get_user_id(&user); + if user_id == 0 { + false + } else { + self.action_signer_ids(action_id).contains(&user_id) + } + } + + /// Indicates user rights. + /// `0` = no rights, + /// `1` = can propose. Can also sign if they have enough stake. + #[view(userRole)] + fn user_role(&self, user: &ManagedAddress) -> UserRole { + self.get_user_role(user) + } + + /// Lists all board members + #[view(getAllBoardMembers)] + fn get_all_board_members(&self) -> MultiValueEncoded { + self.get_all_users_with_role(UserRole::BoardMember) + } + + /// Lists all board members that staked the correct amount. + /// A board member with not enough stake can propose, but cannot sign. + #[view(getAllStakedRelayers)] + fn get_all_staked_relayers(&self) -> MultiValueEncoded { + let relayers = self.get_all_board_members().to_vec(); + let mut staked_relayers = ManagedVec::new(); + + for relayer in &relayers { + if self.has_enough_stake(&relayer) { + staked_relayers.push(relayer); + } + } + + staked_relayers.into() + } + + /// Gets the number of signatures for the action with the given ID + #[view(getActionSignerCount)] + fn get_action_signer_count(&self, action_id: usize) -> usize { + self.action_signer_ids(action_id).len() + } + + /// It is possible for board members to lose their role. + /// They are not automatically removed from all actions when doing so, + /// therefore the contract needs to re-check every time when actions are performed. + /// This function is used to validate the signers before performing an action. + /// It also makes it easy to check before performing an action. + #[view(getActionValidSignerCount)] + fn get_action_valid_signer_count(&self, action_id: usize) -> usize { + self.action_signer_ids(action_id) + .iter() + .filter(|signer_id| { + let signer_role = self.user_id_to_role(*signer_id).get(); + let signer_address = self + .user_mapper() + .get_user_address(*signer_id) + .unwrap_or_default(); + + signer_role.is_board_member() && self.has_enough_stake(&signer_address) + }) + .count() + } + + /// Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`. + #[view(quorumReached)] + fn quorum_reached(&self, action_id: usize) -> bool { + let quorum = self.quorum().get(); + let valid_signers_count = self.get_action_valid_signer_count(action_id); + valid_signers_count >= quorum + } + + /// The index of the last proposed action. + /// 0 means that no action was ever proposed yet. + #[view(getActionLastIndex)] + fn get_action_last_index(&self) -> usize { + self.action_mapper().len() + } + + /// Serialized action data of an action with index. + #[view(getActionData)] + fn get_action_data(&self, action_id: usize) -> Action { + self.action_mapper().get(action_id) + } +} diff --git a/multisig/src/setup.rs b/multisig/src/setup.rs index bf25f6d3..4fa369b0 100644 --- a/multisig/src/setup.rs +++ b/multisig/src/setup.rs @@ -1,220 +1,76 @@ -elrond_wasm::imports!(); +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); -use crate::user_role::UserRole; use eth_address::EthAddress; use fee_estimator_module::ProxyTrait as _; +use max_bridged_amount_module::ProxyTrait as _; +use multi_transfer_esdt::ProxyTrait as _; +use multiversx_sc_modules::pause::ProxyTrait as _; use token_module::ProxyTrait as _; +use tx_batch_module::ProxyTrait as _; -#[elrond_wasm_derive::module] +#[multiversx_sc::module] pub trait SetupModule: crate::multisig_general::MultisigGeneralModule + crate::storage::StorageModule + crate::util::UtilModule + + multiversx_sc_modules::pause::PauseModule { - #[init] - fn init( - &self, - required_stake: Self::BigUint, - slash_amount: Self::BigUint, - quorum: usize, - #[var_args] board: VarArgs
, - ) -> SCResult<()> { - self.quorum().set(&quorum); - - let mut duplicates = false; - self.user_mapper() - .get_or_create_users(board.as_slice(), |user_id, new_user| { - if !new_user { - duplicates = true; - } - self.set_user_id_to_role(user_id, UserRole::BoardMember); - }); - require!(!duplicates, "duplicate board member"); - - self.num_board_members() - .update(|nr_board_members| *nr_board_members += board.len()); - - require!( - slash_amount <= required_stake, - "slash amount must be less than or equal to required stake" - ); - self.required_stake_amount().set(&required_stake); - self.slash_amount().set(&slash_amount); - - Ok(()) - } - - #[only_owner] - #[endpoint(deployChildContracts)] - fn deploy_child_contracts( - &self, - egld_esdt_swap_code: BoxedBytes, - multi_transfer_esdt_code: BoxedBytes, - esdt_safe_code: BoxedBytes, - price_aggregator_contract_address: Address, - esdt_safe_eth_tx_gas_limit: Self::BigUint, - multi_transfer_esdt_eth_tx_gas_limit: Self::BigUint, - wrapped_egld_token_id: TokenIdentifier, - #[var_args] token_whitelist: VarArgs, - ) -> SCResult<()> { - // since contracts can either be all deployed or none, - // it's sufficient to check only for one of them - require!( - self.egld_esdt_swap_address().is_empty(), - "This function was called already." - ); - - let mut all_tokens = token_whitelist.into_vec(); - all_tokens.push(wrapped_egld_token_id.clone()); - - let gas_per_deploy = self.blockchain().get_gas_left() / 3; - - // eGLD ESDT swap deploy - - let opt_egld_esdt_swap_address = self - .setup_egld_esdt_swap_proxy() - .init(wrapped_egld_token_id) - .with_gas_limit(gas_per_deploy) - .deploy_contract(&egld_esdt_swap_code, CodeMetadata::UPGRADEABLE); - - let egld_esdt_swap_address = - opt_egld_esdt_swap_address.ok_or("EgldEsdtSwap deploy failed")?; - self.egld_esdt_swap_address().set(&egld_esdt_swap_address); - - // Multi-transfer ESDT deploy - - let opt_multi_transfer_esdt_address = self - .setup_multi_transfer_esdt_proxy(Address::zero()) - .init( - price_aggregator_contract_address.clone(), - multi_transfer_esdt_eth_tx_gas_limit, - all_tokens.clone().into(), - ) - .with_gas_limit(gas_per_deploy) - .deploy_contract(&multi_transfer_esdt_code, CodeMetadata::UPGRADEABLE); - - let multi_transfer_esdt_address = - opt_multi_transfer_esdt_address.ok_or("MultiTransferEsdt deploy failed")?; - self.multi_transfer_esdt_address() - .set(&multi_transfer_esdt_address); - - // ESDT Safe deploy - - let opt_esdt_safe_address = self - .setup_esdt_safe_proxy(Address::zero()) - .init( - price_aggregator_contract_address, - esdt_safe_eth_tx_gas_limit, - all_tokens.into(), - ) - .with_gas_limit(gas_per_deploy) - .deploy_contract(&esdt_safe_code, CodeMetadata::UPGRADEABLE); - - let esdt_safe_address = opt_esdt_safe_address.ok_or("EsdtSafe deploy failed")?; - self.esdt_safe_address().set(&esdt_safe_address); - - // is set only so we don't have to check for "empty" on the very first call - // trying to deserialize a tuple from an empty storage entry would crash - self.statuses_after_execution().set(&(0, Vec::new())); - - Ok(()) - } - #[only_owner] - #[endpoint(upgradeChildContract)] - fn upgrade_child_contract( + #[endpoint(upgradeChildContractFromSource)] + fn upgrade_child_contract_from_source( &self, - sc_address: Address, - new_code: BoxedBytes, - #[var_args] init_args: VarArgs, - ) -> SCResult<()> { - let gas = self.blockchain().get_gas_left() / 2; - let args = (init_args.into_vec().as_slice()).into(); - - self.send().upgrade_contract( - &sc_address, + child_sc_address: ManagedAddress, + source_address: ManagedAddress, + is_payable: bool, + init_args: MultiValueEncoded, + ) { + let mut metadata = CodeMetadata::UPGRADEABLE; + if is_payable { + // TODO: Replace with PayableBySc when it's available + metadata |= CodeMetadata::PAYABLE; + } + + let gas = self.blockchain().get_gas_left(); + Self::Api::send_api_impl().upgrade_from_source_contract( + &child_sc_address, gas, - &Self::BigUint::zero(), - &new_code, - CodeMetadata::UPGRADEABLE, - &args, + &BigUint::zero(), + &source_address, + metadata, + &init_args.to_arg_buffer(), ); - - Ok(()) - } - - #[only_owner] - #[endpoint] - fn pause(&self) -> SCResult<()> { - self.pause_status().set(&true); - - Ok(()) - } - - #[only_owner] - #[endpoint] - fn unpause(&self) -> SCResult<()> { - self.pause_status().set(&false); - - Ok(()) } #[only_owner] #[endpoint(addBoardMember)] - fn add_board_member(&self, board_member: Address) -> SCResult<()> { - self.change_user_role(board_member, UserRole::BoardMember); - - Ok(()) - } - - #[only_owner] - #[endpoint(addProposer)] - fn add_proposer(&self, proposer: Address) -> SCResult<()> { - self.change_user_role(proposer, UserRole::Proposer); - - // validation required for the scenario when a board member becomes a proposer - require!( - self.quorum().get() <= self.num_board_members().get(), - "quorum cannot exceed board size" - ); - - Ok(()) + fn add_board_member_endpoint(&self, board_member: ManagedAddress) { + self.add_board_member(&board_member); } #[only_owner] #[endpoint(removeUser)] - fn remove_user(&self, user: Address) -> SCResult<()> { - self.change_user_role(user, UserRole::None); + fn remove_user(&self, board_member: ManagedAddress) { + self.remove_board_member(&board_member); let num_board_members = self.num_board_members().get(); - let num_proposers = self.num_proposers().get(); - require!( - num_board_members + num_proposers > 0, - "cannot remove all board members and proposers" - ); + require!(num_board_members > 0, "cannot remove all board members"); require!( self.quorum().get() <= num_board_members, "quorum cannot exceed board size" ); - - Ok(()) } + /// Cuts a fixed amount from a board member's stake. + /// This should be used only in cases where the board member + /// is being actively malicious. + /// + /// After stake is cut, the board member would have to stake again + /// to be able to sign actions. #[only_owner] #[endpoint(slashBoardMember)] - fn slash_board_member(&self, board_member: Address) -> SCResult<()> { - self.change_user_role(board_member.clone(), UserRole::None); - let num_board_members = self.num_board_members().get(); - let num_proposers = self.num_proposers().get(); - - require!( - num_board_members + num_proposers > 0, - "cannot remove all board members and proposers" - ); - require!( - self.quorum().get() <= num_board_members, - "quorum cannot exceed board size" - ); + fn slash_board_member(&self, board_member: ManagedAddress) { + self.remove_user(board_member.clone()); let slash_amount = self.slash_amount().get(); @@ -225,45 +81,40 @@ pub trait SetupModule: // add it to total slashed amount pool self.slashed_tokens_amount() .update(|slashed_amt| *slashed_amt += slash_amount); - - Ok(()) } #[only_owner] #[endpoint(changeQuorum)] - fn change_quorum(&self, new_quorum: usize) -> SCResult<()> { + fn change_quorum(&self, new_quorum: usize) { require!( new_quorum <= self.num_board_members().get(), "quorum cannot exceed board size" ); - self.quorum().set(&new_quorum); - - Ok(()) + self.quorum().set(new_quorum); } + /// Maps an ESDT token to an ERC20 address. Used by relayers. #[only_owner] #[endpoint(addMapping)] - fn add_mapping(&self, erc20_address: EthAddress, token_id: TokenIdentifier) -> SCResult<()> { + fn add_mapping(&self, erc20_address: EthAddress, token_id: TokenIdentifier) { require!( self.erc20_address_for_token_id(&token_id).is_empty(), - "Mapping already exists for ERC20 token" + "Mapping already exists for token ID" ); require!( self.token_id_for_erc20_address(&erc20_address).is_empty(), - "Mapping already exists for token id" + "Mapping already exists for ERC20 token" ); self.erc20_address_for_token_id(&token_id) .set(&erc20_address); self.token_id_for_erc20_address(&erc20_address) .set(&token_id); - - Ok(()) } #[only_owner] #[endpoint(clearMapping)] - fn clear_mapping(&self, erc20_address: EthAddress, token_id: TokenIdentifier) -> SCResult<()> { + fn clear_mapping(&self, erc20_address: EthAddress, token_id: TokenIdentifier) { require!( !self.erc20_address_for_token_id(&token_id).is_empty(), "Mapping does not exist for ERC20 token" @@ -273,33 +124,78 @@ pub trait SetupModule: "Mapping does not exist for token id" ); + let mapped_erc_20 = self.erc20_address_for_token_id(&token_id).get(); + let mapped_token_id = self.token_id_for_erc20_address(&erc20_address).get(); + + require!( + erc20_address.raw_addr == mapped_erc_20.raw_addr && token_id == mapped_token_id, + "Invalid mapping" + ); + self.erc20_address_for_token_id(&token_id).clear(); self.token_id_for_erc20_address(&erc20_address).clear(); + } - Ok(()) + #[only_owner] + #[endpoint(pauseEsdtSafe)] + fn pause_esdt_safe(&self) { + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .pause_endpoint() + .execute_on_dest_context(); } #[only_owner] - #[endpoint(changeFeeEstimatorContractAddress)] - fn change_fee_estimator_contract_address(&self, new_address: Address) { - self.setup_esdt_safe_proxy(self.esdt_safe_address().get()) - .set_fee_estimator_contract_address(new_address.clone()) + #[endpoint(unpauseEsdtSafe)] + fn unpause_esdt_safe(&self) { + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .unpause_endpoint() .execute_on_dest_context(); + } - self.setup_multi_transfer_esdt_proxy(self.esdt_safe_address().get()) + #[only_owner] + #[endpoint(changeFeeEstimatorContractAddress)] + fn change_fee_estimator_contract_address(&self, new_address: ManagedAddress) { + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() .set_fee_estimator_contract_address(new_address) .execute_on_dest_context(); } + /// Sets the gas limit being used for Ethereum transactions + /// This is used in the EsdtSafe contract to determine the fee amount + /// + /// fee_amount = eth_gas_limit * price_per_gas_unit + /// + /// where price_per_gas_unit is queried from the aggregator (fee estimator SC) #[only_owner] - #[endpoint(changeDefaultPricePerGwei)] - fn change_default_price_per_gwei(&self, token_id: TokenIdentifier, new_value: Self::BigUint) { - self.setup_esdt_safe_proxy(self.esdt_safe_address().get()) - .set_default_price_per_gwei(token_id.clone(), new_value.clone()) + #[endpoint(changeElrondToEthGasLimit)] + fn change_elrond_to_eth_gas_limit(&self, new_gas_limit: BigUint) { + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .set_eth_tx_gas_limit(new_gas_limit) .execute_on_dest_context(); + } + + /// Default price being used if the aggregator lacks a mapping for this token + /// or the aggregator address is not set + #[only_owner] + #[endpoint(changeDefaultPricePerGasUnit)] + fn change_default_price_per_gas_unit(&self, token_id: TokenIdentifier, new_value: BigUint) { + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .set_default_price_per_gas_unit(token_id, new_value) + .execute_on_dest_context(); + } - self.setup_multi_transfer_esdt_proxy(self.esdt_safe_address().get()) - .set_default_price_per_gwei(token_id, new_value) + /// Token ticker being used when querying the aggregator for GWEI prices + #[only_owner] + #[endpoint(changeTokenTicker)] + fn change_token_ticker(&self, token_id: TokenIdentifier, new_ticker: ManagedBuffer) { + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .set_token_ticker(token_id, new_ticker) .execute_on_dest_context(); } @@ -308,66 +204,118 @@ pub trait SetupModule: fn esdt_safe_add_token_to_whitelist( &self, token_id: TokenIdentifier, - #[var_args] opt_default_value_in_dollars: OptionalArg, + ticker: ManagedBuffer, + opt_default_price_per_gas_unit: OptionalValue, ) { - self.setup_esdt_safe_proxy(self.esdt_safe_address().get()) - .add_token_to_whitelist(token_id, opt_default_value_in_dollars) + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .add_token_to_whitelist(token_id, ticker, opt_default_price_per_gas_unit) .execute_on_dest_context(); } #[only_owner] #[endpoint(esdtSafeRemoveTokenFromWhitelist)] fn esdt_safe_remove_token_from_whitelist(&self, token_id: TokenIdentifier) { - self.setup_esdt_safe_proxy(self.esdt_safe_address().get()) + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() .remove_token_from_whitelist(token_id) .execute_on_dest_context(); } + /// Sets maximum batch size for the EsdtSafe SC. + /// If a batch reaches this amount of transactions, it is considered full, + /// and a new incoming transaction will be put into a new batch. #[only_owner] #[endpoint(esdtSafeSetMaxTxBatchSize)] fn esdt_safe_set_max_tx_batch_size(&self, new_max_tx_batch_size: usize) { - self.setup_esdt_safe_proxy(self.esdt_safe_address().get()) + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() .set_max_tx_batch_size(new_max_tx_batch_size) .execute_on_dest_context(); } + /// Sets the maximum block duration in which an EsdtSafe batch accepts transactions + /// For a batch to be considered "full", it has to either reach `maxTxBatchSize` transactions, + /// or have txBatchBlockDuration blocks pass since the first tx was added in the batch #[only_owner] #[endpoint(esdtSafeSetMaxTxBatchBlockDuration)] fn esdt_safe_set_max_tx_batch_block_duration(&self, new_max_tx_batch_block_duration: u64) { - self.setup_esdt_safe_proxy(self.esdt_safe_address().get()) + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() .set_max_tx_batch_block_duration(new_max_tx_batch_block_duration) .execute_on_dest_context(); } + /// Sets the maximum bridged amount for the token for the Elrond -> Ethereum direction. + /// Any attempt to transfer over this amount will be rejected. #[only_owner] - #[endpoint(multiTransferEsdtaddTokenToWhitelist)] - fn multi_transfer_esdt_add_token_to_whitelist( + #[endpoint(esdtSafeSetMaxBridgedAmountForToken)] + fn esdt_safe_set_max_bridged_amount_for_token( &self, token_id: TokenIdentifier, - #[var_args] opt_default_value_in_dollars: OptionalArg, + max_amount: BigUint, ) { - self.setup_multi_transfer_esdt_proxy(self.multi_transfer_esdt_address().get()) - .add_token_to_whitelist(token_id, opt_default_value_in_dollars) + let _: IgnoreValue = self + .get_esdt_safe_proxy_instance() + .set_max_bridged_amount(token_id, max_amount) .execute_on_dest_context(); } + /// Same as the function above, but for Ethereum -> Elrond transactions. #[only_owner] - #[endpoint(multiTransferEsdtRemoveTokenFromWhitelist)] - fn multi_transfer_esdt_remove_token_from_whitelist(&self, token_id: TokenIdentifier) { - self.setup_multi_transfer_esdt_proxy(self.multi_transfer_esdt_address().get()) - .remove_token_from_whitelist(token_id) + #[endpoint(multiTransferEsdtSetMaxBridgedAmountForToken)] + fn multi_transfer_esdt_set_max_bridged_amount_for_token( + &self, + token_id: TokenIdentifier, + max_amount: BigUint, + ) { + let _: IgnoreValue = self + .get_multi_transfer_esdt_proxy_instance() + .set_max_bridged_amount(token_id, max_amount) .execute_on_dest_context(); } - #[proxy] - fn setup_egld_esdt_swap_proxy(&self) -> egld_esdt_swap::Proxy; + /// Any failed Ethereum -> Elrond transactions are added into so-called "refund batches" + /// This configures the size of a batch. + #[only_owner] + #[endpoint(multiTransferEsdtSetMaxRefundTxBatchSize)] + fn multi_transfer_esdt_set_max_refund_tx_batch_size(&self, new_max_tx_batch_size: usize) { + let _: IgnoreValue = self + .get_multi_transfer_esdt_proxy_instance() + .set_max_tx_batch_size(new_max_tx_batch_size) + .execute_on_dest_context(); + } - #[proxy] - fn setup_esdt_safe_proxy(&self, sc_address: Address) -> esdt_safe::Proxy; + /// Max block duration for refund batches. Default is "infinite" (u64::MAX) + /// and only max batch size matters + #[only_owner] + #[endpoint(multiTransferEsdtSetMaxRefundTxBatchBlockDuration)] + fn multi_transfer_esdt_set_max_refund_tx_batch_block_duration( + &self, + new_max_tx_batch_block_duration: u64, + ) { + let _: IgnoreValue = self + .get_multi_transfer_esdt_proxy_instance() + .set_max_tx_batch_block_duration(new_max_tx_batch_block_duration) + .execute_on_dest_context(); + } - #[proxy] - fn setup_multi_transfer_esdt_proxy( + /// Sets the wrapping contract address. + /// This contract is used to map multiple tokens to a universal one. + /// Useful in cases where a single token (USDC for example) + /// is being transferred from multiple chains. + /// + /// They will all have different token IDs, but can be swapped 1:1 in the wrapping SC. + /// The wrapping is done automatically, so the user only receives the universal token. + #[only_owner] + #[endpoint(multiTransferEsdtSetWrappingContractAddress)] + fn multi_transfer_esdt_set_wrapping_contract_address( &self, - sc_address: Address, - ) -> multi_transfer_esdt::Proxy; + opt_wrapping_contract_address: OptionalValue, + ) { + let _: IgnoreValue = self + .get_multi_transfer_esdt_proxy_instance() + .set_wrapping_contract_address(opt_wrapping_contract_address) + .execute_on_dest_context(); + } } diff --git a/multisig/src/storage.rs b/multisig/src/storage.rs index 54010217..bbc353c2 100644 --- a/multisig/src/storage.rs +++ b/multisig/src/storage.rs @@ -1,89 +1,78 @@ -elrond_wasm::imports!(); +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); use eth_address::EthAddress; -use multi_transfer_esdt::SingleTransferTuple; -use transaction::TransactionStatus; +use transaction::transaction_status::TransactionStatus; use crate::action::Action; use crate::user_role::UserRole; -#[elrond_wasm_derive::module] +pub type EthBatchHash = ManagedByteArray; // keccak256(ManagedVec) + +#[multiversx_sc::module] pub trait StorageModule { /// Minimum number of signatures needed to perform any action. #[view(getQuorum)] #[storage_mapper("quorum")] - fn quorum(&self) -> SingleValueMapper; + fn quorum(&self) -> SingleValueMapper; #[storage_mapper("user")] - fn user_mapper(&self) -> UserMapper; - - #[storage_get("user_role")] - fn get_user_id_to_role(&self, user_id: usize) -> UserRole; + fn user_mapper(&self) -> UserMapper; - #[storage_set("user_role")] - fn set_user_id_to_role(&self, user_id: usize, user_role: UserRole); + #[storage_mapper("user_role")] + fn user_id_to_role(&self, user_id: usize) -> SingleValueMapper; /// Denormalized board member count. /// It is kept in sync with the user list by the contract. #[view(getNumBoardMembers)] #[storage_mapper("num_board_members")] - fn num_board_members(&self) -> SingleValueMapper; - - /// Denormalized proposer count. - /// It is kept in sync with the user list by the contract. - #[view(getNumProposers)] - #[storage_mapper("num_proposers")] - fn num_proposers(&self) -> SingleValueMapper; + fn num_board_members(&self) -> SingleValueMapper; #[storage_mapper("action_data")] - fn action_mapper(&self) -> VecMapper>; + fn action_mapper(&self) -> VecMapper>; #[storage_mapper("action_signer_ids")] - fn action_signer_ids(&self, action_id: usize) -> SingleValueMapper>; + fn action_signer_ids(&self, action_id: usize) -> UnorderedSetMapper; /// The required amount to stake for accepting relayer position #[view(getRequiredStakeAmount)] #[storage_mapper("requiredStakeAmount")] - fn required_stake_amount(&self) -> SingleValueMapper; + fn required_stake_amount(&self) -> SingleValueMapper; /// Staked amount by each board member. #[view(getAmountStaked)] #[storage_mapper("amountStaked")] - fn amount_staked( - &self, - board_member_address: &Address, - ) -> SingleValueMapper; + fn amount_staked(&self, board_member_address: &ManagedAddress) -> SingleValueMapper; /// Amount of stake slashed if a relayer is misbehaving #[view(getSlashAmount)] #[storage_mapper("slashAmount")] - fn slash_amount(&self) -> SingleValueMapper; + fn slash_amount(&self) -> SingleValueMapper; /// Total slashed tokens accumulated #[view(getSlashedTokensAmount)] #[storage_mapper("slashedTokensAmount")] - fn slashed_tokens_amount(&self) -> SingleValueMapper; + fn slashed_tokens_amount(&self) -> SingleValueMapper; - #[view(isPaused)] - #[storage_mapper("pauseStatus")] - fn pause_status(&self) -> SingleValueMapper; + #[view(getLastExecutedEthBatchId)] + #[storage_mapper("lastExecutedEthBatchId")] + fn last_executed_eth_batch_id(&self) -> SingleValueMapper; + + #[view(getLastExecutedEthTxId)] + #[storage_mapper("lastExecutedEthTxId")] + fn last_executed_eth_tx_id(&self) -> SingleValueMapper; #[storage_mapper("batchIdToActionIdMapping")] fn batch_id_to_action_id_mapping( &self, batch_id: u64, - ) -> MapMapper>, usize>; - - #[storage_mapper("statusesAfterExecution")] - fn statuses_after_execution( - &self, - ) -> SingleValueMapper)>; + ) -> MapMapper, usize>; #[storage_mapper("actionIdForSetCurrentTransactionBatchStatus")] fn action_id_for_set_current_transaction_batch_status( &self, esdt_safe_batch_id: u64, - ) -> MapMapper, usize>; + ) -> MapMapper, usize>; /// Mapping between ERC20 Ethereum address and Elrond ESDT Token Identifiers @@ -92,26 +81,22 @@ pub trait StorageModule { fn erc20_address_for_token_id( &self, token_id: &TokenIdentifier, - ) -> SingleValueMapper; + ) -> SingleValueMapper>; #[view(getTokenIdForErc20Address)] #[storage_mapper("tokenIdForErc20Address")] fn token_id_for_erc20_address( &self, - erc20_address: &EthAddress, - ) -> SingleValueMapper; + erc20_address: &EthAddress, + ) -> SingleValueMapper; // SC addresses - #[view(getEgldEsdtSwapAddress)] - #[storage_mapper("egldEsdtSwapAddress")] - fn egld_esdt_swap_address(&self) -> SingleValueMapper; - #[view(getEsdtSafeAddress)] #[storage_mapper("esdtSafeAddress")] - fn esdt_safe_address(&self) -> SingleValueMapper; + fn esdt_safe_address(&self) -> SingleValueMapper; #[view(getMultiTransferEsdtAddress)] #[storage_mapper("multiTransferEsdtAddress")] - fn multi_transfer_esdt_address(&self) -> SingleValueMapper; + fn multi_transfer_esdt_address(&self) -> SingleValueMapper; } diff --git a/multisig/src/user_role.rs b/multisig/src/user_role.rs index 47a491bd..813974a6 100644 --- a/multisig/src/user_role.rs +++ b/multisig/src/user_role.rs @@ -1,26 +1,14 @@ -elrond_wasm::derive_imports!(); +multiversx_sc::derive_imports!(); #[derive(TopEncode, TopDecode, TypeAbi, Clone, Copy, PartialEq)] pub enum UserRole { None, - Proposer, BoardMember, } impl UserRole { - pub fn can_propose(&self) -> bool { - matches!(*self, UserRole::BoardMember | UserRole::Proposer) - } - - pub fn can_perform_action(&self) -> bool { - self.can_propose() - } - - pub fn can_discard_action(&self) -> bool { - self.can_propose() - } - - pub fn can_sign(&self) -> bool { + #[inline(always)] + pub fn is_board_member(&self) -> bool { matches!(*self, UserRole::BoardMember) } } diff --git a/multisig/src/util.rs b/multisig/src/util.rs index ad668a96..96fc007b 100644 --- a/multisig/src/util.rs +++ b/multisig/src/util.rs @@ -1,145 +1,33 @@ -elrond_wasm::imports!(); +multiversx_sc::imports!(); -use multi_transfer_esdt::SingleTransferTuple; +use transaction::{EthTransaction, EthTxAsMultiValue}; -use crate::action::Action; -use crate::action::ActionFullInfo; +use crate::storage::EthBatchHash; use crate::user_role::UserRole; -#[elrond_wasm_derive::module] +#[multiversx_sc::module] pub trait UtilModule: crate::storage::StorageModule { - /// Iterates through all actions and retrieves those that are still pending. - /// Serialized full action data: - /// - the action id - /// - the serialized action data - /// - (number of signers followed by) list of signer addresses. - #[view(getPendingActionFullInfo)] - fn get_pending_action_full_info(&self) -> MultiResultVec> { - let mut result = Vec::new(); - let action_last_index = self.get_action_last_index(); - let action_mapper = self.action_mapper(); - for action_id in 1..=action_last_index { - let action_data = action_mapper.get(action_id); - if action_data.is_pending() { - result.push(ActionFullInfo { - action_id, - action_data, - signers: self.get_action_signers(action_id), - }); - } - } - result.into() - } - - /// Returns `true` (`1`) if the user has signed the action. - /// Does not check whether or not the user is still a board member and the signature valid. - #[view] - fn signed(&self, user: Address, action_id: usize) -> bool { - let user_id = self.user_mapper().get_user_id(&user); - if user_id == 0 { - false - } else { - let signer_ids = self.action_signer_ids(action_id).get(); - signer_ids.contains(&user_id) - } - } - - /// Indicates user rights. - /// `0` = no rights, - /// `1` = can propose, but not sign, - /// `2` = can propose and sign. - #[view(userRole)] - fn user_role(&self, user: &Address) -> UserRole { + fn get_user_role(&self, user: &ManagedAddress) -> UserRole { let user_id = self.user_mapper().get_user_id(user); if user_id == 0 { UserRole::None } else { - self.get_user_id_to_role(user_id) + self.user_id_to_role(user_id).get() } } - /// Lists all users that can sign actions. - #[view(getAllBoardMembers)] - fn get_all_board_members(&self) -> MultiResultVec
{ - self.get_all_users_with_role(UserRole::BoardMember) - } - - #[view(getAllStakedRelayers)] - fn get_all_staked_relayers(&self) -> MultiResultVec
{ - let mut relayers = self.get_all_board_members().into_vec(); - relayers.retain(|relayer| self.has_enough_stake(relayer)); + fn is_valid_action_id(&self, action_id: usize) -> bool { + let min_id = 1; + let max_id = self.action_mapper().len(); - relayers.into() + action_id >= min_id && action_id <= max_id } - /// Lists all proposers that are not board members. - #[view(getAllProposers)] - fn get_all_proposers(&self) -> MultiResultVec
{ - self.get_all_users_with_role(UserRole::Proposer) - } - - /// Gets addresses of all users who signed an action. - /// Does not check if those users are still board members or not, - /// so the result may contain invalid signers. - #[view(getActionSigners)] - fn get_action_signers(&self, action_id: usize) -> Vec
{ - self.action_signer_ids(action_id) - .get() - .iter() - .map(|signer_id| self.user_mapper().get_user_address_unchecked(*signer_id)) - .collect() - } - - /// Gets addresses of all users who signed an action and are still board members. - /// All these signatures are currently valid. - #[view(getActionSignerCount)] - fn get_action_signer_count(&self, action_id: usize) -> usize { - self.action_signer_ids(action_id).get().len() - } - - /// It is possible for board members to lose their role. - /// They are not automatically removed from all actions when doing so, - /// therefore the contract needs to re-check every time when actions are performed. - /// This function is used to validate the signers before performing an action. - /// It also makes it easy to check before performing an action. - #[view(getActionValidSignerCount)] - fn get_action_valid_signer_count(&self, action_id: usize) -> usize { - let signer_ids = self.action_signer_ids(action_id).get(); - signer_ids - .iter() - .filter(|signer_id| { - let signer_role = self.get_user_id_to_role(**signer_id); - signer_role.can_sign() - }) - .count() - } - - /// Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`. - #[view(quorumReached)] - fn quorum_reached(&self, action_id: usize) -> bool { - let quorum = self.quorum().get(); - let valid_signers_count = self.get_action_valid_signer_count(action_id); - valid_signers_count >= quorum - } - - /// The index of the last proposed action. - /// 0 means that no action was ever proposed yet. - #[view(getActionLastIndex)] - fn get_action_last_index(&self) -> usize { - self.action_mapper().len() - } - - /// Serialized action data of an action with index. - #[view(getActionData)] - fn get_action_data(&self, action_id: usize) -> Action { - self.action_mapper().get(action_id) - } - - fn get_all_users_with_role(&self, role: UserRole) -> MultiResultVec
{ - let mut result = Vec::new(); + fn get_all_users_with_role(&self, role: UserRole) -> MultiValueEncoded { + let mut result = ManagedVec::new(); let num_users = self.user_mapper().get_user_count(); for user_id in 1..=num_users { - if self.get_user_id_to_role(user_id) == role { + if self.user_id_to_role(user_id).get() == role { if let Some(address) = self.user_mapper().get_user_address(user_id) { result.push(address); } @@ -148,38 +36,71 @@ pub trait UtilModule: crate::storage::StorageModule { result.into() } - fn require_esdt_safe_deployed(&self) -> SCResult<()> { - require!( - !self.esdt_safe_address().is_empty(), - "ESDT Safe SC has to be deployed first" - ); - Ok(()) + fn has_enough_stake(&self, board_member_address: &ManagedAddress) -> bool { + let required_stake = self.required_stake_amount().get(); + let amount_staked = self.amount_staked(board_member_address).get(); + + amount_staked >= required_stake } - fn require_multi_transfer_esdt_deployed(&self) -> SCResult<()> { - require!( - !self.multi_transfer_esdt_address().is_empty(), - "Multi-transfer ESDT SC has to be deployed first" - ); - Ok(()) + fn transfers_multi_value_to_eth_tx_vec( + &self, + transfers: MultiValueEncoded>, + ) -> ManagedVec> { + let mut transfers_as_eth_tx = ManagedVec::new(); + for transfer in transfers { + let (from, to, token_id, amount, tx_nonce) = transfer.into_tuple(); + + transfers_as_eth_tx.push(EthTransaction { + from, + to, + token_id, + amount, + tx_nonce, + }); + } + + transfers_as_eth_tx } - fn has_enough_stake(&self, board_member_address: &Address) -> bool { - let required_stake = self.required_stake_amount().get(); - let amount_staked = self.amount_staked(board_member_address).get(); + fn require_valid_eth_tx_ids(&self, eth_tx_vec: &ManagedVec>) { + let last_executed_eth_tx_id = self.last_executed_eth_tx_id().get(); + let mut current_expected_tx_id = last_executed_eth_tx_id + 1; - amount_staked >= required_stake + for eth_tx in eth_tx_vec { + require!(eth_tx.tx_nonce == current_expected_tx_id, "Invalid Tx ID"); + current_expected_tx_id += 1; + } } - fn transfers_multiarg_to_tuples_vec( + fn hash_eth_tx_batch( &self, - transfers: MultiArgVec>, - ) -> Vec> { - let mut transfers_as_tuples = Vec::new(); - for transfer in transfers.into_vec() { - transfers_as_tuples.push(transfer.into_tuple()); + eth_tx_batch: &ManagedVec>, + ) -> EthBatchHash { + let mut serialized = ManagedBuffer::new(); + if eth_tx_batch.top_encode(&mut serialized).is_err() { + sc_panic!("Failed to serialized batch"); } - transfers_as_tuples + self.crypto().keccak256(&serialized) + } + + // proxies + + #[proxy] + fn esdt_safe_proxy(&self, sc_address: ManagedAddress) -> esdt_safe::Proxy; + + #[proxy] + fn multi_transfer_esdt_proxy( + &self, + sc_address: ManagedAddress, + ) -> multi_transfer_esdt::Proxy; + + fn get_esdt_safe_proxy_instance(&self) -> esdt_safe::Proxy { + self.esdt_safe_proxy(self.esdt_safe_address().get()) + } + + fn get_multi_transfer_esdt_proxy_instance(&self) -> multi_transfer_esdt::Proxy { + self.multi_transfer_esdt_proxy(self.multi_transfer_esdt_address().get()) } } diff --git a/multisig/tests/mandos_go_test.rs b/multisig/tests/mandos_go_test.rs deleted file mode 100644 index d481b8ba..00000000 --- a/multisig/tests/mandos_go_test.rs +++ /dev/null @@ -1,46 +0,0 @@ -#[test] -fn create_elrond_to_ethereum_tx_batch_go() { - elrond_wasm_debug::mandos_go("mandos/create_elrond_to_ethereum_tx_batch.scen.json"); -} - -#[test] -fn ethereum_to_elrond_tx_batch_ok_go() { - elrond_wasm_debug::mandos_go("mandos/ethereum_to_elrond_tx_batch_ok.scen.json"); -} - -#[test] -fn ethereum_to_elrond_tx_batch_rejected_go() { - elrond_wasm_debug::mandos_go("mandos/ethereum_to_elrond_tx_batch_rejected.scen.json"); -} - -#[test] -fn execute_elrond_to_ethereum_tx_batch_go() { - elrond_wasm_debug::mandos_go("mandos/execute_elrond_to_ethereum_tx_batch.scen.json"); -} - -#[test] -fn get_empty_batch_go() { - elrond_wasm_debug::mandos_go("mandos/get_empty_batch.scen.json"); -} - -#[test] -fn reject_elrond_to_ethereum_tx_batch_go() { - elrond_wasm_debug::mandos_go("mandos/reject_elrond_to_ethereum_tx_batch.scen.json"); -} - -#[test] -fn setup_go() { - elrond_wasm_debug::mandos_go("mandos/setup.scen.json"); -} - -#[test] -fn unstake_go() { - elrond_wasm_debug::mandos_go("mandos/unstake.scen.json"); -} - -/* -#[test] -fn upgrade_child_sc_go() { - elrond_wasm_debug::mandos_go("mandos/upgrade_child_sc.scen.json"); -} -*/ diff --git a/multisig/tests/scenario_go_test.rs b/multisig/tests/scenario_go_test.rs new file mode 100644 index 00000000..9605e9d4 --- /dev/null +++ b/multisig/tests/scenario_go_test.rs @@ -0,0 +1,46 @@ +#[test] +fn create_elrond_to_ethereum_tx_batch_go() { + multiversx_sc_scenario::run_go("mandos/create_elrond_to_ethereum_tx_batch.scen.json"); +} + +#[test] +fn ethereum_to_elrond_tx_batch_ok_go() { + multiversx_sc_scenario::run_go("mandos/ethereum_to_elrond_tx_batch_ok.scen.json"); +} + +#[test] +fn ethereum_to_elrond_tx_batch_rejected_go() { + multiversx_sc_scenario::run_go("mandos/ethereum_to_elrond_tx_batch_rejected.scen.json"); +} + +#[test] +fn execute_elrond_to_ethereum_tx_batch_go() { + multiversx_sc_scenario::run_go("mandos/execute_elrond_to_ethereum_tx_batch.scen.json"); +} + +#[test] +fn get_empty_batch_go() { + multiversx_sc_scenario::run_go("mandos/get_empty_batch.scen.json"); +} + +#[test] +fn reject_elrond_to_ethereum_tx_batch_go() { + multiversx_sc_scenario::run_go("mandos/reject_elrond_to_ethereum_tx_batch.scen.json"); +} + +#[test] +fn setup_go() { + multiversx_sc_scenario::run_go("mandos/setup.scen.json"); +} + +#[test] +fn unstake_go() { + multiversx_sc_scenario::run_go("mandos/unstake.scen.json"); +} + +/* +#[test] +fn upgrade_child_sc_go() { + multiversx_sc_scenario::run_go("mandos/upgrade_child_sc.scen.json"); +} +*/ diff --git a/multisig/wasm/Cargo.lock b/multisig/wasm/Cargo.lock new file mode 100644 index 00000000..deda533d --- /dev/null +++ b/multisig/wasm/Cargo.lock @@ -0,0 +1,370 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bridged-tokens-wrapper" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "transaction", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "esdt-safe" +version = "0.0.0" +dependencies = [ + "eth-address", + "fee-estimator-module", + "max-bridged-amount-module", + "multiversx-sc", + "multiversx-sc-modules", + "token-module", + "transaction", + "tx-batch-module", +] + +[[package]] +name = "eth-address" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "fee-estimator-module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "max-bridged-amount-module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "multi-transfer-esdt" +version = "0.0.0" +dependencies = [ + "bridged-tokens-wrapper", + "max-bridged-amount-module", + "multiversx-sc", + "transaction", + "tx-batch-module", +] + +[[package]] +name = "multisig" +version = "0.0.0" +dependencies = [ + "esdt-safe", + "eth-address", + "fee-estimator-module", + "max-bridged-amount-module", + "multi-transfer-esdt", + "multiversx-sc", + "multiversx-sc-modules", + "token-module", + "transaction", + "tx-batch-module", +] + +[[package]] +name = "multisig-wasm" +version = "0.0.0" +dependencies = [ + "multisig", + "multiversx-sc-wasm-adapter", +] + +[[package]] +name = "multiversx-sc" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31561133b64837f1a7835ae4ba96383ab07d9ce401e703ad2a37aef45789a1e7" +dependencies = [ + "bitflags", + "hashbrown", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7638cb46a0e99c636fd55443ac534ff0a5fad0bd772e1037fbac9a75e04c3c9" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", + "wee_alloc", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e976002d51367f16140929c10ee695f95dd8d34c150a45db60d3fcd1328a267a" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23187464277575f8055c92cffbd664592825591f5cebb8dae038d2af04c5e639" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-modules" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb0b06f37396484f01d89295091db401b8b515ab781e3580a6bb333e912591" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.39.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af5fee54f1498ec8181593cd54a96ef7436543ad78848e2e805b9b62c27c4095" +dependencies = [ + "multiversx-sc", + "wee_alloc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "token-module" +version = "0.0.0" +dependencies = [ + "fee-estimator-module", + "multiversx-sc", +] + +[[package]] +name = "transaction" +version = "0.0.0" +dependencies = [ + "eth-address", + "multiversx-sc", +] + +[[package]] +name = "tx-batch-module" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "transaction", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/multisig/wasm/Cargo.toml b/multisig/wasm/Cargo.toml index af3f1fa3..cbe703b5 100644 --- a/multisig/wasm/Cargo.toml +++ b/multisig/wasm/Cargo.toml @@ -7,22 +7,17 @@ publish = false [lib] crate-type = ["cdylib"] - [profile.release] codegen-units = 1 opt-level = "z" lto = true debug = false panic = "abort" - [dependencies.multisig] path = ".." -features = ["wasm-output-mode"] -default-features = false -[dependencies.elrond-wasm-output] -version = "0.18" -features = ["wasm-output-mode"] +[dependencies.multiversx-sc-wasm-adapter] +version = "0.41.3" [workspace] members = ["."] diff --git a/multisig/wasm/src/lib.rs b/multisig/wasm/src/lib.rs index d7086fa2..4cf423c2 100644 --- a/multisig/wasm/src/lib.rs +++ b/multisig/wasm/src/lib.rs @@ -1,4 +1,85 @@ +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 61 +// Async Callback (empty): 1 +// Total number of exported functions: 63 + #![no_std] +#![feature(alloc_error_handler, lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + multisig + ( + distributeFeesFromChildContracts + stake + unstake + proposeEsdtSafeSetCurrentTransactionBatchStatus + proposeMultiTransferEsdtBatch + moveRefundBatchToSafe + performAction + sign + upgradeChildContractFromSource + addBoardMember + removeUser + slashBoardMember + changeQuorum + addMapping + clearMapping + pauseEsdtSafe + unpauseEsdtSafe + changeFeeEstimatorContractAddress + changeElrondToEthGasLimit + changeDefaultPricePerGasUnit + changeTokenTicker + esdtSafeAddTokenToWhitelist + esdtSafeRemoveTokenFromWhitelist + esdtSafeSetMaxTxBatchSize + esdtSafeSetMaxTxBatchBlockDuration + esdtSafeSetMaxBridgedAmountForToken + multiTransferEsdtSetMaxBridgedAmountForToken + multiTransferEsdtSetMaxRefundTxBatchSize + multiTransferEsdtSetMaxRefundTxBatchBlockDuration + multiTransferEsdtSetWrappingContractAddress + getQuorum + getNumBoardMembers + getRequiredStakeAmount + getAmountStaked + getSlashAmount + getSlashedTokensAmount + getLastExecutedEthBatchId + getLastExecutedEthTxId + getErc20AddressForTokenId + getTokenIdForErc20Address + getEsdtSafeAddress + getMultiTransferEsdtAddress + getCurrentTxBatch + getCurrentRefundBatch + wasActionExecuted + wasTransferActionProposed + getActionIdForTransferBatch + wasSetCurrentTransactionBatchStatusActionProposed + getActionIdForSetCurrentTransactionBatchStatus + signed + userRole + getAllBoardMembers + getAllStakedRelayers + getActionSignerCount + getActionValidSignerCount + quorumReached + getActionLastIndex + getActionData + pause + unpause + isPaused + ) +} -pub use multisig::*; -pub use elrond_wasm_output::*; +multiversx_sc_wasm_adapter::empty_callback! {} diff --git a/price-aggregator/mandos/deploy.scen.json b/price-aggregator/mandos/deploy.scen.json index c6319132..c7474748 100644 --- a/price-aggregator/mandos/deploy.scen.json +++ b/price-aggregator/mandos/deploy.scen.json @@ -31,11 +31,9 @@ "contractCode": "file:../price-aggregator.wasm", "value": "0", "arguments": [ - "str:EGLD", - "address:oracle", "1", "0", - "0" + "address:oracle" ], "gasLimit": "20,000,000", "gasPrice": "0" @@ -47,11 +45,31 @@ "refund": "*" } }, + { + "step": "scCall", + "txId": "unpause", + "tx": { + "from": "address:aggregator-owner", + "to": "sc:price_aggregator", + "value": "0", + "function": "unpause", + "arguments": [], + "gasLimit": "100,000,000", + "gasPrice": "0" + }, + "expect": { + "status": "0", + "message": "", + "out": [], + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { "address:aggregator-owner": { - "nonce": "1", + "nonce": "2", "balance": "0", "storage": {} }, @@ -59,8 +77,6 @@ "nonce": "0", "balance": "0", "storage": { - "str:payment_token": "str:EGLD", - "str:query_payment_amount": "0", "str:submission_count": "1", "str:decimals": "0", "str:oracle_status.mapped|address:oracle": { diff --git a/price-aggregator/price-aggregator.abi.json b/price-aggregator/price-aggregator.abi.json new file mode 100644 index 00000000..530b91a9 --- /dev/null +++ b/price-aggregator/price-aggregator.abi.json @@ -0,0 +1,263 @@ +{ + "buildInfo": { + "rustc": { + "version": "1.60.0-nightly", + "commitHash": "bd3cb52565faab2755ff1bdb54d88bc91f47b4b9", + "commitDate": "2022-01-16", + "channel": "Nightly", + "short": "rustc 1.60.0-nightly (bd3cb5256 2022-01-16)" + }, + "contractCrate": { + "name": "price-aggregator", + "version": "0.0.1", + "git_version": "49b8105-modified" + }, + "framework": { + "name": "elrond-wasm", + "version": "0.31.1" + } + }, + "name": "PriceAggregator", + "constructor": { + "inputs": [ + { + "name": "submission_count", + "type": "u32" + }, + { + "name": "decimals", + "type": "u8" + }, + { + "name": "oracles", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "addOracles", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "oracles", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "docs": [ + "Also receives submission count,", + "so the owner does not have to update it manually with setSubmissionCount before this call" + ], + "name": "removeOracles", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "submission_count", + "type": "u32" + }, + { + "name": "oracles", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "submit", + "mutability": "mutable", + "inputs": [ + { + "name": "from", + "type": "bytes" + }, + { + "name": "to", + "type": "bytes" + }, + { + "name": "price", + "type": "BigUint" + } + ], + "outputs": [] + }, + { + "name": "submitBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "submissions", + "type": "variadic>", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "latestRoundData", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "latestPriceFeed", + "mutability": "readonly", + "inputs": [ + { + "name": "from", + "type": "bytes" + }, + { + "name": "to", + "type": "bytes" + } + ], + "outputs": [ + { + "type": "u32" + }, + { + "type": "bytes" + }, + { + "type": "bytes" + }, + { + "type": "BigUint" + }, + { + "type": "u8" + } + ] + }, + { + "name": "latestPriceFeedOptional", + "mutability": "readonly", + "inputs": [ + { + "name": "from", + "type": "bytes" + }, + { + "name": "to", + "type": "bytes" + } + ], + "outputs": [ + { + "type": "optional>", + "multi_result": true + } + ] + }, + { + "name": "setSubmissionCount", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "submission_count", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "getOracles", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "submission_count", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "decimals", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u8" + } + ] + }, + { + "name": "isPaused", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "pause", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "unpause", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + } + ], + "hasCallback": false, + "types": { + "PriceFeed": { + "type": "struct", + "fields": [ + { + "name": "round_id", + "type": "u32" + }, + { + "name": "from", + "type": "bytes" + }, + { + "name": "to", + "type": "bytes" + }, + { + "name": "price", + "type": "BigUint" + }, + { + "name": "decimals", + "type": "u8" + } + ] + } + } +} diff --git a/price-aggregator/price-aggregator.wasm b/price-aggregator/price-aggregator.wasm index ab15cdcc..75eabef4 100755 Binary files a/price-aggregator/price-aggregator.wasm and b/price-aggregator/price-aggregator.wasm differ