├── .gitmodules ├── general-docs ├── go-testflags.png ├── VSCode-Replace-Text.png ├── VSCode-TemplateView.png ├── VSCode-TemplateView-Run-AndDebug.png ├── UnitTest-and-debug-SmartContract.md ├── WhyGo.md └── Compile-SmartContract.md ├── smartcontract └── rust │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ └── src │ ├── lib.rs │ └── math_test_functions.rs ├── .gitignore ├── tests ├── testutils │ ├── codesamples │ │ ├── constants.go │ │ ├── wallet_test.go │ │ ├── smart_contract_interactions_test.go │ │ ├── send_and_receive_tokens_test.go │ │ └── chain_samples_test.go │ ├── access_control.go │ ├── testconstants │ │ └── test_constants.go │ └── contractWasmFileFinder.go ├── smartcontract │ └── my_iota_smart_contract_test.go └── iota_sc_utils │ ├── math_test.go │ └── functions_access_test.go ├── .vscode └── settings.json ├── LICENSE ├── README.md └── go.mod /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /general-docs/go-testflags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoamancio/IOTA-SmartContracts/HEAD/general-docs/go-testflags.png -------------------------------------------------------------------------------- /general-docs/VSCode-Replace-Text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoamancio/IOTA-SmartContracts/HEAD/general-docs/VSCode-Replace-Text.png -------------------------------------------------------------------------------- /general-docs/VSCode-TemplateView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoamancio/IOTA-SmartContracts/HEAD/general-docs/VSCode-TemplateView.png -------------------------------------------------------------------------------- /general-docs/VSCode-TemplateView-Run-AndDebug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoamancio/IOTA-SmartContracts/HEAD/general-docs/VSCode-TemplateView-Run-AndDebug.png -------------------------------------------------------------------------------- /general-docs/UnitTest-and-debug-SmartContract.md: -------------------------------------------------------------------------------- 1 | ### Run and debug a smart contract's unit tests 2 | 3 | With the development environment set-up, it is a matter of opening the test files and clicking on "run test" or "debug test" on a unit test, like so: 4 | 5 | ![Run and Debug unit tests](VSCode-TemplateView-Run-AndDebug.png) 6 | 7 | -------------------------------------------------------------------------------- /general-docs/WhyGo.md: -------------------------------------------------------------------------------- 1 | #### Why are Go and Gcc required? 2 | Go and gcc are used by [Solo](https://github.com/iotaledger/wasp/tree/develop/packages/solo) to simulate the behavior of Wasp nodes. Unit tests for smart contracts are written in Go so Solo is accessible. The only other option would be to deploy the SCs under development to Wasp nodes, without the chance to test it locally. 3 | -------------------------------------------------------------------------------- /general-docs/Compile-SmartContract.md: -------------------------------------------------------------------------------- 1 | ### Compile the smart contract 2 | 3 | With VSCode open, in a terminal (In the menu : Terminal > New Terminal), compile the smart contract with: 4 | 5 | ### Smart contract in Rust 6 | ``` 7 | wasm-pack build smartcontract/rust/ 8 | ``` 9 | 10 | The compiled .wasm file is saved in `smartcontract/rust/pkg`. 11 | 12 | ### Smart contract in GO 13 | 14 | TBD 15 | -------------------------------------------------------------------------------- /smartcontract/rust/.gitignore: -------------------------------------------------------------------------------- 1 | # will have compiled files and executables 2 | debug/ 3 | target/ 4 | !**target/*readme.md 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | pkg/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ######### Go ignores 2 | 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | ######### Project ignores 20 | SmartContract/schemaGenerator* -------------------------------------------------------------------------------- /tests/testutils/codesamples/constants.go: -------------------------------------------------------------------------------- 1 | package codesamples 2 | 3 | import ( 4 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils/testconstants" 5 | "github.com/brunoamancio/NotSolo/constants" 6 | ) 7 | 8 | const initialWalletFunds = testconstants.InitialWalletFunds 9 | 10 | // Default amount of IOTAs to transfer in unit tests. 11 | const transferValueIotas = uint64(100) 12 | 13 | const iotaTokensConsumedByRequest = testconstants.IotaTokensConsumedByRequest 14 | 15 | const chainAddressBalanceInL1OnChainCreated = constants.DefaultChainStartingBalance + iotaTokensConsumedByRequest 16 | -------------------------------------------------------------------------------- /tests/testutils/access_control.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/iotaledger/hive.go/crypto/ed25519" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | // RequireAccess fails a unit test if unauthorized access is given to caller 11 | func RequireAccess(t *testing.T, ownerKeyPair *ed25519.KeyPair, callerKeyPair *ed25519.KeyPair, err error) { 12 | unauthozizedAcess := ownerKeyPair != nil && ownerKeyPair != callerKeyPair 13 | if unauthozizedAcess { 14 | require.Error(t, err, "Access given to unauthorized key pair") 15 | } else { 16 | require.NoError(t, err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/Cargo.lock": true, 4 | "**/LICENSE": true, 5 | "**/go.sum": true, 6 | "**/.git*": true, 7 | "*.md": true, 8 | 9 | "general-docs": true, 10 | "**/target/": true, 11 | "**/pkg/*.js": true, 12 | "**/pkg/*.ts": true, 13 | "**/pkg/package.json": true 14 | }, 15 | "go.testFlags": [ 16 | "-v", 17 | //"-buildmode=exe" // Uncomment this on windows. Is is needed due to an issue caused by gcc when compiling the unit tests 18 | ], 19 | "rust-analyzer.updates.askBeforeDownload": false, 20 | "rust-analyzer.linkedProjects": ["smartcontract/rust/Cargo.toml"], 21 | "editor.formatOnSave": true 22 | } -------------------------------------------------------------------------------- /smartcontract/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | edition = "2018" 4 | name = "my_iota_sc" 5 | description = "This is a smart contract for ISCP (IOTA Smart Contract Protocol)" 6 | license = "MIT" 7 | version = "0.0.1" 8 | authors = ["Author name "] 9 | repository = "https://github.com/user/project" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | [features] 15 | default = ["console_error_panic_hook"] 16 | 17 | [dependencies] 18 | console_error_panic_hook = { version = "0.1.6", optional = true } 19 | wee_alloc = { version = "0.4.5", optional = true } 20 | 21 | iota_sc_utils = { git = "https://github.com/brunoamancio/IOTA-SC-Utils", tag = "v0.8.94"} 22 | 23 | [dev-dependencies] 24 | wasm-bindgen-test = "0.3.13" -------------------------------------------------------------------------------- /tests/testutils/codesamples/wallet_test.go: -------------------------------------------------------------------------------- 1 | package codesamples 2 | 3 | import ( 4 | "testing" 5 | 6 | notsolo "github.com/brunoamancio/NotSolo" 7 | "github.com/iotaledger/wasp/packages/iscp/colored" 8 | ) 9 | 10 | // You can generate wallets (key pairs which are key pairs) to use them in your unit tests 11 | func Test_GenerateWalletWithDummyFunds(t *testing.T) { 12 | notSolo := notsolo.New(t) 13 | 14 | // Generates a key pair for a wallet and provides it with dummy funds. 15 | // The amount is defined in Wasp (constant testutil.RequestFundsAmount) and WaspConn plug-in (constant utxodb.RequestFundsAmount) 16 | walletKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 17 | 18 | // Uses the walletKeyPair to get the wallet address and ensures the balance is as expected 19 | notSolo.L1.RequireBalance(walletKeyPair, colored.IOTA, initialWalletFunds) 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Th3B0Y 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /smartcontract/rust/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Th3B0Y 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/smartcontract/my_iota_smart_contract_test.go: -------------------------------------------------------------------------------- 1 | package libtest 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils" 7 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils/testconstants" 8 | notsolo "github.com/brunoamancio/NotSolo" 9 | ) 10 | 11 | // ----------------------------------------------- // 12 | // See code samples in Tests/testutils/codesamples // 13 | // ----------------------------------------------- // 14 | 15 | func TestLib(t *testing.T) { 16 | contractWasmFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) // You can use if file is in SmartContract/rust/pkg 17 | 18 | // Name of the SC function to be requested - Defined in lib.rs > add_call > my_sc_function 19 | functionName := "my_sc_function" 20 | 21 | notSolo := notsolo.New(t) 22 | 23 | chainName := testconstants.ContractName + "Chain" 24 | chain := notSolo.Chain.NewChain(nil, chainName) 25 | 26 | // Uploads wasm of SC and deploys it into chain 27 | notSolo.Chain.DeployWasmContract(chain, nil, testconstants.ContractName, contractWasmFilePath) 28 | 29 | // Call contract 'my_iota_sc', function 'my_sc_function' 30 | notSolo.Request.MustPost(nil, chain, testconstants.ContractName, functionName) 31 | } 32 | -------------------------------------------------------------------------------- /tests/testutils/testconstants/test_constants.go: -------------------------------------------------------------------------------- 1 | package testconstants 2 | 3 | import ( 4 | "github.com/brunoamancio/NotSolo/constants" 5 | ) 6 | 7 | const ( 8 | // ContractName is defined in smartcontract/rust/Cargo.toml > package > name 9 | ContractName = "my_iota_sc" 10 | // Debug is used by Solo. 'true' for logging level 'debug', otherwise 'info' 11 | Debug = false 12 | // StackTrace is used by Solo. 'true' if stack trace must be printed in case of errors 13 | StackTrace = false 14 | 15 | /* INTERESTING FACT: Calls to a smart contract require 1 EXTRA iota token to be sent to the chain it is located in. 16 | It is colored with the chain's color and corresponds to the request. That is how the protocol locates the backlog of 17 | requests to be processed. Basically, it works as a flag. After the request is processed, the token is uncolored 18 | and sent to the chain owner's account in the chain. 19 | */ 20 | IotaTokensConsumedByRequest = constants.IotaTokensConsumedByRequest 21 | 22 | // Used to fund address in NewKeyPairWithFunds. // Defined in iotaledger/wasp/packages/testutiltestutil.RequestFundsAmount. 23 | InitialWalletFunds = uint64(1_000_000) 24 | 25 | // AccountsContractName sets the name of the Accounts contract, which is a root contract present in every chain 26 | AccountsContractName = "accounts" 27 | BlobContractName = "blob" 28 | ) 29 | -------------------------------------------------------------------------------- /tests/testutils/contractWasmFileFinder.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | // MustGetContractWasmFilePath ensures a given smart contract's wasm file exists 11 | func MustGetContractWasmFilePath(t *testing.T, contractName string) string { 12 | const targetPath = "../smartcontract/rust/pkg/" 13 | const parentDirectoryLevel = "../" 14 | 15 | filePath := targetPath + contractName + "_bg.wasm" 16 | 17 | contractWasmFilePath := parentDirectoryLevel + filePath 18 | exists, err := existsFilePath(contractWasmFilePath) 19 | require.NoError(t, err, "Error trying to find file: "+contractWasmFilePath) 20 | 21 | if !exists { 22 | contractWasmFilePath = parentDirectoryLevel + parentDirectoryLevel + filePath 23 | exists, err = existsFilePath(contractWasmFilePath) 24 | require.NoError(t, err, "Error trying to find file: "+contractWasmFilePath) 25 | } 26 | 27 | if !exists { 28 | contractWasmFilePath = parentDirectoryLevel + parentDirectoryLevel + parentDirectoryLevel + filePath 29 | exists, err = existsFilePath(contractWasmFilePath) 30 | require.NoError(t, err, "Error trying to find file: "+contractWasmFilePath) 31 | } 32 | 33 | require.True(t, exists, "File does not exist: "+contractWasmFilePath) 34 | return contractWasmFilePath 35 | } 36 | 37 | func existsFilePath(filePath string) (bool, error) { 38 | _, err := os.Stat(filePath) 39 | if err == nil { 40 | return true, nil 41 | } 42 | if os.IsNotExist(err) { 43 | return false, nil 44 | } 45 | return true, err 46 | } 47 | -------------------------------------------------------------------------------- /tests/iota_sc_utils/math_test.go: -------------------------------------------------------------------------------- 1 | package iotascutils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils" 7 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils/testconstants" 8 | notsolo "github.com/brunoamancio/NotSolo" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func Test_math_functions(t *testing.T) { 13 | notSolo := notsolo.New(t) 14 | chain := notSolo.Chain.NewChain(nil, "mathChain") 15 | 16 | // Deploy contract with chainOwnerKeyPair 17 | contractFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) 18 | notSolo.Chain.DeployWasmContract(chain, nil, testconstants.ContractName, contractFilePath) 19 | 20 | typesToTest := []string{"u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"} 21 | operationsToTest := []string{"add", "sub", "mul", "div"} 22 | 23 | // Map of SC functions and expect success (true if yes) 24 | functionsToTest := make(map[string]bool) 25 | for _, operationToTest := range operationsToTest { 26 | for _, typeToTest := range typesToTest { 27 | // Name of the SC function to be requested and credential required to access it 28 | functionsToTest[typeToTest+"_safe_"+operationToTest+"_no_overflow_function"] = true // expect success 29 | functionsToTest[typeToTest+"_safe_"+operationToTest+"_with_overflow_function"] = false // expect failure 30 | } 31 | } 32 | 33 | for functionName, expectSuccess := range functionsToTest { 34 | t.Run(functionName, func(t *testing.T) { 35 | 36 | // Calls SC function as chainOwner 37 | _, err := notSolo.Request.Post(nil, chain, testconstants.ContractName, functionName) 38 | 39 | // Verifies if SC function call is executed or fails 40 | if expectSuccess { 41 | require.NoError(t, err) 42 | } else { 43 | require.Error(t, err) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Template used to implement IOTA smart contracts 2 | 3 | A simple template used to start developing your own smart contracts for ISCP (IOTA Smart Contract Protocol) in Rust and write unit tests in Go. In order to develop using the environment set up by this template, you need to have good understanding of the Rust and Go languages. 4 | 5 | This is a repository for myself but I welcome anyone interested in playing around with the current state of development of the IOTA Smart Contract Protocol (ISCP). Feel free to contact me on IOTA Foundation's discord server under Th3B0Y#8380. 6 | 7 | ### Simple structure prepared to start with development right away 8 | This is how the templated file structure looks like: 9 | 10 | ![View of the template on VSCode](general-docs/VSCode-TemplateView.png) 11 | 12 | --- 13 | 14 | ### Requirements 15 | - [Go](https://golang.org/dl/) - [Why Go?](general-docs/WhyGo.md) 16 | - Gcc (or equivalent for Windows [(TDM-GCC)](https://jmeubank.github.io/tdm-gcc/)) - [Why Gcc?](general-docs/WhyGo.md) 17 | - [Rust](https://www.rust-lang.org/tools/install) 18 | - [Wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) 19 | - [Visual Studio Code](https://code.visualstudio.com/Download) (VSCode) 20 | - [Go Extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) 21 | - [Rust Analyzer](https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer) 22 | - [Better TOML](https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml) *Optional nice to have 23 | 24 | ### Set code up! 25 | - Use this template repository to create your own. 26 | - Open VSCode and a terminal in it (In the menu : Terminal > New Terminal) 27 | - In the terminal, clone your git repository: 28 | ``` 29 | git clone && cd 30 | ``` 31 | - In the terminal, open your git repository on VSCode 32 | ``` 33 | code -r . 34 | ``` 35 | 36 | - For Windows *only*, open file ".vscode/settings.json" and uncomment the setting "go.testFlags" entry "-buildmode=exe": 37 | ![Go.testFlags for Windows](general-docs/go-testflags.png) 38 | - On VSCode, open the replace functionality (Edit > Replace in files) 39 | - Replace `brunoamancio/IOTA-SmartContracts` for `/` [[How to]](general-docs/VSCode-Replace-Text.png) 40 | 41 | *Done!* Now you can write your smart contract in `smartcontract/rust`, [compile](general-docs/Compile-SmartContract.md) it, [run and debug](general-docs/UnitTest-and-debug-SmartContract.md) unit tests in `tests/smartcontract/my_iota_smart_contract_test.go`! 42 | 43 | --- 44 | [MIT License](LICENSE) 45 | -------------------------------------------------------------------------------- /tests/iota_sc_utils/functions_access_test.go: -------------------------------------------------------------------------------- 1 | package iotascutils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils" 7 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils/testconstants" 8 | notsolo "github.com/brunoamancio/NotSolo" 9 | "github.com/iotaledger/hive.go/crypto/ed25519" 10 | ) 11 | 12 | // Test ensures only the expected callers have access to functions 13 | func Test_access_to_functions(t *testing.T) { 14 | notSolo := notsolo.New(t) 15 | 16 | // Create chain with chainOwnerKeyPair 17 | chainOwnerKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 18 | chain := notSolo.Chain.NewChain(chainOwnerKeyPair, "test_access_chain") 19 | 20 | // Create contractCreator key pair and give it permission to deploy into chain 21 | contractOriginatorKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 22 | notSolo.Chain.GrantDeployPermission(chain, contractOriginatorKeyPair) 23 | 24 | // Deploy contract with contractOwnerKeyPair 25 | contractFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) 26 | notSolo.Chain.DeployWasmContract(chain, contractOriginatorKeyPair, testconstants.ContractName, contractFilePath) 27 | 28 | // Create random key pair 29 | randomKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 30 | 31 | // Map of SC functions and function owners 32 | functionsToTest := make(map[string]*ed25519.KeyPair) 33 | 34 | // Name of the SC function to be requested and credential required to access it 35 | functionsToTest["my_sc_function"] = nil // public function 36 | functionsToTest["contract_creator_only_function"] = contractOriginatorKeyPair // owner-only function 37 | functionsToTest["chain_owner_only_function"] = chainOwnerKeyPair // owner-only function 38 | 39 | for functionName, ownerKeyPair := range functionsToTest { 40 | t.Run(functionName, func(t *testing.T) { 41 | // Calls SC function as chainOwner 42 | _, err := notSolo.Request.Post(chainOwnerKeyPair, chain, testconstants.ContractName, functionName) 43 | 44 | // Verifies if access to SC function was given to caller. Fail if unauthorized access. 45 | testutils.RequireAccess(t, ownerKeyPair, chainOwnerKeyPair, err) 46 | 47 | // Calls SC function as contractCreator 48 | _, err = notSolo.Request.Post(contractOriginatorKeyPair, chain, testconstants.ContractName, functionName) 49 | // Verifies if access to SC function was given to caller. Fail if unauthorized access. 50 | testutils.RequireAccess(t, ownerKeyPair, contractOriginatorKeyPair, err) 51 | 52 | // Calls SC function as anyone else (random) 53 | _, err = notSolo.Request.Post(randomKeyPair, chain, testconstants.ContractName, functionName) 54 | // Verifies if access to SC function was given to caller. Fail if unauthorized access. 55 | testutils.RequireAccess(t, ownerKeyPair, randomKeyPair, err) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /smartcontract/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | use iota_sc_utils::wasmlib::*; 2 | use iota_sc_utils::*; 3 | pub mod math_test_functions; 4 | 5 | #[no_mangle] 6 | fn on_load() { 7 | let exports = ScExports::new(); 8 | 9 | // SC functions 10 | exports.add_func("my_sc_function", my_sc_function); 11 | exports.add_func("contract_creator_only_function", contract_creator_only_function); 12 | exports.add_func("chain_owner_only_function", chain_owner_only_function); 13 | 14 | // SC functions - Used to test the safe-mathmodule. 15 | math_test_functions::register_math_sc_functions(&exports); 16 | 17 | // SC Views 18 | exports.add_view("my_sc_view", my_sc_view); 19 | exports.add_view("view_my_boolean", view_my_boolean); 20 | } 21 | 22 | // Anyone can call this SC function 23 | fn my_sc_function(ctx: &ScFuncContext) { 24 | // Logs a text 25 | ctx.log("my_sc_function"); 26 | 27 | // Reads argument called "my_param" passed to SC function. Empty if not found. 28 | const MY_PARAM : &str = "my_param"; 29 | let param_value = params::get_string(MY_PARAM, ctx); 30 | if !param_value.is_empty() { 31 | ctx.log(¶m_value); 32 | } 33 | 34 | // Uses safe logic to perform an addition. The SC function panics on over/under flows. 35 | let my_i64 : i64 = 0; 36 | let my_i64_to_add : i64 = 1; 37 | // Alternative syntax: my_i64.safe_add(&my_i64_to_add, ctx); 38 | let _result = math::SafeMath::safe_add(&my_i64, &my_i64_to_add, ctx); 39 | } 40 | 41 | // Only the contract creator can call this SC-Function 42 | fn contract_creator_only_function(ctx: &ScFuncContext) { 43 | access::caller_must_be_contract_creator(ctx); 44 | ctx.log("Caller is the contract creator =)"); 45 | } 46 | 47 | // Only the chain owner can call this SC-Function 48 | fn chain_owner_only_function(ctx: &ScFuncContext){ 49 | access::caller_must_be_chain_owner(ctx); 50 | ctx.log("Caller is the chain owner =)"); 51 | } 52 | 53 | // Public view 54 | fn my_sc_view(ctx: &ScViewContext) { 55 | ctx.log("Hello world!"); 56 | } 57 | 58 | 59 | // Parameter passed into view_my_boolean 60 | const PARAM_HEXADECIMAL: &str = "hexadecimal"; 61 | // Parameter returned from view_my_boolean 62 | const PARAM_MATCHES_EXPECTED: &str = "matches_expected"; 63 | 64 | // Public view 65 | // | Type | Param name | Rust type | Go Type | Description 66 | // | Input | PARAM_HEXADECIMAL | Vec | []byte | bytes representation of a hexadecimal number 67 | // | Output | PARAM_MATCHES_EXPECTED | Vec | []byte | true if the input parameter PARAM_HEXADECIMAL is equal to 0x01ffc9a7 68 | fn view_my_boolean(ctx: &ScViewContext) { 69 | 70 | // Reads value passed as argument from the call to this view 71 | let input_param_hexadecimal : Vec = params::must_get_bytes(PARAM_HEXADECIMAL, ctx); 72 | let expected_hexadecimal : &[u8] = &[0x01, 0xff, 0xc9, 0xa7]; // this represents the hexadecimal value 0x01ffc9a7 73 | let is_match = input_param_hexadecimal == expected_hexadecimal; 74 | 75 | // Sets the value of PARAM_MATCHES_EXPECTED to is_match. It is now a property in return structure of this view. 76 | results::set_bool(PARAM_MATCHES_EXPECTED, is_match, ctx); 77 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/brunoamancio/IOTA-SmartContracts 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/brunoamancio/NotSolo v0.9.13 7 | github.com/iotaledger/hive.go v0.0.0-20210625103722-68b2cf52ef4e 8 | github.com/iotaledger/wasp v0.1.1-0.20211005075356-664297e327c9 9 | github.com/stretchr/testify v1.7.0 10 | ) 11 | 12 | require ( 13 | github.com/beorn7/perks v1.0.1 // indirect 14 | github.com/bytecodealliance/wasmtime-go v0.21.0 // indirect 15 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 16 | github.com/cockroachdb/errors v1.8.4 // indirect 17 | github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect 18 | github.com/cockroachdb/redact v1.0.8 // indirect 19 | github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 22 | github.com/fatih/structs v1.1.0 // indirect 23 | github.com/fsnotify/fsnotify v1.4.9 // indirect 24 | github.com/gogo/protobuf v1.3.2 // indirect 25 | github.com/golang/protobuf v1.5.2 // indirect 26 | github.com/iotaledger/goshimmer v0.7.5-0.20210811162925-25c827e8326a // indirect 27 | github.com/knadh/koanf v0.15.0 // indirect 28 | github.com/kr/pretty v0.2.1 // indirect 29 | github.com/kr/text v0.2.0 // indirect 30 | github.com/labstack/echo/v4 v4.2.1 // indirect 31 | github.com/labstack/gommon v0.3.0 // indirect 32 | github.com/linxGnu/grocksdb v1.6.35 // indirect 33 | github.com/mattn/go-colorable v0.1.8 // indirect 34 | github.com/mattn/go-isatty v0.0.12 // indirect 35 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 36 | github.com/mitchellh/mapstructure v1.2.2 // indirect 37 | github.com/mr-tron/base58 v1.2.0 // indirect 38 | github.com/oasisprotocol/ed25519 v0.0.0-20210201150809-58be049e4f78 // indirect 39 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect 40 | github.com/pkg/errors v0.9.1 // indirect 41 | github.com/pmezard/go-difflib v1.0.0 // indirect 42 | github.com/prometheus/client_golang v1.10.0 // indirect 43 | github.com/prometheus/client_model v0.2.0 // indirect 44 | github.com/prometheus/common v0.18.0 // indirect 45 | github.com/prometheus/procfs v0.6.0 // indirect 46 | github.com/sasha-s/go-deadlock v0.2.0 // indirect 47 | github.com/spf13/cast v1.3.1 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | github.com/valyala/bytebufferpool v1.0.0 // indirect 50 | github.com/valyala/fasttemplate v1.2.1 // indirect 51 | go.dedis.ch/fixbuf v1.0.3 // indirect 52 | go.dedis.ch/kyber/v3 v3.0.13 // indirect 53 | go.uber.org/atomic v1.7.0 // indirect 54 | go.uber.org/multierr v1.6.0 // indirect 55 | go.uber.org/zap v1.16.0 // indirect 56 | golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect 57 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect 58 | golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect 59 | golang.org/x/text v0.3.6 // indirect 60 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect 61 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 62 | google.golang.org/protobuf v1.26.0 // indirect 63 | gopkg.in/yaml.v2 v2.4.0 // indirect 64 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 65 | ) 66 | -------------------------------------------------------------------------------- /tests/testutils/codesamples/smart_contract_interactions_test.go: -------------------------------------------------------------------------------- 1 | package codesamples 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils" 8 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils/testconstants" 9 | notsolo "github.com/brunoamancio/NotSolo" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func Test_DeploySmartContractIntoChain(t *testing.T) { 14 | notSolo := notsolo.New(t) 15 | chain := notSolo.Chain.NewChain(nil, "myChain") 16 | 17 | // Uploads wasm of SC and deploys it into chain 18 | contractWasmFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) // You can use if file is in SmartContract/pkg 19 | notSolo.Chain.DeployWasmContract(chain, nil, testconstants.ContractName, contractWasmFilePath) 20 | 21 | // Loads contract information 22 | contract := notSolo.Chain.MustGetContractRecord(chain, testconstants.ContractName) 23 | require.Equal(t, testconstants.ContractName, contract.Name) 24 | } 25 | 26 | func Test_CallSmartContract_PostRequest(t *testing.T) { 27 | notSolo := notsolo.New(t) 28 | chain := notSolo.Chain.NewChain(nil, "myChain") 29 | 30 | // Uploads wasm of SC and deploys it into chain 31 | contractWasmFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) // You can use if file is in SmartContract/pkg 32 | notSolo.Chain.DeployWasmContract(chain, nil, testconstants.ContractName, contractWasmFilePath) 33 | 34 | // Loads contract information 35 | contract := notSolo.Chain.MustGetContractRecord(chain, testconstants.ContractName) 36 | require.Equal(t, testconstants.ContractName, contract.Name) 37 | 38 | // Defines which contract and function will be called by chain.PostRequest 39 | const functionName = "my_sc_function" 40 | 41 | // Calls contract my_iota_sc, function my_sc_function 42 | notSolo.Request.MustPost(nil, chain, testconstants.ContractName, functionName) 43 | } 44 | 45 | func Test_CallSmartContract_CallView(t *testing.T) { 46 | notSolo := notsolo.New(t) 47 | chain := notSolo.Chain.NewChain(nil, "myChain") 48 | 49 | // Uploads wasm of SC and deploys it into chain 50 | contractWasmFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) // You can use if file is in SmartContract/pkg 51 | notSolo.Chain.DeployWasmContract(chain, nil, testconstants.ContractName, contractWasmFilePath) 52 | 53 | // Loads contract information 54 | contract := notSolo.Chain.MustGetContractRecord(chain, testconstants.ContractName) 55 | require.Equal(t, testconstants.ContractName, contract.Name) 56 | 57 | // Defines which contract and function will be called by chain.PostRequest 58 | const functionName = "my_sc_view" 59 | 60 | // Calls contract my_iota_sc, function my_sc_view 61 | result := notSolo.Request.MustView(chain, testconstants.ContractName, functionName) 62 | require.NotNil(t, result) 63 | } 64 | 65 | func Test_ViewMyBoolean(t *testing.T) { 66 | contractWasmFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) // You can use if file is in SmartContract/pkg 67 | 68 | // Name of the SC view to be requested - Defined in lib.rs > add_view > view_my_boolean 69 | functionName := "view_my_boolean" 70 | 71 | notSolo := notsolo.New(t) 72 | 73 | chainName := testconstants.ContractName + "Chain" 74 | chain := notSolo.Chain.NewChain(nil, chainName) 75 | 76 | // Uploads wasm of SC and deploys it into chain 77 | notSolo.Chain.DeployWasmContract(chain, nil, testconstants.ContractName, contractWasmFilePath) 78 | 79 | // Map test case and expected value 80 | caseToTest := make(map[string]bool) 81 | 82 | // hexadecimal number and the expected result 83 | caseToTest["01ffc9a7"] = true 84 | caseToTest["00000000"] = false 85 | caseToTest["11111111"] = false 86 | 87 | for hexadecimalString, expectedResult := range caseToTest { 88 | 89 | hexadecimalAsBytes, err := hex.DecodeString(hexadecimalString) 90 | require.NoError(t, err) 91 | 92 | t.Run(functionName, func(t *testing.T) { 93 | // Input parameter to sc view 94 | const ParamHexadecimal = "hexadecimal" 95 | // Output parameter from sc view 96 | const MatchesExpected = "matches_expected" 97 | 98 | // Call contract 'my_iota_sc', function 'view_my_boolean' 99 | response := notSolo.Request.MustView(chain, testconstants.ContractName, functionName, ParamHexadecimal, hexadecimalAsBytes) 100 | 101 | // Get output parameter MatchesExpected 102 | viewMyBooleanResponse := notSolo.Data.MustGetBool(response[MatchesExpected]) 103 | 104 | // Ensure it matches in the put 105 | require.Equal(t, expectedResult, viewMyBooleanResponse) 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/testutils/codesamples/send_and_receive_tokens_test.go: -------------------------------------------------------------------------------- 1 | package codesamples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils" 7 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils/testconstants" 8 | notsolo "github.com/brunoamancio/NotSolo" 9 | "github.com/iotaledger/wasp/packages/iscp/colored" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | /// This is a sample of how to send tokens from the value tangle to a chain, keeping the ownership of the tokens on that chain 14 | func Test_SendTokensToChain_NoContractFees(t *testing.T) { 15 | notSolo := notsolo.New(t) 16 | 17 | require.GreaterOrEqual(t, initialWalletFunds, transferValueIotas) 18 | 19 | // Generates key pairs for sender and receiver wallets and provides both with dummy funds. 20 | senderWalletKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 21 | 22 | // Wallet balance in value tangle -> Before transfer 23 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds) 24 | 25 | // Generate a dummy chain NOT beloging to sender 26 | chain := notSolo.Chain.NewChain(nil, "myChain") 27 | 28 | // Wallet balance in chain -> Before transfer 29 | notSolo.Chain.RequireBalance(senderWalletKeyPair, chain, colored.IOTA, 0) 30 | 31 | // Transfer from sender's address in the value tangle to his account in 'chain' 32 | notSolo.L1.MustTransferToChainToSelf(senderWalletKeyPair, chain, colored.IOTA, transferValueIotas) 33 | 34 | // Wallet balances -> After transfer to chain 35 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds-transferValueIotas) 36 | notSolo.Chain.RequireBalance(senderWalletKeyPair, chain, colored.IOTA, transferValueIotas) 37 | } 38 | 39 | func Test_SendAndReceiveTokens_NoContractFees(t *testing.T) { 40 | notSolo := notsolo.New(t) 41 | 42 | require.GreaterOrEqual(t, initialWalletFunds, transferValueIotas) 43 | 44 | // Generates key pairs for sender and receiver wallets and provides both with dummy funds. 45 | senderWalletKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 46 | 47 | // Wallet balance in value tangle -> Before transfer 48 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds) 49 | 50 | // Generates key pairs for sender wallet. 51 | receiverWalletKeyPair := notSolo.KeyPair.NewKeyPair() 52 | notSolo.L1.RequireBalance(receiverWalletKeyPair, colored.IOTA, 0) 53 | 54 | // Generate a dummy chain NEITHER belonging to sender NOR receiver 55 | chain := notSolo.Chain.NewChain(nil, "myChain") 56 | 57 | // Transfer from sender's address in the value tangle to the receiver's account in 'chain' 58 | notSolo.L1.MustTransferToChain(senderWalletKeyPair, chain, colored.IOTA, transferValueIotas, receiverWalletKeyPair) 59 | 60 | // Wallet balances -> After transfer 61 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds-transferValueIotas) 62 | notSolo.Chain.RequireBalance(senderWalletKeyPair, chain, colored.IOTA, 0) 63 | 64 | notSolo.L1.RequireBalance(receiverWalletKeyPair, colored.IOTA, 0) 65 | notSolo.Chain.RequireBalance(receiverWalletKeyPair, chain, colored.IOTA, transferValueIotas) 66 | } 67 | 68 | func Test_SendTokensToChain_WithContractFees(t *testing.T) { 69 | notSolo := notsolo.New(t) 70 | 71 | require.GreaterOrEqual(t, initialWalletFunds, transferValueIotas) 72 | 73 | // Generates key pairs for sender and receiver wallets and provides both with dummy funds. 74 | senderWalletKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 75 | 76 | // Wallet balance in value tangle -> Before transfer 77 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds) 78 | 79 | // Generate a dummy chain NOT beloging to sender 80 | chain := notSolo.Chain.NewChain(nil, "myChain") 81 | contractName := "accounts" // This is a root contract, present in every chain 82 | 83 | // Check contract fees before changing them 84 | feeColor, ownerFee, validatorFee := chain.GetFeeInfo(contractName) 85 | require.Equal(t, colored.IOTA, feeColor) 86 | require.Equal(t, uint64(0), ownerFee) 87 | require.Equal(t, uint64(0), validatorFee) 88 | 89 | // Request to change the contract fee settings in 'chain' 90 | const newContractOwnerFee = uint64(2) 91 | notSolo.Chain.ChangeContractFees(chain.OriginatorKeyPair, chain, contractName, newContractOwnerFee) 92 | 93 | // Check contract fees after changing them 94 | feeColor, ownerFee, validatorFee = chain.GetFeeInfo(contractName) 95 | require.Equal(t, colored.IOTA, feeColor) 96 | require.Equal(t, newContractOwnerFee, ownerFee) 97 | require.Equal(t, uint64(0), validatorFee) 98 | 99 | // Wallet balance in chain -> Before transfer 100 | notSolo.Chain.RequireBalance(senderWalletKeyPair, chain, colored.IOTA, 0) 101 | 102 | // Transfer from sender's address in the value tangle to his account in 'chain' 103 | notSolo.L1.MustTransferToChainToSelf(senderWalletKeyPair, chain, colored.IOTA, transferValueIotas) 104 | 105 | // Balances -> After transfer to chain 106 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds-transferValueIotas) // Transfered tokens are debited from the value tangle 107 | notSolo.Chain.RequireBalance(senderWalletKeyPair, chain, colored.IOTA, transferValueIotas-newContractOwnerFee) // His tokens in the chain minus fees 108 | } 109 | 110 | func Test_SendTokensToContract_NoContractFees(t *testing.T) { 111 | notSolo := notsolo.New(t) 112 | 113 | // Generate a dummy chain NOT beloging to sender 114 | chain := notSolo.Chain.NewChain(nil, "myChain") 115 | 116 | // Uploads wasm of SC and deploys it into chain 117 | contractWasmFilePath := testutils.MustGetContractWasmFilePath(t, testconstants.ContractName) // You can use if file is in SmartContract/pkg 118 | notSolo.Chain.DeployWasmContract(chain, nil, testconstants.ContractName, contractWasmFilePath) 119 | 120 | // Generates key pairs for sender wallets, which will send iota tokens to the contract 121 | senderWalletKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 122 | 123 | // Balance in value tangle -> Before transfer 124 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds) 125 | require.GreaterOrEqual(t, initialWalletFunds, transferValueIotas) 126 | 127 | // Transfer from sender's address in the value tangle to the contract's account in 'chain' 128 | notSolo.L1.MustTransferToContract(senderWalletKeyPair, chain, colored.IOTA, transferValueIotas, testconstants.ContractName) 129 | 130 | // Balances -> After transfer 131 | notSolo.L1.RequireBalance(senderWalletKeyPair, colored.IOTA, initialWalletFunds-transferValueIotas) 132 | notSolo.Chain.RequireBalance(senderWalletKeyPair, chain, colored.IOTA, 0) 133 | 134 | // Contract's account balance in the chain -> After transfer 135 | notSolo.Chain.RequireContractBalance(chain, testconstants.ContractName, colored.IOTA, transferValueIotas) 136 | } 137 | -------------------------------------------------------------------------------- /smartcontract/rust/src/math_test_functions.rs: -------------------------------------------------------------------------------- 1 | use iota_sc_utils::wasmlib::{ScBaseContext, ScExports}; 2 | use iota_sc_utils::math::SafeMath; 3 | 4 | pub trait SafeMathScFunction { 5 | fn safe_add_no_overflow_function(ctx : &TContext); 6 | fn safe_add_with_overflow_function(ctx : &TContext); 7 | 8 | fn safe_sub_no_overflow_function(ctx : &TContext); 9 | fn safe_sub_with_overflow_function(ctx : &TContext); 10 | 11 | fn safe_mul_no_overflow_function(ctx : &TContext); 12 | fn safe_mul_with_overflow_function(ctx : &TContext); 13 | 14 | fn safe_div_no_overflow_function(ctx : &TContext); 15 | fn safe_div_with_overflow_function(ctx : &TContext); 16 | } 17 | 18 | macro_rules! add_impl { 19 | ($trait_name:ident, $t:ident) => { 20 | impl $trait_name for $t { 21 | 22 | // ------------------------------------- ADDITION ------------------------------------- 23 | 24 | fn safe_add_no_overflow_function(ctx : &TContext) { 25 | let value_to_calc : $t = 0; 26 | let max : $t = std::$t::MAX; 27 | // Do not cause an overflow 28 | let _result = max.safe_add(&value_to_calc, ctx); 29 | 30 | ctx.log(&format!("Function \"{}\" finished without an overflow)", stringify!(safe_add_no_overflow_function))); 31 | } 32 | 33 | fn safe_add_with_overflow_function(ctx : &TContext) { 34 | let value_to_calc : $t = 1; 35 | let max : $t = std::$t::MAX; 36 | // Cause an overflow 37 | let _result = max.safe_add(&value_to_calc, ctx); 38 | 39 | ctx.log(&format!("Function \"{}\" finished with an overflow)", stringify!(safe_add_with_overflow_function))); 40 | } 41 | 42 | // ------------------------------------- SUBTRACTION ------------------------------------- 43 | 44 | fn safe_sub_no_overflow_function(ctx : &TContext) { 45 | let value_to_calc : $t = 0; 46 | let min : $t = std::$t::MIN; 47 | // Do not cause an overflow 48 | let _result = min.safe_sub(&value_to_calc, ctx); 49 | 50 | ctx.log(&format!("Function \"{}\" finished without an overflow)", stringify!(safe_sub_no_overflow_function))); 51 | } 52 | 53 | fn safe_sub_with_overflow_function(ctx : &TContext) { 54 | let value_to_calc : $t = 1; 55 | let min : $t = std::$t::MIN; 56 | // Cause an overflow 57 | let _result = min.safe_sub(&value_to_calc, ctx); 58 | 59 | ctx.log(&format!("Function \"{}\" finished with an overflow)", stringify!(safe_sub_with_overflow_function))); 60 | } 61 | 62 | // ------------------------------------- MULTIPLICATION ------------------------------------- 63 | 64 | fn safe_mul_no_overflow_function(ctx : &TContext) { 65 | let value_to_calc : $t = 1; 66 | let max : $t = std::$t::MAX; 67 | // Do not cause an overflow 68 | let _result = max.safe_mul(&value_to_calc, ctx); 69 | 70 | ctx.log(&format!("Function \"{}\" finished without an overflow)", stringify!(safe_mul_no_overflow_function))); 71 | } 72 | 73 | fn safe_mul_with_overflow_function(ctx : &TContext) { 74 | let value_to_calc : $t = 2; 75 | let max : $t = std::$t::MAX; 76 | // Cause an overflow 77 | let _result = max.safe_mul(&value_to_calc, ctx); 78 | 79 | ctx.log(&format!("Function \"{}\" finished with an overflow)", stringify!(safe_mul_with_overflow_function))); 80 | } 81 | 82 | // ------------------------------------- DIVISION ------------------------------------- 83 | 84 | fn safe_div_no_overflow_function(ctx : &TContext) { 85 | let value_to_calc : $t = 1; 86 | let max : $t = std::$t::MAX; 87 | // Do not cause an overflow 88 | let _result = max.safe_div(&value_to_calc, ctx); 89 | 90 | ctx.log(&format!("Function \"{}\" finished without an overflow)", stringify!(safe_div_no_overflow_function))); 91 | } 92 | 93 | fn safe_div_with_overflow_function(ctx : &TContext) { 94 | let value_to_calc : $t = 0; 95 | let min : $t = std::$t::MIN; 96 | // Cause an overflow 97 | let _result = min.safe_div(&value_to_calc, ctx); 98 | 99 | ctx.log(&format!("Function \"{}\" finished with an overflow)", stringify!(safe_div_with_overflow_function))); 100 | } 101 | } 102 | }; 103 | 104 | ($trait_name:ident, $t1:ident, $t2:ident, $t3:ident, $t4:ident, $t5:ident) => { 105 | add_impl!($trait_name, $t1); 106 | add_impl!($trait_name, $t2); 107 | add_impl!($trait_name, $t3); 108 | add_impl!($trait_name, $t4); 109 | add_impl!($trait_name, $t5); 110 | }; 111 | } 112 | 113 | add_impl!(SafeMathScFunction, u8, u16, u32, u64, usize); 114 | add_impl!(SafeMathScFunction, i8, i16, i32, i64, isize); 115 | 116 | macro_rules! register_func { 117 | ($sc_exports:tt, $t:ident) => { 118 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_add_no_overflow_function)), $t::safe_add_no_overflow_function); 119 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_add_with_overflow_function)), $t::safe_add_with_overflow_function); 120 | 121 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_sub_no_overflow_function)), $t::safe_sub_no_overflow_function); 122 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_sub_with_overflow_function)), $t::safe_sub_with_overflow_function); 123 | 124 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_mul_no_overflow_function)), $t::safe_mul_no_overflow_function); 125 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_mul_with_overflow_function)), $t::safe_mul_with_overflow_function); 126 | 127 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_div_no_overflow_function)), $t::safe_div_no_overflow_function); 128 | $sc_exports.add_func(&format!("{}_{}", stringify!($t), stringify!(safe_div_with_overflow_function)), $t::safe_div_with_overflow_function); 129 | }; 130 | 131 | ($sc_exports:tt, $t1:ident, $t2:ident, $t3:ident, $t4:ident, $t5:ident) => { 132 | register_func!($sc_exports, $t1); 133 | register_func!($sc_exports, $t2); 134 | register_func!($sc_exports, $t3); 135 | register_func!($sc_exports, $t4); 136 | register_func!($sc_exports, $t5); 137 | }; 138 | } 139 | 140 | // Every registration is a call to sc_exports.add_func for all safe math functions for the specified type. 141 | // This allows testing the SCFunctions from the unit tests on Solo 142 | pub fn register_math_sc_functions(sc_exports : &ScExports){ 143 | register_func!(sc_exports, u8, u16, u32, u64, usize); 144 | register_func!(sc_exports, i8, i16, i32, i64, isize); 145 | } -------------------------------------------------------------------------------- /tests/testutils/codesamples/chain_samples_test.go: -------------------------------------------------------------------------------- 1 | package codesamples 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brunoamancio/IOTA-SmartContracts/tests/testutils/testconstants" 7 | notsolo "github.com/brunoamancio/NotSolo" 8 | "github.com/brunoamancio/NotSolo/constants" 9 | "github.com/iotaledger/wasp/packages/iscp/colored" 10 | "github.com/iotaledger/wasp/packages/vm/core/blob" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func Test_CreateChain_chainCreatorSpecified(t *testing.T) { 15 | notSolo := notsolo.New(t) 16 | 17 | // Create a key pair with dummy tokens in it. 18 | chainOriginatorsKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 19 | 20 | // Balance in value tangle before chain is created 21 | notSolo.L1.RequireBalance(chainOriginatorsKeyPair, colored.IOTA, initialWalletFunds) 22 | 23 | // Create a chain where chainOriginatorsKeyPair is the owner. 24 | chain := notSolo.Chain.NewChain(chainOriginatorsKeyPair, "myChain") 25 | 26 | // IMPORTANT: When a chain is created >>> USING SOLO <<<, a default amount of IOTA is sent to ChainID in L1 27 | // Another IOTA is consumed by the request and also sent to ChainID in L1 28 | expectedChainIdBalance := constants.DefaultChainStartingBalance + constants.IotaTokensConsumedByRequest 29 | notSolo.L1.RequireAddressBalance(chain.ChainID.AsAddress(), colored.IOTA, expectedChainIdBalance) 30 | 31 | // IMPORTANT: Originator has no balance in the chain 32 | notSolo.Chain.RequireBalance(chainOriginatorsKeyPair, chain, colored.IOTA, 0) 33 | 34 | // IMPORTANT: Originator has initial balance - the amount transfered from L1 35 | notSolo.L1.RequireBalance(chainOriginatorsKeyPair, colored.IOTA, initialWalletFunds-expectedChainIdBalance) 36 | } 37 | 38 | // Sample of how to create chain without specifying a chainOriginator. 39 | // A dummy chain originator is created in the background (by NewChain). 40 | func Test_CreateChain_NoChainCreatorSpecified(t *testing.T) { 41 | notSolo := notsolo.New(t) 42 | 43 | // Create a chain where chainOriginatorsKeyPair is the owner. 44 | chain := notSolo.Chain.NewChain(nil, "myChain") 45 | 46 | // IMPORTANT: When a chain is created >>> USING SOLO <<<, a default amount of IOTA is sent to ChainID in L1 47 | // Another IOTA is consumed by the request and also sent to ChainID in L1 48 | notSolo.L1.RequireAddressBalance(chain.ChainID.AsAddress(), colored.IOTA, chainAddressBalanceInL1OnChainCreated) 49 | 50 | // IMPORTANT: Originator has no balance in the chain 51 | notSolo.Chain.RequireBalance(chain.OriginatorKeyPair, chain, colored.IOTA, 0) 52 | 53 | // IMPORTANT: Originator has initial balance - the amount transfered from L1 54 | notSolo.L1.RequireBalance(chain.OriginatorKeyPair, colored.IOTA, initialWalletFunds-chainAddressBalanceInL1OnChainCreated) 55 | } 56 | 57 | func Test_SetChainFees(t *testing.T) { 58 | notSolo := notsolo.New(t) 59 | 60 | // Generate a dummy chain beloging to chain.OriginatorKeyPair 61 | chain := notSolo.Chain.NewChain(nil, "myChain") 62 | 63 | // Initial chain fees 64 | feeColor, ownerFee, validatorFee := chain.GetFeeInfo(testconstants.AccountsContractName) 65 | require.Equal(t, colored.IOTA, feeColor) 66 | require.Equal(t, uint64(0), ownerFee) 67 | require.Equal(t, uint64(0), validatorFee) 68 | 69 | // Request to chain - change contract owner fee settings 70 | const newChainOwnerFee = uint64(1000) 71 | notSolo.Chain.ChangeContractFees(chain.OriginatorKeyPair, chain, testconstants.AccountsContractName, newChainOwnerFee) 72 | 73 | // Request to chain - change validator fee settings 74 | const newValidatorFee = uint64(200) 75 | notSolo.Chain.ChangeValidatorFees(chain.OriginatorKeyPair, chain, testconstants.AccountsContractName, newValidatorFee) 76 | 77 | // Chain fees after change 78 | feeColor, ownerFee, validatorFee = chain.GetFeeInfo(testconstants.AccountsContractName) 79 | require.Equal(t, colored.IOTA, feeColor) 80 | require.Equal(t, newChainOwnerFee, ownerFee) 81 | require.Equal(t, newValidatorFee, validatorFee) 82 | } 83 | 84 | func Test_SetChainFees_TestCharge(t *testing.T) { 85 | notSolo := notsolo.New(t) 86 | 87 | // Generate a KeyPair for the chain originator 88 | chainOriginatorKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 89 | notSolo.L1.RequireBalance(chainOriginatorKeyPair, colored.IOTA, initialWalletFunds) 90 | 91 | // Generate a KeyPair for a wallet using the chain (will be charged for it) 92 | userKeyPair := notSolo.KeyPair.NewKeyPairWithFunds() 93 | notSolo.L1.RequireBalance(userKeyPair, colored.IOTA, initialWalletFunds) 94 | 95 | // Generate a dummy chain beloging to the chain originator 96 | chain := notSolo.Chain.NewChain(chainOriginatorKeyPair, "myChain") 97 | // Request cost is credited to chain's agentID: 'iotaTokensConsumedByRequest' 98 | expectedChainBalance := uint64(iotaTokensConsumedByRequest) 99 | notSolo.Chain.RequireChainBalance(chain, colored.IOTA, expectedChainBalance) 100 | 101 | chainOriginatorBalanceInL1AfterChainIsCreated := uint64(initialWalletFunds) - chainAddressBalanceInL1OnChainCreated 102 | chainOriginatorBalanceInChainAfterChainIsCreated := uint64(0) 103 | notSolo.L1.RequireBalance(chainOriginatorKeyPair, colored.IOTA, chainOriginatorBalanceInL1AfterChainIsCreated) 104 | notSolo.Chain.RequireBalance(chainOriginatorKeyPair, chain, colored.IOTA, chainOriginatorBalanceInChainAfterChainIsCreated) 105 | 106 | // Initial chain fees 107 | feeColor, ownerFee, validatorFee := chain.GetFeeInfo(testconstants.BlobContractName) 108 | require.Equal(t, colored.IOTA, feeColor) 109 | require.Equal(t, uint64(0), ownerFee) 110 | require.Equal(t, uint64(0), validatorFee) 111 | 112 | // Request to chain change fee settings 113 | const newOwnerFee = uint64(100) 114 | notSolo.Chain.ChangeContractFees(chainOriginatorKeyPair, chain, testconstants.BlobContractName, newOwnerFee) 115 | // IMPORTANT: calls to change contracts must send at least 1 token with the request (NotSolo sends "iotaTokensConsumedByRequest") 116 | chainOriginatorBalanceInL1AfterFeeIsChanged := chainOriginatorBalanceInL1AfterChainIsCreated - iotaTokensConsumedByRequest 117 | // Request cost is credited to chain's agentID: 'iotaTokensConsumedByRequest' 118 | expectedChainBalance += iotaTokensConsumedByRequest 119 | notSolo.L1.RequireBalance(chainOriginatorKeyPair, colored.IOTA, chainOriginatorBalanceInL1AfterFeeIsChanged) 120 | notSolo.Chain.RequireBalance(chainOriginatorKeyPair, chain, colored.IOTA, chainOriginatorBalanceInChainAfterChainIsCreated) 121 | 122 | notSolo.Chain.RequireChainBalance(chain, colored.IOTA, expectedChainBalance) 123 | 124 | // Chain fees after change 125 | feeColor, ownerFee, validatorFee = chain.GetFeeInfo(testconstants.BlobContractName) 126 | require.Equal(t, colored.IOTA, feeColor) 127 | require.Equal(t, newOwnerFee, ownerFee) 128 | require.Equal(t, uint64(0), validatorFee) 129 | 130 | // User sends a request to the contract (which needs to pay the fee) 131 | _, err := chain.UploadBlob(userKeyPair, 132 | blob.VarFieldVMType, "dummyType", 133 | blob.VarFieldProgramBinary, "dummyBinary", 134 | ) 135 | require.NoError(t, err) 136 | 137 | // Fee is charged from the user's wallet 138 | notSolo.L1.RequireBalance(userKeyPair, colored.IOTA, initialWalletFunds-ownerFee) // His tokens minus fees 139 | notSolo.Chain.RequireBalance(userKeyPair, chain, colored.IOTA, 0) 140 | 141 | // Owner fee is credited to chain's agentID (upload request does not charge 'iotaTokensConsumedByRequest') 142 | expectedChainBalance += ownerFee 143 | notSolo.Chain.RequireChainBalance(chain, colored.IOTA, expectedChainBalance) 144 | 145 | // No change in the chain owner addresses neither in the chain nor in L1 146 | notSolo.L1.RequireBalance(chainOriginatorKeyPair, colored.IOTA, chainOriginatorBalanceInL1AfterFeeIsChanged) 147 | notSolo.Chain.RequireBalance(chainOriginatorKeyPair, chain, colored.IOTA, chainOriginatorBalanceInChainAfterChainIsCreated) 148 | 149 | // Chain originator harvests fees from chain to his account in the chain 150 | notSolo.Chain.Harvest(chain, colored.IOTA, newOwnerFee) 151 | // Request cost is credited to and hardvested amount is debited from chain's agentID 152 | expectedChainBalance = expectedChainBalance + iotaTokensConsumedByRequest - newOwnerFee 153 | 154 | notSolo.L1.RequireBalance(chainOriginatorKeyPair, colored.IOTA, chainOriginatorBalanceInL1AfterFeeIsChanged-iotaTokensConsumedByRequest) 155 | notSolo.Chain.RequireBalance(chainOriginatorKeyPair, chain, colored.IOTA, newOwnerFee) 156 | notSolo.Chain.RequireChainBalance(chain, colored.IOTA, expectedChainBalance) 157 | } 158 | --------------------------------------------------------------------------------