├── rust-toolchain.toml ├── examples ├── challenge_example_2 │ ├── sources │ │ ├── framework-solve │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── Cargo.lock │ │ │ ├── dependency │ │ │ │ ├── Move.toml │ │ │ │ └── sources │ │ │ │ │ └── city.move │ │ │ ├── solve │ │ │ │ ├── Move.toml │ │ │ │ └── sources │ │ │ │ │ └── solve.move │ │ │ └── src │ │ │ │ └── main.rs │ │ ├── run_server.sh │ │ ├── run_client.sh │ │ └── framework │ │ │ ├── chall │ │ │ ├── Move.toml │ │ │ └── sources │ │ │ │ └── city.move │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── main.rs │ └── README.md ├── challenge_example │ ├── README.md │ ├── run_server.sh │ ├── run_client.sh │ ├── framework-solve │ │ ├── Cargo.toml │ │ ├── Cargo.lock │ │ ├── solve │ │ │ ├── Move.toml │ │ │ └── sources │ │ │ │ └── solve.move │ │ ├── dependency │ │ │ ├── Move.toml │ │ │ └── sources │ │ │ │ ├── osec.move │ │ │ │ ├── router.move │ │ │ │ ├── merchstore.move │ │ │ │ ├── ctf.move │ │ │ │ ├── otterloan.move │ │ │ │ └── otterswap.move │ │ └── src │ │ │ └── main.rs │ └── framework │ │ ├── chall │ │ ├── Move.toml │ │ └── sources │ │ │ ├── osec.move │ │ │ ├── router.move │ │ │ ├── merchstore.move │ │ │ ├── ctf.move │ │ │ ├── otterloan.move │ │ │ └── otterswap.move │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── challenge_example_3 │ ├── run_server.sh │ ├── run_client.sh │ ├── framework-solve │ ├── Cargo.toml │ ├── solve │ │ ├── Move.toml │ │ └── sources │ │ │ └── solution.move │ ├── dependency │ │ ├── Move.toml │ │ └── sources │ │ │ └── interactive_ctf.move │ ├── Cargo.lock │ └── src │ │ └── main.rs │ ├── README.md │ └── framework │ ├── chall │ ├── Move.toml │ └── sources │ │ └── interactive_ctf.move │ ├── Cargo.toml │ └── src │ └── main.rs ├── .gitignore ├── LICENSE ├── Cargo.toml ├── README.md └── src └── lib.rs /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.86" 3 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/challenge_example/README.md: -------------------------------------------------------------------------------- 1 | # Sample Challenge - released at Sui Basecamp CTF 2024 -------------------------------------------------------------------------------- /examples/challenge_example_2/README.md: -------------------------------------------------------------------------------- 1 | # Sample Challenge - released at Sui Basecamp CTF 2024 -------------------------------------------------------------------------------- /examples/challenge_example/run_server.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | cd framework/chall && sui move build 4 | cd .. 5 | cargo r --release 6 | -------------------------------------------------------------------------------- /examples/challenge_example_3/run_server.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | cd framework/chall && sui move build 4 | cd .. 5 | cargo r --release -------------------------------------------------------------------------------- /examples/challenge_example/run_client.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | cd framework-solve/solve && sui move build 4 | cd .. 5 | cargo r --release 6 | -------------------------------------------------------------------------------- /examples/challenge_example_3/run_client.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | cd framework-solve/solve && sui move build 4 | cd .. 5 | cargo r --release -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/run_server.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | cd framework/chall && sui move build 4 | cd .. 5 | cargo r --release 6 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/run_client.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | cd framework-solve/solve && sui move build 4 | cd .. 5 | cargo r --release 6 | -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solve-framework" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solve-framework" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "solve-framework" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "solve-framework" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/challenge_example_3/framework-solve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "framework-solve" 3 | version = "0.1.0" 4 | edition = "2021" 5 | exclude = ["solve/", "dependency/"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | serde_json = "1.0" -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework/chall/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = 'challenge' 3 | version = '1.0.0' 4 | edition = "2024.alpha" 5 | 6 | [dependencies] 7 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet" } 8 | 9 | [addresses] 10 | challenge = "0x0" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | 4 | # Rust build directory 5 | **/target 6 | .pre-commit* 7 | 8 | # Move build directory 9 | build 10 | storage 11 | 12 | # Move-related files 13 | Move.lock 14 | .trace 15 | .coverage_map.mvcov 16 | 17 | # IDE 18 | **/.history/ 19 | **/.vscode/ 20 | **/.idea/ 21 | 22 | # Demo 23 | example/challenge_example/**/Cargo.lock -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/dependency/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = 'challenge' 3 | version = '1.0.0' 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet" } 7 | 8 | [addresses] 9 | challenge = "0x9df1d39af36aebcb5f8d3159479d75d4721a54b4094cbaccb064a2763a5bf423" -------------------------------------------------------------------------------- /examples/challenge_example/framework/chall/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "challenge" 3 | version = "0.0.1" 4 | edition = "2024.alpha" 5 | 6 | [dependencies] 7 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet" } 8 | 9 | [addresses] 10 | challenge = "0x0" 11 | admin = "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/solve/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solution" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet" } 7 | 8 | [dependencies.challenge] 9 | version = '1.0.0' 10 | local = '../dependency' 11 | 12 | [addresses] 13 | solution = "0x0" 14 | -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/solve/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gringotts_solution" 3 | version = "0.0.1" 4 | edition = "2024.alpha" 5 | 6 | [dependencies] 7 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet" } 8 | 9 | [dependencies.challenge] 10 | version = '1.0.0' 11 | local = '../dependency' 12 | 13 | [addresses] 14 | solution = "0x0" 15 | -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/dependency/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "challenge" 3 | version = "0.0.1" 4 | edition = "2024.alpha" 5 | 6 | [dependencies] 7 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet" } 8 | 9 | [addresses] 10 | challenge = "0xffb3399bdd3fdac706232c5e919b19e95dcaadfe2daf574a4ce651d02b7521a8" 11 | admin = "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" -------------------------------------------------------------------------------- /examples/challenge_example_3/README.md: -------------------------------------------------------------------------------- 1 | # Interactive Challenge Example 2 | 3 | This example demonstrates an interactive CTF challenge using the sui-ctf-framework with a menu-based interface. 4 | 5 | ## Running the Challenge 6 | 7 | 1. Start the server: 8 | ```bash 9 | ./run_server.sh 10 | ``` 11 | 12 | 2. In another terminal, start the client: 13 | ```bash 14 | ./run_client.sh 15 | ``` 16 | 17 | The client will connect to the server and present an interactive menu for solving the challenge. -------------------------------------------------------------------------------- /examples/challenge_example_3/framework/chall/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "challenge" 3 | edition = "2024.beta" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "devnet-v1.49.0" } 7 | MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "devnet-v1.49.0" } 8 | 9 | [addresses] 10 | challenge = "0x0" 11 | std = "0x1" 12 | sui = "0x2" -------------------------------------------------------------------------------- /examples/challenge_example_3/framework-solve/solve/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solution" 3 | edition = "2024.beta" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "devnet-v1.49.0" } 7 | MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "devnet-v1.49.0" } 8 | challenge = { local = "../dependency/" } 9 | 10 | [addresses] 11 | solution = "0x0" 12 | std = "0x1" 13 | sui = "0x2" -------------------------------------------------------------------------------- /examples/challenge_example_3/framework-solve/dependency/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "challenge" 3 | edition = "2024.beta" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "devnet-v1.49.0" } 7 | MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "devnet-v1.49.0" } 8 | 9 | [addresses] 10 | challenge = "0x134620dbb8a942881a4265f0db9b83f1a7db5669293a707c573bd6ad3b59c489" 11 | std = "0x1" 12 | sui = "0x2" -------------------------------------------------------------------------------- /examples/challenge_example_3/framework/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "framework" 3 | version = "0.1.0" 4 | edition = "2021" 5 | exclude = ["chall/"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | quote = "1.0.26" 11 | threadpool = "1.8.1" 12 | proc-macro2 = "1.0.66" 13 | serde_json = "1.0" 14 | 15 | tokio = { version = "1", features = ["full"] } 16 | 17 | move-core-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-core-types" } 18 | move-bytecode-source-map = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-bytecode-source-map" } 19 | move-binary-format = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-binary-format" } 20 | move-symbol-pool = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-symbol-pool" } 21 | move-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-transactional-test-runner" } 22 | 23 | sui-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-types"} 24 | sui-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-transactional-test-runner"} 25 | 26 | # sui-ctf-framework = { git = "https://github.com/otter-sec/sui-ctf-framework" } 27 | sui-ctf-framework = { path = "../../../../sui-ctf-framework" } -------------------------------------------------------------------------------- /examples/challenge_example/framework/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "framework" 3 | version = "0.1.0" 4 | edition = "2021" 5 | exclude = ["chall/"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | quote = "1.0.26" 11 | threadpool = "1.8.1" 12 | proc-macro2 = "1.0.66" 13 | serde_json = "1.0" 14 | 15 | tokio = { version = "1", features = ["full"] } 16 | 17 | move-core-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-core-types" } 18 | move-bytecode-source-map = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-bytecode-source-map" } 19 | move-binary-format = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-binary-format" } 20 | move-symbol-pool = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-symbol-pool" } 21 | move-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-transactional-test-runner" } 22 | 23 | sui-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-types"} 24 | sui-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-transactional-test-runner"} 25 | 26 | # sui-ctf-framework = { git = "https://github.com/otter-sec/sui-ctf-framework" } 27 | sui-ctf-framework = { path = "../../../../sui-ctf-framework" } 28 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "framework" 3 | version = "0.1.0" 4 | edition = "2021" 5 | exclude = ["chall/"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | quote = "1.0.26" 11 | threadpool = "1.8.1" 12 | proc-macro2 = "1.0.66" 13 | serde_json = "1.0" 14 | 15 | tokio = { version = "1", features = ["full"] } 16 | 17 | move-core-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-core-types" } 18 | move-bytecode-source-map = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-bytecode-source-map" } 19 | move-binary-format = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-binary-format" } 20 | move-symbol-pool = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-symbol-pool" } 21 | move-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-transactional-test-runner" } 22 | 23 | sui-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-types"} 24 | sui-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-transactional-test-runner"} 25 | 26 | # sui-ctf-framework = { git = "https://github.com/otter-sec/sui-ctf-framework" } 27 | sui-ctf-framework = { path = "../../../../../sui-ctf-framework" } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024 OtterSec LLC 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /examples/challenge_example_3/framework-solve/solve/sources/solution.move: -------------------------------------------------------------------------------- 1 | module solution::solution { 2 | use challenge::interactive_ctf::{Self, Challenge, UserProgress}; 3 | use sui::tx_context::TxContext; 4 | 5 | // Step 1: Complete first challenge step 6 | public entry fun solve_step_one(progress: &mut UserProgress) { 7 | interactive_ctf::step_one(progress, 100); 8 | } 9 | 10 | // Step 2: Complete second challenge step with the secret list 11 | public entry fun solve_step_two(progress: &mut UserProgress, challenge: &mut Challenge) { 12 | let list = vector[1u8, 3u8, 3u8, 7u8]; 13 | interactive_ctf::step_two(progress, challenge, list); 14 | } 15 | 16 | // Step 3: Complete third challenge step with the secret number 17 | public entry fun solve_step_three(progress: &mut UserProgress, challenge: &Challenge) { 18 | interactive_ctf::step_three(progress, challenge, 42); 19 | } 20 | 21 | // Final: Complete the challenge 22 | public entry fun complete_challenge(progress: &mut UserProgress, challenge: &mut Challenge) { 23 | interactive_ctf::final_solve(progress, challenge); 24 | } 25 | 26 | // All-in-one solution for testing 27 | public entry fun solve_all( 28 | progress: &mut UserProgress, 29 | challenge: &mut Challenge, 30 | ctx: &mut TxContext 31 | ) { 32 | // Step 1: Call step_one with value 100 33 | interactive_ctf::step_one(progress, 100); 34 | 35 | // Step 2: Call step_two with list [1, 3, 3, 7] 36 | let list = vector[1u8, 3u8, 3u8, 7u8]; 37 | interactive_ctf::step_two(progress, challenge, list); 38 | 39 | // Step 3: Call step_three with secret value 42 40 | interactive_ctf::step_three(progress, challenge, 42); 41 | 42 | // Final solve 43 | interactive_ctf::final_solve(progress, challenge); 44 | } 45 | } -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/solve/sources/solve.move: -------------------------------------------------------------------------------- 1 | module solution::solution { 2 | // [*] Import dependencies 3 | use sui::tx_context::TxContext; 4 | use std::vector; 5 | // use std::debug; 6 | use std::bcs; 7 | use challenge::MileHighCity; 8 | 9 | public entry fun solve(status: &mut MileHighCity::Status, ctx: &mut TxContext) { 10 | let original_plaintext : vector = vector[73,110,115,116,101,97,100,32,111,102,32, 11 | 112,117,116,116,105,110,103,32,116,104, 12 | 101,32,116,97,120,105,32,100,114,105,118, 13 | 101,114,32,111,117,116,32,111,102,32,97, 14 | 32,106,111,98,44,32,98,108,111,99,107,99, 15 | 104,97,105,110,32,112,117,116,115,32,85, 16 | 98,101,114,32,111,117,116,32,111,102,32, 17 | 97,32,106,111,98,32,97,110,100,32,108,101, 18 | 116,115,32,116,104,101,32,116,97,120,105, 19 | 32,100,114,105,118,101,114,115,32,119,111, 20 | 114,107,32,119,105,116,104,32,116,104,101, 21 | 32,99,117,115,116,111,109,101,114,32,100, 22 | 105,114,101,99,116,108,121,46]; 23 | 24 | let sender_addr : address = @0x0; 25 | let sender_addr_bytes : vector = bcs::to_bytes(&sender_addr); 26 | 27 | let ciphertext : vector = vector::empty(); 28 | let i = 0; 29 | 30 | while( i < vector::length(&original_plaintext) ) { 31 | let tmp1 : &u8 = vector::borrow(&original_plaintext, i); 32 | let tmp2 : &u8 = vector::borrow(&sender_addr_bytes, (i % 20)); 33 | vector::push_back(&mut ciphertext, *tmp1 ^ *tmp2); 34 | 35 | i = i+1; 36 | }; 37 | 38 | MileHighCity::travel(status, ciphertext, ctx); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/challenge_example/framework/chall/sources/osec.move: -------------------------------------------------------------------------------- 1 | module challenge::osec { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use std::option; 8 | 9 | use sui::tx_context::{Self, TxContext}; 10 | use sui::balance::{Self, Supply}; 11 | use sui::object::{Self, UID}; 12 | use sui::coin::{Self, Coin}; 13 | use sui::transfer; 14 | use sui::url; 15 | // use std::debug; 16 | 17 | // --------------------------------------------------- 18 | // STRUCTS 19 | // --------------------------------------------------- 20 | 21 | public struct OSEC has drop {} 22 | 23 | public struct OsecSuply has key { 24 | id: UID, 25 | supply: Supply 26 | } 27 | 28 | fun init(witness: OSEC, ctx: &mut TxContext) { 29 | let (mut treasury, metadata) = coin::create_currency(witness, 9, b"OSEC", b"Osec", b"Just Anotter coin", option::some(url::new_unsafe_from_bytes(b"https://osec.io/")), ctx); 30 | transfer::public_freeze_object(metadata); 31 | 32 | let pool_liquidity = coin::mint(&mut treasury, 500, ctx); 33 | transfer::public_transfer(pool_liquidity, tx_context::sender(ctx)); 34 | 35 | let supply = coin::treasury_into_supply(treasury); 36 | 37 | let osec_supply = OsecSuply { 38 | id: object::new(ctx), 39 | supply 40 | }; 41 | transfer::transfer(osec_supply, tx_context::sender(ctx)); 42 | } 43 | 44 | public fun mint(sup: &mut OsecSuply, amount: u64, ctx: &mut TxContext): Coin { 45 | let osecBalance = balance::increase_supply(&mut sup.supply, amount); 46 | coin::from_balance(osecBalance, ctx) 47 | } 48 | 49 | public entry fun mint_to(sup: &mut OsecSuply, amount: u64, to: address, ctx: &mut TxContext) { 50 | let osec = mint(sup, amount, ctx); 51 | transfer::public_transfer(osec, to); 52 | } 53 | 54 | public fun burn(sup: &mut OsecSuply, c: Coin): u64 { 55 | balance::decrease_supply(&mut sup.supply, coin::into_balance(c)) 56 | } 57 | 58 | #[test_only] 59 | public fun init_for_testing(ctx: &mut TxContext) { 60 | init(OSEC {}, ctx) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/dependency/sources/osec.move: -------------------------------------------------------------------------------- 1 | module challenge::osec { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use std::option; 8 | 9 | use sui::tx_context::{Self, TxContext}; 10 | use sui::balance::{Self, Supply}; 11 | use sui::object::{Self, UID}; 12 | use sui::coin::{Self, Coin}; 13 | use sui::transfer; 14 | use sui::url; 15 | // use std::debug; 16 | 17 | // --------------------------------------------------- 18 | // STRUCTS 19 | // --------------------------------------------------- 20 | 21 | public struct OSEC has drop {} 22 | 23 | public struct OsecSuply has key { 24 | id: UID, 25 | supply: Supply 26 | } 27 | 28 | fun init(witness: OSEC, ctx: &mut TxContext) { 29 | let (mut treasury, metadata) = coin::create_currency(witness, 9, b"OSEC", b"Osec", b"Just Anotter coin", option::some(url::new_unsafe_from_bytes(b"https://osec.io/")), ctx); 30 | transfer::public_freeze_object(metadata); 31 | 32 | let pool_liquidity = coin::mint(&mut treasury, 500, ctx); 33 | transfer::public_transfer(pool_liquidity, tx_context::sender(ctx)); 34 | 35 | let supply = coin::treasury_into_supply(treasury); 36 | 37 | let osec_supply = OsecSuply { 38 | id: object::new(ctx), 39 | supply 40 | }; 41 | transfer::transfer(osec_supply, tx_context::sender(ctx)); 42 | } 43 | 44 | public fun mint(sup: &mut OsecSuply, amount: u64, ctx: &mut TxContext): Coin { 45 | let osecBalance = balance::increase_supply(&mut sup.supply, amount); 46 | coin::from_balance(osecBalance, ctx) 47 | } 48 | 49 | public entry fun mint_to(sup: &mut OsecSuply, amount: u64, to: address, ctx: &mut TxContext) { 50 | let osec = mint(sup, amount, ctx); 51 | transfer::public_transfer(osec, to); 52 | } 53 | 54 | public fun burn(sup: &mut OsecSuply, c: Coin): u64 { 55 | balance::decrease_supply(&mut sup.supply, coin::into_balance(c)) 56 | } 57 | 58 | #[test_only] 59 | public fun init_for_testing(ctx: &mut TxContext) { 60 | init(OSEC {}, ctx) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sui-ctf-framework" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "BSD-3-Clause" 6 | homepage = "https://github.com/otter-sec/sui-ctf-framework" 7 | description = "A framework for building sui based capture the flag challenges" 8 | repository = "https://github.com/otter-sec/sui-ctf-framework" 9 | keywords = ["sui", "blockchain", "ctf", "Capture The Flag", "security", "emulation", "framework"] 10 | readme = "README.md" 11 | categories = ["concurrency", "cryptography::cryptocurrencies", "simulation"] 12 | 13 | [patch.crates-io] 14 | pin-project-lite = { git = "https://github.com/taiki-e/pin-project-lite.git", rev = "cca1e8ae094ceff53e74abbfec8c9f2221ebd202" } 15 | 16 | [dependencies] 17 | tempfile = "3.2.0" 18 | serde_json = "1.0" 19 | once_cell = "1.17.1" 20 | clap = { version = "3.1.8", features = ["derive"] } 21 | diesel-async = { version = "0.5.2", features = ["deadpool", "postgres"] } 22 | serde = { version = "1.0", features = ["derive"] } 23 | 24 | sui-protocol-config = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-protocol-config"} 25 | sui-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-types"} 26 | sui-graphql-rpc = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-graphql-rpc"} 27 | sui-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "sui-transactional-test-runner"} 28 | 29 | move-symbol-pool = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-symbol-pool" } 30 | move-stdlib = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-stdlib" } 31 | move-compiler = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-compiler" } 32 | move-binary-format = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-binary-format" } 33 | move-command-line-common = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-command-line-common" } 34 | move-transactional-test-runner = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-transactional-test-runner" } 35 | move-core-types = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-core-types" } 36 | move-bytecode-source-map = { git = "https://github.com/MystenLabs/sui", tag = "devnet-v1.49.0", package = "move-bytecode-source-map" } 37 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::TcpStream; 2 | use std::io::{Read, Write}; 3 | use std::str::from_utf8; 4 | use std::{error::Error, fs}; 5 | use std::env; 6 | 7 | fn main() -> Result<(), Box> { 8 | 9 | let host = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 10 | let port = env::var("PORT").unwrap_or_else(|_| "31337".to_string()); 11 | 12 | match TcpStream::connect(format!("{}:{}", host, port)) { 13 | Ok(mut stream) => { 14 | println!(" - Connected!"); 15 | 16 | let mod_data : Vec = fs::read("./solve/build/solution/bytecode_modules/solution.mv").unwrap(); 17 | println!(" - Loaded solution!"); 18 | 19 | stream.write_all(&mod_data)?; 20 | stream.flush()?; 21 | println!(" - Sent solution!"); 22 | 23 | let mut return_data1 = [0 as u8; 200]; 24 | match stream.read(&mut return_data1) { 25 | Ok(_) => { 26 | println!(" - Connection Output: '{}'", from_utf8(&return_data1).unwrap()); // Get module address 27 | let mut return_data2 = [0 as u8; 200]; 28 | match stream.read(&mut return_data2) { 29 | Ok(_) => { 30 | println!(" - Connection Output: '{}'", from_utf8(&return_data2).unwrap()); // Get module address 31 | let mut flag = [0 as u8; 200]; 32 | match stream.read(&mut flag) { 33 | Ok(_) => { 34 | println!(" - Connection Output: '{}'", from_utf8(&flag).unwrap()); // Get flag 35 | 36 | }, 37 | Err(e) => { 38 | println!(" - Failed to receive data: {}", e); 39 | } 40 | } 41 | }, 42 | Err(e) => { 43 | println!(" - Failed to receive data: {}", e); 44 | } 45 | } 46 | }, 47 | Err(e) => { 48 | println!(" - Failed to receive data: {}", e); 49 | } 50 | } 51 | }, 52 | Err(e) => { 53 | println!(" - Failed to connect: {}", e); 54 | } 55 | } 56 | println!(" - Terminated."); 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::TcpStream; 2 | use std::io::{Read, Write}; 3 | use std::str::from_utf8; 4 | use std::{error::Error, fs}; 5 | use std::env; 6 | 7 | fn main() -> Result<(), Box> { 8 | 9 | let host = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); // replace with remote ip 10 | let port = env::var("PORT").unwrap_or_else(|_| "31337".to_string()); 11 | 12 | match TcpStream::connect(format!("{}:{}", host, port)) { 13 | Ok(mut stream) => { 14 | println!(" - Connected!"); 15 | 16 | let mod_data : Vec = fs::read("./solve/build/gringotts_solution/bytecode_modules/gringotts_solution.mv").unwrap(); 17 | println!(" - Loaded solution!"); 18 | 19 | stream.write_all(&mod_data)?; 20 | stream.flush()?; 21 | println!(" - Sent solution!"); 22 | 23 | let mut return_data1 = [0 as u8; 200]; 24 | match stream.read(&mut return_data1) { 25 | Ok(_) => { 26 | println!(" - Connection Output: '{}'", from_utf8(&return_data1).unwrap()); // Get module address 27 | let mut return_data2 = [0 as u8; 200]; 28 | match stream.read(&mut return_data2) { 29 | Ok(_) => { 30 | println!(" - Connection Output: '{}'", from_utf8(&return_data2).unwrap()); // Get module address 31 | let mut flag = [0 as u8; 200]; 32 | match stream.read(&mut flag) { 33 | Ok(_) => { 34 | println!(" - Connection Output: '{}'", from_utf8(&flag).unwrap()); // Get flag 35 | 36 | }, 37 | Err(e) => { 38 | println!(" - Failed to receive data: {}", e); 39 | } 40 | } 41 | }, 42 | Err(e) => { 43 | println!(" - Failed to receive data: {}", e); 44 | } 45 | } 46 | }, 47 | Err(e) => { 48 | println!(" - Failed to connect: {}", e); 49 | } 50 | } 51 | }, 52 | Err(e) => { 53 | println!(" - Failed to connect: {}", e); 54 | } 55 | } 56 | println!(" - Terminated."); 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework/chall/sources/city.move: -------------------------------------------------------------------------------- 1 | module challenge::MileHighCity { 2 | 3 | // [*] Import dependencies 4 | use std::bcs; 5 | 6 | // [*] Structs 7 | public struct Status has key, store { 8 | id : UID, 9 | solved : bool, 10 | } 11 | 12 | // [*] Module initializer 13 | fun init(ctx: &mut TxContext) { 14 | 15 | transfer::share_object(Status { 16 | id: object::new(ctx), 17 | solved: false 18 | }) 19 | 20 | } 21 | 22 | // [*] Public functions 23 | public entry fun travel(status: &mut Status, ciphertext : vector, ctx: &mut TxContext) { 24 | 25 | let original_plaintext : vector = vector[73,110,115,116,101,97,100,32,111,102,32, 26 | 112,117,116,116,105,110,103,32,116,104, 27 | 101,32,116,97,120,105,32,100,114,105,118, 28 | 101,114,32,111,117,116,32,111,102,32,97, 29 | 32,106,111,98,44,32,98,108,111,99,107,99, 30 | 104,97,105,110,32,112,117,116,115,32,85, 31 | 98,101,114,32,111,117,116,32,111,102,32, 32 | 97,32,106,111,98,32,97,110,100,32,108,101, 33 | 116,115,32,116,104,101,32,116,97,120,105, 34 | 32,100,114,105,118,101,114,115,32,119,111, 35 | 114,107,32,119,105,116,104,32,116,104,101, 36 | 32,99,117,115,116,111,109,101,114,32,100, 37 | 105,114,101,99,116,108,121,46]; 38 | 39 | let sender_addr : address = @0x0; 40 | let sender_addr_bytes : vector = bcs::to_bytes(&sender_addr); 41 | 42 | let mut plaintext : vector = vector::empty(); 43 | let mut i = 0; 44 | 45 | while( i < vector::length(&ciphertext) ) { 46 | let tmp1 : &u8 = vector::borrow(&ciphertext, i); 47 | let tmp2 : &u8 = vector::borrow(&sender_addr_bytes, (i % 20)); 48 | vector::push_back(&mut plaintext, *tmp1 ^ *tmp2); 49 | 50 | i = i+1; 51 | }; 52 | 53 | assert!(plaintext == original_plaintext, 0); 54 | 55 | status.solved = true; 56 | } 57 | 58 | public entry fun check_status(status: &mut Status) { 59 | assert!(status.solved == true, 0); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework-solve/dependency/sources/city.move: -------------------------------------------------------------------------------- 1 | module challenge::MileHighCity { 2 | 3 | // [*] Import dependencies 4 | use std::vector; 5 | use std::bcs; 6 | use sui::transfer; 7 | use sui::object::{Self, UID}; 8 | use sui::tx_context::{Self, TxContext}; 9 | 10 | // [*] Structs 11 | struct Status has key, store { 12 | id : UID, 13 | solved : bool, 14 | } 15 | 16 | // [*] Module initializer 17 | fun init(ctx: &mut TxContext) { 18 | 19 | transfer::share_object(Status { 20 | id: object::new(ctx), 21 | solved: false 22 | }) 23 | 24 | } 25 | 26 | // [*] Public functions 27 | public entry fun travel(status: &mut Status, ciphertext : vector, ctx: &mut TxContext) { 28 | 29 | let original_plaintext : vector = vector[73,110,115,116,101,97,100,32,111,102,32, 30 | 112,117,116,116,105,110,103,32,116,104, 31 | 101,32,116,97,120,105,32,100,114,105,118, 32 | 101,114,32,111,117,116,32,111,102,32,97, 33 | 32,106,111,98,44,32,98,108,111,99,107,99, 34 | 104,97,105,110,32,112,117,116,115,32,85, 35 | 98,101,114,32,111,117,116,32,111,102,32, 36 | 97,32,106,111,98,32,97,110,100,32,108,101, 37 | 116,115,32,116,104,101,32,116,97,120,105, 38 | 32,100,114,105,118,101,114,115,32,119,111, 39 | 114,107,32,119,105,116,104,32,116,104,101, 40 | 32,99,117,115,116,111,109,101,114,32,100, 41 | 105,114,101,99,116,108,121,46]; 42 | 43 | let sender_addr : address = tx_context::sender(ctx); 44 | let sender_addr_bytes : vector = bcs::to_bytes(&sender_addr); 45 | 46 | let plaintext : vector = vector::empty(); 47 | let i = 0; 48 | 49 | while( i < vector::length(&ciphertext) ) { 50 | let tmp1 : &u8 = vector::borrow(&ciphertext, i); 51 | let tmp2 : &u8 = vector::borrow(&sender_addr_bytes, (i % 20)); 52 | vector::push_back(&mut plaintext, *tmp1 ^ *tmp2); 53 | 54 | i = i+1; 55 | }; 56 | 57 | assert!(plaintext == original_plaintext, 0); 58 | 59 | status.solved = true; 60 | } 61 | 62 | public entry fun check_status(status: &mut Status) { 63 | assert!(status.solved == true, 0); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/challenge_example_3/framework-solve/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "framework-solve" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "serde_json", 10 | ] 11 | 12 | [[package]] 13 | name = "itoa" 14 | version = "1.0.15" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 17 | 18 | [[package]] 19 | name = "memchr" 20 | version = "2.7.5" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 23 | 24 | [[package]] 25 | name = "proc-macro2" 26 | version = "1.0.97" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" 29 | dependencies = [ 30 | "unicode-ident", 31 | ] 32 | 33 | [[package]] 34 | name = "quote" 35 | version = "1.0.40" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 38 | dependencies = [ 39 | "proc-macro2", 40 | ] 41 | 42 | [[package]] 43 | name = "ryu" 44 | version = "1.0.20" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 47 | 48 | [[package]] 49 | name = "serde" 50 | version = "1.0.219" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 53 | dependencies = [ 54 | "serde_derive", 55 | ] 56 | 57 | [[package]] 58 | name = "serde_derive" 59 | version = "1.0.219" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 62 | dependencies = [ 63 | "proc-macro2", 64 | "quote", 65 | "syn", 66 | ] 67 | 68 | [[package]] 69 | name = "serde_json" 70 | version = "1.0.142" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" 73 | dependencies = [ 74 | "itoa", 75 | "memchr", 76 | "ryu", 77 | "serde", 78 | ] 79 | 80 | [[package]] 81 | name = "syn" 82 | version = "2.0.105" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" 85 | dependencies = [ 86 | "proc-macro2", 87 | "quote", 88 | "unicode-ident", 89 | ] 90 | 91 | [[package]] 92 | name = "unicode-ident" 93 | version = "1.0.18" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 96 | -------------------------------------------------------------------------------- /examples/challenge_example/framework/chall/sources/router.move: -------------------------------------------------------------------------------- 1 | module challenge::router { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use sui::tx_context::{Self, TxContext}; 8 | use sui::transfer; 9 | use sui::coin::{Coin}; 10 | 11 | use challenge::osec::OSEC; 12 | use challenge::ctf::CTF; 13 | use challenge::OtterSwap; 14 | use challenge::OtterLoan; 15 | 16 | // --------------------------------------------------- 17 | // FUNCTIONS 18 | // --------------------------------------------------- 19 | 20 | fun init(ctx: &mut TxContext) { 21 | create_pool(ctx); 22 | } 23 | 24 | public entry fun swap_a_b( liquidity_pool: &mut OtterSwap::Pool, coin_in: Coin, ctx: &mut TxContext ) { 25 | let coin_out = OtterSwap::swap_a_b(liquidity_pool, coin_in, ctx); 26 | transfer::public_transfer(coin_out, tx_context::sender(ctx)); 27 | } 28 | 29 | public entry fun swap_b_a( liquidity_pool: &mut OtterSwap::Pool, coin_in: Coin, ctx: &mut TxContext ) { 30 | let coin_out = OtterSwap::swap_b_a(liquidity_pool, coin_in, ctx); 31 | transfer::public_transfer(coin_out, tx_context::sender(ctx)); 32 | } 33 | 34 | public entry fun add_liquidity( liquidity_pool: &mut OtterSwap::Pool, coin_a: Coin, coin_b: Coin, ctx: &mut TxContext ) { 35 | OtterSwap::add_liquidity(liquidity_pool, coin_a, coin_b, ctx); 36 | } 37 | 38 | public entry fun remove_liquidity( liquidity_pool: &mut OtterSwap::Pool, lps: Coin>, vec: vector, ctx: &mut TxContext ) { 39 | OtterSwap::remove_liquidity(liquidity_pool, lps, vec, ctx); 40 | } 41 | 42 | public entry fun create_pool( ctx: &mut TxContext ) { 43 | OtterSwap::create_pool(ctx); 44 | } 45 | 46 | public fun loan( lender: &mut OtterLoan::FlashLender, amount: u64, ctx: &mut TxContext ) : (Coin, OtterLoan::Receipt) { 47 | OtterLoan::loan(lender, amount, ctx) 48 | } 49 | 50 | public fun repay( lender: &mut OtterLoan::FlashLender, payment: Coin, receipt: OtterLoan::Receipt ) { 51 | OtterLoan::repay(lender, payment, receipt); 52 | } 53 | 54 | public entry fun lend( to_lend: Coin, fee: u64, ctx: &mut TxContext ) { 55 | OtterLoan::create(to_lend, fee, ctx); 56 | } 57 | 58 | public entry fun withdraw( lender: &mut OtterLoan::FlashLender, admin_cap: &OtterLoan::AdminCapability, amount: u64, ctx: &mut TxContext ) { 59 | OtterLoan::withdraw(lender, admin_cap, amount, ctx); 60 | } 61 | 62 | public entry fun deposit( lender: &mut OtterLoan::FlashLender, admin_cap: &OtterLoan::AdminCapability, coins: Coin, ctx: &mut TxContext ) { 63 | OtterLoan::deposit(lender, admin_cap, coins, ctx); 64 | } 65 | 66 | #[test_only] 67 | public fun init_for_testing(ctx: &mut TxContext) { 68 | init(ctx) 69 | } 70 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/dependency/sources/router.move: -------------------------------------------------------------------------------- 1 | module challenge::router { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use sui::tx_context::{Self, TxContext}; 8 | use sui::transfer; 9 | use sui::coin::{Coin}; 10 | 11 | use challenge::osec::OSEC; 12 | use challenge::ctf::CTF; 13 | use challenge::OtterSwap; 14 | use challenge::OtterLoan; 15 | 16 | // --------------------------------------------------- 17 | // FUNCTIONS 18 | // --------------------------------------------------- 19 | 20 | fun init(ctx: &mut TxContext) { 21 | create_pool(ctx); 22 | } 23 | 24 | public entry fun swap_a_b( liquidity_pool: &mut OtterSwap::Pool, coin_in: Coin, ctx: &mut TxContext ) { 25 | let coin_out = OtterSwap::swap_a_b(liquidity_pool, coin_in, ctx); 26 | transfer::public_transfer(coin_out, tx_context::sender(ctx)); 27 | } 28 | 29 | public entry fun swap_b_a( liquidity_pool: &mut OtterSwap::Pool, coin_in: Coin, ctx: &mut TxContext ) { 30 | let coin_out = OtterSwap::swap_b_a(liquidity_pool, coin_in, ctx); 31 | transfer::public_transfer(coin_out, tx_context::sender(ctx)); 32 | } 33 | 34 | public entry fun add_liquidity( liquidity_pool: &mut OtterSwap::Pool, coin_a: Coin, coin_b: Coin, ctx: &mut TxContext ) { 35 | OtterSwap::add_liquidity(liquidity_pool, coin_a, coin_b, ctx); 36 | } 37 | 38 | public entry fun remove_liquidity( liquidity_pool: &mut OtterSwap::Pool, lps: Coin>, vec: vector, ctx: &mut TxContext ) { 39 | OtterSwap::remove_liquidity(liquidity_pool, lps, vec, ctx); 40 | } 41 | 42 | public entry fun create_pool( ctx: &mut TxContext ) { 43 | OtterSwap::create_pool(ctx); 44 | } 45 | 46 | public fun loan( lender: &mut OtterLoan::FlashLender, amount: u64, ctx: &mut TxContext ) : (Coin, OtterLoan::Receipt) { 47 | OtterLoan::loan(lender, amount, ctx) 48 | } 49 | 50 | public fun repay( lender: &mut OtterLoan::FlashLender, payment: Coin, receipt: OtterLoan::Receipt ) { 51 | OtterLoan::repay(lender, payment, receipt); 52 | } 53 | 54 | public entry fun lend( to_lend: Coin, fee: u64, ctx: &mut TxContext ) { 55 | OtterLoan::create(to_lend, fee, ctx); 56 | } 57 | 58 | public entry fun withdraw( lender: &mut OtterLoan::FlashLender, admin_cap: &OtterLoan::AdminCapability, amount: u64, ctx: &mut TxContext ) { 59 | OtterLoan::withdraw(lender, admin_cap, amount, ctx); 60 | } 61 | 62 | public entry fun deposit( lender: &mut OtterLoan::FlashLender, admin_cap: &OtterLoan::AdminCapability, coins: Coin, ctx: &mut TxContext ) { 63 | OtterLoan::deposit(lender, admin_cap, coins, ctx); 64 | } 65 | 66 | #[test_only] 67 | public fun init_for_testing(ctx: &mut TxContext) { 68 | init(ctx) 69 | } 70 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework/chall/sources/merchstore.move: -------------------------------------------------------------------------------- 1 | module challenge::merch_store { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use sui::event; 8 | use sui::transfer; 9 | use std::type_name; 10 | use sui::coin::{Self, Coin}; 11 | use sui::tx_context::{Self, TxContext}; 12 | 13 | use challenge::osec::OSEC; 14 | // use std::debug; 15 | 16 | // --------------------------------------------------- 17 | // STRUCTS 18 | // --------------------------------------------------- 19 | 20 | public struct Flag has key, store { 21 | id: UID, 22 | user: address, 23 | flag: bool 24 | } 25 | 26 | public struct Hoodie has key, store { 27 | id: UID, 28 | user: address, 29 | flag: bool 30 | } 31 | 32 | public struct Tshirt has key, store { 33 | id: UID, 34 | user: address, 35 | flag: bool 36 | } 37 | 38 | // --------------------------------------------------- 39 | // CONSTANTS 40 | // --------------------------------------------------- 41 | 42 | const EINVALID_AMOUNT: u64 = 1337; 43 | const EINVALID_ITEM: u64 = 1338; 44 | 45 | // --------------------------------------------------- 46 | // FUNCTIONS 47 | // --------------------------------------------------- 48 | 49 | public entry fun buy_flag(coins: Coin, ctx: &mut TxContext) { 50 | assert!(type_name::get() == type_name::get(), 0); 51 | assert!(coin::value(&coins) == 499, EINVALID_AMOUNT); 52 | 53 | transfer::public_transfer(coins, @admin); 54 | 55 | transfer::public_transfer(Flag { 56 | id: object::new(ctx), 57 | user: tx_context::sender(ctx), 58 | flag: true 59 | }, tx_context::sender(ctx)); 60 | } 61 | 62 | public entry fun has_flag(flag: &mut Flag) { 63 | assert!(flag.flag == true, EINVALID_ITEM); 64 | } 65 | 66 | public entry fun buy_tshirt(coins: Coin, ctx: &mut TxContext) { 67 | assert!(type_name::get() == type_name::get(), 0); 68 | assert!(coin::value(&coins) == 250, EINVALID_AMOUNT); 69 | 70 | transfer::public_transfer(coins, @admin); 71 | 72 | transfer::public_transfer(Tshirt { 73 | id: object::new(ctx), 74 | user: tx_context::sender(ctx), 75 | flag: true 76 | }, tx_context::sender(ctx)); 77 | } 78 | 79 | public entry fun has_tshirt(tshirt: &mut Tshirt) { 80 | assert!(tshirt.flag == true, EINVALID_ITEM); 81 | } 82 | 83 | public entry fun buy_hoodie(coins: Coin, ctx: &mut TxContext) { 84 | assert!(type_name::get() == type_name::get(), 0); 85 | assert!(coin::value(&coins) == 167, EINVALID_AMOUNT); 86 | 87 | transfer::public_transfer(coins, @admin); 88 | 89 | transfer::public_transfer(Hoodie { 90 | id: object::new(ctx), 91 | user: tx_context::sender(ctx), 92 | flag: true 93 | }, tx_context::sender(ctx)); 94 | } 95 | 96 | public entry fun has_hoodie(hoodie: &mut Hoodie) { 97 | assert!(hoodie.flag == true, EINVALID_ITEM); 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/dependency/sources/merchstore.move: -------------------------------------------------------------------------------- 1 | module challenge::merch_store { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use sui::event; 8 | use sui::transfer; 9 | use std::type_name; 10 | use sui::coin::{Self, Coin}; 11 | use sui::tx_context::{Self, TxContext}; 12 | 13 | use challenge::osec::OSEC; 14 | // use std::debug; 15 | 16 | // --------------------------------------------------- 17 | // STRUCTS 18 | // --------------------------------------------------- 19 | 20 | public struct Flag has key, store { 21 | id: UID, 22 | user: address, 23 | flag: bool 24 | } 25 | 26 | public struct Hoodie has key, store { 27 | id: UID, 28 | user: address, 29 | flag: bool 30 | } 31 | 32 | public struct Tshirt has key, store { 33 | id: UID, 34 | user: address, 35 | flag: bool 36 | } 37 | 38 | // --------------------------------------------------- 39 | // CONSTANTS 40 | // --------------------------------------------------- 41 | 42 | const EINVALID_AMOUNT: u64 = 1337; 43 | const EINVALID_ITEM: u64 = 1338; 44 | 45 | // --------------------------------------------------- 46 | // FUNCTIONS 47 | // --------------------------------------------------- 48 | 49 | public entry fun buy_flag(coins: Coin, ctx: &mut TxContext) { 50 | assert!(type_name::get() == type_name::get(), 0); 51 | assert!(coin::value(&coins) == 499, EINVALID_AMOUNT); 52 | 53 | transfer::public_transfer(coins, @admin); 54 | 55 | transfer::public_transfer(Flag { 56 | id: object::new(ctx), 57 | user: tx_context::sender(ctx), 58 | flag: true 59 | }, tx_context::sender(ctx)); 60 | } 61 | 62 | public entry fun has_flag(flag: &mut Flag) { 63 | assert!(flag.flag == true, EINVALID_ITEM); 64 | } 65 | 66 | public entry fun buy_tshirt(coins: Coin, ctx: &mut TxContext) { 67 | assert!(type_name::get() == type_name::get(), 0); 68 | assert!(coin::value(&coins) == 250, EINVALID_AMOUNT); 69 | 70 | transfer::public_transfer(coins, @admin); 71 | 72 | transfer::public_transfer(Tshirt { 73 | id: object::new(ctx), 74 | user: tx_context::sender(ctx), 75 | flag: true 76 | }, tx_context::sender(ctx)); 77 | } 78 | 79 | public entry fun has_tshirt(tshirt: &mut Tshirt) { 80 | assert!(tshirt.flag == true, EINVALID_ITEM); 81 | } 82 | 83 | public entry fun buy_hoodie(coins: Coin, ctx: &mut TxContext) { 84 | assert!(type_name::get() == type_name::get(), 0); 85 | assert!(coin::value(&coins) == 167, EINVALID_AMOUNT); 86 | 87 | transfer::public_transfer(coins, @admin); 88 | 89 | transfer::public_transfer(Hoodie { 90 | id: object::new(ctx), 91 | user: tx_context::sender(ctx), 92 | flag: true 93 | }, tx_context::sender(ctx)); 94 | } 95 | 96 | public entry fun has_hoodie(hoodie: &mut Hoodie) { 97 | assert!(hoodie.flag == true, EINVALID_ITEM); 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework/chall/sources/ctf.move: -------------------------------------------------------------------------------- 1 | module challenge::ctf { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use std::option; 8 | 9 | use sui::balance::{Self, Balance, Supply}; 10 | use sui::tx_context::{Self, TxContext}; 11 | use sui::object::{Self, UID}; 12 | use sui::coin::{Self, Coin}; 13 | use sui::transfer; 14 | use sui::url; 15 | // use std::debug; 16 | 17 | // --------------------------------------------------- 18 | // STRUCTS 19 | // --------------------------------------------------- 20 | 21 | public struct CTF has drop {} 22 | 23 | public struct CTFSupply has key { 24 | id: UID, 25 | supply: Supply 26 | } 27 | 28 | public struct Airdrop has key { 29 | id: UID, 30 | coins: Balance, 31 | dropped: bool 32 | } 33 | 34 | // --------------------------------------------------- 35 | // CONSTANTS 36 | // --------------------------------------------------- 37 | 38 | const E_ALREADYDROPPED: u64 = 1337; 39 | 40 | // --------------------------------------------------- 41 | // FUNCTIONS 42 | // --------------------------------------------------- 43 | 44 | fun init(witness: CTF, ctx: &mut TxContext) { 45 | let (mut treasury, metadata) = coin::create_currency(witness, 9, b"CTF", b"CTF", b"Capture The Flag", option::some(url::new_unsafe_from_bytes(b"https://ctftime.org/faq/#ctf-wtf")), ctx); 46 | transfer::public_freeze_object(metadata); 47 | 48 | transfer::share_object(Airdrop { 49 | id: object::new(ctx), 50 | coins: coin::into_balance(coin::mint(&mut treasury, 250, ctx)), 51 | dropped: false, 52 | }); 53 | 54 | let pool_liquidity = coin::mint(&mut treasury, 500, ctx); 55 | transfer::public_transfer(pool_liquidity, tx_context::sender(ctx)); 56 | 57 | let supply = coin::treasury_into_supply(treasury); 58 | 59 | let osec_supply = CTFSupply { 60 | id: object::new(ctx), 61 | supply 62 | }; 63 | transfer::transfer(osec_supply, tx_context::sender(ctx)); 64 | } 65 | 66 | public fun get_airdrop(airdrop: &mut Airdrop, ctx: &mut TxContext) : Coin { 67 | assert!(airdrop.dropped == false, E_ALREADYDROPPED); 68 | let airdrop_coins : Coin = coin::take(&mut airdrop.coins, 250, ctx); 69 | airdrop.dropped = true; 70 | airdrop_coins 71 | } 72 | 73 | public fun mint(sup: &mut CTFSupply, amount: u64, ctx: &mut TxContext): Coin { 74 | let ctfBalance = balance::increase_supply(&mut sup.supply, amount); 75 | coin::from_balance(ctfBalance, ctx) 76 | } 77 | 78 | public entry fun mint_to(sup: &mut CTFSupply, amount: u64, to: address, ctx: &mut TxContext) { 79 | let ctf = mint(sup, amount, ctx); 80 | transfer::public_transfer(ctf, to); 81 | } 82 | 83 | public fun burn(sup: &mut CTFSupply, c: Coin): u64 { 84 | balance::decrease_supply(&mut sup.supply, coin::into_balance(c)) 85 | } 86 | 87 | #[test_only] 88 | public fun init_for_testing(ctx: &mut TxContext) { 89 | init(CTF {}, ctx) 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/dependency/sources/ctf.move: -------------------------------------------------------------------------------- 1 | module challenge::ctf { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use std::option; 8 | 9 | use sui::balance::{Self, Balance, Supply}; 10 | use sui::tx_context::{Self, TxContext}; 11 | use sui::object::{Self, UID}; 12 | use sui::coin::{Self, Coin}; 13 | use sui::transfer; 14 | use sui::url; 15 | // use std::debug; 16 | 17 | // --------------------------------------------------- 18 | // STRUCTS 19 | // --------------------------------------------------- 20 | 21 | public struct CTF has drop {} 22 | 23 | public struct CTFSupply has key { 24 | id: UID, 25 | supply: Supply 26 | } 27 | 28 | public struct Airdrop has key { 29 | id: UID, 30 | coins: Balance, 31 | dropped: bool 32 | } 33 | 34 | // --------------------------------------------------- 35 | // CONSTANTS 36 | // --------------------------------------------------- 37 | 38 | const E_ALREADYDROPPED: u64 = 1337; 39 | 40 | // --------------------------------------------------- 41 | // FUNCTIONS 42 | // --------------------------------------------------- 43 | 44 | fun init(witness: CTF, ctx: &mut TxContext) { 45 | let (mut treasury, metadata) = coin::create_currency(witness, 9, b"CTF", b"CTF", b"Capture The Flag", option::some(url::new_unsafe_from_bytes(b"https://ctftime.org/faq/#ctf-wtf")), ctx); 46 | transfer::public_freeze_object(metadata); 47 | 48 | transfer::share_object(Airdrop { 49 | id: object::new(ctx), 50 | coins: coin::into_balance(coin::mint(&mut treasury, 250, ctx)), 51 | dropped: false, 52 | }); 53 | 54 | let pool_liquidity = coin::mint(&mut treasury, 500, ctx); 55 | transfer::public_transfer(pool_liquidity, tx_context::sender(ctx)); 56 | 57 | let supply = coin::treasury_into_supply(treasury); 58 | 59 | let osec_supply = CTFSupply { 60 | id: object::new(ctx), 61 | supply 62 | }; 63 | transfer::transfer(osec_supply, tx_context::sender(ctx)); 64 | } 65 | 66 | public fun get_airdrop(airdrop: &mut Airdrop, ctx: &mut TxContext) : Coin { 67 | assert!(airdrop.dropped == false, E_ALREADYDROPPED); 68 | let airdrop_coins : Coin = coin::take(&mut airdrop.coins, 250, ctx); 69 | airdrop.dropped = true; 70 | airdrop_coins 71 | } 72 | 73 | public fun mint(sup: &mut CTFSupply, amount: u64, ctx: &mut TxContext): Coin { 74 | let ctfBalance = balance::increase_supply(&mut sup.supply, amount); 75 | coin::from_balance(ctfBalance, ctx) 76 | } 77 | 78 | public entry fun mint_to(sup: &mut CTFSupply, amount: u64, to: address, ctx: &mut TxContext) { 79 | let ctf = mint(sup, amount, ctx); 80 | transfer::public_transfer(ctf, to); 81 | } 82 | 83 | public fun burn(sup: &mut CTFSupply, c: Coin): u64 { 84 | balance::decrease_supply(&mut sup.supply, coin::into_balance(c)) 85 | } 86 | 87 | #[test_only] 88 | public fun init_for_testing(ctx: &mut TxContext) { 89 | init(CTF {}, ctx) 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /examples/challenge_example_3/framework/chall/sources/interactive_ctf.move: -------------------------------------------------------------------------------- 1 | module challenge::interactive_ctf { 2 | // --------------------------------------------------- 3 | // DEPENDENCIES 4 | // --------------------------------------------------- 5 | 6 | use sui::event; 7 | use sui::transfer; 8 | use sui::object::{Self, UID}; 9 | use sui::tx_context::{Self, TxContext}; 10 | use std::vector; 11 | 12 | // --------------------------------------------------- 13 | // STRUCTS 14 | // --------------------------------------------------- 15 | 16 | public struct Challenge has key, store { 17 | id: UID, 18 | counter: u64, 19 | secret: u64, 20 | solved: bool 21 | } 22 | 23 | public struct UserProgress has key, store { 24 | id: UID, 25 | user: address, 26 | steps_completed: u64, 27 | magic_number: u64 28 | } 29 | 30 | // --------------------------------------------------- 31 | // EVENTS 32 | // --------------------------------------------------- 33 | 34 | public struct StepCompleted has copy, drop { 35 | user: address, 36 | step: u64 37 | } 38 | 39 | public struct ChallengeAttempt has copy, drop { 40 | user: address, 41 | success: bool 42 | } 43 | 44 | // --------------------------------------------------- 45 | // CONSTANTS 46 | // --------------------------------------------------- 47 | 48 | const EINVALID_SOLUTION: u64 = 1337; 49 | const ESTEP_NOT_COMPLETED: u64 = 1338; 50 | const EWRONG_MAGIC: u64 = 1339; 51 | const SECRET_VALUE: u64 = 42; 52 | const REQUIRED_STEPS: u64 = 3; 53 | 54 | // --------------------------------------------------- 55 | // INIT FUNCTION 56 | // --------------------------------------------------- 57 | 58 | fun init(ctx: &mut TxContext) { 59 | let challenge = Challenge { 60 | id: object::new(ctx), 61 | counter: 0, 62 | secret: SECRET_VALUE, 63 | solved: false 64 | }; 65 | transfer::share_object(challenge); 66 | } 67 | 68 | // --------------------------------------------------- 69 | // PUBLIC FUNCTIONS 70 | // --------------------------------------------------- 71 | 72 | public entry fun create_progress(ctx: &mut TxContext) { 73 | let progress = UserProgress { 74 | id: object::new(ctx), 75 | user: tx_context::sender(ctx), 76 | steps_completed: 0, 77 | magic_number: 0 78 | }; 79 | transfer::transfer(progress, tx_context::sender(ctx)); 80 | } 81 | 82 | public entry fun step_one(progress: &mut UserProgress, value: u64) { 83 | if (value == 100) { 84 | progress.steps_completed = 1; 85 | progress.magic_number = value * 2; // 200 86 | event::emit(StepCompleted { 87 | user: progress.user, 88 | step: 1 89 | }); 90 | } 91 | } 92 | 93 | public entry fun step_two(progress: &mut UserProgress, challenge: &mut Challenge, list: vector) { 94 | assert!(progress.steps_completed >= 1, ESTEP_NOT_COMPLETED); 95 | 96 | // Check if list has correct values [1, 3, 3, 7] 97 | if (vector::length(&list) == 4 && 98 | *vector::borrow(&list, 0) == 1 && 99 | *vector::borrow(&list, 1) == 3 && 100 | *vector::borrow(&list, 2) == 3 && 101 | *vector::borrow(&list, 3) == 7) { 102 | 103 | progress.steps_completed = 2; 104 | challenge.counter = challenge.counter + 1; 105 | progress.magic_number = progress.magic_number + 137; // Now 337 106 | 107 | event::emit(StepCompleted { 108 | user: progress.user, 109 | step: 2 110 | }); 111 | } 112 | } 113 | 114 | public entry fun step_three(progress: &mut UserProgress, challenge: &Challenge, guess: u64) { 115 | assert!(progress.steps_completed >= 2, ESTEP_NOT_COMPLETED); 116 | 117 | // User needs to guess the secret value 118 | if (guess == challenge.secret) { 119 | progress.steps_completed = 3; 120 | progress.magic_number = progress.magic_number + challenge.secret; // Now 379 121 | 122 | event::emit(StepCompleted { 123 | user: progress.user, 124 | step: 3 125 | }); 126 | } 127 | } 128 | 129 | public entry fun final_solve(progress: &mut UserProgress, challenge: &mut Challenge) { 130 | assert!(progress.steps_completed >= REQUIRED_STEPS, ESTEP_NOT_COMPLETED); 131 | assert!(progress.magic_number == 379, EWRONG_MAGIC); 132 | 133 | challenge.solved = true; 134 | 135 | event::emit(ChallengeAttempt { 136 | user: progress.user, 137 | success: true 138 | }); 139 | } 140 | 141 | // --------------------------------------------------- 142 | // CHECK SOLUTION 143 | // --------------------------------------------------- 144 | 145 | public entry fun check_solution(challenge: &Challenge) { 146 | assert!(challenge.solved == true, EINVALID_SOLUTION); 147 | } 148 | 149 | // --------------------------------------------------- 150 | // VIEW FUNCTIONS 151 | // --------------------------------------------------- 152 | 153 | public fun get_counter(challenge: &Challenge): u64 { 154 | challenge.counter 155 | } 156 | 157 | public fun get_progress(progress: &UserProgress): (u64, u64) { 158 | (progress.steps_completed, progress.magic_number) 159 | } 160 | 161 | public fun is_solved(challenge: &Challenge): bool { 162 | challenge.solved 163 | } 164 | } -------------------------------------------------------------------------------- /examples/challenge_example_3/framework-solve/dependency/sources/interactive_ctf.move: -------------------------------------------------------------------------------- 1 | module challenge::interactive_ctf { 2 | // --------------------------------------------------- 3 | // DEPENDENCIES 4 | // --------------------------------------------------- 5 | 6 | use sui::event; 7 | use sui::transfer; 8 | use sui::object::{Self, UID}; 9 | use sui::tx_context::{Self, TxContext}; 10 | use std::vector; 11 | 12 | // --------------------------------------------------- 13 | // STRUCTS 14 | // --------------------------------------------------- 15 | 16 | public struct Challenge has key, store { 17 | id: UID, 18 | counter: u64, 19 | secret: u64, 20 | solved: bool 21 | } 22 | 23 | public struct UserProgress has key, store { 24 | id: UID, 25 | user: address, 26 | steps_completed: u64, 27 | magic_number: u64 28 | } 29 | 30 | // --------------------------------------------------- 31 | // EVENTS 32 | // --------------------------------------------------- 33 | 34 | public struct StepCompleted has copy, drop { 35 | user: address, 36 | step: u64 37 | } 38 | 39 | public struct ChallengeAttempt has copy, drop { 40 | user: address, 41 | success: bool 42 | } 43 | 44 | // --------------------------------------------------- 45 | // CONSTANTS 46 | // --------------------------------------------------- 47 | 48 | const EINVALID_SOLUTION: u64 = 1337; 49 | const ESTEP_NOT_COMPLETED: u64 = 1338; 50 | const EWRONG_MAGIC: u64 = 1339; 51 | const SECRET_VALUE: u64 = 42; 52 | const REQUIRED_STEPS: u64 = 3; 53 | 54 | // --------------------------------------------------- 55 | // INIT FUNCTION 56 | // --------------------------------------------------- 57 | 58 | fun init(ctx: &mut TxContext) { 59 | let challenge = Challenge { 60 | id: object::new(ctx), 61 | counter: 0, 62 | secret: SECRET_VALUE, 63 | solved: false 64 | }; 65 | transfer::share_object(challenge); 66 | } 67 | 68 | // --------------------------------------------------- 69 | // PUBLIC FUNCTIONS 70 | // --------------------------------------------------- 71 | 72 | public entry fun create_progress(ctx: &mut TxContext) { 73 | let progress = UserProgress { 74 | id: object::new(ctx), 75 | user: tx_context::sender(ctx), 76 | steps_completed: 0, 77 | magic_number: 0 78 | }; 79 | transfer::transfer(progress, tx_context::sender(ctx)); 80 | } 81 | 82 | public entry fun step_one(progress: &mut UserProgress, value: u64) { 83 | if (value == 100) { 84 | progress.steps_completed = 1; 85 | progress.magic_number = value * 2; // 200 86 | event::emit(StepCompleted { 87 | user: progress.user, 88 | step: 1 89 | }); 90 | } 91 | } 92 | 93 | public entry fun step_two(progress: &mut UserProgress, challenge: &mut Challenge, list: vector) { 94 | assert!(progress.steps_completed >= 1, ESTEP_NOT_COMPLETED); 95 | 96 | // Check if list has correct values [1, 3, 3, 7] 97 | if (vector::length(&list) == 4 && 98 | *vector::borrow(&list, 0) == 1 && 99 | *vector::borrow(&list, 1) == 3 && 100 | *vector::borrow(&list, 2) == 3 && 101 | *vector::borrow(&list, 3) == 7) { 102 | 103 | progress.steps_completed = 2; 104 | challenge.counter = challenge.counter + 1; 105 | progress.magic_number = progress.magic_number + 137; // Now 337 106 | 107 | event::emit(StepCompleted { 108 | user: progress.user, 109 | step: 2 110 | }); 111 | } 112 | } 113 | 114 | public entry fun step_three(progress: &mut UserProgress, challenge: &Challenge, guess: u64) { 115 | assert!(progress.steps_completed >= 2, ESTEP_NOT_COMPLETED); 116 | 117 | // User needs to guess the secret value 118 | if (guess == challenge.secret) { 119 | progress.steps_completed = 3; 120 | progress.magic_number = progress.magic_number + challenge.secret; // Now 379 121 | 122 | event::emit(StepCompleted { 123 | user: progress.user, 124 | step: 3 125 | }); 126 | } 127 | } 128 | 129 | public entry fun final_solve(progress: &mut UserProgress, challenge: &mut Challenge) { 130 | assert!(progress.steps_completed >= REQUIRED_STEPS, ESTEP_NOT_COMPLETED); 131 | assert!(progress.magic_number == 379, EWRONG_MAGIC); 132 | 133 | challenge.solved = true; 134 | 135 | event::emit(ChallengeAttempt { 136 | user: progress.user, 137 | success: true 138 | }); 139 | } 140 | 141 | // --------------------------------------------------- 142 | // CHECK SOLUTION 143 | // --------------------------------------------------- 144 | 145 | public entry fun check_solution(challenge: &Challenge) { 146 | assert!(challenge.solved == true, EINVALID_SOLUTION); 147 | } 148 | 149 | // --------------------------------------------------- 150 | // VIEW FUNCTIONS 151 | // --------------------------------------------------- 152 | 153 | public fun get_counter(challenge: &Challenge): u64 { 154 | challenge.counter 155 | } 156 | 157 | public fun get_progress(progress: &UserProgress): (u64, u64) { 158 | (progress.steps_completed, progress.magic_number) 159 | } 160 | 161 | public fun is_solved(challenge: &Challenge): bool { 162 | challenge.solved 163 | } 164 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sui CTF Framework 2 | A modular framework for running CTF challenges on the Sui blockchain. 3 | It was used during: 4 | - Sui Basecamp CTF 2024 & 2025 5 | - MetaTrust CTF 2023 6 | - JustCTF 2023 7 | 8 | 9 | To get started, just add the following line to the `dependencies` section in your `Cargo.toml`: 10 | ```toml 11 | [dependencies] 12 | sui-ctf-framework = { git = "https://github.com/otter-sec/sui-ctf-framework" } 13 | ``` 14 | 15 | ## initialize 16 | Initializes a new Sui test environment with specified named addresses and optional accounts. 17 | 18 | **Signature:** 19 | ```rust 20 | pub async fn initialize<'a>( 21 | named_addresses: Vec<(String, NumericalAddress)>, 22 | accounts: Option>, 23 | ) -> SuiTestAdapter 24 | ``` 25 | 26 | **Example:** 27 | ```rust 28 | let named_addresses = vec![ 29 | ("challenge".to_string(), NumericalAddress::parse_str("0x0")?), 30 | ("solution".to_string(), NumericalAddress::parse_str("0x0")?), 31 | ("admin".to_string(), NumericalAddress::parse_str("0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e")?), 32 | ]; 33 | 34 | let mut adapter = sui_ctf_framework::initialize( 35 | named_addresses, 36 | Some(vec!["challenger".to_string(), "solver".to_string()]), 37 | ).await; 38 | ``` 39 | 40 | ## publish_compiled_module 41 | Publishes a compiled module to the Sui network. 42 | 43 | **Signature:** 44 | ```rust 45 | pub async fn publish_compiled_module( 46 | adapter: &mut SuiTestAdapter, 47 | modules: Vec, 48 | module_dependencies: Vec, 49 | sender: Option 50 | ) -> Option 51 | ``` 52 | 53 | **Example:** 54 | ```rust 55 | // Publish challenge modules 56 | let chall_dependencies: Vec = Vec::new(); 57 | let chall_addr = match sui_ctf_framework::publish_compiled_module( 58 | &mut adapter, 59 | mncp_modules, 60 | chall_dependencies, 61 | Some(String::from("challenger")), 62 | ).await { 63 | Some(addr) => addr, 64 | None => { 65 | // Handle error 66 | return Ok(()); 67 | } 68 | }; 69 | 70 | // Publish solution module 71 | let mut sol_dependencies: Vec = Vec::new(); 72 | sol_dependencies.push(String::from("challenge")); 73 | let sol_addr = match sui_ctf_framework::publish_compiled_module( 74 | &mut adapter, 75 | mncp_solution, 76 | sol_dependencies, 77 | Some(String::from("solver")), 78 | ).await { 79 | Some(addr) => addr, 80 | None => { 81 | // Handle error 82 | return Ok(()); 83 | } 84 | }; 85 | ``` 86 | 87 | ## call_function 88 | Calls a function in a published Move module. 89 | 90 | **Signature:** 91 | ```rust 92 | pub async fn call_function( 93 | adapter: &mut SuiTestAdapter, 94 | mod_addr: AccountAddress, 95 | mod_name: &str, 96 | fun_name: &str, 97 | args: Vec, 98 | type_args: Vec, 99 | signer: Option, 100 | ) -> Result, Box> 101 | ``` 102 | 103 | **Example:** 104 | ```rust 105 | // Prepare function arguments 106 | let mut args_liq: Vec = Vec::new(); 107 | let arg_liq1 = SuiValue::Object(FakeID::Enumerated(2, 1), None); 108 | let arg_liq2 = SuiValue::Object(FakeID::Enumerated(2, 5), None); 109 | let arg_liq3 = SuiValue::Object(FakeID::Enumerated(2, 6), None); 110 | args_liq.push(arg_liq1); 111 | args_liq.push(arg_liq2); 112 | args_liq.push(arg_liq3); 113 | 114 | // Prepare type arguments 115 | let mut type_args: Vec = Vec::new(); 116 | let type1 = TypeTag::Struct(Box::new(StructTag { 117 | address: chall_addr, 118 | module: Identifier::from_str("ctf").unwrap(), 119 | name: Identifier::from_str("CTF").unwrap(), 120 | type_params: Vec::new(), 121 | })); 122 | let type2 = TypeTag::Struct(Box::new(StructTag { 123 | address: chall_addr, 124 | module: Identifier::from_str("osec").unwrap(), 125 | name: Identifier::from_str("OSEC").unwrap(), 126 | type_params: Vec::new(), 127 | })); 128 | type_args.push(type1); 129 | type_args.push(type2); 130 | 131 | // Call function 132 | let ret_val = match sui_ctf_framework::call_function( 133 | &mut adapter, 134 | chall_addr, 135 | "OtterSwap", 136 | "initialize_pool", 137 | args_liq, 138 | type_args, 139 | Some("challenger".to_string()), 140 | ).await { 141 | Ok(output) => output, 142 | Err(e) => { 143 | // Handle error 144 | return Err("error during call".into()) 145 | } 146 | }; 147 | ``` 148 | 149 | ## view_object 150 | Retrieves and parses information about an object on the Sui blockchain. 151 | 152 | **Signature:** 153 | ```rust 154 | pub async fn view_object( 155 | adapter: &mut SuiTestAdapter, 156 | id: FakeID 157 | ) -> Result, Box> 158 | ``` 159 | 160 | **Example:** 161 | ```rust 162 | // View an object with ID 0:0 163 | let object_output: Value = match sui_ctf_framework::view_object( 164 | &mut adapter, 165 | FakeID::Enumerated(0, 0) 166 | ).await { 167 | Ok(output) => { 168 | println!("Object Output: {:#?}", output); 169 | output.unwrap() 170 | } 171 | Err(_error) => { 172 | // Handle error 173 | return Err("error when viewing the object".into()) 174 | } 175 | }; 176 | 177 | // Access object properties 178 | let bytes_str = object_output 179 | .get("Contents") 180 | .and_then(|contents| contents.get("id")) 181 | .and_then(|id| id.get("id")) 182 | .and_then(|inner_id| inner_id.get("bytes")) 183 | .and_then(|bytes| bytes.as_str()) 184 | .unwrap(); 185 | 186 | ``` 187 | 188 | ## fund_account 189 | Sends funds to an account. 190 | 191 | **Signature:** 192 | ```rust 193 | pub async fn fund_account( 194 | adapter: &mut SuiTestAdapter, 195 | sender: String, 196 | amount: u64, 197 | account_address: String 198 | ) 199 | ``` 200 | 201 | **Example:** 202 | ```rust 203 | // Fund the solver account with 1000 tokens from the challenger account 204 | sui_ctf_framework::fund_account( 205 | &mut adapter, 206 | "challenger".to_string(), 207 | 1000, 208 | "solver".to_string() 209 | ).await; 210 | ``` -------------------------------------------------------------------------------- /examples/challenge_example/framework/chall/sources/otterloan.move: -------------------------------------------------------------------------------- 1 | module challenge::OtterLoan { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use sui::coin::{Self, Coin}; 8 | use sui::balance::{Self, Balance}; 9 | use sui::object::{Self, ID, UID}; 10 | use sui::transfer; 11 | use sui::tx_context::{Self, TxContext}; 12 | 13 | // --------------------------------------------------- 14 | // STRUCTS 15 | // --------------------------------------------------- 16 | 17 | public struct OSEC has drop {} 18 | 19 | public struct FlashLender has key { 20 | id: UID, 21 | to_lend: Balance, 22 | fee: u64, 23 | } 24 | 25 | public struct Receipt has drop{ 26 | flash_lender_id: ID, 27 | repay_amount: u64 28 | } 29 | 30 | public struct AdminCapability has key, store { 31 | id: UID, 32 | flash_lender_id: ID, 33 | } 34 | 35 | // --------------------------------------------------- 36 | // CONSTANTS 37 | // --------------------------------------------------- 38 | 39 | const ELOAN_TOO_LARGE: u64 = 0; 40 | const EINVALID_REPAYMENT_AMOUNT: u64 = 1; 41 | const EREPAY_TO_WRONG_LENDER: u64 = 2; 42 | const EADMIN_ONLY: u64 = 3; 43 | const EWITHDRAW_TOO_LARGE: u64 = 4; 44 | 45 | // --------------------------------------------------- 46 | // FUNCTIONS 47 | // --------------------------------------------------- 48 | 49 | public fun new( to_lend: Coin, fee: u64, ctx: &mut TxContext ): AdminCapability { 50 | 51 | let flash_lender_uid : UID = object::new(ctx); 52 | let flash_lender_id = object::uid_to_inner(&flash_lender_uid); 53 | let balance_to_lend: Balance = coin::into_balance(to_lend); 54 | let flash_lender = FlashLender { 55 | id: flash_lender_uid, 56 | to_lend: balance_to_lend, 57 | fee: fee 58 | }; 59 | 60 | transfer::share_object(flash_lender); 61 | AdminCapability { id: object::new(ctx), flash_lender_id } 62 | } 63 | 64 | public fun create( to_lend: Coin, fee: u64, ctx: &mut TxContext ) { 65 | 66 | let admin_cap = new(to_lend, fee, ctx); 67 | transfer::transfer(admin_cap, tx_context::sender(ctx)) 68 | } 69 | 70 | 71 | public fun loan( self: &mut FlashLender, amount: u64, ctx: &mut TxContext ): (Coin, Receipt) { 72 | 73 | let to_lend = &mut self.to_lend; 74 | assert!(balance::value(to_lend) >= amount, ELOAN_TOO_LARGE); 75 | let loan = coin::take(to_lend, amount, ctx); 76 | 77 | let repay_amount = amount + self.fee; 78 | let receipt = Receipt { flash_lender_id: object::uid_to_inner(&self.id), repay_amount }; 79 | (loan, receipt) 80 | } 81 | 82 | 83 | public fun repay( self: &mut FlashLender, payment: Coin, receipt: Receipt ) { 84 | 85 | let Receipt { flash_lender_id, repay_amount } = receipt; 86 | assert!(object::uid_to_inner(&self.id) == flash_lender_id, EREPAY_TO_WRONG_LENDER); 87 | assert!(coin::value(&payment) == repay_amount, EINVALID_REPAYMENT_AMOUNT); 88 | 89 | coin::put(&mut self.to_lend, payment) 90 | } 91 | 92 | public fun withdraw( self: &mut FlashLender, admin_cap: &AdminCapability, amount: u64, ctx: &mut TxContext ) { 93 | 94 | check_admin(self, admin_cap); 95 | 96 | let to_lend = &mut self.to_lend; 97 | assert!(balance::value(to_lend) >= amount, EWITHDRAW_TOO_LARGE); 98 | let coins = coin::take(to_lend, amount, ctx); 99 | 100 | let sender = tx_context::sender(ctx); 101 | transfer::public_transfer(coins, sender) 102 | } 103 | 104 | 105 | public fun deposit( self: &mut FlashLender, admin_cap: &AdminCapability, coin: Coin, _ctx: &mut TxContext ) { 106 | 107 | check_admin(self, admin_cap); 108 | coin::put(&mut self.to_lend, coin) 109 | } 110 | 111 | 112 | public fun update_fee( self: &mut FlashLender, _admin_cap: &AdminCapability, new_fee: u64, _ctx: &mut TxContext ) { 113 | self.fee = new_fee 114 | } 115 | 116 | 117 | fun check_admin(self: &FlashLender, admin_cap: &AdminCapability ) { 118 | assert!(object::uid_to_inner(&self.id) == admin_cap.flash_lender_id, EADMIN_ONLY); 119 | } 120 | 121 | 122 | public fun fee( self: &FlashLender ): u64 { 123 | self.fee 124 | } 125 | 126 | 127 | public fun max_loan( self: &FlashLender ): u64 { 128 | balance::value(&self.to_lend) 129 | } 130 | 131 | 132 | public fun repay_amount( self: &Receipt ): u64 { 133 | self.repay_amount 134 | } 135 | 136 | 137 | public fun flash_lender_id(self: &Receipt): ID { 138 | self.flash_lender_id 139 | } 140 | 141 | // --------------------------------------------------- 142 | // TESTS 143 | // --------------------------------------------------- 144 | 145 | #[test_only] 146 | use sui::test_scenario::{Self, next_tx}; 147 | 148 | #[test_only] 149 | use sui::coin::{mint_for_testing}; 150 | 151 | #[test_only] 152 | use sui::sui::SUI; 153 | 154 | #[test] 155 | fun test_flashloan() { 156 | let lender = @0x111; 157 | let borrower = @0x222; 158 | let _contract = @admin; 159 | 160 | let mut scenario_val = test_scenario::begin(lender); 161 | let scenario = &mut scenario_val; 162 | 163 | next_tx(scenario, lender); 164 | { 165 | let coin : Coin = mint_for_testing(500, test_scenario::ctx(scenario)); 166 | create(coin, 100, test_scenario::ctx(scenario)); 167 | }; 168 | 169 | next_tx(scenario, borrower); 170 | { 171 | let mut flash_lender = test_scenario::take_shared>(scenario); 172 | let (mut tokens, receipt) = loan(&mut flash_lender, 100, test_scenario::ctx(scenario)); 173 | assert!(coin::value(&tokens) == 100, 0); 174 | let fee : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 175 | coin::join(&mut tokens, fee); 176 | repay(&mut flash_lender, tokens, receipt); 177 | test_scenario::return_shared(flash_lender); 178 | }; 179 | 180 | test_scenario::end(scenario_val); 181 | } 182 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/dependency/sources/otterloan.move: -------------------------------------------------------------------------------- 1 | module challenge::OtterLoan { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use sui::coin::{Self, Coin}; 8 | use sui::balance::{Self, Balance}; 9 | use sui::object::{Self, ID, UID}; 10 | use sui::transfer; 11 | use sui::tx_context::{Self, TxContext}; 12 | 13 | // --------------------------------------------------- 14 | // STRUCTS 15 | // --------------------------------------------------- 16 | 17 | public struct OSEC has drop {} 18 | 19 | public struct FlashLender has key { 20 | id: UID, 21 | to_lend: Balance, 22 | fee: u64, 23 | } 24 | 25 | public struct Receipt has drop{ 26 | flash_lender_id: ID, 27 | repay_amount: u64 28 | } 29 | 30 | public struct AdminCapability has key, store { 31 | id: UID, 32 | flash_lender_id: ID, 33 | } 34 | 35 | // --------------------------------------------------- 36 | // CONSTANTS 37 | // --------------------------------------------------- 38 | 39 | const ELOAN_TOO_LARGE: u64 = 0; 40 | const EINVALID_REPAYMENT_AMOUNT: u64 = 1; 41 | const EREPAY_TO_WRONG_LENDER: u64 = 2; 42 | const EADMIN_ONLY: u64 = 3; 43 | const EWITHDRAW_TOO_LARGE: u64 = 4; 44 | 45 | // --------------------------------------------------- 46 | // FUNCTIONS 47 | // --------------------------------------------------- 48 | 49 | public fun new( to_lend: Coin, fee: u64, ctx: &mut TxContext ): AdminCapability { 50 | 51 | let flash_lender_uid : UID = object::new(ctx); 52 | let flash_lender_id = object::uid_to_inner(&flash_lender_uid); 53 | let balance_to_lend: Balance = coin::into_balance(to_lend); 54 | let flash_lender = FlashLender { 55 | id: flash_lender_uid, 56 | to_lend: balance_to_lend, 57 | fee: fee 58 | }; 59 | 60 | transfer::share_object(flash_lender); 61 | AdminCapability { id: object::new(ctx), flash_lender_id } 62 | } 63 | 64 | public fun create( to_lend: Coin, fee: u64, ctx: &mut TxContext ) { 65 | 66 | let admin_cap = new(to_lend, fee, ctx); 67 | transfer::transfer(admin_cap, tx_context::sender(ctx)) 68 | } 69 | 70 | 71 | public fun loan( self: &mut FlashLender, amount: u64, ctx: &mut TxContext ): (Coin, Receipt) { 72 | 73 | let to_lend = &mut self.to_lend; 74 | assert!(balance::value(to_lend) >= amount, ELOAN_TOO_LARGE); 75 | let loan = coin::take(to_lend, amount, ctx); 76 | 77 | let repay_amount = amount + self.fee; 78 | let receipt = Receipt { flash_lender_id: object::uid_to_inner(&self.id), repay_amount }; 79 | (loan, receipt) 80 | } 81 | 82 | 83 | public fun repay( self: &mut FlashLender, payment: Coin, receipt: Receipt ) { 84 | 85 | let Receipt { flash_lender_id, repay_amount } = receipt; 86 | assert!(object::uid_to_inner(&self.id) == flash_lender_id, EREPAY_TO_WRONG_LENDER); 87 | assert!(coin::value(&payment) == repay_amount, EINVALID_REPAYMENT_AMOUNT); 88 | 89 | coin::put(&mut self.to_lend, payment) 90 | } 91 | 92 | public fun withdraw( self: &mut FlashLender, admin_cap: &AdminCapability, amount: u64, ctx: &mut TxContext ) { 93 | 94 | check_admin(self, admin_cap); 95 | 96 | let to_lend = &mut self.to_lend; 97 | assert!(balance::value(to_lend) >= amount, EWITHDRAW_TOO_LARGE); 98 | let coins = coin::take(to_lend, amount, ctx); 99 | 100 | let sender = tx_context::sender(ctx); 101 | transfer::public_transfer(coins, sender) 102 | } 103 | 104 | 105 | public fun deposit( self: &mut FlashLender, admin_cap: &AdminCapability, coin: Coin, _ctx: &mut TxContext ) { 106 | 107 | check_admin(self, admin_cap); 108 | coin::put(&mut self.to_lend, coin) 109 | } 110 | 111 | 112 | public fun update_fee( self: &mut FlashLender, _admin_cap: &AdminCapability, new_fee: u64, _ctx: &mut TxContext ) { 113 | self.fee = new_fee 114 | } 115 | 116 | 117 | fun check_admin(self: &FlashLender, admin_cap: &AdminCapability ) { 118 | assert!(object::uid_to_inner(&self.id) == admin_cap.flash_lender_id, EADMIN_ONLY); 119 | } 120 | 121 | 122 | public fun fee( self: &FlashLender ): u64 { 123 | self.fee 124 | } 125 | 126 | 127 | public fun max_loan( self: &FlashLender ): u64 { 128 | balance::value(&self.to_lend) 129 | } 130 | 131 | 132 | public fun repay_amount( self: &Receipt ): u64 { 133 | self.repay_amount 134 | } 135 | 136 | 137 | public fun flash_lender_id(self: &Receipt): ID { 138 | self.flash_lender_id 139 | } 140 | 141 | // --------------------------------------------------- 142 | // TESTS 143 | // --------------------------------------------------- 144 | 145 | #[test_only] 146 | use sui::test_scenario::{Self, next_tx}; 147 | 148 | #[test_only] 149 | use sui::coin::{mint_for_testing}; 150 | 151 | #[test_only] 152 | use sui::sui::SUI; 153 | 154 | #[test] 155 | fun test_flashloan() { 156 | let lender = @0x111; 157 | let borrower = @0x222; 158 | let _contract = @admin; 159 | 160 | let mut scenario_val = test_scenario::begin(lender); 161 | let scenario = &mut scenario_val; 162 | 163 | next_tx(scenario, lender); 164 | { 165 | let coin : Coin = mint_for_testing(500, test_scenario::ctx(scenario)); 166 | create(coin, 100, test_scenario::ctx(scenario)); 167 | }; 168 | 169 | next_tx(scenario, borrower); 170 | { 171 | let mut flash_lender = test_scenario::take_shared>(scenario); 172 | let (mut tokens, receipt) = loan(&mut flash_lender, 100, test_scenario::ctx(scenario)); 173 | assert!(coin::value(&tokens) == 100, 0); 174 | let fee : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 175 | coin::join(&mut tokens, fee); 176 | repay(&mut flash_lender, tokens, receipt); 177 | test_scenario::return_shared(flash_lender); 178 | }; 179 | 180 | test_scenario::end(scenario_val); 181 | } 182 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/solve/sources/solve.move: -------------------------------------------------------------------------------- 1 | module solution::gringotts_solution { 2 | 3 | // [*] Import dependencies 4 | use sui::tx_context::{Self, TxContext}; 5 | use sui::coin::{Self, Coin}; 6 | // use sui::sui::SUI; 7 | // use std::vector; 8 | use sui::transfer; 9 | 10 | // use challenge::OtterLoan; 11 | use challenge::OtterSwap; 12 | use challenge::osec::{Self, OSEC}; 13 | use challenge::router; 14 | use challenge::ctf::{Self, CTF, Airdrop}; 15 | use challenge::merch_store; 16 | 17 | use std::debug; 18 | use std::string; 19 | 20 | #[test_only] 21 | use sui::test_scenario::{Self, next_tx, ctx}; 22 | #[test_only] 23 | use std::bcs; 24 | 25 | // use challenge::coin_flip; 26 | 27 | fun pool_debug(liquidity_pool: &mut OtterSwap::Pool) { 28 | let mut debug_message = string::utf8(b"-----------------------------"); 29 | debug::print(&debug_message); 30 | 31 | let (a, b) = OtterSwap::get_balance(liquidity_pool); 32 | let a_price = OtterSwap::get_a_price(liquidity_pool); 33 | let b_price = OtterSwap::get_b_price(liquidity_pool); 34 | 35 | debug_message = string::utf8(b"[CTF] = "); 36 | debug::print(&debug_message); 37 | debug::print(&a); 38 | debug::print(&a_price); 39 | 40 | debug_message = string::utf8(b"[OSEC] = "); 41 | debug::print(&debug_message); 42 | debug::print(&b); 43 | debug::print(&b_price); 44 | 45 | let k = a * b; 46 | debug_message = string::utf8(b"K = "); 47 | debug::print(&debug_message); 48 | debug::print(&k); 49 | 50 | debug_message = string::utf8(b"-----------------------------"); 51 | debug::print(&debug_message); 52 | } 53 | 54 | fun debug_operation_name(operation_name : string::String) { 55 | debug::print(&operation_name); 56 | } 57 | 58 | #[allow(lint(self_transfer))] 59 | public fun solve( 60 | liquidity_pool: &mut OtterSwap::Pool, 61 | airdrop_shared: &mut Airdrop, 62 | ctx: &mut TxContext 63 | ) { 64 | 65 | // RETRIEVE AIRDROP 66 | let mut coin_ctf = ctf::get_airdrop(airdrop_shared, ctx); 67 | // transfer::public_transfer(coin_ctf, tx_context::sender(ctx)); 68 | 69 | // DEBUG 70 | pool_debug(liquidity_pool); 71 | 72 | // step3: swap a -> b 73 | let mut counter = 1; 74 | let mut coin_osec = coin::zero(ctx); 75 | while (counter < 250) { 76 | let mut coin_a = coin::split(&mut coin_ctf, 1, ctx); 77 | let coin_out = OtterSwap::swap_a_b(liquidity_pool, coin_a, ctx); 78 | coin::join(&mut coin_osec, coin_out); 79 | debug::print(&counter); 80 | counter = counter + 1; 81 | }; 82 | 83 | // DEBUG 84 | pool_debug(liquidity_pool); 85 | 86 | // assert!( new_k <= k, 0 ); 87 | 88 | let mut counter = 1; 89 | while (counter < 251) { 90 | 91 | let one_coin : Coin = coin::split(&mut coin_osec, 1, ctx); 92 | let coin_out = OtterSwap::swap_b_a(liquidity_pool, one_coin, ctx); 93 | 94 | coin::join(&mut coin_ctf, coin_out); 95 | 96 | let param2 = string::utf8(b"CACA"); 97 | debug::print(¶m2); 98 | let one_coin = coin::split(&mut coin_ctf, 1, ctx); 99 | let remaining_amount = coin::value(&coin_ctf); 100 | debug::print(&remaining_amount); 101 | let remaining_coins = coin::split(&mut coin_ctf, remaining_amount, ctx); 102 | let coin_osec1 = OtterSwap::swap_a_b(liquidity_pool, one_coin, ctx); 103 | let coin_osec2 = OtterSwap::swap_a_b(liquidity_pool, remaining_coins, ctx); 104 | coin::join(&mut coin_osec, coin_osec1); 105 | coin::join(&mut coin_osec, coin_osec2); 106 | debug::print(&counter); 107 | counter = counter + 1; 108 | }; 109 | 110 | let ctf_total = coin::value(&coin_ctf); 111 | debug::print(&ctf_total); 112 | 113 | let osec_total = coin::value(&coin_osec); 114 | debug::print(&osec_total); 115 | 116 | merch_store::buy_flag(coin_osec, ctx); 117 | 118 | transfer::public_transfer(coin_ctf, tx_context::sender(ctx)); 119 | // transfer::public_transfer(coin_osec, tx_context::sender(ctx)); 120 | 121 | // DEBUG 122 | pool_debug(liquidity_pool); 123 | } 124 | 125 | #[test] 126 | public fun test_bug3() { 127 | let investor = @0x111; 128 | let swapper = @0x222; 129 | let contract = @admin; 130 | 131 | let mut scenario_val = test_scenario::begin(@admin); 132 | let scenario = &mut scenario_val; 133 | 134 | // step1: create pool 135 | debug_operation_name(string::utf8(b" CREATE ")); 136 | next_tx(scenario, contract); 137 | { 138 | router::init_for_testing(test_scenario::ctx(scenario)); 139 | ctf::init_for_testing(test_scenario::ctx(scenario)); 140 | osec::init_for_testing(test_scenario::ctx(scenario)); 141 | }; 142 | 143 | // step2: add liquidity 144 | debug_operation_name(string::utf8(b" ADD LIQ ")); 145 | next_tx(scenario, contract); 146 | { 147 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 148 | let coin_ctf = test_scenario::take_from_sender>(scenario); 149 | let coin_osec = test_scenario::take_from_sender>(scenario); 150 | OtterSwap::initialize_pool(&mut liquidity_pool, coin_ctf, coin_osec, test_scenario::ctx(scenario)); 151 | test_scenario::return_shared(liquidity_pool); 152 | }; 153 | 154 | // DEBUG 155 | next_tx(scenario, swapper); 156 | { 157 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 158 | pool_debug(&mut liquidity_pool); 159 | test_scenario::return_shared(liquidity_pool); 160 | }; 161 | 162 | // step2.5: redeem airdrop 163 | debug_operation_name(string::utf8(b" CALL SOLVE ")); 164 | next_tx(scenario, swapper); 165 | { 166 | let param1 = string::utf8(b"REDEEMING AIRDROP"); 167 | let param2 = string::utf8(b"DONE REDEEMING AIRDROP"); 168 | debug::print(¶m1); 169 | let mut airdrop_shared = test_scenario::take_shared>(scenario); 170 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 171 | solve(&mut liquidity_pool, &mut airdrop_shared, test_scenario::ctx(scenario)); 172 | debug::print(¶m2); 173 | test_scenario::return_shared(airdrop_shared); 174 | test_scenario::return_shared(liquidity_pool); 175 | }; 176 | 177 | test_scenario::end(scenario_val); 178 | 179 | } 180 | 181 | } -------------------------------------------------------------------------------- /examples/challenge_example_2/sources/framework/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::io::{Read, Write}; 5 | use std::mem::drop; 6 | use std::net::{TcpListener, TcpStream}; 7 | use std::path::Path; 8 | use std::str::FromStr; 9 | 10 | use serde_json::Value; 11 | 12 | use tokio; 13 | 14 | use move_transactional_test_runner::framework::{MaybeNamedCompiledModule, MoveTestAdapter}; 15 | use move_bytecode_source_map::{source_map::SourceMap, utils::source_map_from_file}; 16 | use move_binary_format::file_format::CompiledModule; 17 | use move_symbol_pool::Symbol; 18 | use move_core_types::{ 19 | account_address::AccountAddress, 20 | language_storage::{TypeTag, StructTag}}; 21 | 22 | use sui_types::Identifier; 23 | use sui_ctf_framework::{NumericalAddress, SuiTF}; 24 | use sui_transactional_test_runner::{args::SuiValue, test_adapter::FakeID}; 25 | 26 | macro_rules! handle_err { 27 | ($stream:expr, $msg:expr, $err:expr) => {{ 28 | let full = format!("[SERVER ERROR] {}: {}", $msg, $err); 29 | eprintln!("{}", full); 30 | let _ = $stream.write_all(full.as_bytes()); // ignore write failures 31 | drop($stream); // close socket 32 | return Err::<(), Box>(full.into()); 33 | }}; 34 | } 35 | 36 | async fn handle_client(mut stream: TcpStream) -> Result<(), Box> { 37 | // Initialize SuiTestAdapter 38 | let module_name = "MileHighCity"; 39 | let mut deployed_modules: Vec = Vec::new(); 40 | 41 | let named_addresses = vec![ 42 | ( 43 | "challenge".to_string(), 44 | NumericalAddress::parse_str( 45 | "0x0", 46 | )?, 47 | ), 48 | ( 49 | "solution".to_string(), 50 | NumericalAddress::parse_str( 51 | "0x0", 52 | )?, 53 | ), 54 | ]; 55 | 56 | let mut suitf = match sui_ctf_framework::SuiTF::initialize( 57 | named_addresses, 58 | Some(vec!["challenger".to_string(), "solver".to_string()]), 59 | ).await { 60 | Ok(adapter) => adapter, 61 | Err(e) => handle_err!(stream, "SuiTF initialization failed", e), 62 | }; 63 | 64 | // Publish challenge module 65 | let mut mncp_modules : Vec = Vec::new(); 66 | let mod_path = format!("./chall/build/challenge/bytecode_modules/{}.mv", module_name); 67 | let src_path = format!("./chall/build/challenge/debug_info/{}.json", module_name); 68 | let mod_bytes: Vec = match std::fs::read(mod_path) { 69 | Ok(data) => data, 70 | Err(e) => handle_err!(stream, format!("Failed to read {}", module_name), e), 71 | }; 72 | 73 | let module: CompiledModule = match CompiledModule::deserialize_with_defaults(&mod_bytes) { 74 | Ok(data) => data, 75 | Err(e) => { 76 | return Err(Box::new(e)) 77 | } 78 | }; 79 | let named_addr_opt: Option = Some(Symbol::from("challenge")); 80 | let source_map: Option = match source_map_from_file(Path::new(&src_path)) { 81 | Ok(data) => Some(data), 82 | Err(e) => handle_err!(stream, format!("Deserialization failed for {}", module.name()), e), 83 | }; 84 | 85 | mncp_modules.push( MaybeNamedCompiledModule { 86 | named_address: named_addr_opt, 87 | module: module, 88 | source_map: source_map, 89 | }); 90 | 91 | let chall_dependencies: Vec = Vec::new(); 92 | let chall_addr = match suitf.publish_compiled_module( 93 | mncp_modules, 94 | chall_dependencies, 95 | Some(String::from("challenger")), 96 | ).await { 97 | Ok(addr) => addr, 98 | Err(e) => handle_err!(stream, "Challenge module publish failed", e), 99 | }; 100 | 101 | deployed_modules.push(chall_addr); 102 | println!("[SERVER] Module published at: {:?}", chall_addr); 103 | 104 | // Read Solution Module 105 | let mut solution_data = [0 as u8; 2000]; 106 | let _solution_size = match stream.read(&mut solution_data) { 107 | Ok(size) => { 108 | if size == 0 { 109 | handle_err!(stream, "No data read from stream", "size is zero"); 110 | } else{ 111 | size 112 | } 113 | } 114 | Err(e) => handle_err!(stream, "Failed to read solution data", e), 115 | }; 116 | 117 | // Send Challenge Address 118 | let mut output = String::new(); 119 | fmt::write( 120 | &mut output, 121 | format_args!( 122 | "[SERVER] Challenge modules published at: {}", 123 | chall_addr.to_string().as_str(), 124 | ), 125 | ) 126 | .unwrap(); 127 | stream.write_all(output.as_bytes())?; 128 | 129 | // Publish Solution Module 130 | let mut sol_dependencies: Vec = Vec::new(); 131 | sol_dependencies.push(String::from("challenge")); 132 | 133 | let mut mncp_solution : Vec = Vec::new(); 134 | let module : CompiledModule = match CompiledModule::deserialize_with_defaults(&solution_data.to_vec()) { 135 | Ok(m) => m, 136 | Err(e) => handle_err!(stream, "Solution deserialization failed", e), 137 | }; 138 | let named_addr_opt: Option = Some(Symbol::from("solution")); 139 | let source_map : Option = None; 140 | 141 | mncp_solution.push( MaybeNamedCompiledModule { 142 | named_address: named_addr_opt, 143 | module: module, 144 | source_map: source_map, 145 | }); 146 | 147 | let sol_addr = match suitf.publish_compiled_module( 148 | mncp_solution, 149 | sol_dependencies, 150 | Some(String::from("solver")), 151 | ).await { 152 | Ok(addr) => addr, 153 | Err(e) => handle_err!(stream, "Solution module publish failed", e), 154 | }; 155 | println!("[SERVER] Solution published at: {:?}", sol_addr); 156 | 157 | // Send Solution Address 158 | output = String::new(); 159 | fmt::write( 160 | &mut output, 161 | format_args!( 162 | "[SERVER] Solution published at {}", 163 | sol_addr.to_string().as_str() 164 | ), 165 | ) 166 | .unwrap(); 167 | stream.write_all(output.as_bytes())?; 168 | 169 | // Call solve Function 170 | 171 | let object_output2 : Value = match suitf.view_object(FakeID::Enumerated(0, 0)).await { 172 | Ok(output) => { 173 | println!("[SERVER] Object Output: {:#?}", output); 174 | output.unwrap() 175 | } 176 | Err(e) => handle_err!(stream, "Error viewing object 1:1", e), 177 | }; 178 | let bytes_str2 = match object_output2.get("Contents") 179 | .and_then(|c| c.get("id")) 180 | .and_then(|id| id.get("id")) 181 | .and_then(|inner| inner.get("bytes")) 182 | .and_then(|bytes| bytes.as_str()) { 183 | Some(s) => s.to_owned(), 184 | None => handle_err!(stream, "Malformed JSON response for object bytes", "missing field"), 185 | }; 186 | println!("Objet Bytes: {}", bytes_str2); 187 | 188 | let mut type_args_sol : Vec = Vec::new(); 189 | let mut args_sol: Vec = Vec::new(); 190 | args_sol.push(SuiValue::Object(FakeID::Enumerated(1, 1), None)); 191 | let ret_val = match suitf.call_function( 192 | sol_addr, 193 | "solution", 194 | "solve", 195 | args_sol, 196 | type_args_sol, 197 | Some("solver".to_string()), 198 | ).await { 199 | Ok(output) => output, 200 | Err(e) => handle_err!(stream, "Calling solve failed", e), 201 | }; 202 | println!("[SERVER] Return value {:#?}", ret_val); 203 | println!(""); 204 | 205 | // Check Solution 206 | let mut args2: Vec = Vec::new(); 207 | args2.push(SuiValue::Object(FakeID::Enumerated(1, 1), None)); 208 | 209 | let type_args_valid : Vec = Vec::new(); 210 | 211 | // Validate Solution 212 | let _sol_ret = match suitf.call_function( 213 | chall_addr, 214 | "MileHighCity", 215 | "check_status", 216 | args2, 217 | type_args_valid, 218 | Some("challenger".to_string()), 219 | ).await { 220 | Ok(_output) => { 221 | println!("[SERVER] Correct Solution!"); 222 | println!(""); 223 | if let Ok(flag) = env::var("FLAG") { 224 | let message = format!("[SERVER] Congrats, flag: {}", flag); 225 | stream.write(message.as_bytes()).unwrap(); 226 | } else { 227 | stream.write("[SERVER] Flag not found, please contact admin".as_bytes()).unwrap(); 228 | } 229 | } 230 | Err(e) => handle_err!(stream, "Calling has_flag failed", e), 231 | }; 232 | 233 | Ok(()) 234 | } 235 | 236 | #[tokio::main] 237 | async fn main() -> Result<(), Box> { 238 | // Create Socket - Port 31337 239 | let listener = TcpListener::bind("0.0.0.0:31337")?; 240 | println!("[SERVER] Starting server at port 31337!"); 241 | 242 | let local = tokio::task::LocalSet::new(); 243 | 244 | // Wait For Incoming Solution 245 | for stream in listener.incoming() { 246 | match stream { 247 | Ok(stream) => { 248 | println!("[SERVER] New connection: {}", stream.peer_addr()?); 249 | let _ = local.run_until( async move { 250 | tokio::task::spawn_local( async { 251 | if let Err(e) = handle_client(stream).await { 252 | eprintln!("[SERVER] Connection Closed. Error: {}", e); 253 | } 254 | }).await.unwrap(); 255 | }).await; 256 | } 257 | Err(e) => { 258 | println!("[SERVER] Error: {}", e); 259 | } 260 | } 261 | } 262 | 263 | // Close Socket Server 264 | drop(listener); 265 | Ok(()) 266 | } 267 | -------------------------------------------------------------------------------- /examples/challenge_example/framework/chall/sources/otterswap.move: -------------------------------------------------------------------------------- 1 | module challenge::OtterSwap { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use std::vector; 8 | 9 | use sui::coin::{Self, Coin}; 10 | use sui::balance::{Self, Balance, Supply}; 11 | use sui::object::{Self, UID}; 12 | use sui::transfer; 13 | use sui::tx_context::{Self, TxContext}; 14 | 15 | // --------------------------------------------------- 16 | // STRUCTS 17 | // --------------------------------------------------- 18 | 19 | public struct LP has drop {} 20 | 21 | public struct Pool has key, store { 22 | id: UID, 23 | type_a: Balance, 24 | type_b: Balance, 25 | lp_supply: Supply>, 26 | fee: u64, 27 | } 28 | 29 | // --------------------------------------------------- 30 | // CONSTANTS 31 | // --------------------------------------------------- 32 | 33 | const EADMIN_ONLY: u64 = 1337; 34 | const EINVALID_AMOUNT: u64 = 1338; 35 | const EALREADY_INITIALIZED: u64 = 1339; 36 | const EBROKEN_INVARIANT: u64 = 1340; 37 | const EINVALID_PARAMS: u64 = 1341; 38 | const ERESERVES_EMPTY: u64 = 1342; 39 | 40 | // --------------------------------------------------- 41 | // FUNCTIONS 42 | // --------------------------------------------------- 43 | 44 | public fun create_pool( ctx: &mut TxContext ) { 45 | 46 | let sender = tx_context::sender(ctx); 47 | assert!( sender == @admin, EADMIN_ONLY ); 48 | 49 | transfer::share_object( Pool { 50 | id: object::new(ctx), 51 | type_a: coin::into_balance(coin::zero(ctx)), 52 | type_b: coin::into_balance(coin::zero(ctx)), 53 | lp_supply: balance::create_supply>(LP {}), 54 | fee: 100, 55 | }); 56 | } 57 | 58 | 59 | public fun initialize_pool( liquidity_pool: &mut Pool, coin_a: Coin, coin_b: Coin, ctx: &mut TxContext ) { 60 | 61 | let coin_a_value = coin::value(&coin_a); 62 | let coin_b_value = coin::value(&coin_b); 63 | 64 | assert!( coin_a_value > 0 && coin_b_value > 0, EINVALID_AMOUNT ); 65 | 66 | let pool_a_value : u64 = balance::value(&liquidity_pool.type_a); 67 | let pool_b_value : u64 = balance::value(&liquidity_pool.type_b); 68 | 69 | assert!( pool_a_value == 0 && pool_b_value == 0, EALREADY_INITIALIZED ); 70 | 71 | coin::put(&mut liquidity_pool.type_a, coin_a); 72 | coin::put(&mut liquidity_pool.type_b, coin_b); 73 | 74 | let lp_bal = balance::increase_supply(&mut liquidity_pool.lp_supply, coin_a_value + coin_b_value); 75 | transfer::public_transfer(coin::from_balance(lp_bal, ctx), tx_context::sender(ctx)); 76 | } 77 | 78 | 79 | public fun swap_a_b( liquidity_pool: &mut Pool, coin_in: Coin, ctx: &mut TxContext ) : Coin { 80 | 81 | let coin_in_value = coin::value(&coin_in); 82 | 83 | let balance_a : u64 = balance::value(&liquidity_pool.type_a); 84 | let balance_b : u64 = balance::value(&liquidity_pool.type_b); 85 | 86 | assert!(balance_a > 0 && balance_b > 0, ERESERVES_EMPTY); 87 | assert!( coin_in_value < balance_a, EINVALID_AMOUNT ); 88 | 89 | let coin_out_value = (balance_b - (((balance_a as u128) * (balance_b as u128)) / ((balance_a as u128) + (coin_in_value as u128)) as u64)); 90 | 91 | coin::put(&mut liquidity_pool.type_a, coin_in); 92 | let coin_out = coin::take(&mut liquidity_pool.type_b, coin_out_value, ctx); 93 | coin_out 94 | } 95 | 96 | public fun swap_b_a( liquidity_pool: &mut Pool, coin_in: Coin, ctx: &mut TxContext ) : Coin { 97 | 98 | let coin_in_value = coin::value(&coin_in); 99 | 100 | let balance_a : u64 = balance::value(&liquidity_pool.type_b); 101 | let balance_b : u64 = balance::value(&liquidity_pool.type_a); 102 | 103 | assert!(balance_a > 0 && balance_b > 0, ERESERVES_EMPTY); 104 | assert!( coin_in_value < balance_a, EINVALID_AMOUNT ); 105 | 106 | let coin_out_value = ( ((balance_b as u128) * (coin_in_value as u128) / ( (balance_a as u128) + (coin_in_value as u128))) as u64); 107 | 108 | coin::put(&mut liquidity_pool.type_b, coin_in); 109 | let coin_out = coin::take(&mut liquidity_pool.type_a, coin_out_value, ctx); 110 | coin_out 111 | } 112 | 113 | 114 | public fun add_liquidity( liquidity_pool: &mut Pool, coin_a: Coin, coin_b: Coin, ctx: &mut TxContext ) { 115 | 116 | let coin_a_value = coin::value(&coin_a); 117 | let coin_b_value = coin::value(&coin_b); 118 | 119 | assert!( coin_a_value > 0 && coin_b_value > 0, EINVALID_AMOUNT ); 120 | 121 | let pool_a_value = balance::value(&liquidity_pool.type_a); 122 | let pool_b_value = balance::value(&liquidity_pool.type_b); 123 | 124 | if (pool_a_value > pool_b_value) { 125 | assert!( (pool_a_value / pool_b_value) == (pool_a_value + coin_a_value ) / (pool_b_value + coin_b_value), EBROKEN_INVARIANT ); 126 | } 127 | else { 128 | assert!( (pool_b_value / pool_a_value) == (pool_b_value + coin_b_value ) / (pool_a_value + coin_a_value), EBROKEN_INVARIANT ); 129 | }; 130 | 131 | coin::put(&mut liquidity_pool.type_a, coin_a); 132 | coin::put(&mut liquidity_pool.type_b, coin_b); 133 | 134 | let lp_bal = balance::increase_supply(&mut liquidity_pool.lp_supply, coin_a_value + coin_b_value); 135 | 136 | transfer::public_transfer(coin::from_balance(lp_bal, ctx), tx_context::sender(ctx)); 137 | } 138 | 139 | 140 | public fun remove_liquidity( liquidity_pool: &mut Pool, lp: Coin>, vec: vector, ctx: &mut TxContext ) { 141 | 142 | assert!( vector::length(&vec) == 2, EINVALID_PARAMS ); 143 | let coin_x_out = *vector::borrow(&vec, 0); 144 | let coin_y_out = *vector::borrow(&vec, 1); 145 | 146 | let lp_balance_value = coin::value>(&lp); 147 | 148 | assert!( lp_balance_value == coin_x_out + coin_y_out, EINVALID_AMOUNT ); 149 | assert!( balance::value(&liquidity_pool.type_a) > coin_x_out, EINVALID_AMOUNT ); 150 | assert!( balance::value(&liquidity_pool.type_b) > coin_y_out, EINVALID_AMOUNT ); 151 | 152 | balance::decrease_supply(&mut liquidity_pool.lp_supply, coin::into_balance(lp)); 153 | 154 | let coin_a = coin::take(&mut liquidity_pool.type_a, coin_x_out, ctx); 155 | let coin_b = coin::take(&mut liquidity_pool.type_b, coin_y_out, ctx); 156 | 157 | let sender = tx_context::sender(ctx); 158 | transfer::public_transfer(coin_a, sender); 159 | transfer::public_transfer(coin_b, sender); 160 | } 161 | 162 | 163 | public fun get_balance(liquidity_pool: &mut Pool): (u64, u64) { 164 | 165 | let pool_a_value = balance::value(&liquidity_pool.type_a); 166 | let pool_b_value = balance::value(&liquidity_pool.type_b); 167 | 168 | (pool_a_value, pool_b_value) 169 | } 170 | 171 | 172 | public fun get_a_price(liquidity_pool: &mut Pool): u64 { 173 | 174 | let pool_a_value = balance::value(&liquidity_pool.type_a); 175 | let pool_b_value = balance::value(&liquidity_pool.type_b); 176 | 177 | pool_b_value / pool_a_value 178 | } 179 | 180 | public fun get_b_price(liquidity_pool: &mut Pool): u64 { 181 | 182 | let pool_a_value = balance::value(&liquidity_pool.type_a); 183 | let pool_b_value = balance::value(&liquidity_pool.type_b); 184 | 185 | pool_a_value / pool_b_value 186 | } 187 | 188 | 189 | // --------------------------------------------------- 190 | // TESTS 191 | // --------------------------------------------------- 192 | 193 | #[test_only] 194 | use sui::test_scenario::{Self, next_tx}; 195 | 196 | #[test_only] 197 | use sui::coin::{mint_for_testing}; 198 | 199 | #[test_only] 200 | use std::debug; 201 | 202 | #[test_only] 203 | use challenge::osec::OSEC; 204 | 205 | #[test_only] 206 | use challenge::ctf::CTF; 207 | 208 | #[test] 209 | fun test_create_pool() { 210 | let investor = @0x111; 211 | let swapper = @0x222; 212 | let contract = @admin; 213 | 214 | let mut scenario_val = test_scenario::begin(@admin); 215 | let scenario = &mut scenario_val; 216 | 217 | next_tx(scenario, contract); 218 | { 219 | create_pool(test_scenario::ctx(scenario)); 220 | }; 221 | 222 | next_tx(scenario, contract); 223 | { 224 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 225 | let coin_a : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 226 | let coin_b : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 227 | initialize_pool(&mut liquidity_pool, coin_a, coin_b, test_scenario::ctx(scenario)); 228 | test_scenario::return_shared(liquidity_pool); 229 | }; 230 | 231 | next_tx(scenario, investor); 232 | { 233 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 234 | let coin_a : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 235 | let coin_b : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 236 | add_liquidity(&mut liquidity_pool, coin_a, coin_b, test_scenario::ctx(scenario)); 237 | test_scenario::return_shared(liquidity_pool); 238 | }; 239 | 240 | next_tx(scenario, swapper); 241 | { 242 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 243 | let coin_a : Coin = mint_for_testing(25, test_scenario::ctx(scenario)); 244 | let coin_out = swap_a_b(&mut liquidity_pool, coin_a, test_scenario::ctx(scenario)); 245 | transfer::public_transfer(coin_out, swapper); 246 | test_scenario::return_shared(liquidity_pool); 247 | }; 248 | 249 | next_tx(scenario, investor); 250 | { 251 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 252 | let vec = vector[100, 100]; 253 | let lp = test_scenario::take_from_sender>>(scenario); 254 | remove_liquidity(&mut liquidity_pool, lp, vec, test_scenario::ctx(scenario)); 255 | test_scenario::return_shared(liquidity_pool); 256 | }; 257 | 258 | test_scenario::end(scenario_val); 259 | } 260 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework-solve/dependency/sources/otterswap.move: -------------------------------------------------------------------------------- 1 | module challenge::OtterSwap { 2 | 3 | // --------------------------------------------------- 4 | // DEPENDENCIES 5 | // --------------------------------------------------- 6 | 7 | use std::vector; 8 | 9 | use sui::coin::{Self, Coin}; 10 | use sui::balance::{Self, Balance, Supply}; 11 | use sui::object::{Self, UID}; 12 | use sui::transfer; 13 | use sui::tx_context::{Self, TxContext}; 14 | 15 | // --------------------------------------------------- 16 | // STRUCTS 17 | // --------------------------------------------------- 18 | 19 | public struct LP has drop {} 20 | 21 | public struct Pool has key, store { 22 | id: UID, 23 | type_a: Balance, 24 | type_b: Balance, 25 | lp_supply: Supply>, 26 | fee: u64, 27 | } 28 | 29 | // --------------------------------------------------- 30 | // CONSTANTS 31 | // --------------------------------------------------- 32 | 33 | const EADMIN_ONLY: u64 = 1337; 34 | const EINVALID_AMOUNT: u64 = 1338; 35 | const EALREADY_INITIALIZED: u64 = 1339; 36 | const EBROKEN_INVARIANT: u64 = 1340; 37 | const EINVALID_PARAMS: u64 = 1341; 38 | const ERESERVES_EMPTY: u64 = 1342; 39 | 40 | // --------------------------------------------------- 41 | // FUNCTIONS 42 | // --------------------------------------------------- 43 | 44 | public fun create_pool( ctx: &mut TxContext ) { 45 | 46 | let sender = tx_context::sender(ctx); 47 | assert!( sender == @admin, EADMIN_ONLY ); 48 | 49 | transfer::share_object( Pool { 50 | id: object::new(ctx), 51 | type_a: coin::into_balance(coin::zero(ctx)), 52 | type_b: coin::into_balance(coin::zero(ctx)), 53 | lp_supply: balance::create_supply>(LP {}), 54 | fee: 100, 55 | }); 56 | } 57 | 58 | 59 | public fun initialize_pool( liquidity_pool: &mut Pool, coin_a: Coin, coin_b: Coin, ctx: &mut TxContext ) { 60 | 61 | let coin_a_value = coin::value(&coin_a); 62 | let coin_b_value = coin::value(&coin_b); 63 | 64 | assert!( coin_a_value > 0 && coin_b_value > 0, EINVALID_AMOUNT ); 65 | 66 | let pool_a_value : u64 = balance::value(&liquidity_pool.type_a); 67 | let pool_b_value : u64 = balance::value(&liquidity_pool.type_b); 68 | 69 | assert!( pool_a_value == 0 && pool_b_value == 0, EALREADY_INITIALIZED ); 70 | 71 | coin::put(&mut liquidity_pool.type_a, coin_a); 72 | coin::put(&mut liquidity_pool.type_b, coin_b); 73 | 74 | let lp_bal = balance::increase_supply(&mut liquidity_pool.lp_supply, coin_a_value + coin_b_value); 75 | transfer::public_transfer(coin::from_balance(lp_bal, ctx), tx_context::sender(ctx)); 76 | } 77 | 78 | 79 | public fun swap_a_b( liquidity_pool: &mut Pool, coin_in: Coin, ctx: &mut TxContext ) : Coin { 80 | 81 | let coin_in_value = coin::value(&coin_in); 82 | 83 | let balance_a : u64 = balance::value(&liquidity_pool.type_a); 84 | let balance_b : u64 = balance::value(&liquidity_pool.type_b); 85 | 86 | assert!(balance_a > 0 && balance_b > 0, ERESERVES_EMPTY); 87 | assert!( coin_in_value < balance_a, EINVALID_AMOUNT ); 88 | 89 | let coin_out_value = (balance_b - (((balance_a as u128) * (balance_b as u128)) / ((balance_a as u128) + (coin_in_value as u128)) as u64)); 90 | 91 | coin::put(&mut liquidity_pool.type_a, coin_in); 92 | let coin_out = coin::take(&mut liquidity_pool.type_b, coin_out_value, ctx); 93 | coin_out 94 | } 95 | 96 | public fun swap_b_a( liquidity_pool: &mut Pool, coin_in: Coin, ctx: &mut TxContext ) : Coin { 97 | 98 | let coin_in_value = coin::value(&coin_in); 99 | 100 | let balance_a : u64 = balance::value(&liquidity_pool.type_b); 101 | let balance_b : u64 = balance::value(&liquidity_pool.type_a); 102 | 103 | assert!(balance_a > 0 && balance_b > 0, ERESERVES_EMPTY); 104 | assert!( coin_in_value < balance_a, EINVALID_AMOUNT ); 105 | 106 | let coin_out_value = ( ((balance_b as u128) * (coin_in_value as u128) / ( (balance_a as u128) + (coin_in_value as u128))) as u64); 107 | 108 | coin::put(&mut liquidity_pool.type_b, coin_in); 109 | let coin_out = coin::take(&mut liquidity_pool.type_a, coin_out_value, ctx); 110 | coin_out 111 | } 112 | 113 | 114 | public fun add_liquidity( liquidity_pool: &mut Pool, coin_a: Coin, coin_b: Coin, ctx: &mut TxContext ) { 115 | 116 | let coin_a_value = coin::value(&coin_a); 117 | let coin_b_value = coin::value(&coin_b); 118 | 119 | assert!( coin_a_value > 0 && coin_b_value > 0, EINVALID_AMOUNT ); 120 | 121 | let pool_a_value = balance::value(&liquidity_pool.type_a); 122 | let pool_b_value = balance::value(&liquidity_pool.type_b); 123 | 124 | if (pool_a_value > pool_b_value) { 125 | assert!( (pool_a_value / pool_b_value) == (pool_a_value + coin_a_value ) / (pool_b_value + coin_b_value), EBROKEN_INVARIANT ); 126 | } 127 | else { 128 | assert!( (pool_b_value / pool_a_value) == (pool_b_value + coin_b_value ) / (pool_a_value + coin_a_value), EBROKEN_INVARIANT ); 129 | }; 130 | 131 | coin::put(&mut liquidity_pool.type_a, coin_a); 132 | coin::put(&mut liquidity_pool.type_b, coin_b); 133 | 134 | let lp_bal = balance::increase_supply(&mut liquidity_pool.lp_supply, coin_a_value + coin_b_value); 135 | 136 | transfer::public_transfer(coin::from_balance(lp_bal, ctx), tx_context::sender(ctx)); 137 | } 138 | 139 | 140 | public fun remove_liquidity( liquidity_pool: &mut Pool, lp: Coin>, vec: vector, ctx: &mut TxContext ) { 141 | 142 | assert!( vector::length(&vec) == 2, EINVALID_PARAMS ); 143 | let coin_x_out = *vector::borrow(&vec, 0); 144 | let coin_y_out = *vector::borrow(&vec, 1); 145 | 146 | let lp_balance_value = coin::value>(&lp); 147 | 148 | assert!( lp_balance_value == coin_x_out + coin_y_out, EINVALID_AMOUNT ); 149 | assert!( balance::value(&liquidity_pool.type_a) > coin_x_out, EINVALID_AMOUNT ); 150 | assert!( balance::value(&liquidity_pool.type_b) > coin_y_out, EINVALID_AMOUNT ); 151 | 152 | balance::decrease_supply(&mut liquidity_pool.lp_supply, coin::into_balance(lp)); 153 | 154 | let coin_a = coin::take(&mut liquidity_pool.type_a, coin_x_out, ctx); 155 | let coin_b = coin::take(&mut liquidity_pool.type_b, coin_y_out, ctx); 156 | 157 | let sender = tx_context::sender(ctx); 158 | transfer::public_transfer(coin_a, sender); 159 | transfer::public_transfer(coin_b, sender); 160 | } 161 | 162 | 163 | public fun get_balance(liquidity_pool: &mut Pool): (u64, u64) { 164 | 165 | let pool_a_value = balance::value(&liquidity_pool.type_a); 166 | let pool_b_value = balance::value(&liquidity_pool.type_b); 167 | 168 | (pool_a_value, pool_b_value) 169 | } 170 | 171 | 172 | public fun get_a_price(liquidity_pool: &mut Pool): u64 { 173 | 174 | let pool_a_value = balance::value(&liquidity_pool.type_a); 175 | let pool_b_value = balance::value(&liquidity_pool.type_b); 176 | 177 | pool_b_value / pool_a_value 178 | } 179 | 180 | public fun get_b_price(liquidity_pool: &mut Pool): u64 { 181 | 182 | let pool_a_value = balance::value(&liquidity_pool.type_a); 183 | let pool_b_value = balance::value(&liquidity_pool.type_b); 184 | 185 | pool_a_value / pool_b_value 186 | } 187 | 188 | 189 | // --------------------------------------------------- 190 | // TESTS 191 | // --------------------------------------------------- 192 | 193 | #[test_only] 194 | use sui::test_scenario::{Self, next_tx}; 195 | 196 | #[test_only] 197 | use sui::coin::{mint_for_testing}; 198 | 199 | #[test_only] 200 | use std::debug; 201 | 202 | #[test_only] 203 | use challenge::osec::OSEC; 204 | 205 | #[test_only] 206 | use challenge::ctf::CTF; 207 | 208 | #[test] 209 | fun test_create_pool() { 210 | let investor = @0x111; 211 | let swapper = @0x222; 212 | let contract = @admin; 213 | 214 | let mut scenario_val = test_scenario::begin(@admin); 215 | let scenario = &mut scenario_val; 216 | 217 | next_tx(scenario, contract); 218 | { 219 | create_pool(test_scenario::ctx(scenario)); 220 | }; 221 | 222 | next_tx(scenario, contract); 223 | { 224 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 225 | let coin_a : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 226 | let coin_b : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 227 | initialize_pool(&mut liquidity_pool, coin_a, coin_b, test_scenario::ctx(scenario)); 228 | test_scenario::return_shared(liquidity_pool); 229 | }; 230 | 231 | next_tx(scenario, investor); 232 | { 233 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 234 | let coin_a : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 235 | let coin_b : Coin = mint_for_testing(100, test_scenario::ctx(scenario)); 236 | add_liquidity(&mut liquidity_pool, coin_a, coin_b, test_scenario::ctx(scenario)); 237 | test_scenario::return_shared(liquidity_pool); 238 | }; 239 | 240 | next_tx(scenario, swapper); 241 | { 242 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 243 | let coin_a : Coin = mint_for_testing(25, test_scenario::ctx(scenario)); 244 | let coin_out = swap_a_b(&mut liquidity_pool, coin_a, test_scenario::ctx(scenario)); 245 | transfer::public_transfer(coin_out, swapper); 246 | test_scenario::return_shared(liquidity_pool); 247 | }; 248 | 249 | next_tx(scenario, investor); 250 | { 251 | let mut liquidity_pool = test_scenario::take_shared>(scenario); 252 | let vec = vector[100, 100]; 253 | let lp = test_scenario::take_from_sender>>(scenario); 254 | remove_liquidity(&mut liquidity_pool, lp, vec, test_scenario::ctx(scenario)); 255 | test_scenario::return_shared(liquidity_pool); 256 | }; 257 | 258 | test_scenario::end(scenario_val); 259 | } 260 | } -------------------------------------------------------------------------------- /examples/challenge_example_3/framework-solve/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::TcpStream; 2 | use std::io::{Read, Write}; 3 | use std::str::from_utf8; 4 | use std::{error::Error, fs}; 5 | use std::env; 6 | use std::thread; 7 | use std::time::Duration; 8 | use serde_json; 9 | 10 | 11 | fn main() -> Result<(), Box> { 12 | let host = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 13 | let port = env::var("PORT").unwrap_or_else(|_| "31337".to_string()); 14 | 15 | println!(" - Connecting to server..."); 16 | let mut stream = TcpStream::connect(format!("{}:{}", host, port))?; 17 | 18 | // Set read timeout to prevent infinite hanging 19 | stream.set_read_timeout(Some(Duration::from_secs(10)))?; 20 | stream.set_write_timeout(Some(Duration::from_secs(10)))?; 21 | 22 | println!(" - Connected to server!"); 23 | 24 | // Read welcome message 25 | let mut welcome_buf = [0u8; 200]; 26 | stream.read(&mut welcome_buf)?; 27 | println!(" - {}", from_utf8(&welcome_buf)?.trim_matches('\0')); 28 | 29 | // Step 1: Upload Module 30 | println!("\n=== STEP 1: Upload Module ==="); 31 | 32 | // Read and display menu, then select option 1 33 | let mut menu_buf = [0u8; 500]; 34 | let n = stream.read(&mut menu_buf)?; 35 | println!(" - Menu received"); 36 | 37 | // Send option 1 38 | stream.write_all(b"1")?; 39 | stream.flush()?; 40 | println!(" - Selected: Upload Module"); 41 | 42 | // Read module name prompt and send module name 43 | thread::sleep(Duration::from_millis(100)); 44 | let mut name_prompt = [0u8; 100]; 45 | stream.read(&mut name_prompt)?; 46 | 47 | stream.write_all(b"solution")?; 48 | stream.flush()?; 49 | println!(" - Module name: solution"); 50 | 51 | // Read bytecode prompt 52 | thread::sleep(Duration::from_millis(100)); 53 | let mut bytecode_prompt = [0u8; 100]; 54 | stream.read(&mut bytecode_prompt)?; 55 | 56 | // Load and send module bytecode 57 | let mod_data = fs::read("./solve/build/solution/bytecode_modules/solution.mv")?; 58 | println!(" - Loaded solution module ({} bytes)", mod_data.len()); 59 | 60 | stream.write_all(&mod_data)?; 61 | stream.flush()?; 62 | 63 | // Read upload response 64 | thread::sleep(Duration::from_millis(200)); 65 | let mut upload_response = [0u8; 500]; 66 | let n = stream.read(&mut upload_response)?; 67 | println!(" - Upload result: {}", from_utf8(&upload_response[..n])?.trim()); 68 | 69 | // Step 2: View Object (Challenge state) 70 | println!("\n=== STEP 2: View Object ==="); 71 | 72 | // The menu is already displayed, just send option 2 73 | stream.write_all(b"2")?; 74 | stream.flush()?; 75 | println!(" - Selected: View Object"); 76 | 77 | // Wait for prompt and send first number 78 | thread::sleep(Duration::from_millis(100)); 79 | let mut prompt_buf = [0u8; 100]; 80 | stream.read(&mut prompt_buf)?; 81 | 82 | stream.write_all(b"1")?; 83 | stream.flush()?; 84 | println!(" - First number: 1"); 85 | 86 | // Wait for prompt and send second number 87 | thread::sleep(Duration::from_millis(100)); 88 | let mut prompt_buf2 = [0u8; 100]; 89 | stream.read(&mut prompt_buf2)?; 90 | 91 | stream.write_all(b"0")?; 92 | stream.flush()?; 93 | println!(" - Second number: 0"); 94 | 95 | // Now read the object response 96 | thread::sleep(Duration::from_millis(200)); 97 | let mut object_response = String::new(); 98 | let mut temp_buf = [0u8; 4096]; 99 | let mut total_read = 0; 100 | 101 | println!(" - DEBUG: Reading object response..."); 102 | 103 | // Read until we get the delimiter 104 | loop { 105 | match stream.read(&mut temp_buf) { 106 | Ok(0) => { 107 | println!(" - DEBUG: Connection closed after {} bytes", total_read); 108 | break; 109 | } 110 | Ok(n) => { 111 | total_read += n; 112 | let chunk = String::from_utf8_lossy(&temp_buf[..n]); 113 | object_response.push_str(&chunk); 114 | 115 | // Check if we've received the delimiter 116 | if object_response.contains("---END---") { 117 | println!(" - DEBUG: Found END delimiter after {} bytes", total_read); 118 | 119 | // Send acknowledgment 120 | stream.write_all(b"ACK")?; 121 | stream.flush()?; 122 | 123 | // Extract and display object data 124 | if let Some(start) = object_response.find("[OBJECT]") { 125 | if let Some(end) = object_response.find("\n---END---") { 126 | let object_data = &object_response[start..end]; 127 | println!(" - Challenge object state:"); 128 | println!("{}", object_data); 129 | 130 | // Parse JSON to verify it's valid 131 | if let Some(json_start) = object_data.find('{') { 132 | let json_str = &object_data[json_start..]; 133 | match serde_json::from_str::(json_str) { 134 | Ok(json) => { 135 | if let Some(contents) = json.get("contents") { 136 | println!(" - Object contents found: {} fields", contents.as_object().map(|o| o.len()).unwrap_or(0)); 137 | } 138 | } 139 | Err(e) => println!(" - Warning: Could not parse JSON: {}", e), 140 | } 141 | } 142 | } 143 | } 144 | 145 | // Now read and discard the menu that follows 146 | thread::sleep(Duration::from_millis(100)); 147 | let mut menu_buf = [0u8; 500]; 148 | match stream.read(&mut menu_buf) { 149 | Ok(n) => println!(" - DEBUG: Discarded {} bytes of menu", n), 150 | Err(_) => {}, 151 | } 152 | 153 | break; 154 | } 155 | } 156 | Err(e) => { 157 | println!(" - DEBUG: Read error after {} bytes: {}", total_read, e); 158 | return Err(e.into()); 159 | } 160 | } 161 | } 162 | 163 | // Step 3: Call Functions (Complete the challenge step by step) 164 | println!("\n=== STEP 3: Execute Solution Functions ==="); 165 | 166 | // Step 3a: Call solve_step_one (UserProgress is at 2,0) 167 | println!(" - Calling solve_step_one..."); 168 | call_function(&mut stream, "solution", "solve_step_one", vec![("object", "2", "0")])?; 169 | 170 | // Step 3b: Call solve_step_two 171 | println!(" - Calling solve_step_two..."); 172 | call_function(&mut stream, "solution", "solve_step_two", vec![("object", "2", "0"), ("object", "1", "0")])?; 173 | 174 | // Step 3c: Call solve_step_three 175 | println!(" - Calling solve_step_three..."); 176 | call_function(&mut stream, "solution", "solve_step_three", vec![("object", "2", "0"), ("object", "1", "0")])?; 177 | 178 | // Step 3d: Call complete_challenge 179 | println!(" - Calling complete_challenge..."); 180 | call_function(&mut stream, "solution", "complete_challenge", vec![("object", "2", "0"), ("object", "1", "0")])?; 181 | 182 | // Step 4: Get Flag 183 | println!("\n=== STEP 4: Get Flag ==="); 184 | 185 | // Add delay and send option 4 directly 186 | thread::sleep(Duration::from_millis(300)); 187 | stream.write_all(b"4")?; 188 | stream.flush()?; 189 | println!(" - Selected: Get Flag"); 190 | 191 | // Read flag response 192 | thread::sleep(Duration::from_millis(200)); 193 | let mut flag_response = [0u8; 500]; 194 | let n = stream.read(&mut flag_response)?; 195 | println!(" - Flag result: {}", from_utf8(&flag_response[..n])?.trim()); 196 | 197 | // Step 5: Exit 198 | println!("\n=== STEP 5: Exit ==="); 199 | 200 | // Read the menu first, then send option 5 201 | thread::sleep(Duration::from_millis(200)); 202 | let mut menu_buf = [0u8; 200]; 203 | stream.read(&mut menu_buf)?; 204 | 205 | stream.write_all(b"5")?; 206 | stream.flush()?; 207 | println!(" - Selected: Exit"); 208 | 209 | // Read goodbye message 210 | thread::sleep(Duration::from_millis(100)); 211 | let mut goodbye = [0u8; 100]; 212 | let n = stream.read(&mut goodbye)?; 213 | println!(" - Server response: {}", from_utf8(&goodbye[..n])?.trim()); 214 | 215 | println!(" - Challenge completed successfully!"); 216 | Ok(()) 217 | } 218 | 219 | fn call_function( 220 | stream: &mut TcpStream, 221 | module: &str, 222 | function: &str, 223 | params: Vec<(&str, &str, &str)> 224 | ) -> Result<(), Box> { 225 | // Add delay to avoid timing issues 226 | thread::sleep(Duration::from_millis(300)); 227 | 228 | // Send option 3 for Call Function 229 | stream.write_all(b"3")?; 230 | stream.flush()?; 231 | println!(" - Selected: Call Function"); 232 | 233 | // Read module name prompt and send module name 234 | thread::sleep(Duration::from_millis(100)); 235 | let mut prompt_buf = [0u8; 100]; 236 | stream.read(&mut prompt_buf)?; 237 | 238 | stream.write_all(module.as_bytes())?; 239 | stream.flush()?; 240 | println!(" - Module: {}", module); 241 | 242 | // Read function name prompt and send function name 243 | thread::sleep(Duration::from_millis(100)); 244 | let mut prompt_buf2 = [0u8; 100]; 245 | stream.read(&mut prompt_buf2)?; 246 | 247 | stream.write_all(function.as_bytes())?; 248 | stream.flush()?; 249 | println!(" - Function: {}", function); 250 | 251 | // Read parameter count prompt and send parameter count 252 | thread::sleep(Duration::from_millis(100)); 253 | let mut prompt_buf3 = [0u8; 100]; 254 | stream.read(&mut prompt_buf3)?; 255 | 256 | let param_count = params.len().to_string(); 257 | stream.write_all(param_count.as_bytes())?; 258 | stream.flush()?; 259 | println!(" - Parameters: {}", param_count); 260 | 261 | // Send each parameter 262 | for (i, (param_type, num1, num2)) in params.iter().enumerate() { 263 | println!(" - Parameter {}: {} ({}, {})", i + 1, param_type, num1, num2); 264 | 265 | // Read parameter type prompt and send parameter type 266 | thread::sleep(Duration::from_millis(100)); 267 | let mut type_prompt_buf = [0u8; 100]; 268 | stream.read(&mut type_prompt_buf)?; 269 | 270 | stream.write_all(param_type.as_bytes())?; 271 | stream.flush()?; 272 | 273 | if *param_type == "object" { 274 | // Read first number prompt and send first number 275 | thread::sleep(Duration::from_millis(100)); 276 | let mut num1_prompt_buf = [0u8; 100]; 277 | stream.read(&mut num1_prompt_buf)?; 278 | 279 | stream.write_all(num1.as_bytes())?; 280 | stream.flush()?; 281 | 282 | // Read second number prompt and send second number 283 | thread::sleep(Duration::from_millis(100)); 284 | let mut num2_prompt_buf = [0u8; 100]; 285 | stream.read(&mut num2_prompt_buf)?; 286 | 287 | stream.write_all(num2.as_bytes())?; 288 | stream.flush()?; 289 | } 290 | // Add handling for other parameter types if needed 291 | } 292 | 293 | // Read function result 294 | thread::sleep(Duration::from_millis(200)); 295 | let mut result = [0u8; 1000]; 296 | let n = stream.read(&mut result)?; 297 | let result_text = from_utf8(&result[..n])?.trim(); 298 | println!(" - Result: {}", result_text); 299 | 300 | // Read and discard the menu that follows 301 | thread::sleep(Duration::from_millis(100)); 302 | let mut menu_buf = [0u8; 500]; 303 | match stream.read(&mut menu_buf) { 304 | Ok(n) => println!(" - DEBUG: Discarded {} bytes of menu", n), 305 | Err(_) => {}, 306 | } 307 | 308 | Ok(()) 309 | } -------------------------------------------------------------------------------- /examples/challenge_example/framework/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::io::{Read, Write}; 5 | use std::mem::drop; 6 | use std::net::{TcpListener, TcpStream}; 7 | use std::path::Path; 8 | use std::str::FromStr; 9 | 10 | use serde_json::Value; 11 | 12 | use tokio; 13 | 14 | use move_transactional_test_runner::framework::{MaybeNamedCompiledModule, MoveTestAdapter}; 15 | use move_bytecode_source_map::{source_map::SourceMap, utils::source_map_from_file}; 16 | use move_binary_format::file_format::CompiledModule; 17 | use move_symbol_pool::Symbol; 18 | use move_core_types::{ 19 | account_address::AccountAddress, 20 | language_storage::{TypeTag, StructTag}}; 21 | 22 | use sui_types::Identifier; 23 | use sui_ctf_framework::{NumericalAddress, SuiTF}; 24 | use sui_transactional_test_runner::{args::SuiValue, test_adapter::FakeID}; 25 | 26 | macro_rules! handle_err { 27 | ($stream:expr, $msg:expr, $err:expr) => {{ 28 | let full = format!("[SERVER ERROR] {}: {}", $msg, $err); 29 | eprintln!("{}", full); 30 | let _ = $stream.write_all(full.as_bytes()); // ignore write failures 31 | drop($stream); // close socket 32 | return Err::<(), Box>(full.into()); 33 | }}; 34 | } 35 | 36 | async fn handle_client(mut stream: TcpStream) -> Result<(), Box> { 37 | // Initialize SuiTestAdapter 38 | let modules = vec!["router", "OtterLoan", "OtterSwap", "ctf", "osec", "merch_store"]; 39 | let sources = vec!["router", "otterloan", "otterswap", "ctf", "osec", "merchstore"]; 40 | let mut deployed_modules: Vec = Vec::new(); 41 | 42 | let named_addresses = vec![ 43 | ( 44 | "challenge".to_string(), 45 | NumericalAddress::parse_str( 46 | "0x0", 47 | )?, 48 | ), 49 | ( 50 | "solution".to_string(), 51 | NumericalAddress::parse_str( 52 | "0x0", 53 | )?, 54 | ), 55 | ( 56 | "admin".to_string(), 57 | NumericalAddress::parse_str( 58 | "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e", 59 | )?, 60 | ), 61 | ]; 62 | 63 | let mut suitf = match sui_ctf_framework::SuiTF::initialize( 64 | named_addresses, 65 | Some(vec!["challenger".to_string(), "solver".to_string()]), 66 | ).await { 67 | Ok(adapter) => adapter, 68 | Err(e) => handle_err!(stream, "SuiTF initialization failed", e), 69 | }; 70 | 71 | // Check Admin Account 72 | let object_output1 : Value = match suitf.view_object(FakeID::Enumerated(0, 0)).await { 73 | Ok(output) => { 74 | println!("[SERVER] Object Output: {:#?}", output); 75 | output.unwrap() 76 | } 77 | Err(e) => handle_err!(stream, "Error viewing object 0:0", e), 78 | }; 79 | 80 | let bytes_str = match object_output1.get("Contents") 81 | .and_then(|c| c.get("id")) 82 | .and_then(|id| id.get("id")) 83 | .and_then(|inner| inner.get("bytes")) 84 | .and_then(|bytes| bytes.as_str()) { 85 | Some(s) => s.to_owned(), 86 | None => handle_err!(stream, "Malformed JSON response for object bytes", "missing field"), 87 | }; 88 | 89 | println!("Objet Bytes: {}", bytes_str); 90 | 91 | let mut mncp_modules : Vec = Vec::new(); 92 | 93 | for (module, source) in modules.iter().zip(sources.iter()) { 94 | let mod_path = format!("./chall/build/challenge/bytecode_modules/{}.mv", module); 95 | let src_path = format!("./chall/build/challenge/debug_info/{}.json", module); 96 | let mod_bytes: Vec = match std::fs::read(mod_path) { 97 | Ok(data) => data, 98 | Err(e) => handle_err!(stream, format!("Failed to read {}", module), e), 99 | }; 100 | 101 | let module: CompiledModule = match CompiledModule::deserialize_with_defaults(&mod_bytes) { 102 | Ok(data) => data, 103 | Err(e) => { 104 | return Err(Box::new(e)) 105 | } 106 | }; 107 | let named_addr_opt: Option = Some(Symbol::from("challenge")); 108 | let source_map: Option = match source_map_from_file(Path::new(&src_path)) { 109 | Ok(data) => Some(data), 110 | Err(e) => handle_err!(stream, format!("Deserialization failed for {}", module.name()), e), 111 | }; 112 | 113 | mncp_modules.push( MaybeNamedCompiledModule { 114 | named_address: named_addr_opt, 115 | module: module, 116 | source_map: source_map, 117 | }); 118 | } 119 | 120 | // Publish Challenge Module 121 | let chall_dependencies: Vec = Vec::new(); 122 | let chall_addr = match suitf.publish_compiled_module( 123 | mncp_modules, 124 | chall_dependencies, 125 | Some(String::from("challenger")), 126 | ).await { 127 | Ok(addr) => addr, 128 | Err(e) => handle_err!(stream, "Challenge module publish failed", e), 129 | }; 130 | 131 | deployed_modules.push(chall_addr); 132 | println!("[SERVER] Module published at: {:?}", chall_addr); 133 | 134 | let mut solution_data = [0 as u8; 2000]; 135 | let _solution_size = match stream.read(&mut solution_data) { 136 | Ok(size) => { 137 | if size == 0 { 138 | handle_err!(stream, "No data read from stream", "size is zero"); 139 | } else{ 140 | size 141 | } 142 | } 143 | Err(e) => handle_err!(stream, "Failed to read solution data", e), 144 | }; 145 | 146 | // Send Challenge Address 147 | let mut output = String::new(); 148 | fmt::write( 149 | &mut output, 150 | format_args!( 151 | "[SERVER] Challenge modules published at: {}", 152 | chall_addr.to_string().as_str(), 153 | ), 154 | ) 155 | .unwrap(); 156 | stream.write_all(output.as_bytes())?; 157 | 158 | // Publish Solution Module 159 | let mut sol_dependencies: Vec = Vec::new(); 160 | sol_dependencies.push(String::from("challenge")); 161 | 162 | let mut mncp_solution : Vec = Vec::new(); 163 | let module : CompiledModule = match CompiledModule::deserialize_with_defaults(&solution_data.to_vec()) { 164 | Ok(m) => m, 165 | Err(e) => handle_err!(stream, "Solution deserialization failed", e), 166 | }; 167 | let named_addr_opt: Option = Some(Symbol::from("solution")); 168 | let source_map : Option = None; 169 | 170 | mncp_solution.push( MaybeNamedCompiledModule { 171 | named_address: named_addr_opt, 172 | module: module, 173 | source_map: source_map, 174 | }); 175 | 176 | let sol_addr = match suitf.publish_compiled_module( 177 | mncp_solution, 178 | sol_dependencies, 179 | Some(String::from("solver")), 180 | ).await { 181 | Ok(addr) => addr, 182 | Err(e) => handle_err!(stream, "Solution module publish failed", e), 183 | }; 184 | println!("[SERVER] Solution published at: {:?}", sol_addr); 185 | 186 | // Send Solution Address 187 | output = String::new(); 188 | fmt::write( 189 | &mut output, 190 | format_args!( 191 | "[SERVER] Solution published at {}", 192 | sol_addr.to_string().as_str() 193 | ), 194 | ) 195 | .unwrap(); 196 | stream.write_all(output.as_bytes())?; 197 | 198 | // Prepare Function Call Arguments 199 | let mut args_liq: Vec = Vec::new(); 200 | args_liq.push(SuiValue::Object(FakeID::Enumerated(2, 1), None)); 201 | args_liq.push(SuiValue::Object(FakeID::Enumerated(2, 5), None)); 202 | args_liq.push(SuiValue::Object(FakeID::Enumerated(2, 6), None)); 203 | 204 | let mut type_args : Vec = Vec::new(); 205 | type_args.push(TypeTag::Struct(Box::new(StructTag { 206 | address: chall_addr, 207 | module: Identifier::from_str("ctf").unwrap(), 208 | name: Identifier::from_str("CTF").unwrap(), 209 | type_params: Vec::new(), 210 | }))); 211 | type_args.push(TypeTag::Struct(Box::new(StructTag { 212 | address: chall_addr, 213 | module: Identifier::from_str("osec").unwrap(), 214 | name: Identifier::from_str("OSEC").unwrap(), 215 | type_params: Vec::new(), 216 | }))); 217 | 218 | // Call Add Liquidity Function 219 | let ret_val = match suitf.call_function( 220 | chall_addr, 221 | "OtterSwap", 222 | "initialize_pool", 223 | args_liq, 224 | type_args, 225 | Some("challenger".to_string()), 226 | ).await { 227 | Ok(output) => output, 228 | Err(e) => handle_err!(stream, "Calling initialize_pool failed", e), 229 | }; 230 | println!("[SERVER] Return value {:#?}", ret_val); 231 | println!(""); 232 | 233 | // Prepare Function Call Arguments 234 | let mut args_sol: Vec = Vec::new(); 235 | args_sol.push(SuiValue::Object(FakeID::Enumerated(2, 1), None)); 236 | args_sol.push(SuiValue::Object(FakeID::Enumerated(2, 2), None)); 237 | 238 | let mut type_args_sol : Vec = Vec::new(); 239 | type_args_sol.push(TypeTag::Struct(Box::new(StructTag { 240 | address: chall_addr, 241 | module: Identifier::from_str("ctf").unwrap(), 242 | name: Identifier::from_str("CTF").unwrap(), 243 | type_params: Vec::new(), 244 | }))); 245 | type_args_sol.push(TypeTag::Struct(Box::new(StructTag { 246 | address: chall_addr, 247 | module: Identifier::from_str("osec").unwrap(), 248 | name: Identifier::from_str("OSEC").unwrap(), 249 | type_params: Vec::new(), 250 | }))); 251 | 252 | // Call solve Function 253 | let ret_val = match suitf.call_function( 254 | sol_addr, 255 | "gringotts_solution", 256 | "solve", 257 | args_sol, 258 | type_args_sol, 259 | Some("solver".to_string()), 260 | ).await { 261 | Ok(output) => output, 262 | Err(e) => handle_err!(stream, "Calling solve failed", e), 263 | }; 264 | println!("[SERVER] Return value {:#?}", ret_val); 265 | println!(""); 266 | 267 | // Check Solution 268 | let mut args2: Vec = Vec::new(); 269 | args2.push(SuiValue::Object(FakeID::Enumerated(5, 0), None)); 270 | 271 | let type_args_valid : Vec = Vec::new(); 272 | 273 | // Validate Solution 274 | let _sol_ret = match suitf.call_function( 275 | chall_addr, 276 | "merch_store", 277 | "has_flag", 278 | args2, 279 | type_args_valid, 280 | Some("solver".to_string()), 281 | ).await { 282 | Ok(_output) => { 283 | println!("[SERVER] Correct Solution!"); 284 | println!(""); 285 | if let Ok(flag) = env::var("FLAG") { 286 | let message = format!("[SERVER] Congrats, flag: {}", flag); 287 | stream.write(message.as_bytes()).unwrap(); 288 | } else { 289 | stream.write("[SERVER] Flag not found, please contact admin".as_bytes()).unwrap(); 290 | } 291 | } 292 | Err(e) => handle_err!(stream, "Calling has_flag failed", e), 293 | }; 294 | 295 | Ok(()) 296 | } 297 | 298 | #[tokio::main] 299 | async fn main() -> Result<(), Box> { 300 | // Create Socket - Port 31337 301 | let listener = TcpListener::bind("0.0.0.0:31337")?; 302 | println!("[SERVER] Starting server at port 31337!"); 303 | 304 | let local = tokio::task::LocalSet::new(); 305 | 306 | // Wait For Incoming Solution 307 | for stream in listener.incoming() { 308 | match stream { 309 | Ok(stream) => { 310 | println!("[SERVER] New connection: {}", stream.peer_addr()?); 311 | let _ = local.run_until( async move { 312 | tokio::task::spawn_local( async { 313 | if let Err(e) = handle_client(stream).await { 314 | eprintln!("[SERVER] Connection Closed. Error: {}", e); 315 | } 316 | }).await.unwrap(); 317 | }).await; 318 | } 319 | Err(e) => { 320 | println!("[SERVER] Error: {}", e); 321 | } 322 | } 323 | } 324 | 325 | // Close Socket Server 326 | drop(listener); 327 | Ok(()) 328 | } 329 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, HashMap}; 2 | use std::path::Path; 3 | use std::io::Write; 4 | use std::sync::Arc; 5 | use std::fs::File; 6 | use std::error; 7 | 8 | use once_cell::sync::Lazy; 9 | use tempfile::NamedTempFile; 10 | use serde_json::Value; 11 | 12 | use sui_graphql_rpc::test_infra::cluster::SnapshotLagConfig; 13 | use sui_transactional_test_runner::{ 14 | args::{ 15 | SuiInitArgs, 16 | SuiPublishArgs, 17 | SuiRunArgs, 18 | SuiSubcommand, 19 | SuiValue, 20 | ViewObjectCommand, 21 | ProgrammableTransactionCommand 22 | }, 23 | test_adapter::{ 24 | FakeID, 25 | SuiTestAdapter, 26 | PRE_COMPILED 27 | } 28 | }; 29 | pub use sui_types::{ 30 | object::Object, 31 | MOVE_STDLIB_ADDRESS, 32 | SUI_FRAMEWORK_ADDRESS 33 | }; 34 | 35 | use move_core_types::parsing::{ 36 | address::ParsedAddress, 37 | values::ParsedValue, 38 | }; 39 | pub use move_compiler::{ 40 | diagnostics::report_diagnostics, 41 | shared::{NumberFormat, NumericalAddress, PackagePaths}, 42 | Flags, FullyCompiledProgram, construct_pre_compiled_lib 43 | }; 44 | use move_core_types::{ 45 | account_address::AccountAddress, 46 | identifier::{IdentStr, Identifier}, 47 | language_storage::{ModuleId, TypeTag}, 48 | u256::U256, 49 | }; 50 | use move_transactional_test_runner::{ 51 | framework::{MoveTestAdapter, MaybeNamedCompiledModule, store_modules}, 52 | tasks::{InitCommand, SyntaxChoice, TaskInput}, 53 | }; 54 | 55 | static NAMED_ADDRESSES: Lazy> = Lazy::new(|| { 56 | let mut map = move_stdlib::move_stdlib_named_addresses(); 57 | assert!(map.get("std").unwrap().into_inner() == MOVE_STDLIB_ADDRESS); 58 | 59 | map.insert( 60 | "sui".to_string(), 61 | NumericalAddress::new(SUI_FRAMEWORK_ADDRESS.into_bytes(), NumberFormat::Hex), 62 | ); 63 | map 64 | }); 65 | 66 | // Sui CTF framework environment 67 | pub struct SuiTF { 68 | adapter: SuiTestAdapter, 69 | account_map: HashMap, 70 | package_map: HashMap, 71 | } 72 | 73 | impl SuiTF { 74 | fn _get_precompiled( 75 | sui_files: &Path 76 | ) -> Result> { 77 | // Prepare paths for Sui framework and Move standard library sources 78 | let sui_sources = { 79 | let mut buf = sui_files.to_path_buf(); 80 | buf.push("Sui"); 81 | buf.to_string_lossy().to_string() 82 | }; 83 | let sui_deps = { 84 | let mut buf = sui_files.to_path_buf(); 85 | buf.push("MoveStdlib"); 86 | buf.to_string_lossy().to_string() 87 | }; 88 | let named_address_map = NAMED_ADDRESSES.clone(); 89 | 90 | // Compile the Sui framework and Move stdlib 91 | let fully_compiled_res_outer = construct_pre_compiled_lib( 92 | vec![PackagePaths { 93 | name: None, 94 | paths: vec![sui_sources, sui_deps], 95 | named_address_map, 96 | }], 97 | None, 98 | Flags::empty(), 99 | None, 100 | ); 101 | 102 | // Handle any outer error 103 | let fully_compiled_res_inner = match fully_compiled_res_outer { 104 | Ok(inner) => inner, 105 | Err(err) => { 106 | let msg = format!("Failed to compile Move frameworks: {}", err); 107 | return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, msg))); 108 | } 109 | }; 110 | 111 | // Handle compilation diagnostics 112 | match fully_compiled_res_inner { 113 | Err((_files, _diags)) => { 114 | eprintln!("[*] Sui framework failed to compile!"); 115 | // report_diagnostics(&files, diags); 116 | Err(Box::new(std::io::Error::new( 117 | std::io::ErrorKind::Other, 118 | "Failed to compile Sui Move framework" 119 | ))) 120 | } 121 | Ok(res) => { 122 | Ok(res) 123 | } 124 | } 125 | } 126 | 127 | pub async fn initialize<'a>( 128 | named_addresses: Vec<(String, NumericalAddress)>, 129 | accounts: Option>, 130 | ) -> Result> { 131 | // Initialize the SuiTestAdapter with optional accounts and default protocol version 132 | // let protocol_version = Some(ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown).version.as_u64()); 133 | let protocol_version = None; 134 | let snapshot_config = SnapshotLagConfig { 135 | snapshot_min_lag: 5, 136 | sleep_duration: 0, 137 | }; 138 | 139 | let command = ( 140 | InitCommand { 141 | named_addresses: named_addresses.clone() 142 | }, 143 | SuiInitArgs { 144 | accounts, 145 | protocol_version, 146 | max_gas: None, 147 | shared_object_deletion: None, 148 | simulator: true, 149 | custom_validator_account: false, 150 | reference_gas_price: None, 151 | default_gas_price: None, 152 | snapshot_config: snapshot_config, 153 | flavor: None, 154 | epochs_to_keep: None, 155 | data_ingestion_path: None, 156 | rest_api_url: None, 157 | } 158 | ); 159 | let name = "init".to_string(); 160 | let number = 0; 161 | let start_line = 1; 162 | let command_lines_stop = 1; 163 | let stop_line = 1; 164 | let data = None; 165 | let command_text = "init --addresses challenge=0x0 solution=0x0"; 166 | let task_text = "//#".to_owned() + &command_text.replace('\n', "\n//#"); 167 | 168 | let init_opt: Option> = Some(TaskInput { 169 | command, 170 | name, 171 | number, 172 | start_line, 173 | command_lines_stop, 174 | stop_line, 175 | data, 176 | task_text, 177 | }); 178 | 179 | let default_syntax = SyntaxChoice::Source; 180 | let fully_compiled_program_opt = Some(Arc::new(PRE_COMPILED.clone())); 181 | 182 | // Perform initialization (publishing frameworks and creating accounts) 183 | let (adapter, result_opt) = SuiTestAdapter::init( 184 | default_syntax, 185 | fully_compiled_program_opt, 186 | init_opt, 187 | Path::new("") 188 | ).await; 189 | 190 | println!("[*] Initialization Result: {:#?}", result_opt); 191 | println!("[*] Successfully Initialized"); 192 | 193 | let mut account_map = HashMap::new(); 194 | for (name, num_addr) in named_addresses.iter() { 195 | let addr: AccountAddress = num_addr.into_inner(); 196 | account_map.insert(addr, name.clone()); 197 | } 198 | 199 | let sui_tf = SuiTF { 200 | adapter, 201 | account_map, 202 | package_map: HashMap::new(), 203 | }; 204 | 205 | Ok(sui_tf) 206 | } 207 | 208 | pub async fn publish_compiled_module( 209 | &mut self, 210 | modules: Vec, 211 | module_dependencies: Vec, 212 | sender: Option 213 | ) -> Result> { 214 | if modules.is_empty() { 215 | return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "No modules to publish"))); 216 | } 217 | 218 | let gas_budget: Option = None; 219 | let extra = SuiPublishArgs { 220 | sender, 221 | upgradeable: true, 222 | dependencies: module_dependencies, 223 | gas_price: None 224 | }; 225 | 226 | // Attempt to publish the compiled modules to the Sui environment 227 | let result = self.adapter.publish_modules(modules, gas_budget, extra).await; 228 | let (output, published_modules) = match result { 229 | Ok(res) => res, 230 | Err(e) => { 231 | eprintln!("[!] Failed to publish modules: {:?}", e); 232 | return Err(e.into()); 233 | } 234 | }; 235 | 236 | if published_modules.is_empty() { 237 | return Err("No modules were published".into()); 238 | } 239 | 240 | // Store the published modules and retrieve the address 241 | let package_name = published_modules[0].named_address.clone().unwrap().as_str().to_string(); 242 | let published_address = published_modules[0].module.address_identifiers[0]; 243 | let default_syntax = SyntaxChoice::Source; 244 | let data = NamedTempFile::new().expect("Failed to create temp file for modules"); 245 | store_modules(&mut self.adapter, default_syntax, data, published_modules); 246 | 247 | println!("[*] Successfully published package '{}' at {:?}", package_name, published_address); 248 | println!("[*] Publish output: {:#?}\n", output.unwrap_or_else(|| "".to_string())); 249 | 250 | self.package_map.insert(package_name, published_address); 251 | 252 | Ok(published_address) 253 | } 254 | 255 | pub async fn call_function( 256 | &mut self, 257 | mod_addr: AccountAddress, 258 | mod_name: &str, 259 | fun_name: &str, 260 | args: Vec, 261 | type_args: Vec, 262 | signer: Option, 263 | ) -> Result, Box> { 264 | // Prepare module and function identifiers 265 | let module_id = ModuleId::new(mod_addr, Identifier::new(mod_name).map_err(|e| -> Box { e.into() })?); 266 | let function: &IdentStr = IdentStr::new(fun_name).map_err(|e| -> Box { e.into() })?; 267 | let signers: Vec = Vec::new(); 268 | let gas_budget: Option = None; 269 | let extra_args = SuiRunArgs { 270 | sender: signer, 271 | gas_price: None, 272 | summarize: false, 273 | }; 274 | 275 | // Call the Move function via the test adapter 276 | match self.adapter.call_function( 277 | &module_id, function, type_args, signers, args, gas_budget, extra_args, 278 | ).await { 279 | Ok((output, _return_values)) => { 280 | println!("[*] Successfully called {}", fun_name); 281 | println!("[*] Call output: {:#?}", output.clone().unwrap_or_else(|| "".to_string())); 282 | Ok(output) 283 | } 284 | Err(err) => { 285 | eprintln!("[!] Failed to call function: {:?}", err); 286 | Err(err.into()) 287 | } 288 | } 289 | } 290 | 291 | pub async fn view_object( 292 | &mut self, 293 | id: FakeID 294 | ) -> Result, Box> { 295 | // Construct the command to view an object by its ID 296 | let command_text = "run".to_string(); 297 | let task_text = "//#".to_owned() + &command_text.replace('\n', "\n//#"); 298 | let arg_view = TaskInput { 299 | command: SuiSubcommand::ViewObject(ViewObjectCommand { id }), 300 | name: "view-object".to_string(), 301 | number: 0, 302 | start_line: 1, 303 | command_lines_stop: 1, 304 | stop_line: 1, 305 | data: None, 306 | task_text, 307 | }; 308 | 309 | // Execute the view command 310 | match self.adapter.handle_subcommand(arg_view).await { 311 | Ok(out) => { 312 | println!("[*] Successfully viewed object {:#?}", id); 313 | if let Some(output_str) = out { 314 | let parsed_output = Self::parse_output(&output_str); 315 | Ok(Some(parsed_output)) 316 | } else { 317 | Ok(None) 318 | } 319 | } 320 | Err(err) => { 321 | eprintln!("[!] Failed to view object: {:?}", err); 322 | Err(err.into()) 323 | } 324 | } 325 | } 326 | fn parse_output( 327 | output: &str 328 | ) -> Value { 329 | let mut lines = output.lines(); 330 | let mut result = serde_json::Map::new(); 331 | 332 | while let Some(line) = lines.next() { 333 | if let Some((key, value)) = line.split_once(": ") { 334 | let key = key.trim().to_string(); 335 | let value = value.trim(); 336 | 337 | if value.ends_with('{') { 338 | let nested_object = Self::parse_nested_object(&mut lines, value); 339 | result.insert(key, nested_object); 340 | } else { 341 | result.insert(key, Value::String(value.to_string())); 342 | } 343 | } 344 | } 345 | 346 | Value::Object(result) 347 | } 348 | 349 | fn parse_nested_object( 350 | lines: &mut std::str::Lines, 351 | initial_value: &str 352 | ) -> Value { 353 | let mut nested_result = serde_json::Map::new(); 354 | let mut buffer = String::new(); 355 | 356 | if !initial_value.trim_end().ends_with('}') { 357 | buffer.push_str(initial_value); 358 | } 359 | 360 | while let Some(line) = lines.next() { 361 | buffer.push_str("\n"); 362 | buffer.push_str(line.trim()); 363 | 364 | if line.trim().ends_with('}') { 365 | break; 366 | } 367 | } 368 | 369 | let inner_content = buffer 370 | .trim_start_matches('{') 371 | .trim_end_matches('}') 372 | .trim(); 373 | 374 | let mut inner_lines = inner_content.lines(); 375 | 376 | while let Some(line) = inner_lines.next() { 377 | if let Some((key, value)) = line.split_once(": ") { 378 | let key = key.trim().to_string(); 379 | let value = value.trim(); 380 | 381 | if value.ends_with('{') { 382 | let nested_object = Self::parse_nested_object(&mut inner_lines, value); 383 | nested_result.insert(key, nested_object); 384 | } else { 385 | nested_result.insert(key, Value::String(value.to_string())); 386 | } 387 | } 388 | } 389 | 390 | Value::Object(nested_result) 391 | } 392 | 393 | pub async fn fund_account( 394 | &mut self, 395 | account_address: String, 396 | amount: u64, 397 | sender: AccountAddress 398 | ) -> Result<(), Box> { 399 | // Prepare inputs for a programmable transaction to fund an address 400 | let mut input = vec![]; 401 | input.push(ParsedValue::InferredNum(U256::from(amount))); 402 | input.push(ParsedValue::Address(ParsedAddress::Named(account_address.to_string()))); 403 | 404 | // Create a temporary Move script that splits and transfers coins 405 | let temp_file = NamedTempFile::new().map_err(|e| -> Box { e.into() })?; 406 | { 407 | let mut file = File::create(temp_file.path()).map_err(|e| -> Box { e.into() })?; 408 | let txn_script = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n//> SplitCoins(Gas, [Input(0)]);\n//> TransferObjects([Result(0)], Input(1))"; 409 | file.write_all(txn_script.as_bytes()).map_err(|e| -> Box { e.into() })?; 410 | file.flush().map_err(|e| -> Box { e.into() })?; 411 | } 412 | 413 | let command_text = "run".to_string(); 414 | let task_text = "//#".to_owned() + &command_text.replace('\n', "\n//#"); 415 | let arg_view = TaskInput { 416 | command: SuiSubcommand::ProgrammableTransaction(ProgrammableTransactionCommand { 417 | sender: Some(sender.to_string()), 418 | sponsor: None, 419 | gas_budget: Some(5_000_000_000), 420 | gas_price: Some(1000), 421 | gas_payment: None, 422 | dev_inspect: false, 423 | dry_run: false, 424 | inputs: input, 425 | }), 426 | name: "fund".to_string(), 427 | number: 0, 428 | start_line: 1, 429 | command_lines_stop: 1, 430 | stop_line: 1, 431 | data: Some(temp_file), 432 | task_text: task_text, 433 | }; 434 | 435 | // Execute the funding transaction 436 | match self.adapter.handle_subcommand(arg_view).await { 437 | Ok(out) => { 438 | println!("[*] Successfully funded account '{}' with {}", account_address, amount); 439 | println!("[*] Fund transaction output: {:#?}", out.unwrap_or_else(|| "".to_string())); 440 | Ok(()) 441 | } 442 | Err(err) => { 443 | eprintln!("[!] Failed to fund address: {:?}", err); 444 | Err(err.into()) 445 | } 446 | } 447 | } 448 | 449 | pub fn get_account_address( 450 | &self, 451 | account_name: &str 452 | ) -> Option { 453 | self.account_map.iter().find_map(|(&addr, name)| { 454 | if name == account_name { 455 | Some(addr) 456 | } else { 457 | None 458 | } 459 | }) 460 | } 461 | 462 | pub fn get_package_address( 463 | &self, 464 | package_name: &str 465 | ) -> Option { 466 | self.package_map.get(package_name).cloned() 467 | } 468 | } -------------------------------------------------------------------------------- /examples/challenge_example_3/framework/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::env; 3 | use std::error::Error; 4 | use std::io::{Read, Write}; 5 | use std::mem::drop; 6 | use std::net::{TcpListener, TcpStream}; 7 | use std::path::Path; 8 | use std::{thread, time::Duration}; 9 | 10 | use serde_json; 11 | use tokio; 12 | 13 | use move_transactional_test_runner::framework::MaybeNamedCompiledModule; 14 | use move_bytecode_source_map::{source_map::SourceMap, utils::source_map_from_file}; 15 | use move_binary_format::file_format::CompiledModule; 16 | use move_symbol_pool::Symbol; 17 | use move_core_types::{ 18 | account_address::AccountAddress, 19 | language_storage::TypeTag, 20 | runtime_value::MoveValue}; 21 | 22 | use sui_ctf_framework::NumericalAddress; 23 | use sui_transactional_test_runner::{args::SuiValue, test_adapter::FakeID}; 24 | 25 | macro_rules! handle_err { 26 | ($stream:expr, $msg:expr, $err:expr) => {{ 27 | let full = format!("[SERVER ERROR] {}: {}", $msg, $err); 28 | eprintln!("{}", full); 29 | let _ = $stream.write_all(full.as_bytes()); // ignore write failures 30 | let _ = $stream.write_all(b"\n[SERVER] Connection will be closed due to error.\n"); 31 | let _ = $stream.flush(); 32 | drop($stream); // close socket 33 | return Err::<(), Box>(full.into()); 34 | }}; 35 | } 36 | 37 | macro_rules! handle_input_error { 38 | ($stream:expr, $msg:expr) => {{ 39 | let err_msg = format!("[ERROR] {}\n", $msg); 40 | eprintln!("{}", err_msg); 41 | if let Err(e) = $stream.write_all(err_msg.as_bytes()) { 42 | eprintln!("[SERVER] Failed to send error message: {}", e); 43 | return Err(e.into()); 44 | } 45 | let _ = $stream.flush(); 46 | }}; 47 | } 48 | 49 | macro_rules! read_input_with_timeout { 50 | ($stream:expr, $buf:expr, $timeout_msg:expr) => {{ 51 | match $stream.read($buf) { 52 | Ok(n) if n > 0 => n, 53 | Ok(0) => { 54 | eprintln!("[SERVER] Client disconnected"); 55 | return Ok(()); 56 | } 57 | Err(e) => { 58 | eprintln!("[SERVER] Read error: {}", e); 59 | handle_input_error!($stream, $timeout_msg); 60 | return Err(e.into()); 61 | } 62 | } 63 | }}; 64 | } 65 | 66 | async fn handle_client(mut stream: TcpStream) -> Result<(), Box> { 67 | // Set connection timeouts to prevent hanging 68 | stream.set_read_timeout(Some(std::time::Duration::from_secs(300)))?; // 5 minutes 69 | stream.set_write_timeout(Some(std::time::Duration::from_secs(30)))?; // 30 seconds 70 | 71 | println!("[SERVER] Client connected with timeouts set"); 72 | 73 | // Initialize SuiTestAdapter 74 | let module_name = "interactive_ctf"; 75 | let mut deployed_modules: Vec = Vec::new(); 76 | let mut module_name_to_address: HashMap = HashMap::new(); 77 | 78 | let named_addresses = vec![ 79 | ( 80 | "challenge".to_string(), 81 | NumericalAddress::parse_str( 82 | "0x0", 83 | )?, 84 | ), 85 | ( 86 | "solution".to_string(), 87 | NumericalAddress::parse_str( 88 | "0x0", 89 | )?, 90 | ), 91 | ]; 92 | 93 | let mut suitf = match sui_ctf_framework::SuiTF::initialize( 94 | named_addresses, 95 | Some(vec!["challenger".to_string(), "solver".to_string()]), 96 | ).await { 97 | Ok(adapter) => adapter, 98 | Err(e) => handle_err!(stream, "SuiTF initialization failed", e), 99 | }; 100 | 101 | // Publish challenge module 102 | let mut mncp_modules : Vec = Vec::new(); 103 | let mod_path = format!("./chall/build/challenge/bytecode_modules/{}.mv", module_name); 104 | let src_path = format!("./chall/build/challenge/debug_info/{}.json", module_name); 105 | let mod_bytes: Vec = match std::fs::read(mod_path) { 106 | Ok(data) => data, 107 | Err(e) => handle_err!(stream, format!("Failed to read {}", module_name), e), 108 | }; 109 | 110 | let module: CompiledModule = match CompiledModule::deserialize_with_defaults(&mod_bytes) { 111 | Ok(data) => data, 112 | Err(e) => { 113 | return Err(Box::new(e)) 114 | } 115 | }; 116 | let named_addr_opt: Option = Some(Symbol::from("challenge")); 117 | let source_map: Option = match source_map_from_file(Path::new(&src_path)) { 118 | Ok(data) => Some(data), 119 | Err(e) => handle_err!(stream, format!("Deserialization failed for {}", module.name()), e), 120 | }; 121 | 122 | mncp_modules.push( MaybeNamedCompiledModule { 123 | named_address: named_addr_opt, 124 | module: module, 125 | source_map: source_map, 126 | }); 127 | 128 | let chall_dependencies: Vec = Vec::new(); 129 | let chall_addr = match suitf.publish_compiled_module( 130 | mncp_modules, 131 | chall_dependencies, 132 | Some(String::from("challenger")), 133 | ).await { 134 | Ok(addr) => addr, 135 | Err(e) => handle_err!(stream, "Challenge module publish failed", e), 136 | }; 137 | 138 | deployed_modules.push(chall_addr); 139 | module_name_to_address.insert("challenge".to_string(), chall_addr); 140 | println!("[SERVER] Module published at: {:?}", chall_addr); 141 | 142 | // Create UserProgress for the solver 143 | let mut create_progress_args: Vec = Vec::new(); // No args needed 144 | let create_progress_type_args: Vec = Vec::new(); 145 | 146 | match suitf.call_function( 147 | chall_addr, 148 | "interactive_ctf", 149 | "create_progress", 150 | create_progress_args, 151 | create_progress_type_args, 152 | Some("solver".to_string()), 153 | ).await { 154 | Ok(_) => { 155 | println!("[SERVER] UserProgress created for solver"); 156 | } 157 | Err(e) => handle_err!(stream, "Failed to create UserProgress", e), 158 | }; 159 | 160 | // Send welcome message 161 | let welcome_msg = "[SERVER] Welcome to the Interactive CTF Challenge!\n"; 162 | stream.write_all(welcome_msg.as_bytes())?; 163 | 164 | // Interactive menu loop 165 | loop { 166 | // Send menu 167 | let menu = "\n[MENU]\n1. Upload Module\n2. View Object\n3. Call Function\n4. Get Flag\n5. Exit\nSelect option: "; 168 | stream.write_all(menu.as_bytes())?; 169 | stream.flush()?; 170 | 171 | // Read user choice with timeout handling 172 | let mut choice_buf = [0u8; 10]; 173 | let n = read_input_with_timeout!(stream, &mut choice_buf, "Connection timed out waiting for menu choice"); 174 | 175 | let choice = String::from_utf8_lossy(&choice_buf[..n]).trim().to_string(); 176 | 177 | // Validate choice 178 | if choice.is_empty() { 179 | handle_input_error!(stream, "Empty input received. Please enter a valid option (1-5)"); 180 | continue; 181 | } 182 | 183 | match choice.as_str() { 184 | "1" => { 185 | // Upload Module 186 | stream.write_all(b"Enter module name for named address: ")?; 187 | stream.flush()?; 188 | 189 | let mut name_buf = [0u8; 100]; 190 | let n = read_input_with_timeout!(stream, &mut name_buf, "Timeout waiting for module name"); 191 | let module_name = String::from_utf8_lossy(&name_buf[..n]).trim().to_string(); 192 | 193 | if module_name.is_empty() { 194 | handle_input_error!(stream, "Module name cannot be empty"); 195 | continue; 196 | } 197 | 198 | if module_name.len() > 50 { 199 | handle_input_error!(stream, "Module name too long (max 50 characters)"); 200 | continue; 201 | } 202 | 203 | stream.write_all(b"Send module bytecode (max 2000 bytes): ")?; 204 | stream.flush()?; 205 | 206 | let mut module_data = [0u8; 2000]; 207 | let module_size = read_input_with_timeout!(stream, &mut module_data, "Timeout waiting for module bytecode"); 208 | 209 | if module_size == 0 { 210 | handle_input_error!(stream, "No module bytecode received"); 211 | continue; 212 | } 213 | 214 | // Publish module 215 | let mut sol_dependencies: Vec = Vec::new(); 216 | sol_dependencies.push(String::from("challenge")); 217 | 218 | let mut mncp_solution : Vec = Vec::new(); 219 | let module : CompiledModule = match CompiledModule::deserialize_with_defaults(&module_data[..module_size].to_vec()) { 220 | Ok(m) => m, 221 | Err(e) => { 222 | let err_msg = format!("[ERROR] Module deserialization failed: {}\n", e); 223 | stream.write_all(err_msg.as_bytes())?; 224 | continue; 225 | } 226 | }; 227 | let named_addr_opt: Option = Some(Symbol::from(module_name.as_str())); 228 | let source_map : Option = None; 229 | 230 | mncp_solution.push( MaybeNamedCompiledModule { 231 | named_address: named_addr_opt, 232 | module: module, 233 | source_map: source_map, 234 | }); 235 | 236 | match suitf.publish_compiled_module( 237 | mncp_solution, 238 | sol_dependencies, 239 | Some(String::from("solver")), 240 | ).await { 241 | Ok(addr) => { 242 | module_name_to_address.insert(module_name.clone(), addr); 243 | let success_msg = format!("[SUCCESS] Module '{}' published at: {}\n", module_name, addr); 244 | stream.write_all(success_msg.as_bytes())?; 245 | } 246 | Err(e) => { 247 | let err_msg = format!("[ERROR] Module publish failed: {}\n", e); 248 | stream.write_all(err_msg.as_bytes())?; 249 | } 250 | }; 251 | } 252 | "2" => { 253 | // View Object 254 | stream.write_all(b"Enter first number for object ID: ")?; 255 | stream.flush()?; 256 | 257 | let mut num1_buf = [0u8; 20]; 258 | let n = read_input_with_timeout!(stream, &mut num1_buf, "Timeout waiting for first object number"); 259 | let num1_str = String::from_utf8_lossy(&num1_buf[..n]).trim(); 260 | 261 | if num1_str.is_empty() { 262 | handle_input_error!(stream, "First number cannot be empty"); 263 | continue; 264 | } 265 | 266 | let num1: u64 = match num1_str.parse() { 267 | Ok(n) => n, 268 | Err(_) => { 269 | handle_input_error!(stream, format!("Invalid first number: '{}'. Please enter a valid integer", num1_str)); 270 | continue; 271 | } 272 | }; 273 | 274 | stream.write_all(b"Enter second number for object ID: ")?; 275 | stream.flush()?; 276 | 277 | let mut num2_buf = [0u8; 20]; 278 | let n = read_input_with_timeout!(stream, &mut num2_buf, "Timeout waiting for second object number"); 279 | let num2_str = String::from_utf8_lossy(&num2_buf[..n]).trim(); 280 | 281 | if num2_str.is_empty() { 282 | handle_input_error!(stream, "Second number cannot be empty"); 283 | continue; 284 | } 285 | 286 | let num2: u64 = match num2_str.parse() { 287 | Ok(n) => n, 288 | Err(_) => { 289 | handle_input_error!(stream, format!("Invalid second number: '{}'. Please enter a valid integer", num2_str)); 290 | continue; 291 | } 292 | }; 293 | 294 | // View object 295 | match suitf.view_object(FakeID::Enumerated(num1, num2)).await { 296 | Ok(Some(output)) => { 297 | println!("[SERVER] Object view returned data: {:#?}", output); 298 | let output_str = format!("[OBJECT] {}\n", serde_json::to_string_pretty(&output)?); 299 | println!("[SERVER] Sending object response: {}", output_str); 300 | stream.write_all(output_str.as_bytes())?; 301 | stream.write_all(b"\n---END---\n")?; // Add delimiter 302 | stream.flush()?; 303 | println!("[SERVER] Object response sent and flushed"); 304 | 305 | // Wait for client acknowledgment before continuing 306 | let mut ack_buf = [0u8; 10]; 307 | match stream.read(&mut ack_buf) { 308 | Ok(_) => println!("[SERVER] Client acknowledged object response"), 309 | Err(e) => println!("[SERVER] Warning: No client ack: {}", e), 310 | } 311 | } 312 | Ok(None) => { 313 | println!("[SERVER] Object view returned None"); 314 | stream.write_all(b"[OBJECT] No output\n")?; 315 | stream.flush()?; 316 | } 317 | Err(e) => { 318 | println!("[SERVER] Object view error: {:?}", e); 319 | let err_msg = format!("[ERROR] Failed to view object: {}\n", e); 320 | stream.write_all(err_msg.as_bytes())?; 321 | stream.flush()?; 322 | } 323 | } 324 | } 325 | "3" => { 326 | // Call Function 327 | stream.write_all(b"Enter module name: ")?; 328 | stream.flush()?; 329 | 330 | let mut mod_name_buf = [0u8; 100]; 331 | let n = read_input_with_timeout!(stream, &mut mod_name_buf, "Timeout waiting for module name"); 332 | let mod_name = String::from_utf8_lossy(&mod_name_buf[..n]).trim().to_string(); 333 | 334 | if mod_name.is_empty() { 335 | handle_input_error!(stream, "Module name cannot be empty"); 336 | continue; 337 | } 338 | 339 | let mod_addr = match module_name_to_address.get(&mod_name) { 340 | Some(addr) => *addr, 341 | None => { 342 | handle_input_error!(stream, format!("Module '{}' not found. Available modules: {}", mod_name, 343 | module_name_to_address.keys().collect::>().join(", "))); 344 | continue; 345 | } 346 | }; 347 | 348 | stream.write_all(b"Enter function name: ")?; 349 | stream.flush()?; 350 | 351 | let mut func_name_buf = [0u8; 100]; 352 | let n = read_input_with_timeout!(stream, &mut func_name_buf, "Timeout waiting for function name"); 353 | let func_name = String::from_utf8_lossy(&func_name_buf[..n]).trim().to_string(); 354 | 355 | if func_name.is_empty() { 356 | handle_input_error!(stream, "Function name cannot be empty"); 357 | continue; 358 | } 359 | 360 | stream.write_all(b"Enter number of parameters: ")?; 361 | stream.flush()?; 362 | 363 | let mut param_count_buf = [0u8; 10]; 364 | let n = read_input_with_timeout!(stream, &mut param_count_buf, "Timeout waiting for parameter count"); 365 | let param_count_str = String::from_utf8_lossy(¶m_count_buf[..n]).trim(); 366 | 367 | if param_count_str.is_empty() { 368 | handle_input_error!(stream, "Parameter count cannot be empty"); 369 | continue; 370 | } 371 | 372 | let param_count: usize = match param_count_str.parse() { 373 | Ok(n) if n <= 10 => n, // Reasonable limit 374 | Ok(n) => { 375 | handle_input_error!(stream, format!("Too many parameters: {}. Maximum allowed is 10", n)); 376 | continue; 377 | } 378 | Err(_) => { 379 | handle_input_error!(stream, format!("Invalid parameter count: '{}'. Please enter a valid number", param_count_str)); 380 | continue; 381 | } 382 | }; 383 | 384 | let mut args: Vec = Vec::new(); 385 | 386 | for i in 0..param_count { 387 | let param_msg = format!("Parameter {} - Enter type (number/list/object): ", i + 1); 388 | stream.write_all(param_msg.as_bytes())?; 389 | stream.flush()?; 390 | 391 | let mut type_buf = [0u8; 20]; 392 | let n = read_input_with_timeout!(stream, &mut type_buf, "Timeout waiting for parameter type"); 393 | let param_type = String::from_utf8_lossy(&type_buf[..n]).trim().to_string(); 394 | 395 | if param_type.is_empty() { 396 | handle_input_error!(stream, "Parameter type cannot be empty"); 397 | continue; 398 | } 399 | 400 | match param_type.as_str() { 401 | "number" => { 402 | stream.write_all(b"Enter number type (u8/u16/u32/u64): ")?; 403 | stream.flush()?; 404 | 405 | let mut num_type_buf = [0u8; 10]; 406 | let n = stream.read(&mut num_type_buf)?; 407 | let num_type = String::from_utf8_lossy(&num_type_buf[..n]).trim().to_string(); 408 | 409 | stream.write_all(b"Enter value: ")?; 410 | stream.flush()?; 411 | 412 | let mut value_buf = [0u8; 20]; 413 | let n = stream.read(&mut value_buf)?; 414 | let value: u64 = match String::from_utf8_lossy(&value_buf[..n]).trim().parse() { 415 | Ok(n) => n, 416 | Err(_) => { 417 | stream.write_all(b"[ERROR] Invalid value\n")?; 418 | continue; 419 | } 420 | }; 421 | 422 | match num_type.as_str() { 423 | "u8" => args.push(SuiValue::MoveValue(MoveValue::U8(value as u8))), 424 | "u16" => args.push(SuiValue::MoveValue(MoveValue::U16(value as u16))), 425 | "u32" => args.push(SuiValue::MoveValue(MoveValue::U32(value as u32))), 426 | "u64" => args.push(SuiValue::MoveValue(MoveValue::U64(value))), 427 | _ => { 428 | stream.write_all(b"[ERROR] Invalid number type\n")?; 429 | continue; 430 | } 431 | } 432 | } 433 | "list" => { 434 | stream.write_all(b"Enter list length: ")?; 435 | stream.flush()?; 436 | 437 | let mut len_buf = [0u8; 10]; 438 | let n = stream.read(&mut len_buf)?; 439 | let len: usize = match String::from_utf8_lossy(&len_buf[..n]).trim().parse() { 440 | Ok(n) => n, 441 | Err(_) => { 442 | stream.write_all(b"[ERROR] Invalid length\n")?; 443 | continue; 444 | } 445 | }; 446 | 447 | let mut list_values = Vec::new(); 448 | for j in 0..len { 449 | let elem_msg = format!("Enter element {} (u8): ", j); 450 | stream.write_all(elem_msg.as_bytes())?; 451 | stream.flush()?; 452 | 453 | let mut elem_buf = [0u8; 10]; 454 | let n = stream.read(&mut elem_buf)?; 455 | let elem: u8 = match String::from_utf8_lossy(&elem_buf[..n]).trim().parse() { 456 | Ok(n) => n, 457 | Err(_) => { 458 | stream.write_all(b"[ERROR] Invalid element\n")?; 459 | continue; 460 | } 461 | }; 462 | list_values.push(elem); 463 | } 464 | let move_values: Vec = list_values.into_iter().map(MoveValue::U8).collect(); 465 | args.push(SuiValue::MoveValue(MoveValue::Vector(move_values))); 466 | } 467 | "object" => { 468 | stream.write_all(b"Enter first number for object ID: ")?; 469 | stream.flush()?; 470 | 471 | let mut num1_buf = [0u8; 20]; 472 | let n = read_input_with_timeout!(stream, &mut num1_buf, "Timeout waiting for first object number"); 473 | let num1_str = String::from_utf8_lossy(&num1_buf[..n]).trim(); 474 | 475 | if num1_str.is_empty() { 476 | handle_input_error!(stream, "First object number cannot be empty"); 477 | continue; 478 | } 479 | 480 | let num1: u64 = match num1_str.parse() { 481 | Ok(n) => n, 482 | Err(_) => { 483 | handle_input_error!(stream, format!("Invalid first object number: '{}'", num1_str)); 484 | continue; 485 | } 486 | }; 487 | 488 | stream.write_all(b"Enter second number for object ID: ")?; 489 | stream.flush()?; 490 | 491 | let mut num2_buf = [0u8; 20]; 492 | let n = read_input_with_timeout!(stream, &mut num2_buf, "Timeout waiting for second object number"); 493 | let num2_str = String::from_utf8_lossy(&num2_buf[..n]).trim(); 494 | 495 | if num2_str.is_empty() { 496 | handle_input_error!(stream, "Second object number cannot be empty"); 497 | continue; 498 | } 499 | 500 | let num2: u64 = match num2_str.parse() { 501 | Ok(n) => n, 502 | Err(_) => { 503 | handle_input_error!(stream, format!("Invalid second object number: '{}'", num2_str)); 504 | continue; 505 | } 506 | }; 507 | 508 | args.push(SuiValue::Object(FakeID::Enumerated(num1, num2), None)); 509 | } 510 | _ => { 511 | handle_input_error!(stream, format!("Invalid parameter type: '{}'. Valid types are: number, list, object", param_type)); 512 | continue; 513 | } 514 | } 515 | } 516 | 517 | // Call function 518 | let type_args: Vec = Vec::new(); // Simplified for now 519 | 520 | // Determine the actual module name based on the address 521 | let actual_module_name = if mod_name == "challenge" { 522 | "interactive_ctf" 523 | } else { 524 | "solution" // Default for user modules 525 | }; 526 | 527 | match suitf.call_function( 528 | mod_addr, 529 | actual_module_name, 530 | &func_name, 531 | args, 532 | type_args, 533 | Some("solver".to_string()), 534 | ).await { 535 | Ok(Some(output)) => { 536 | let output_msg = format!("[SUCCESS] Function output: {}\n", output); 537 | stream.write_all(output_msg.as_bytes())?; 538 | } 539 | Ok(None) => { 540 | stream.write_all(b"[SUCCESS] Function executed (no output)\n")?; 541 | } 542 | Err(e) => { 543 | let err_msg = format!("[ERROR] Function call failed: {}\n", e); 544 | stream.write_all(err_msg.as_bytes())?; 545 | } 546 | } 547 | } 548 | "4" => { 549 | // Get Flag - check if challenge is solved 550 | let mut args: Vec = Vec::new(); 551 | args.push(SuiValue::Object(FakeID::Enumerated(1, 0), None)); // Challenge object 552 | 553 | let type_args: Vec = Vec::new(); 554 | 555 | match suitf.call_function( 556 | chall_addr, 557 | "interactive_ctf", 558 | "check_solution", 559 | args, 560 | type_args, 561 | Some("solver".to_string()), 562 | ).await { 563 | Ok(_) => { 564 | if let Ok(flag) = env::var("FLAG") { 565 | let message = format!("[FLAG] Congrats! Flag: {}\n", flag); 566 | stream.write_all(message.as_bytes())?; 567 | } else { 568 | stream.write_all(b"[FLAG] Flag not found, please contact admin\n")?; 569 | } 570 | } 571 | Err(e) => { 572 | let err_msg = format!("[ERROR] Solution check failed: {}\n", e); 573 | stream.write_all(err_msg.as_bytes())?; 574 | } 575 | } 576 | } 577 | "5" => { 578 | // Exit 579 | stream.write_all(b"[SERVER] Goodbye!\n")?; 580 | break; 581 | } 582 | _ => { 583 | handle_input_error!(stream, format!("Invalid option: '{}'. Please select 1-5", choice)); 584 | } 585 | } 586 | } 587 | 588 | Ok(()) 589 | } 590 | 591 | #[tokio::main] 592 | async fn main() -> Result<(), Box> { 593 | // Create Socket - Port 31337 594 | let listener = TcpListener::bind("0.0.0.0:31337")?; 595 | println!("[SERVER] Starting interactive server at port 31337!"); 596 | 597 | let local = tokio::task::LocalSet::new(); 598 | 599 | // Wait For Incoming Solution 600 | for stream in listener.incoming() { 601 | match stream { 602 | Ok(stream) => { 603 | println!("[SERVER] New connection: {}", stream.peer_addr()?); 604 | let _ = local.run_until( async move { 605 | tokio::task::spawn_local( async { 606 | if let Err(e) = handle_client(stream).await { 607 | eprintln!("[SERVER] Connection Closed. Error: {}", e); 608 | } 609 | }).await.unwrap(); 610 | }).await; 611 | } 612 | Err(e) => { 613 | println!("[SERVER] Error: {}", e); 614 | } 615 | } 616 | } 617 | 618 | // Close Socket Server 619 | drop(listener); 620 | Ok(()) 621 | } --------------------------------------------------------------------------------