Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
A description of CosmWasm and the framework around it as implemented on Secret Network
Additional development concepts to improve your experience building on Secret Network.
An explainer of the Instantiate file inside of the CosmWasm code framework
The Instantiate function is the function that will run once (and only once) immediately upon initializing your smart contract after uploading it to the network. It is meant to set up the smart contract with all vital code that must be run first before any users have the ability to execute the contract. For those familiar with a solidity constructor on Ethereum, Instantiate serves the exact same purpose.
If you have any vital info to save to the state such as Config parameters or an admin/operator address to name a few options, those should probably be specified here. In addition, if your contract requires entropy for randomization or needs to register to receive a SNIP-20 token for payment, those operations should also be performed here, as other execution messages will rely on that data to be already instantiated.
Instantiate takes four arguments:
"deps"
: allows the contract to interact with the outside world by reading and updating the contract state, accessing other contract states, and using helper functions to work with contract addresses.
"env":
is an object that represents the current state of the blockchain when the contract is executed, including the blockchain's height, timestamp, and the address of the contract being called.
"info"
: metadata about the message that triggered the contract's execution, such as the sender's address and the native tokens that were sent with the message.
"msg":
is the message that triggers the contract's execution, which is currently represented by the "Empty" type in the codeblock above.
Before we understand what the PrefixedStorage struct is, we should first understand the problem they attempt to solve. We may be tempted to think that the key-value model as described in Storage solves all our storage needs. And we'd technically be correct in thinking that. It is possible to efficiently store vast amounts of user data by just using the methods described in that page. However, we will see that doing this would be cumbersome for the developer in many cases. That's why there are many additional storage structures built on top of the methods described in Storage. Prefixed storage is an additional structure built on top of these methods for the convenience of the developer.
We will go over a storage problem and discuss how we might tackle it with the methods we learnt in Storage to get to the root of what prefixed storage actually is. Suppose that we have contract that stores a different password (String) for each wallet address that interacts with it (so that the wallets can later give that password when querying the contract). How would we want to store all these passwords so that they can be checked when it is time for queries?
Our first approach might be to use a hashmap that stores all the passwords, and save it as binary using the methods we already know. So we would add the following lines to the init
And then we can proceed to load, and add each users' password whenever a user sends the create_password
HandleMsg. Note that I'm assuming we would generate the &[u8]
key for the hashmap from the user's wallet address. This approach has a huge problem! That is whenever we want to add another wallet's password to the hashmap, we must load the entire hashmap with all the passwords stored inside it. This will increase gas costs as we gain thousands of users. Moreover, loading the hashmap for queries will strain the node.
We now realize that we can actually generate&[u8]
storage keys for each user wallet and save the password directly to deps.storage
as described in Storage. This way, we can use may_load
to check if a user has a password, and if he does, learn what it is without loading all the other passwords. Then we would use the following lines of code to save a password
This method does work! However, what if we want to save additional things that is associated to each user, such as token balances. Then using save on the same key would overwrite our previous data. The solution is to add a prefix to the storage key so that we know it belongs to the password property. One way to implement this is the following:
This works but can be cumbersome and ugly, especially if you need to build more functionalities around this idea. This is exactly what prefixed storage does in the background! Prefixed Storage is built to solve this problem while hiding away all the ugliness.
PrefixedStorage is a struct that keeps track of all the storage keys that are used to store various data under a shared namespace similar to described above. Prefixed storage is often used to keep track of storage keys of deps.storage
, but it can in fact be used to store the keys of other storage structures.
The compiler only allows one mutable reference to the underlying storage to be valid at one point. Thus, you cannot have more than one mutable Prefixed Storage objects at the same time.
Let's solve the password problem that was mentioned above with prefixed storage. The solution to this problem resembles viewing keys for permissioned viewing. You will learn more about this in the coming sections. We first define a namespace (prefix) in state.rs
.
The idea then is that all the wrapper functions that we've learnt in Storage also work on prefixed storage. We will only demonstrate save
and may_load
by example.
We would instantiate a mutable PrefixedStorage object using the following lines of code to save a new password
Notice that we are using the same storage wrapper functions from Storage on prefixed storage.
The reason why we can use the same wrapper functions is because Prefixed Storage implements the Storage trait, even though most of what it's doing is to add a prefix on top of the keys we provide to it.
We may use both load
and may_load
We may load data from a mutable Prefixed Storage instance with the following lines of code
In queries, we don't want to use PrefixedStorage struct. Instead we use ReadonlyPrefixedStorage struct. It works in the exact same way, except that you cannot save to it.
You can have as many ReadonlyPrefixedStorage objects as you want at the same time, unlike PrefixedStorage.
We can use the remove
wrapper function for this.
An explainer on the varying storage frameworks for Secret contracts
CosmWasm storage uses a key-value storage design. Smart contracts can store data in binary, access it through a storage key, edit it, and save it. Similar to a HashMap, each storage key is associated with a specific piece of data stored in binary. The storage keys are formatted as references to byte arrays (&[u8]
).
One advantage of the key-value design is that a particular data value is only loaded when the user explicitly loads it using its storage key. This prevents any unnecessary data from being processed, saving resources.
Any type of data may be stored this way as long as the user can serialize/deserialize (serde) the data to/from binary. Doing this manually every single time is cumbersome and repetitive, this is why we have wrapper functions that does this serde process for us.
All the data is actually stored in deps.storage
, and the examples below show how to save/load data to/from there with a storage key.
Creating a storage key is simple, any way of generating a constant &[u8]
suffices. Developers often prefer generating these keys from strings as shown in the example below.
For example, the above key is likely used to store some data related to core configuration values of the contract. The convention is that storage keys are often all created in state.rs
, and then imported to contract.rs
. However, since storage keys are just constants, they could be declared anywhere in the contract.
The example above also highlights that storage keys are not meant to be secret nor hard to guess. Anyone who has the open source code can see what the storage keys are (and of course this is not enough for a user to load any data from the smart contract).
One common technique in smart contracts, especially when multiple types of data are being stored, is to create separate sub-stores with unique prefixes. Thus instead of directly dealing with storage, we wrap it and put all Foo
in a Storage with key "foo" + id
, and all Bar
in a Storage with key "bar" + id
. This lets us add multiple types of objects without too much cognitive overhead. Similar separation like Mongo collections or SQL tables.
Since we have different types for Storage
and ReadonlyStorage
, we use two different constructors:
Please note that only one mutable reference to the underlying store may be valid at one point. The compiler sees we do not ever use foos
after constructing bars
, so this example is valid. However, if we did use foos
again at the bottom, it would properly complain about violating unique mutable reference.
The takeaway is to create the PrefixedStorage
objects when needed and not to hang around to them too long.
As we divide our storage space into different subspaces or "buckets", we will quickly notice that each "bucket" works on a unique type. This leads to a lot of repeated serialization and deserialization boilerplate that can be removed. We do this by wrapping a Storage
with a type-aware TypedStorage
struct that provides us a higher-level access to the data.
Note that TypedStorage
itself does not implement the Storage
interface, so when combining with PrefixStorage
, make sure to wrap the prefix first.
Beyond the basic save
, load
, and may_load
, there is a higher-level API exposed, update
. Update
will load the data, apply an operation and save it again (if the operation was successful). It will also return any error that occurred, or the final state that was written if successful.
Since the above idiom (a subspace for a class of items) is so common and useful, and there is no easy way to return this from a function (bucket holds a reference to space, and cannot live longer than the local variable), the two are often combined into a Bucket
. A Bucket works just like the example above, except the creation can be in another function:
Singleton is another wrapper around the TypedStorage
API. There are cases when we don't need a whole subspace to hold arbitrary key-value lookup for typed data, but rather a single storage key. The simplest example is some configuration information for a contract. For example, in the name service example, there is a Bucket
to look up name to name data, but we also have a Singleton
to store global configuration - namely the price of buying a name.
Please note that in this context, the term "singleton" does not refer to the singleton pattern but a container for a single element.
Singleton
works just like Bucket
, except the save
, load
, update
methods don't take a key, and update
requires the object to already exist, so the closure takes type T
, rather than Option<T>
. (Use save
to create the object the first time). For Buckets
, we often don't know which keys exist, but Singleton
s should be initialized when the contract is instantiated.
Since the heart of much of the smart contract code is simply transformations upon some stored state, we may be able to just code the state transitions and let the TypedStorage
APIs take care of all the boilerplate.
CosmWasm storage is built on a key-value storage design, similar to a HashMap, where data is stored in binary format and accessed using storage keys represented as byte arrays (&[u8]
). Developers can serialize and deserialize data to/from binary, simplifying this process through wrapper functions. Storage keys are typically constants declared in state files and are not meant to be secret.
Prefixed storage allows for creating separate sub-stores with unique prefixes for organizing different data types, while Typed Storage provides a type-aware interface to reduce serialization boilerplate. CosmWasm also supports advanced abstractions like Buckets and Singletons. Buckets create subspaces for storing collections of items, while Singletons manage single elements, typically used for global contract configuration data. These APIs streamline storage handling, enabling efficient data management in smart contracts.
Now, we will explore some of these storage methods, such as PrefixedStorage
, Singleton
, and Keymap
, in more depth to see how they interact with on-chain data.
An explainer of the Execute file inside of the CosmWasm code framework
Execution Messages are contract messages that trigger the execution of a smart contract and perform specific operations, such as updating the contract state or transferring tokens. If you’re familiar with RPC or AJAX, you can think of execution messages as the code that runs when a remote procedure is called. On Secret Network, execution messages are usually designed to be functions that run as quickly as possible and exit as early as possible when any errors are encountered, as this will help save gas. You can learn more about contract optimization here.
The standard practice on Secret Network is to have an enum
with all the valid message types and reject all messages that don’t follow the usage pattern dictated in that enum; this enum is conventionally called ExecuteMsg
and is usually in a file called msg.rs
As mentioned earlier, the standard way to choose what the contract should execute is to look at which ExecuteMsg
is passed to the function. Let’s look at an example.
In this simple example, the code looks at the message passed, if the passage is the Increment
message, it calls the function try_increment
, otherwise, if the message is the Reset
message, it calls the function try_reset
with the count.
Remember, Rust has implicit returns for lines that don’t end in a semicolon, so the result of the two functions above is returned as the result of the execution message.
You may have noticed that the execution message takes two arguments in addition to the msg: deps
and env
. Let’s look at those more closely.
As the name perhaps implies, env
contains all the information about the environment the contract is running in, but what does that mean exactly? On Secret Network the properties available in the Env struct are as follows:
block: this contains all the information about the current block. This is the block height (height
), the current time as a unix timestamp (time
) and the chain id (chain_id
) such as secret-4 or pulsar-3.
message: contains information that was sent as part of the payload for the execution. This is the sender, or the wallet address of the person that called the handle function and sent_funds which contains a vector of native funds sent by the caller (SNIP-20s are not included).
contract: contains a property with the contract’s address.
contract_code_hash: this is a String containing the code hash of the current contract, it’s useful when registering with other contracts such as SNIP20s or SNIP721s or when working on a factory contract.
Now that you have an overview on what all those properties are, let’s take a look at a simple Execution Message.
The execution message above reads the state from the storage(Deps
) and increments the count property by one, then prints out the new count, if successful.
As mentioned previously, developers don’t really like working with raw binary data, so many resort to using more human friendly ways; you can find out more on the page about storage, here. But for the sake of this example, let’s look at what that config
function is doing.
The developers of this contract opted for using storage singletons. A storage singleton can be thought as a prefixed typed storage solution with simple-to-use methods. This allows the developer to define a structure for the data that needs to be stored and handles encoding end decoding it, so the developer doesn’t have to think about it. This is how it’s implemented in this contract:
The config
function returns a singleton over the config key using the State
struct as its type, this means that when reading and writing data from the storage, the singleton automatically serializes or deserializes the State struct.
You may have noticed that the return type for a handle message is Response
. Let's take a look at how Response
is defined:
In CosmWasm, the Ok
result and the Response
object are essential parts of handling contract execution and signaling the outcome to the blockchain. Here's a breakdown of how these work:
Ok
Result in CosmWasmIn Rust (and by extension, CosmWasm), functions that interact with the blockchain often return a Result
type. This is a way to express whether the function was successful or if it encountered an error.
Result<T, E>
: A Result
can either be:
Ok(T)
: Signifying the function executed successfully and returns a value of type T
.
Err(E)
: Signifying that an error of type E
occurred.
In the context of CosmWasm, the Ok
result generally signifies that a contract's execution completed successfully, returning a Response
object.
For example:
This line means that the contract execution was successful, and it returns a default Response
. In CosmWasm, we use StdResult<Response>
for contract execution functions.
This overview examines the core components of Secret Network smart contracts
An explainer of the query file inside of the CosmWasm code framework
Query messages retrieve the relevant smart contract and execute the query message. The results of the query are then returned to the sender of the query message, providing users with information about the state of the blockchain and the smart contracts running on it.
Contracts can define query functions, or read-only operations meant for data-retrieval. Doing so allows contracts to expose rich, custom data endpoints with JSON responses instead of raw bytes from the low-level key-value store. Because the blockchain state cannot be changed, the node can directly run the query without a transaction.
Users can specify which query function alongside any arguments with a JSON QueryMsg
. Even though there is no gas fee, the query function’s execution is capped by gas determined by metered execution, which is not charged, as a form of spam protection.
Explanation of executing modules inside a CosmWasm contract using sending Native SCRT as an example.
Sending Native SCRT from a contract is realtively trivial and can be done using the message. This is a call. Any native denoms can be send in this way (ex. native IBC tokens).
Having Trouble? Always make sure your contract has enough funds to begin with!
You can send some coins to your contract like this:
You can always see decrypted error messages from the contract like so:
You can see the spent coins as events when you query for the transaction:
This hashmap-like storage structure uses generic typed keys to store objects.
A Keymap is hashmap-like storage structure that uses generic typed keys to store objects. It allows iteration with paging over keys and/or items without guaranteed ordering, although the order of insertion is preserved until you remove objects.
An example use-case for a keymap is if you want to contain a large amount of votes and iterate over them in the future. Since iterating over large amounts of data at once may be prohibitive, a keymap allows you to specify the amount of data that will be returned in each page. We will implement this voting example below to show how keymaps can be utilized in your own projects.
To import this package (and also the packages that we will be using for unit tests), add the following dependencies to your Cargo.toml
file
To import and initialize a keymap, use the following packages in your test environment:
Now let's write our first test!
Let's start by creating a function that inserts key-value pairs into a keymap. This code defines a test function called test_keymap_perf_vote_insert()
that creates a new Keymap
that maps Vec<u8>
keys to String
values, and then inserts 1000 key-value pairs into the Keymap
. Each key is a Vec<u8>
containing an integer from 0 to 999, and each value is a String
containing the text "I vote yes".
This test is passing! Which means that it asserts that the Keymap
contains 1000 key-value pairs, which map a number from 0 - 999 with the string "I vote yes."
Iterating with Keymaps
There are two methods that create an iterator in Keymap. These are .iter
and .iter_keys
. iter_keys
only iterates over the keys whereas iter
iterates over (key, item) pairs. Let's use iter
to test the iterator functionality of aKeymap
that maps Vec<u8>
keys to a struct Vote.
We are going to create a new Keymap
that maps Vec<u8>
keys to a struct Vote
that has two fields, vote
and person
. It then inserts two key-value pairs into the Keymap
, where the keys are the byte vectors b"key1".to_vec()
and b"key2".to_vec()
, and the values are Vote
structs containing information about the vote and the person who cast it.
We then use iter()
to check that the size of the iterator is 2, which means there are two key-value pairs in the Keymap:
Our test is passing! This means that the first element returned by the iterator matches the expected key-value pair for key1
, and that the second element returned by the iterator matches the expected key-value pair for key2
.
Learn how to create a SNIP-20 token on Secret Network
In this tutorial, we are going to create our own SNIP-20 token on Secret Network using Secret Labs' SNIP-20 reference implementation contract, and we will learn how to upload, instantiate, execute, and query our SNIP-20 contract using Secret.js. Let's dive in!
You can clone the source code , which we will reference throughout the course of this documentation.
Use to set up your developer environment.
Now that you've cloned the SNIP-20 reference implementation repo above, let's compile the contract. In your terminal run make build-mainnet-reproducible
.
The compiled wasm file will be output in ./optimized-wasm
.
Now let's upload and instantiate the SNIP-20 Contract. We have already written an for your convenience. If you would like to make any changes to your SNIP-20 token, you can do so .
The initMsg
object in our upload.js
file is referencing the instantiation message defined in . Notice the optional config
variable. If we include config
, there is a variety of additional contract functionality that we could program, such as burn, mint, admin privileges, etc.
Now we are going to instantiate some ZBRA coin. If you want to create your own coin name, update the name, symbol,
and amount
fields respectively.
Otherwise, run node upload:
Upon successful execution, a codeId, contract hash, and contract address will be returned:
To check that the instantiation of our SNIP-20 ZEBRA token was successful, let's query the smart contract's token info.
Run node query_token_info
:
The following is returned upon successful query:
The reason total supply
is null
is because we chose to make total supply
hidden in our instantiation message. If you want it to be public, then in the InitConfig
variable set public_total_supply
to true.
Now let's execute the transfer message with secret.js. Be sure to update the recipient
wallet address with your own wallet before executing the code below. For testing purposes, I am using two Keplr wallet connected to the Secret Network testnet in order to move funds back and forth:
Congrats! You just successfully transferred your own SNIP-20 token on Secret Network! 🎉
An explainer of the dependecies inside of the CosmWasm code framework
Deps
(or the mutable version DepsMut
) holds all external dependencies of the contract, and is designed to allow easy dependency injection at runtime. Those external dependencies are as follows:
As the name suggests, storage allows reading and writing data in a contract's memory. Secret Network contracts use a key-value store for reading and writing data and provide three easy-to-use methods to do so:
get(&self, key: &[u8]) -> Option<Vec<u8>>
reads data stored in the specified key
set(&self, key: &[u8], value: &[u8])
which writes data to the storage in the specified key
remove(&self, key: &[u8])
which deletes a key alongside its data.
You may have noticed that storage uses byte arrays exclusively to read and write data, so to make it more user-friendly there are certain libraries that the community has developed. You can learn more about this .
Often the question of, "How much data can be stored in a contract?" is asked. The answer is that it’s complicated. Technically there’s no upper-bound on how much data a contract can store, but there are practical per-transaction limits.
Reading one byte of data “uses” 3 gas and writing one byte of data “uses” 30 gas, so with the current limit of 6_000_000 gas per block, the practical limit is that a contract can read — at most — 13.33Mb of data per transaction and can write — at most — 1.3Mb of data per transaction. This assumes that the contract doesn’t do anything but read and write data, the more complex the contract, the lower the amount of data you can read and write. Note that the limits are per transaction, you could write several gigabytes of data if you wanted to but it would take several transactions.
Api consists of a collection of callbacks to system functions defined outside of the wasm modules. Those are functions that are defined in the chain’s code, this allows calling useful functions without needing to include them in the contract’s code and assures a smaller binary size as well as a higher certainty that they’re going to be implemented correctly.
canonical_address(&self, human: &HumanAddr) -> StdResult<CanonicalAddr>
Takes a human readable address and returns a binary representation of it.
human_address(&self, canonical: &CanonicalAddr) -> StdResult<HumanAddr>
Takes a canonical address and returns a human readble address. This is the inverse of canonical_address
secp256k1_verify(&self, message_hash: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool, VerificationError>
Verifies message hashes (hashed unsing SHA-256) against a signature, with the public key of the signer, using the secp256k1 elliptic curve digital signature parametrization / algorithm.
secp256k1_recover_pubkey( &self, message_hash: &[u8], signature: &[u8], recovery_param: u8 ) -> Result<Vec, RecoverPubkeyError>
Recovers a public key from a message hash and a signature.
ed25519_verify( &self, message: &[u8], signature: &[u8], public_key: &[u8] ) -> Result<bool, VerificationError>
Verifies messages against a signature, with the public key of the signer, using the ed25519 elliptic curve digital signature parametrization / algorithm.
ed25519_batch_verify( &self, messages: &[&[u8]], signatures: &[&[u8]], public_keys: &[&[u8]] ) -> Result<bool, VerificationError>
Performs batch Ed25519 signature verification.
Batch verification asks whether all signatures in some set are valid, rather than asking whether each of them is valid. This allows sharing computations among all signature verifications, performing less work overall, at the cost of higher latency (the entire batch must complete), complexity of caller code (which must assemble a batch of signatures across work-items), and loss of the ability to easily pinpoint failing signatures.
This batch verification implementation is adaptive, in the sense that it detects multiple signatures created with the same verification key, and automatically coalesces terms in the final verification equation.
secp256k1_sign( &self, message: &[u8], private_key: &[u8] ) -> Result<Vec, SigningError>
Signs a message with a private key using the secp256k1 elliptic curve digital signature parametrization / algorithm.
ed25519_sign( &self, message: &[u8], private_key: &[u8] ) -> Result<Vec, SigningError>
Signs a message with a private key using the ed25519 elliptic curve digital signature parametrisation / algorithm.
(CosmWasm 1.0) debug
- a function that allows for debug prints, which can be used during development to print to STDOUT in a testnet or LocalSecret environment (replaces debug_print
from CosmWasm v0.10)
(New in Secret-Cosmwasm v1.1.10) gas_evaporate(evaporate: u32) -> u32
- this API function allows a contract to consume a set amount of gas. Use together with check_gas
in order to create contract calls that consume an exact amount of gas regardless of the code path taken.
(New in Secret-Cosmwasm v1.1.10) check_gas() -> u64
- this API returns the current amount of gas consumed by a contract call.
AppendStore is meant to replicate the functionality of an append list in a cosmwasm efficient manner. The length of the list is stored and used to pop/push items to the list. It also has a method to create a read only iterator.
This storage object also has the method remove
to remove a stored object from an arbitrary position in the list, but this can be extremely inefficient.
❗ Removing a storage object further from the tail gets increasingly inefficient. We recommend you use
pop
andpush
whenever possible.
The same conventions from Item
also apply here, that is:
AppendStore has to be told the type of the stored objects. And the serde optionally.
Every methods needs it's own reference to deps.storage
.
To import and initialize this storage object as a static constant in state.rs
, do the following:
❗ Initializing the object as const instead of static will also work but be less efficient since the variable won't be able to cache length data.
Often times we need these storage objects to be associated to a user address or some other key that is variable. In this case, you need not initialize a completely new AppendStore inside contract.rs
. Instead, you can create a new AppendStore by adding a suffix to an already existing AppendStore. This has the benefit of preventing you from having to rewrite the signature of the AppendStore. For example
Sometimes when iterating these objects, we may want to load the next n
objects at once. This may be prefered if the objects we are iterating over are cheap to store or if we know that multiple objects will need to be accessed back to back. In such cases we may want to change the internal indexing size (default of 1). We do this in state.rs
:
The main user facing methods to read/write to AppendStore are pop
, push
, get_len
, set_at
(which replaces data at a position within the length bound), clear
(which deletes all data in the storage), remove
(which removes an item in an arbitrary position, this is very inefficient). An extensive list of examples of these being used can be found inside the unit tests of AppendStore found in append_store.rs
.
AppendStore also implements a readonly iterator feature. This feature is also used to create a paging wrapper method called paging
. The way you create the iterator is:
More examples can be found in the unit tests. And the paging wrapper is used in the following manner:
For further examples demonstrating the usage of keymaps, refer to the .
Now that we have successfully instantiated our SNIP-20 contract, let's send an to better understand the contract's functionality.
Start by adding the token to your Keplr wallet. Click on Keplr, select the hamburger icon, select "Add Token", and then paste in your token's contract address. If you need to fund your wallet to execute the transaction, you can do so using the . You should now see your token in your Keplr wallet!
Let's to another wallet address. The transfer message is defined in msg.rs as follows:
A high-level overview of Secret smart contracts
Your start on privacy design for Secret smart contracts
Secret Network provides all the tools necessary to build fully private decentralized applications, but contracts do not automatically inherit perfect privacy. Privacy has a cost. Developers have both the freedom and the burden of choosing a privacy model that fits their applications' needs.
Protecting sensitive user data is not even the whole story when it comes to Secret dApps. Achieving a proper level of privacy can be mission critical in certain areas, such as competitive gaming, where an information leak could lead to a compromised system, say, if an attacker were able to cheat.
As a developer, it is your responsibility to understand the gamut of privacy risks, their implications, and how to best mitigate them.
Let's start off with a thought experiment. In an ideal world, a perfectly private application would be one that an attacker could not plausibly deduce any information about regarding its state, history, or participants. To an observer, any time a participant reads from or writes to the contract, it would be indistinguishable from every other action.
All outputs would appear to be random and unpredictable, with no discernible patterns that could be used to infer the underlying operations or data. On top of all that, the timing and frequency of executions would need to be consistent across the lifespan of the application.
In reality, there are many forms of contextual data that an attacker can use to infer information, de-anonymize users, or compromise the privacy of applications purely through analysis:
transaction sender's address, their public transaction history, the timing and frequency of their transactions, and their relation to other accounts on the network
public inputs and outputs of an execution, such as sent or received funds (e.g., wrapping/unwrapping SCRT or IBC tokens)
timing of an execution, its temporal locality to other transactions (e.g., clustering, dispersion, co-ocurrence), and an execution's order relative to other on-chain events
size of the encrypted inputs and outputs of executions, shape and size of the event log, and public transaction metadata including gas_used
and nested calls to other contracts
timing, frequency, shape and size of query requests and query responses
storage access patterns when a contract reads from and writes to the encrypted application database
Additionally, there are active attacks that can expose unwanted leaks of information:
executions that produce measurable side-effects
tx replay attacks, side-chain attacks, the Millionaire's Problem
targeted storage access pattern attacks
Its important to note that a few of the risks listed above can only be mitigated by user OPSEC. For example, the timing and frequency of executions, tx sender address, and relations to other accounts are risks typically assumed by end-users. In these cases, education is the best instrument to protect users' privacy.
Some of the other risks should be mitigated by client-side software, such as wallets and dApp front-ends. This includes padding inputs to executions and queries, being strategic about the timing and distribution of query requests, and creating workflows that allow users to more easily create identities that are not linked to their public account.
The remaining privacy risks are those which can only be mitigated through contract design. Developers should strive to make all queries and executions appear indistinguishable from one another to an outside observer. This includes:
padding query and execution results (see pad_query_result
and pad_handle_result
from the Secret Toolkit)
using Gas Evaporation to make all methods consume a consistent amount of gas
adding decoy attributes to the event log when necessary
understanding storage access pattern attacks and employing a system of decoys or oblivious data models (or even UTXO)
Find code implementation of the above privacy preserving methods under our Privacy design section.
This page describes some of the core information required to get up to speed about the state of Secret Contracts in comparison to public (cosmos) networks.
Smart contracts on Secret Network follow the widely used CosmWasm framework. The framework is built as a module for the Cosmos SDK and allows developers to build smart contracts with Rust which compile to WebAssmebly (Wasm). This type of efficient and fast binary, combined with the power of a low-level typed coding language, makes CosmWasm a secure and efficient way for developers to write smart contracts. Contracts on Secret have a public binary just like any other smart contract network, only the contract input, output and state is encrypted.
Secret Contracts slightly differ from any standard CosmWasm implementation as they handle encrypted data, secure enclaves, and the key design around that. Secret currently emulates CosmWasm v1.1.9 and is completely compatible over IBC with other V1.0+ Cosmos Smart contract networks.
Private data (contract input, output and state) on Secret is encrypted using an unforgeable contract encryption key which is unique for every contract. Moreover, the user dedicated state in a contract is only visible to the user themselves. Because of this design, contract migration is a privacy risk. If enabled, a contract admin could upload a malicious contract that reads and prints data entrusted to the old contract. Additionally, iterators and raw queries are not supported as one can not iterate over data stored by a different user as there is no access to their dedicated encryption key.
More information about the differences in implementations for a contract between Vanilla and Secret CosmWasm are listed here.
Secret contract encrypted input and access-controlled output is very powerful and enables many different usecases, combining this with novel development tools creates a powerful framework for developers to leverage. Below some of these tools and ideas are listed to get your brain going.
Privacy as a Service - More utility for other blockchains by leveraging Secret contracts cross-chain. Want to build on polygon, ethereum, Sei, Neutron or Aptos but want additional features only possible with data privacy? You can use Secret contracts cross-chain to keep your application decentralized and offer users new and interesting ways to explore your dApp.
Storage of private/encryption key (fragments) - One can use Secret contracts to store very sensitive info to enable cool usecases like access-controlled content or even cross-chain account ownership for DAOs and unstoppable threshold wallets.
on-chain randomness (secret-vrf) and hidden (game) state - Games on Secret benefit of true on-chain randomness that can remain hiddin inside the contract forever if so desired. Together with a hidden gamestate this brings a whole new dimension and fairness to turn-based games for Web3 like Poker or Mario party and more clarity and ownership to casino games and lootboxes.
Frontrunning resistant and privatized DeFi - because of an encrypted mempool secret is frontrunning resistant by default. Order size - unknown, Placed bids - unknown, liquidation points - unknown and many other features from orderbooks to dark pools have these privacy features as well.
Secret.js - a Javascript library for interacting with the Secret network, Secret contracts, providing encrypted input and creating viewing keys/permits for encrypted output. The library can also be considered as a more complete Cosm.js and can be used on every other cosmos chain. - Alternatives in Python, Kotlin, .Net and more exist as well.
Polar, Fadroma, Secret-toolkit (CW-plus) and Secret IDE - Secret has dedicated tooling sets built by external teams and core development that abstract boilerplate code (for DeFi), help with testing scenarios, simplify contract development nightmares like storage and type handling and even catch errors as you work in a dedicated IDE.
IBC, Interchain accounts/Queries/contracts, Axelar GMP, Bidshop messaging and more - Want to build your application crosschain? Secret has many options for both Message and Asset bridging protocols with the most renowned being IBC (current version IBC-go V4).
The privacy of Secret Network is reliant on secure enclaves, which impacts how the network performs but also how one should design their contracts to enhance privacy. Firstly, it is important to note that only data originating from inside the enclave is verifiable and any data coming from external resources has to be verified before relying on it for execution. This means data coming from contract callbacks is to be trusted, but the sender address is not. If I allow the sender to create an item on-chain, then only the sender should be able to remove that item. Verifying that it is indeed the sender (data owner) that is requesting this change is done by "input data verification". Explanation of the various network level integrated methods is available here.
In practice a Secret contract developer does not have to think too much about this as its abstracted away in the data verification process in the contract module of the network.
When designing contracts for Secret network, one should take into account the usage of so-called replay attacks and other theoretical attack vectors on secure enclave derived privacy. An example of this is the abundant usage of the Contract hash in Secret contract development. The usage of a contract hash is required to link the input to the specific contract being called. Otherwise one could make a forked chain and replace the contract with one that reads and prints the input parameters. Although encryption for Secret contracts is handled by the protocol, developers should still be mindful of their contract design to optimize for these privacy aspects.
One example of such a privacy optimized design would be the use of the Gas evaporation function as added in the v1.9 upgrade and documented here (link coming soon).
There are various types of outputs that can be expected, including:
An updated contract state (i.e., the user’s data should update the state or be stored for future computations)
A computation result encrypted for the transaction sender (i.e., a result should be returned privately to the sender)
Callbacks to other contracts (i.e., a contract is called conditional on the outcome of a Secret Contract function)
Send messages to other modules (i.e., for sending value transfer messages that depend on the outcome of a computation).
Reply - Send more detailed error logs of various submessages as explained here.
Want to dive deeper into the CosmWasm framework? - You can read more about it's design in the CosmWasm book.
Secret Contracts have private input, output and state. This means all data on-chain is stored as cyphertext and is only accessible by the owner (either the contract or the user). More details are available in the encryption specs.
The binary of any contract is public, meaning the execution logic is verifiable just like any other smart contract blockchain.
Secret smart contracts are IBC compatible (CosmWasm v1.0+), allowing for cross-chain logic with other IBC-connected chains.
from v1.11 Secret now supports migrate-able contracts just like vanilla CW, it does not support iterators due to contract privacy design. Workarounds are highlighted under storage.
When designing Secret contracts, the verification of input-data is key to ensure the secure execution of transactions as not all blockchain data is exposed automatically to the enclave.
Developers need to be mindful of data leakage via replay attacks, improper padding, and more so that the privacy for users of their contracts is optimized.
As explained in the Gas and fee section Secret computational load is denominated in gas
. The amount of gas that can be spend at every block is capped via the max_gas
block parameter. This cap is currently set at 6,000,000 (6 million) gas
meaning a block will never contain more computational load than what is represented by 6 million gas. The computational load per gas unit is calculated and then denominated in the chain binary.
You can find the Gas settings for Secret Network in the Network code here - [SDK/Go message types] , [Wasm messages]
The max_gas
parameter is chosen in such a way that validators can ensure they process the full computational load within the target block execution time (~6s). If this computation and expected blocktime don't align the blocks will become longer and longer as they await 66% of voting power to sign the block. When block time increases the TPS of the blockchain drastically decreases only complicating the situation further.
You can read more about this interplay between blocktime, TPS and scalability in this post mortem of the Shade airdrop.
One can expect the overhead of all enclave and encryption transactions to be roughly 30% over a vanilla wasm engine. This overhead comes from the decryption and re-encryption done in the enclave and the varying verification's that are required of the message input data. This overhead does not exist for standard SDK messages like "stake", "transfer", "vote" etc.
Additionally Secret leverages an older engine for the wasm VM dubbed "Wasm3". The vanilla engine "Wasmer" is roughly10-15x more performant. This engine is not yet supported as there is no Enclave compatible version, development effort to support it though is underway as listed in the core roadmap.
Because of above factors the TPS of Secret is limited in comparison to vanilla Tendermint and Cosmos SDK benchmarks. Based on an average of 100.000 gas for a contract interaction (something like an NFT mint or non-routed DEX swap) Secret can process 60 TXs per block which at ~6s per block 10 WASM transactions per second. SDK interactions take a lot less gas meaning the overall TPS of Secret in practice is ~25.
As explained in the above section this number can still be significantly increased by upgrading the WASM engine but has proven sufficient so far in periods of high demand
Important to note is that Queries related to contract state require the API node to access the enclave, these are therefore more resource expensive than SDK queries. Secret API infrastructure needs to be scaled accordingly to fit heavy load user interfaces. One can expect to require more Secret API nodes than standard Cosmos API nodes for a similar application. As transaction complexity overhead decreases with better engines so does increase the ability for a node to process queries. Verified queries on Secret have already become a hundred times more performant with 1.4-1.6 upgrades, and with the current community API they are in a very stable spot.
Short term scalability improvements can come from a WASM engine replacement and increase in Light client verification methods so to lower the enclave load. Also storage access can become more performant with an SGX backend change which is on the short term roadmap. Long term Secret does not have to worry about blockspace with the plenty opportunities of replicated security and the cosmos sdk. For more on the core development roadmap check out the documentation here.
Secret network uses Tendermint as a consensus engine combined with the Cosmos SDK, as such it has a relatively high throughput and the possibility for easy horizontal scalability. Because of this, there is no fee market on Secret Network, which means there is no fee-based prioritization built into the standard tendermint binary required to run Secret. Using a higher transaction fee will only increase the number of block signers (validators) that are willing to process your transaction. This might speed up finality but does not prioritize your transaction in the cue.
The fee a user pays for a transaction is based on two things: the computational resources/block space that is used and the fee that validators ask for these resources. These two elements are called gas
and the gas fee
.
The gas
required for the computation is determined by the smart contract engine. The current gas estimates per type of contract interaction are listed here (for SDK components) and here (for enclave components). More complex transactions will consume more gas.
The gas fee
is determined by validators themselves as a parameter in their node service. The min_gas_fee
parameter allows validators to set a minimum fee denominated in uSCRT
. If a transaction has a lower allocated fee, they will not add it to blocks they propose. Validators propose blocks based on random selection following their Voting power. The min_gas_fee
for each block is dependent on the proposing validator. If blocks become too full or an attacker spams the network, the validators can increase their fee.
Secret validators run three tiers of fee's; this choice is up to the validator. Overall, we can assume more than 40% of validators support the "low" fee option, meaning pushing a TX with the low fee often gets processed within a few blocks. Pushing a TX with the "High" fee will increase the chance that the next block can fit your transaction. These TXs are therefore considered "faster". The current recommended fee options are listed in the Cosmos chain registry.
Now let's assume we execute a swap for a single Dex pool. We can expect this to take roughly 150000 Gas
. Executing this at the "low" fee option, we will pay 0.0125 uSCRT
for each Gas
unit used. The total fee for this transaction then becomes: 150000 * 0.0125 = 1.875 uSCRT = 1.875/1000000 = 0.001875 SCRT
. At 40% voting power supporting a min_gas_fee
of 0.0125 uSCRT
we can expect this transaction to be finalized after 4 blocks ( 4*6s = 24s
) with a 97.5% certainty [1- (40%**4) = 0,9744
].
Secret also allows for Gas abstraction for users by leveraging the CosmosSDK FeeGrant module. This module allows one to submit transactions where a different wallet is paying the gas fees as long as they granted you a budget to do that.
Documentation to create Feegrant functionality in your UI are here. This tool is widely used in different Secret UIs (for ex: Secret dashboard) and there is a community run FeeGrant faucet available for dApps to use. - Faucet - Code
This page explains the concept of input data verification, a required mechanism to ensure reliable privacy for secret contracts.
As Secret runs the compute module from within the enclave of the validator node it handles 2 types of data, data coming from within the enclave or outside of it. This data can then be separated as trusted (in-enclave) and un-trusted (outside enclave) meaning you can only rely on the trusted to be true in all cases, the untrusted data can be forged either with replay attacks on forked chains or state modification attacks.
The amount of trusted data options can be increased by additional verification mechanisms.
During execution, some contracts may want to use "external-data", meaning data generated outside of the enclave and sent into the enclave, such as the tx sender address, the funds sent with the tx, block height, etc...
As these parameters are sent to the enclave, they can theoretically be tampered with, and an attacker might send false data; making relying on this data conceivably risky.
For example, let's say we are implementing an admin interface for a contract, i.e. functionality only for a predefined address. We want to ensure the env.message.sender
parameter provided during contract execution is legitimate, so we will confirm that env.message.sender == predefined_address
. If this condition is met we can provide admin functionality. If the env.message.sender
parameter can be tampered with — we effectively can't rely on it and cannot implement the admin interface.
This verification is done by a light-client implemented inside of the enclave which validates all compute transactions and their order. It matches the set of transactions to be executed with the data hash field of the block at the current height. This means we can trust contract executions (Attention: not other modules like Bank), User address, inputs and sent funds are all part of the signed and validated inputs.
The light client does not validate internal contract calls - when your contract calls another during runtime - but these are signed in such a way that it can only be generated by another contract inside a genuine enclave. This can be considered trusted execution as well.
Looking at state modification attacks - these refer to attacks that attempt to modify the internal state of the chain to create behaviour that allows to compromise confidential data. This can be done by changing the internal state values of the chain (even encrypted values can be replaced with other valid encrypted values) and executing the real transactions to see what happens.
For example, imagine a contract that has the following logic:
Receive transaction from user with some amount of SCRT
Validate that payment is exactly 1000 SCRT
If payment is correct, reveal secret value
An attacker can use a modified chain to attack this logic in the following way:
Send a transaction on the real chain with 1000 SCRT from an address that has 1 SCRT
Wait for the transaction to fail on mainnet
On a modified chain fund the attacker's address with 1000 SCRT
Replay the real block from mainnet
Reveal the secret value
This scenario illustrates how payment systems need to consider these attack values.
Protecting against modified state is being actively developed, and will be solved by validating that values that are read from the chain are part of the merkle tree of the chain itself - Coming end of 2023
Trusted = No
means this data can easily be forged. An attacker can take its node offline and replay old inputs. This data that is Trusted = No
by itself cannot be trusted in order to reveal secrets. This is more applicable to init
and handle
, but know that an attacker can replay the input msg
to its offline node.
The equivalent of padding for gas_used
When a smart contract is executed, CosmWasm meters how much gas the execution consumes while the program is running. On Secret Network, this all happens within the enclave. However, once execution completes, the total amount of gas that was consumed leaves the enclave and is made public in transaction metadata.
The public gas_used
result of a contract execution message reflects the sum of costs for the actual instructions that were executed. Within a typical smart contract, most of its methods consume different amounts of gas. With SNIP-20 for example, an attacker could deduce which method a transaction executed with a high degree of certainty; create_viewing_key
vs. transfer
vs. increase_allowance
, each of which produce a distinct gas_used
amount in the public transaction metadata.
In some cases, this can leak even more information such as which code path was taken, the value of a conditional, how many iterations a loop completed, and so on.
Evaporation is a concept that was introduced to overcome the privacy risks associated with gas_used
. Evaporation refers to the practice of deliberately consuming extra gas during execution in order to pad the gas_used
amount before it leaves the enclave.
With evaporation, clients can now instruct contracts on exactly how much gas their execution should consume, yielding a consistent gas_used
across all methods.
A contract can evaporate an arbitrary amount of gas using the built-in API function gas_evaporate
. In order to evaporate any remaining gas, the check_gas
API function allows contracts to calculate the difference between the amount consumed up to that point, and the amount the client wants the contract to consume by the end of its execution:
See the evaporation example contract's for more details.
Note that there is no API function to get the amount of actual gas remaining. Instead, that information must come from the client. This practice ensures that users are still able to execute multiple messages in a single transaction by specifying gas limits to each contract execution individually.
The new check_gas
API function essentially gives contracts read access to CosmWasm's internal gas metering. Calling this function more than once during execution allows contracts to track changes in the amount of gas consumed. This simple feature enables a realm of interesting use cases not previously possible.
In its simplest form, gas tracking can be used as a development aid to inspect the amount of gas used by certain blocks of code, giving developers more insight and help with optimizing their gas footprint.
A more advanced use case involving gas tracking is opportunistic execution, where contracts take advantage of excess gas that would otherwise be evaporated by performing work that needs to be done anyway. That work could be anything from generic housecleaning duties, such as processing items in a queue, to more intentional duties, such as notarizing previous user actions to protect against tx-replay attacks.
To give an example, imagine an NFT auction contract that accepts bids on listed items. When a listing expires, the listed NFT should be transferred to the highest bidder. In typical contract design, the transfer would only actually happen once the winning bidder calls a method to "claim" their winnings. However, with opportunistic execution, the contract can automatically perform the transfer during any execution. For example, someone creating a viewing key could end up paying the gas fees to complete the transfer of a previous listing's NFT to its winning bidder.
Query permits are an alternative querying method introduced in the SNIP-24 design specification. Query permits use of a cryptographic technique known as public-key encryption coupled with digital signatures.
A permit is a formatted message, it outlines several arguments such as what tokens the permit applies to and what permissions the permit should allow (e.g. should the permit allow the querier to view a user’s transaction history, balance, etc.). Permits are not saved in the smart contract state and do not require the initiation of a blockchain transaction. Therefore, permits are a less permanent way of gaining viewing access with less network strain.
Users can sign permits with their account’s private key to give certain dApps or parties viewing access to specific parts of their private data for a specified amount of time. To get viewing access a user sends a query, with the signed permit as an argument, to a smart contract. Once received, the smart contract, using the user’s public key, can validate the identity based on the signature the user provided. If the user’s identity is confirmed, the smart contract returns the data as requested.
For more information on Permits check out the under development or the .
An in-depth explanation of Secret VRF, a secure and verifiable random number generator
Want to develop with decentralized on-chain randomness?
The ability to generate fair and verifiable random numbers on blockchain without compromising security or usability is critical for many decentralized applications. However, generating true randomness is difficult because blockchains rely on consensus mechanisms, such as Proof of Work or Proof of Stake, to reach agreement on the state of the network, and these consensus mechanisms are deterministic and therefore do not provide a source of true randomness. There have been attempts to solve this problem by various methods, such as using random data from external sources (the hash of a previous block, etc) or by using off-chain solutions, such as Chainlink’s VRF oracle, but these solutions are not optimal as they are either deterministic, rely on trusted parties, and/or require additional fees for gas and infrastructure. What is needed is a verifiable random number generator that exists on-chain, and this is now possible across blockchain ecosystems through Secret Network Random Number Generator (RNG).
The ability to generate true randomness is a fundamental requirement for blockchains. Verifiable randomness guarantees that operations such as the minting of NFTs, on-chain games, and DAOs are equitable and secure. In the case of NFT minting, randomness allows for features such as unordered minting, trait randomization, and identity numbering, all of which are critical for verifying the authenticity and security of NFT collections. In Web3 gaming, components such as gambling, damage calculation, loot boxes, boss drops, etc, rely on randomness to create trust amongst players and ensure that no player has an unfair advantage. And in the case of DAO tooling, randomness is required for features such as wallet initialization, task assigning, unordered voting/liquidations, order book ordering, etc. By using Secret Network RNG in these and other applications, the Interchain can help to ensure the security, transparency, and fairness of blockchain operations, which in turn promotes the growth and adoption of the Cosmos.
Secret Network is built on top of Tendermint, a peer-to-peer networking protocol that provides Byzantine-Fault-Tolerance (BFT) and Proof-of-Stake (PoS) consensus. This consensus ensures that all nodes maintain the same blockchain and that all nodes have the ability to propose new blocks. From a high-level standpoint, a new consensus round is:
Initiated with a Proposer Node
The Proposer selects transactions in the mempool to be included in a new block
This proposed block is broadcasted to all nodes (Pre-vote phase) and nodes verify that the block is valid, simultaneously verifying that the Proposer is also valid and sign the pre-vote message
If >2/3 of nodes pre-vote for this block, the block is deemed valid
Once a block is committed, there is no way to revert it and there is instant finality
The process of block proposal and execution involves a crucial intermediary step known as the "prevote phase." Secret Network RNG creates verifiable on-chain randomness by generating a random number that is attached to each Proposer’s block proposal, and this random number can then be used for on-chain randomness for the current block and exhibits the following properties:
The number is generated with sufficient entropy
The block proposer cannot predict or influence the entropy generation
Outside actors cannot predict random numbers before they are used to compute network transactions
Secret Network achieves this by implementing the following privacy network consensus:
Secret Network is unique from other blockchains because it utilizes Software Guard Extensions (SGX) to provide a Trusted Execution Environment (TEE) by creating a secure area in memory, called an "enclave," where sensitive data and code can be processed in a way that is isolated from the rest of the blockchain. This means that even if the operating system, other applications, or an attacker were to somehow compromise the security of the blockchain, the data and code within the SGX enclave remains protected.
Secret Network’s SGX allows for private computation on-chain and uniquely positions Secret Network to be the privacy provider of the interchain. In order to provide randomness as an interchain service, Secret Network’s SGX generates a random number on-chain during the block proposal stage of Tendermint. Block proposers generate a random number inside of the Trusted Execution Environment using RDRAND/RDSEED instruction.
RDRAND and RDSEED are Intel Secure Key technology instructions used in computer processors to generate random numbers
The RDRAND instruction is used to generate random numbers, while the RDSEED instruction is used to generate seed values for cryptographic purposes. Externally influencing RDRAND that is executed inside an SGX enclave is very difficult to do, with no known practical or theoretical attacks that can reduce the resulting entropy of the instructions.
In addition to generating random numbers using SGX, Secret Network adds an additional layer of security to the output of the random number generation by pairing it with an Initial Randomness Seed (IRS). This seed originates from a shared consensus seed among all network nodes, and the random number and IRS are combined using an algorithm called HKDF(rfc5869) to certify that the final number is truly random and cannot be predicted by anyone, even if they were to influence the original random number generation.
Secret Network also includes an additional key that is available to all nodes on the network within the TEE called the Random Encryption Key (REK). The REK is used to encrypt the generated random number using symmetrical encryption, and the encrypted blob (Erand) is attached as a new field to the block proposal. The REK is derived from the consensus seed, so all network nodes can derive it without the need for key-sharing protocols.
In order to make sure the random number generated by the block proposer is secure, a majority of network validators must signal their approval of the proposed block before the encrypted number can be decrypted. Tendermint’s pre-vote mechanism allows the blockchain to “signal” that the proposed block has been authorized by a majority of network validators, and guarantees attempted execution of the block. Therefore, the only way to decrypt the Initial Randomness Seed is for nodes to submit a 66% majority of pre commits/commits to the TEE. The TEE will then verify this majority and decrypt the seed, returning it to the caller in its decrypted state, which can be then used for network randomness.
To prevent errors or malicious behavior, the encrypted number will be accompanied by a proof, which is defined so that an SGX enclave can verify that the proposed encrypted random number matches the block height and active set of the current Tendermint block. The proof is created using a one-way-function that takes the encrypted random value, the block height, active validator set, and the Random Encryption Key (known only to network nodes). With the proof key, SGX enclaves can verify that the proof was made within the enclave.
Secret Network’s TEE is continuously updated in response to changes to the active set of network validators. In order to avoid the issue of verifying changes to network validators, the active network validators influence the result of the derived random value during the decryption and verification process of the random number. Thus, changes to the active validator set produce a different random number, thereby rendering attacks using modified active validator sets ineffective. In addition, attempts to fork the chain and decrypt the number using a different active validator set are also rendered ineffective.
If a harmful actor were to attempt to fork the chain to predict the next number, the attack is rendered ineffective because the next number is generated randomly by the block proposer's enclave and cannot be predicted. Furthermore, a validator cannot execute blocks without the ability to decrypt the random number, which requires submitting prevotes to do so. If the malicious validator attempts to reuse a previous random number, the validator must submit a proof that links the encrypted random number with the block height and validator set, which it cannot.
To avoid being able to replay previous signatures with a new encrypted value, the encrypted number must be part of the transactions signed by the validators when creating their approval votes. To validate the prevotes, the TEE needs to receive the signatures and the original transactions. As an optimization, validation of the prevotes by Tendermint can be offloaded to the TEE to avoid verifying the same data twice.
Given that the voting power of validators fluctuates constantly and the computation of the active set's hash can be demanding, Secret Network achieves optimization by setting a minimum sensitivity to the changes in voting power. However, this can lead to disparities between the pre-vote weight and the weight calculated by the TEE, so the validation threshold must be lowered for the TEE. For instance, on Secret Network, if a 0.1% sensitivity to voting power changes is established, a prevote threshold of 61% would be necessary to consider the worst-case scenario where 50 validators, who together make up 66% of voting power, submitted prevotes with a 0.1%-ε higher voting power than what the TEE is aware of.
Finally, if a malicious actor were to somehow break Secret Network’s SGX and steal the REK it would be inconsequential because random numbers are generated independently for each block, so knowledge of a single random value is of no use without the ability to influence block creation. Even in a scenario where the malicious actor was a validator and could submit random numbers for blocks they propose, it would be ineffective because they do not have access to the consensus seed as stated above.
In conclusion, the Secret Network Random Number Generator (RNG) is a significant breakthrough in the generation of verifiable and fair random numbers on-chain. By leveraging the power of SGX and Tendermint consensus, Secret Network has created a first-in-class on-chain solution that generates random numbers with sufficient entropy, and these numbers are then encrypted, paired with an Initial Randomness Seed, and attached to block proposals where they are decrypted in order to generate true on-chain randomness. Coupled with cross-chain interoperable smart contracts over IBC, Secret Network enables thousands of developers and projects across the Cosmos access to state-of-the-art on-chain RNG.
Full example guide on mitigating privacy risks.
Here we use a sample contract for selling a secret message to illustrate padding, gas evaporation, and side-chain attack prevention using a trusted actor to confirm transactions. In the contract we define three methods: posting a secret for sale, buying a secret, and confirming purchases. All three message types have two optional parameters: gas_target
and padding
. These will be used to help mask which message is being called.
execute
Padding and evaporation are parameters that are set for all message types.
The padding
parameter is used by the client to pad the size of message being sent to the contract. The padding
parameter is not used within the contract itself and is only necessary when using Keplr's amino format. In other cases the dapp or wallet can simply add extra space characters to the end of the json message.
We also want to pad the result sent back to the client. We can use the pad_handle_result
function from secret toolkit to format the response to a fixed block size. If you have responses that are large, you will need a sufficiently large block size to successfully mask the method being run.
After padding the result at the end of execute
entry point function, we use gas_target
along with the evaporation api functions to evaporate any remaining gas to hit the gas target. Note, we simply ignore both the padding
and the gas_target
message parameters using ..
in the match statement.
Just like we want to use padding to hide the message type when sending between the client and the contract, the number of bytes written to the chain can leak information about what is written. For example, a coin amount stored as a Uint128
will write a different number of bytes depending on the size of the number, and also in our example the secret message could leak information based on its length. For data structs we want to store on chain we usually create a stored version and create conversion methods between the unstored and stored versions. Note in the example, we use the secret toolkit space_pad
function to make all secrets stored as 256 bytes long masking the content.
Padding and evaporation help to protect privacy about what types of functions are being called in a contract and about what data is being read and written from storage. However, our contract is still vulnerable to a side chain attack if a buyer can simply purchase a secret without a trusted actor confirming the transaction. This is because a malicious actor could fork the chain and buy the secret on the forked chain without ever paying any coins on the mainnet.
In our case a trusted actor who can confirm the purchase is the seller of the secret. If all purchases must have the ConfirmPurchase
transaction performed after buying, then it is impossible for the side-chain attack to occur. Other contracts that need to implement this functionality might require a trusted third-party actor instead, for example a game contract where turns are executed on-chain.
Although query
cannot change the contract's state and the attacker cannot decrypt the query output, the attacker might be able to deduce private information by monitoring output sizes at different times. See to learn more about this kind of attack and how to mitigate it.
For documentation on using Secret VRF in a native Secret contract, .
Storage
No
Can be rolled back - merkle proofs will make it trusted (end 2023)
Env
yes
Protected against fork attack
Sender
yes
verified - fork protected
Inputs from sender
yes
verified - fork protected
Funds (input from non-compute module)
no
Fork attack can replay failed Tx from mainnet
Contract callback input
yes
anything that originates from within the enclave is trusted
A technical deep-dive into how Secret can provide confidential computation to dApps on other blockchains by leveraging cross-chain communication and Secret contracts.
This section will go over 4 different design options with highlighted use cases. This section should hopefully spark interest into using Secret's Confidential Computing Layer (or "CCL") as a whole and help developers understand the possibilities and limitations of this contract development paradigm.
The CCL in its most basic form is the idea that one can create products with privacy or additional utility for users who are not on Secret Network itself while using Secret contracts in the backend for the application.
Do you want to tap into Ethereum's liquidity, the polygon NFT scene or just want to add additional encryption-powered use cases to your current techstack? Secret's CCL might be a perfect fit.
The below design paradigms are separated based on the data flow in the app. The simplest apps only take data to and from Secret, while more complex apps either keep all the data separate, or take private data in and return public output.
The first design pathway only takes data from a Secret contract but is non-interactive beyond that. This means implementing use cases in this path can be considered relatively simple by leveraging IBC, Axelar GMP or other cross-chain messaging solutions.
In this dApp design scheme, we see Secret as a blackbox that has data accessible useful to any other chain on request. The requested data will have to be transferred publicly. The best example is on-chain randomness via Secret-VRF.
By leveraging a slightly altered version of Tendermint and the Secure Enclave of every validator, Secret managed to create a truly random number generator on-chain. Every block and every contract call, the number is different and the numbers are verifiable via a callback after the block finalises. A developer on another blockchain can design a lottery completely on-chain by determining the random outcome using Secret-vrf. Simply make a contract on the consumer chain that requests a random-number (over IBC) from a Secret contract. Make sure the lottery entries close before requesting the number as it will arrive over public channels after the block closes on Secret. The number is now used within the contract on the consumer chain to assign the winner; no data has to return to the Secret contract.
Want to leverage Secret-VRF for your application on EVM or IBC? - Read more here!
For the second design pathway, data flows towards Secret in an encrypted format with the output merely verifying if the input matches a pre-set pattern or any other defined criteria. Secret contracts here are mainly a decentralized and immutable data-storage mechanism with easily definable access controls.
The best example for this design pathway is web2-esque authentication for application login or other access controlled services. The first dApp leveraging this to its full extent was ALTER, a p2p encrypted communication platform enhanced with Secret smart contracts. The ALTER dApp allows users to store their session encryption key on the Secret Network inside of a Secret contract. Access to this contract and the key is then gated by a Wallet login functionality that ties one's crypto wallet public key to this contract - users can now sign in with their wallet and start messaging. However, this could be taken much further into the web2 realm by allowing sign in via Google web-Auth, password or even biometrics. You can also have usecases where we merely check if a user is who they say they are against identity, credit scoring or other personal information oracles.
The Authentication use case however can be taken much further. A Secret contract could not only check the input against a string it contains, but also influence actions on another chain. The first use case in this region is Unstoppable wallets, a form of on-chain Threshold wallets with unparalleled access control and ownership designed by SCRT labs. For unstoppable wallets, a Secret contract governs one part of a larger private key (MPC) and by programming access control policies into the contract, it will decide to sign or not sign a specific message. Every wallet signer commits the signature for the proposed TX to the Secret contract, the contract checks if the set policies are met and returns a valid signature that can be posted to Ethereum or any other blockchain for that matter. One could even decide to provide the Secret contract with a majority share of the private key allowing it to process transactions on its own for automated strategies or even Walletless applications where users can sign TXs with web2 authentication methods instead.
Want to learn more about Unstoppable wallets? - Watch this talk from the SCRT labs CEO and Secret Network founder Guy Zyskind at Gateway to Cosmos Prague 2023.
In a "sister contract" design scheme the Secret contract and the CCL consumer chain act as two separate entities with which the user interacts separately. This allows developers to work with encrypted output, something none of the other CCL design flows allow.
Sister Contracts is the idea that one can accompany applications on other networks with access controlled data or functions by leveraging a Secret contract. The simplest usecase to explain here is in the realm of NFTs, certificates or other non-fungible personally assigned data objects. It is of paramount importance to many dApps that their usecase is accessible by many, hence why developers often choose for EVM or specifically Ethereum as their home network. With Sister contracts, one can have NFTs, certificates, Invoices or any other item be created on this user-rich chain while simultaneously linking it to a counterpart on Secret network. At any moment in time, the user who owns the item on Ethereum can verify their ownership to the Secret counter-party contract to reveal additional information only visible to them.
The above scenario requires users to interact with two networks to get the full experience of the respective dApp even if cross-chain messaging is already used by the developer. This is because private metadata can only remain private if the user interacts directly with the Secret blockchain without intermediaries like public bridging solutions. Developers can abstract many of these complexities by making intuitive UIs that allow users to use the same wallet for both networks (Metamask for ex.).
Do you want to flare up your NFT with additional private metadata, have access-controlled content for your marketplace or content platform, or store sensitive information related to invoices, IDS or certificates all while keeping your assets or dApps in the most liquid markets? - Then start building a Sister contract with Data privacy for your application now!
Lastly, a hybrid approach is possible between the previous design flows, one where there is a private input from any other chain but what is returned is a delayed public output so that all interactions for the user remain to a single chain. As explained before, we can only guarantee output privacy for a user if they are directly interacting with Secret network, but sometimes privacy is not needed for the output - it is only needed for the input.
One such example is private voting. Voting is inherently a public system, the result matters to everyone who voted and maybe beyond that. However, it is important participants can vote at their discretion with full privacy so there is an honest and non-biased outcome. In blockchain, this is not the case: close to all DAOs and chain governance is public by default.
One can design a dApp that allows Ethereum (or any other chain) DAOs to vote privately by voting via a Secret contract instead. Users encrypt their vote and their VP with the TX encryption key of the Secret network contract. The contract remains open for a set amount of time to collect votes after which it tallies results and emits the output. The output can be sent back to the consumer chain to complete the official voting process.
This same concept applies to many different use cases like sealed bid auctions and obscuring the game-state of turn-based games. The first mainnet application on Polygon called Bidshop is using Secret Network in this way to enable a gamified auction platform. To enable these use cases, cross-chain messaging is needed from either Axelar GMP, IBC or other solutions. Bidshop will be allowing integrations for other dApps on their novel trustless message bridge as well, want to get connected? Let us know!
Want to get started with CCL or explore the possibilities but need some extra help? Get in Contact!
Secret contracts are an awesome tool for native account abstraction. This all comes down to the fact that Secret-contracts can store (parts of) private keys in their state without revealing them to other network participants or observers. This concept was first thought out by scrt labs in a proof of concept multiple years ago [Code], then uttilised by Alter for easy log-in (as explained here), but the newest version uses novel additive HE and MPC to create a form of Unstoppable Threshold wallets [Code]. This proof of concept is now taken by obi.money and primevault to develop walletless experiences and secure threshold wallets for the next million users.
Obi.money can help you setup smart accounts for your Secret dApp already, feel free to reach out to them!
Want to learn more about Unstoppable wallets? - Watch this talk from the SCRT labs CEO and Secret Network founder Guy Zyskind at Gateway to Cosmos Prague 2023.
One can also use the native AuthZ module supported on Secret to have other accounts perform any specified action for any specified amount of time and limit. A usecase for this is more secure voting and autocompounding interfaces like Restake.app.
Smart contracts can also use ICA/ICS-999 (although host module is not supported yet) to control accounts on other chains over IBC and manage funds in interesting ways. Feel free to check out the codebases/Apps of Quasar and calc.finance to get an idea of what that can look like.
One can also leverage cosmos wide forms of account abstraction like this one implemented by Larry from delphi.
Secret network has active integrations with:
Axelar Satellite/app.squidrouter.com
Using the Axelar SDK users can bridge Ethereum and other EVM/Non-EVM assets into Cosmos and Secret arriving immediately as Wrapped SNIP-20 private tokens into their Secret account.
Squid is a cheaper (gas wise) version for Axelar based bridging one can use as a widget.
Houdiniswap/Xblock
One can uttilise the Xblock integration or Houdini widget to let users onboard Crypto assets like XMR, BTC, ETH and more privately directly into SCRT
Kado.money
Kado supports SCRT and direct Noble USDC onboarding onto Secret network. One can uttilise their Widget to allow for in-app onboarding via Paypal, Credit, Debit and more.
TransAk
TransAk support SCRT onboarding using Debit, Credit, paypal and other gateways
OJO - Testnet
Band - Mainnet
Deciding who can and can't see your private data, and how to do it.
Permissioned viewing is entirely unique to Secret Network with its encrypted state. It gives you ability to have precise control over who has access to what data. In order to determine when data is allowed to be released however, you need to have a way to prove who is trying to access it. Our two ways of doing this are through Viewing Keys and Permits/Certs.
With the new Shockwave alpha update, Permits have become more efficient in almost every application. The exception is inter-contract communication permissions. Here, viewing keys still hold a lead. However, in some cases you may want to include an option to use both in your smart contract and leave the decision up to the users. Some users will have a preference for one over the other.
A description of CosmWasm and the framework around it as implemented on Secret Network
Smart contracts on Secret Network follow the widely used CosmWasm framework. The framework is built as a module for the Cosmos SDK and allows developers to build smart contracts with Rust which compile to WebAssmebly (Wasm). This type of efficient and fast binary, combined with the power of a low-level typed coding language, makes CosmWasm a secure and efficient way for developers to write smart contracts. Contracts on Secret have a public binary just like any other smart contract network, only the contract input, output and state is encrypted. The contracts are executed in the Wasm VM as deployed via the Wasmd Cosmos SDK module on the Secret blockchain.
Secret Contracts slightly differ from any standard CosmWasm implementation as they handle encrypted data, secure enclaves, and the key design around that. Secret currently emulates CosmWasm v1.1.9 and is completely compatible over IBC with other V1.0+ Cosmos Smart contract networks.
Want to learn about how Secret handles the Contract-state encryption and other details that relate to how CosmWasm works on Secret? -- Read the implementation documentation here!
A Secret contract has 4 different components as listed below each serving a different function. A deeper overview of the implementation of these 4 components/files is given in the Contract-components section.
Instantiate - the logic that is ran once on initialization of the contract. This will usually be the initial conditions and configuration of the contract.
Execute - this is where the transactional logic resides. These are functions that modify contract data
Query - these are functions that are ran as read-only and cannot modify contract data
State/storage - the long-term storage of the contract. By default, data used by contracts will be lost between transactions.
Each contract will contain these basic building blocks, with their logic being built around how they work. You can read more about the interplay of Entry-points as depicted in the graphic below in this starter guide.
Want to get started with CosmWasm development? - Get started with our 4 part crash course. Dont have much time? - Try our guide on the millionaires problem instead! - It teaches everything about the CosmWasm framework, message types and environment setup in a single page.
Learn how to use submessages on Secret Network
In the CosmWasm SDK, submessages are a powerful feature that allows a contract to execute additional messages within the context of its own execution. SubMsg
just wraps the CosmosMsg
, adding some info to it: the id
field, and reply_on
. reply_on
describes if the reply
message should be sent on processing success, on failure, or both. To create submessages, instead of setting all the fields separately, you would typically use helper constructors: SubMsg::reply_on_success
, SubMsg::reply_on_error
and SubMsg::reply_always
.
You can learn more about submessages !
In this tutorial, you will learn how to use submessages to execute a counter smart contract on Secret Network from another Secret Network smart contract 😊
git clone
the :
In this tutorial, we will use submessages to execute a that is already deployed to the Secret Network Testnet. Thus, we are working with two smart contracts:
- which executes the Counter Contract using submessages
- which is executed by the Manager Contract
This tutorial follows the same design patterns of the tutorial, but uses submessages in place of Wasm messages
Increment
contains one parameter, the string contract.
This is the contract address of the counter contract, which we will increment.
Where is the code hash?
Unlike other Cosmos chains, Secret Network requires the hash of the smart contract in addition to the address when executing calls to smart contracts.
Contract hashes are what binds a transaction to the specific contract code being called. Otherwise, it would be possible to perform a replay attack in a forked chain with a modified contract that decrypts and prints the input message.
Submessages offer different options for the other contract to provide a reply. There are four reply options you can choose:
Here is an example of how to handle the reply:
The submessage returns a Result
containing:
Ok(Response)
if the submessage executed successfully, with an action attribute set to "increment".
Err(ContractError)
if the submessage execution failed, with the error encapsulated in a custom contract error.
Now let's use this manager smart contract to increment a counter smart contract with submessages!
The counter contract we will be executing is deployed here:
cd
into manager/node:
Install the dependencies:
Upon successful execution, you will see a tx
returned in your terminal:
Now, let's query the counter contract to make sure it was executed correctly!
cd
into counter/node:
Install the dependencies:
And run node query
:
You will see that the counter contract has been incremented by 1 :)
In this tutorial, you learned how to utilize submessages in the CosmWasm SDK to perform complex, atomic operations within smart contracts on the Secret Network. This guide walked you through executing a counter smart contract from another contract, detailing the setup of a Manager Contract that triggered the Counter Contract using submessages, managing replies, and verifying the execution results 🤓
As of Secret Network V1.7 delegators can autocompound their SCRT Staking rewards using the native Restake feature.
Normally SCRT delegated to a validator accrues SCRT rewards every block as a claimable balance. With the autorestaking feature enabled however the SCRT rewards will never be claimable and also never in the claimed balance state.
Every x amount of blocks (currently 1000 set by the chain parameter - restake_period
) all claimable rewards, on the Restake enabled validators by the user, will be removed from the reward balance and accredited to the staked balance.
The code implementation for native Restake is available
Delegators can enable Restaking independently for every validator they stake with using the set-auto-restaking
command available in or on any UI implementing the Secret.JS command.
The feature only activates if the delegator has a balance with the validators larger than minimum_restake_threshold
which is currently set to 10 SCRT
Start autocompouding your rewards now at the !
An introduction to Secret VRF, a secure and verifiable random number generator
Secret Network's randomness API allows developers to access random numbers in their CosmWasm contracts, enhancing the capabilities of the platform. The randomness feature is accessible within Secret Contracts through the Env struct. It includes an optional random field, which contains a random number as a Binary type. The random field is only available when the "random" feature is enabled.
Randomness is essential in many applications, including:
Gaming and gambling platforms, where fair and unpredictable outcomes are crucial
Cryptographic systems that require secure random keys or nonces
Randomized algorithms for various use cases, such as distributed systems or optimization problems
The proposer for each block generates a strong, random seed inside .
This seed is then included in the block header and signed by all validators who can verify its authenticity inside their SGX.
Secret Network's in-SGX light client prevents the proposer from simulating a block before all other validators sign it. Consequently, the proposer cannot gain maximal extractable value (MEV) by generating random seeds until they find a favorable simulation of the block.
Before calling the contract, the chain injects env.block.random = hkdf_sha256(block_random_seed + wasm_call_count)
.
Thus each contract call gets a unique random seed.
For a more in-depth explanation of why and how this method of randomness works feel free to read the
We will be designing a Manager Smart Contract which can execute a Counter Contract that is deployed to the Secret testnet. Let's start by examining the message that we want to execute on the counter smart contract. In the src
directory, open and review the Increment
msg:
However, there is no need to pass a code_hash
when doing cross contract or sub message calls because we have designed the helper function which we call internally when executing the Increment
function.
Notice that we implement for our ExecuteMsg
enum, which is what allows our submessages to be converted into a CosmosMsg
and executed:
In order to handle the reply from the other contract, the calling contract must implement a new entry point, called :
Or deploy your own counter contract :)
Run node to execute the counter contract:
Optimizing your code is especially important on blockchain due to the fact that inefficient code costs your users money and can jam up the entire network. With any function you make, try to complete it in as few steps as possible and avoid unnecessary computation. One of the most costly actions you can make is loading and saving data, thus you should ensure you are only saving what is needed. Aside from this, there are a few other tips we can provide.
When dealing with large sets of data, vectors and arrays can be especially problematic because in order to save load a single item within them, you must effectively load the entire vector and all of its contents, which naturally can amount to an enormous amount of inefficiency when you only need one item! There are several ways around this: Keymap, AppendStore, or even deconstructing vectors into a set of data keyed to a counter variable. Research each options pros and cons, and choose which one is best for your particular contract.
An introduction to the differences between standard CosmWasm smart contracts and Secret Contracts
Secret Contracts are an implementation of the Rust-based library CosmWasm, while additionally enabling computation with private metadata. This brings unique use cases to Secret Network which aren’t possible on other blockchains, and also means that Secret Contracts have features that are different from standard CosmWasm contracts including: contract hashes, a specialized approach to iterators, raw queries, and contract migration.
An in-depth analysis of a smart contract that can be deployed on a vanilla CosmWasm chain as well as Secret Network can be found in our cross-deployment tutorial.
Contract hashes are required to bind a transaction to the specific contract being called. Otherwise, in a forked chain you could replace the called contract with one that decrypts and prints the input message.
Secret contracts do not have access to standard CosmWasm iterators because keys and values of data are encrypted when stored on-chain. This means that there is no logical structure that can allow iteration over these keys. However, Secret Labs has developed iterator functionality that can be imported using the Secret Toolkit storage package.
Raw queries are not available since raw data is encrypted when stored on-chain. Data can be decrypted only by the contract that owns the data, and so data is only available via permissioned contract queries (smart queries).
CW-plus, a toolkit for CosmWasm contracts is largely supported but might not be optimal to use in all cases. Secret has its own Toolkit called Secret-Toolkit that has the majority of CW-plus functionality. Alternatively one can alter small parts of CW-plus around the missing Iterator feature to leverage that library as well.
Working with a Singleton in CosmWasm
In CosmWasm smart contracts, a Singleton
is a specialized structure that combines the concepts of PrefixedStorage and TypedStorage, allowing you to store and manage a single data entity efficiently under a specific storage key. This is particularly useful when you need to store just one instance of a state (such as contract configuration or global data) without the need for complex key management.
How Singleton Works:
storage: &'a mut dyn Storage
: The Singleton
holds a mutable reference to the storage layer, which is where the contract data is stored on-chain. This reference allows it to interact with and modify the stored data.
key: Vec<u8>
: The Singleton
operates on a single storage key, provided during its instantiation. This key is typically transformed using to_length_prefixed
to avoid collisions with other stored data, ensuring that the contract can safely manage this unique piece of state.
data: PhantomData<T>
: Rust’s PhantomData
is a marker for the data type T
without actually holding an instance of it. This allows the Singleton
to enforce that it works with a specific type (T
) that must implement Serialize
and DeserializeOwned
traits, which are essential for encoding/decoding the data stored in the singleton.
No Need for Multiple Keys: A Singleton
simplifies storage management by removing the need to manually manage multiple keys. The storage key is predefined in the constructor, and all operations (read, write, update) are tied to this single key. This ensures you are only ever working with one data object in storage.
Type-Safe Storage: The Singleton
uses TypedStorage functionality, which ensures that the data stored and retrieved from the storage is type-safe. This means you always work with the exact type you’ve defined (in this case, T
), reducing the risk of errors during serialization and deserialization.
Avoids Key Collisions: By applying the to_length_prefixed
transformation to the given storage key, the Singleton
ensures there are no name collisions in storage. This is particularly useful in larger contracts where multiple data points might otherwise share similar key names.
There are common wrapper functions that are included in the state.rs
of most secret contract templates, which are save,
load,
may_load,
remove,
and update
.
save
The save
function overwrites previously saved data associated to that storage key. save
will serialize the model and store, returns an error on serialization issues:
Purpose: It takes a reference to the storage and the data to be saved (usually the contract's state).
Usage: You call save
when you want to persist new data or update existing data in storage.
load
This function reads or retrieves data from the contract's state. It is commonly used when you need to access a piece of data stored in the contract, such as the current state or configuration.
Purpose: It accesses the storage and loads the state associated with the provided key.
Usage: load
is used whenever the contract needs to read existing data, such as fetching the configuration or state before making any modifications.
may_load
Purpose: This function attempts to retrieve and deserialize data from storage, returning Ok(None)
if no data exists at the given key. It returns an error only if deserialization fails.
Usage: may_load
is used when the existence of the data is optional. It allows you to safely check if data exists and handle cases where the data might not be present.
Behavior: It fetches the data associated with the key and attempts to deserialize it using may_deserialize
. If the key does not have any data, it returns Ok(None)
instead of an error, making it more forgiving than load
.
remove
Purpose: This function deletes or removes data associated with a specific key from the contract’s storage.
Usage: remove
is used when you no longer need to store certain data in the contract and want to free up space by removing the key-value pair from storage.
Behavior: It accesses the storage
object and deletes the data associated with the provided key.
update
This function loads, modifies, and then saves data back to the contract's state. It’s commonly used when the contract state needs to be modified, such as incrementing a counter or updating a setting.
Purpose: It allows for atomic updates to the state by accepting a closure (a function passed as an argument) that modifies the data and then saves it back to storage.
Usage: update
is ideal for changing existing data in the state, like incrementing a value, while ensuring that the storage operation is performed safely in one transaction.
save
: Persists or updates state in the contract’s storage.
load
: Retrieves and reads existing data from the contract’s storage.
may_load
: Fetches and deserializes data if it exists, returning Ok(None)
if no data is found at the key, and an error if deserialization fails.
remove
: Deletes the data associated with a specific key from storage.
update
: Modifies existing data in storage, allowing for atomic and safe updates.
These wrapper functions provide clean abstractions to manage the contract's state, making the contract logic easier to write and maintain. They are especially useful in ensuring that storage operations (which are critical for on-chain data) are handled efficiently and consistently across the contract.
get_contract_code_hash helper function
To retrieve a Secret Network smart contract's code hash from within a Secret contract call, you can use Stargate queries to directly query the on-chain data from inside the contract.
This helper function is particularly interesting if you desire to make complicated contract structures that involve submessages or just cross-contract messages to different Secret Network contracts. With this code snippet, you do not need to supply the code_hash
of each contract that you are going to call in submessage or normal messages. It is sufficient to know the contract address of the other contract, the code snipped will fetch the latest on chain code_hash
for you.
Be aware that contracts can be upgraded on Secret Network! Since this code snippet always fetch the code_hash
directly from the chain without any extra check (which was implictly done by manually supplying the code_hash
), you need to be careful about silently (perhaps maliciously) upgraded contracts which potentially releveal confidential information.
The Secret Network team has designed a helper function, get_contract_code_hash
, exactly for this purpose.
See an example usage of get_contract_code_hash here.
Simply add the Anybuf package and the "stargate"
feature for cosmwasm-std to your cargo.toml
:
And then add the function to your contract:
Since all computations are done privately inside the TEE of the network full nodes and the state of the blockchain is encrypted we would expect that users have no access to their own balance, debt positions, tokens, and other important information.
However, because users sign transactions with their own private key the protocol knows they should have access to their information. Viewing keys and Permits are the tools used to provide only the owner access to the private data of their signed transactions.
This part of the documentation will cover the no-code overview of the workings of Permits and viewing keys.
Please follow the guide here!
Permits are the successor to viewing keys. With increases in efficiency made in the Shockwave update they are now the defacto viewing permission method in almost every situation.
Permits are simply a dummy transaction signed by a wallet. The wallet is able to use this signature as proof that the transactor is who they say they are. Any wallet capable of using Tendermint is able to carry out this process.
For an in-depth implementation of query permits, navigate to the snip-24 section of the docs here.
Generating a permit happens outside of the smart-contract, but checking them in your smart contract is extremely simple. Once again the Secret-Toolkit provides a package for us, aptly named permit
. The function that checks the permit is called validate
shown below.
To use this, simply call validate
inside of your query provided with the necessary permit variables.
Permits can be used at any time, and are amazing for almost every permissioned viewing application. The only time it may be more efficient to use viewing keys instead is during inter-contract communication.
Introduction to Secret Network viewing keys with code examples
Viewing keys are passwords meant to validate users at times when the blockchain cannot. Specifically in queries, the query sender isn't authenticated and the contract doesn't know who is the querier. Therefore viewing keys were invented to provide a way of access control for users:
Alice sends a transaction set_viewing_key(password)
The contract stores (alice,password)
Later on, a query is sent to the contract query("balance",alice,password)
If (alice,password)
matches what's in storage, the contract returns Alice's balance to the querier.
To see an implementation of viewing keys in a Secret smart contract, check out the Secret Labs Viewing Keys example repository
Secret Network developed the secret-toolkit
viewing key package for creating and managing viewing keys in Secret smart contracts. The methods provided include:
set_seed
: Sets an initial pseudorandom number generator (PRNG) seed for the store.
create
: Creates a new viewing key, saves it to the storage, and returns it.
set
: Sets a new viewing key based on a predetermined value.
check
: Checks if a viewing key matches an account.
To make use of the secret-toolkit viewing keys package, import it into your cargo.toml
file:
If you would like to see an example implementation of Secret Network viewing keys, see the Secret Labs examples repository here.
This contract is designed to create top secret messages that can only be decrypted if the user has the correct viewing key that is associated with the secret_message
struct.
Let's review the try_create_secret_message()
function:
This function stores a secret message at a specified index in the contract's storage, which is mapped to a viewing key. This ensures that the secret message can only be accessed by the correct viewing key, and that each secret message has its own unique viewing key.
Let's go over it in more detail:
A new viewing key is created by calling ViewingKey::create
. The parameters passed include the mutable dependencies (which includes the storage), the environment env
, the MessageInfo
, the sender account, and a hard-coded entropy value b"entropy"
.
The secret message is then stored in the contract's storage and is mapped to the viewing key. The SECRET_MESSAGE.insert
line is performing this task.
Next, the viewing key itself is stored in the contract's storage, but this time indexed with a user-defined index
parameter and prefixed by the sender's account address. This is done by VIEWING_KEY.add_suffix(info.sender.as_bytes()).insert(deps.storage, &index, &viewing_key)
.
Finally, the function returns a default Response
instance indicating successful execution of the function.
Viewing keys provide a mechanism for access control in blockchain applications when the blockchain itself can't authenticate the query sender. Secret-toolkit
allows developers to set seeds, create and check viewing keys, and set a new viewing key based on a predetermined value 🎉
Should you have further questions, please reach out on discord and a Secret developer will get back to you shortly 🚀
A new feature in Secret Network v1.9 is Execution Finalization.
Execution Finalization is a concept where the contract notifies the chain that the current execution must be the last massage in a single transaction. Or, in other words, the last part of an execution unit. This will cause the chain to revert the transaction if any action is taken after this flag is set.
Secret Network (and in fact all Cosmos chains) run discrete blocks of execution instructions called transactions. A single transaction can contain multiple messages. Each message contains a specific instruction. Any failure of any message in the transaction will cause the entire transaction to be reverted. This behavior can be exploited by attackers (or even non-maliciously) to select only favorable scenarios to be executed, while the unfavorable ones to be reverted.
For example, consider a dice game. The user sends some bet. If he makes the correct bet, he will receive his original bet plus his winnings. Otherwise, his bet is lost. An attacker can abuse such logic by sending a transaction with two messages. The first is the bet message, while the second is message that will conditionally fail if the bet is lost. This way, he is guaranteed to make a profit.
This same behavior can also be used for flash-loans, which are not inherently malicious but can be used to leverage capital for exploitation without risk of significant loss.
The new FinalizeTx message is now available for developers to toggle in case a specific execution path should be the last in a transaction. That way, it is guaranteed to be executed as the contract expects and cannot be reverted by the user.
To use FinalizeTx, simple append the FinalizeTx message when returning a result from your contract:
Messages are executed FIFO (first in, first out). If you want to send other messages before finalizing the execution, simply append them to the response before the FinalizeTx message.
We suggest developers consider whether or not a user conditionally reverting transactions is behavior that may affect the security of their application, as it may allow for scenarios that reveal confidential information or result in loss of funds. That being said, the trade-off is that such executions cannot be chained with other contract interactions, so usage of this feature should not be used without careful consideration.
Factory contracts are contracts that create other contracts. They are useful for:
Dividing your app into parts, each with different responsibilities.
Extending the scope of your app by dynamically adding a piece of functionality.
Managing different parts of the app from a single location, etc.
The Factory Contract Template is a good starting point for setting up your own factory contracts and experimenting with them.
Here are some examples of active projects that use factory contracts:
SecretSwap, where a Factory contract creates a new Child contract for every new pair supported on the exchange. (Cosmwasm v0.10).
SecretJack, where a Parent "Bank" Contract which manages the funds creates a single "Game" Contract which manages the game logic. (Cosmwasm v0.10).
After the child contract is stored on chain, you can instantiate child contracts from the factory in the following manner:
Assume the child contract expects the following instantiate message:
To instantiate the child contract, you can send a submessage from the parent with the child's instantiate message:
If you don't care about the reply, you can send a regular message instead:
Assume the child contract expects the following instantiate message:
To instantiate the child contract, send a message on the parent's response:
Introduction to writing tests for Secret Smart Contracts
Tests help ensure the quality, stability, and correctness of your Secret smart contracts. A single bug or vulnerability in a smart contract can lead to consequences such as the loss of funds or the compromise of sensitive information. By thoroughly testing Secret smart contracts with unit tests, developers can identify and address issues early in the development process, reducing the risk of errors and vulnerabilities for contracts that are deployed to Mainnet. Let's begin by learning about unit tests in Secret Contracts. 🎉
Secret network has active integrations with:
Using IBC Hooks, a contract on Ethereum can call a contract on Secret and get a response back.
Axelar Satellite/Squid Router
Using the Axelar SDK users can bridge Ethereum and other EVM/Non-EVM assets into Cosmos and Secret arriving immediately as Wrapped SNIP-20 private tokens into their Secret account.
is a cheaper (gas wise) version for Axelar based bridging one can use as a widget.
Uses Secret's confidential computation to open a connection to any EVM chain - find
Coming soon:
To access all of our current token bridging interfaces, see the
Secret has integrations with the following wallets:
Keplr
Leap
Fina.Cash (mobile friendly)
Starshell (added privacy)
Metamask (only signing, not SCRT storage)
Cosmostation
Citadel.One
You can check out app.shadeprotocol.io or dash.scrt.network to try out these various wallets. Documentation for wallet integration is available here:
Secret Network is the only non-EVM cosmos chain that supports native Metamask signing! It is an awesome onboarding tool for new users or can be the missing tool to have people use Secret in your Privacy as a Service flow without ever needing a 2nd wallet or other gas.
Secret Network uses the Cosmos SDK and its infrastructure which makes it so that the identity of a querier (someone requesting data) cannot be cryptographically authenticated. On public networks this might not be a problem for users as they can query data by using their public key. However, on private networks (where only the owners should have access) this is a problem. To solve this problem, viewing keys were implemented as a part of the
Viewing keys act as an encrypted password for the viewing of data related to a specific smart contract and private key. The password can only be created by the private key owner, but anyone with the password who knows the accompanying public key gets access.
To create a viewing key a user signs a transaction for a specific contract (ex sSCRT token), this transaction asks for a random input from which it generates a viewing key. The viewing key is saved in the contract state together with the user's public key (address). To query for private data (ex balance, history) both the viewing key and the accompanying address is required.
Anyone who knows the correct combination of key + address can view the private data without needing access to the private key of the address. Secret Network allows users to maintain control over their data and decide what is shared and with whom.
for more on info on Viewing keys check out the Development section for and the SNIP-20 specification
You can use the Feegrant module or Smart contract based fee abstraction to simplify the usage of your dApp as explained
Secret also allows for Gas abstraction for users by leveraging the CosmosSDK . This module allows one to submit transactions where a different wallet is paying the gas fees as long as they granted you a budget to do that.
Documentation to create Feegrant functionality in your UI are . This tool is widely used in different Secret UIs (for ex: ) and there is a community run FeeGrant faucet available for dApps to use. - -
You can use Smart-contracts with Fees deposited in them to abstract fee usage and automate tasks for users - An example of this is the which deposits users rewards into their wallet daily. There is also the feature of "opportunistic execution" which allows additional privacy to perform automated actions by using leftover gas from the Gas evaporation function in the Secret compute module. -
Optionally perform code and state migrations of Secret Network smart contracts
In some cases, for example when wanting to deliver support to users and deploy bug fixes, it makes sense to have the ability to change the code. This is especially true for services which wish to change algorithms, add features, etc. For these use cases, contracts can be written to allow various modes of migration. Doing so means that anyone inspecting the source code will be able to know that such a migration is possible and will require that users have a higher level of trust in the contract administrators, as they can effectively install a backdoor in their product.
Secret used to not support the native CosmWasm implementation of contract migratability, since v1.11 it does. The old manual examples are still displayed here as a reference.
During the v1.11 upgrade a set of old contracts were allowed to become migrateable, these contracts behave slightly different in that they are callable by both their new and old code hash. For more information about the transition period please read
How to write Unit Tests for Secret Contracts
To ensure that Secret smart contract code is reliable and free of errors, it's important to test it thoroughly. One effective way to achieve this is through unit testing.
Unit testing involves breaking down a program into its smallest components or units and testing each one in isolation. By testing each unit separately, developers can pinpoint the root cause of any errors or bugs and fix them quickly.
In Rust, unit testing is supported by the , which provides a set of macros and utilities for writing and running tests. Rust's testing framework is built into the standard library and is designed to:
Set up any needed data or state.
Run the code you want to test.
Assert the results are what you expect.
Secret Contracts utilize the Rust testing framework as well as additional cosmwasm-std
utilities, such as and functions.
We will explore the basics of unit testing for Secret Contracts, including how to write and run tests, organize test code, and use test-driven development (TDD) to ensure code quality. With this foundation, you'll be well on your way to writing robust and reliable Secret contracts.
For a practical understanding of writing unit tests for Secret Contracts, navigate to the . Here you will find 3 unit tests:
proper_initialization()
increment()
reset()
Let's examine proper_initialization()
to understand how unit tests check for correctness of various execution messages in Secret Smart Contracts.
The use cosmwasm_std::testing::*;
line imports all the testing utilities from the cosmwasm_std
crate.
The #[cfg(test)]
annotation on the tests module tells Rust to compile and run this test code only when you run cargo test
, and not when you run cargo build
. This saves compile time when you only want to build the library and saves space in the resulting compiled artifact because the tests are not included.
The mod tests { }
block defines a new module named tests
. This is a conventional way of organizing test functions in Rust. Tests can be run with cargo test
.
If you run cargo test
, the terminal will return 3 passing tests!
Since Rust's testing framework, Cargo, runs tests in parallel by default, this can lead to nondeterministic behavior if the code being tested is not designed to handle concurrent execution safely (for example, keymap
with iterator
and AppendStore
). The immediate fix for this issue is to enforce the tests to run serially, thus avoiding the problems caused by concurrent access and modification. This can be achieved by using the following command:
This command tells Cargo to run the tests with only one thread, effectively serializing test execution and preventing concurrent access to shared resources.
Mock dependenices
The let mut deps = mock_dependencies();
line creates a new set of mock dependencies, which simulate the necessary dependencies of a smart contract in a testing environment. These dependencies include storage, a message handler, and an API client.
The let info = mock_info("creator", &[Coin { denom: "earth".to_string(), amount: Uint128::new(1000), }], );
line creates a new mock transaction context, which simulates the context in which a transaction would be executed on the blockchain. This context includes information about the sender of the transaction (in this case, the creator), as well as any tokens (in this case, a single "earth" token with a balance of 1000).
Taken together, these lines set up a mock environment in which the counter contract can be tested. The proper_initialization()
function can now perform its tests using these mock dependencies and transaction context.
Init_msg
The let init_msg = InstantiateMsg { count: 17 };
line creates a new InstantiateMsg
struct with an initial count value of 17. This message is used to initialize the counter contract's state.
The let res = instantiate(deps.as_mut(), mock_env(), info, init_msg).unwrap();
line instantiates the smart contract with the given dependencies that we defined above, as well as the transaction context, and instantiation message. This initializes the smart contract's state and returns a Response
struct that contains any messages that the contract emits during initialization.
The assert_eq!(0, res.messages.len());
line checks that no messages were emitted during smart contract instantiation. If any messages were emitted, this assertion would fail and the test would panic.
The let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
line queries the smart contract's state using the QueryMsg::GetCount
message. This retrieves the current count value stored in the smart contract's state.
The let value: CountResponse = from_binary(&res).unwrap();
line deserializes the query response data (which is in binary format) into a CountResponse
struct. This struct represents the result of the query and contains the current count value.
Finally, the assert_eq!(17, value.count);
line checks that the current count value retrieved from the smart contract's state is equal to the expected value of 17. If the current count value is not equal to 17, this assertion would fail and the test would panic.
A description of building the same code for both vanilla CosmWasm and the Secret version.
The crosschain contract that we are examining can be cloned from . This repo is a demo of writing a simple voting contract that is intended to be deployed on multiple chains with different types of CosmWasm in a single codebase. This demo supports two types of CosmWasm: vanilla
(i.e. original, like Terra or Juno supported) and secret
(i.e. SecretNetwork supported).
The contract code uses conditional compilation and feature flags in Rust to support different configurations within a single codebase. If you navigate to , you will find two sets of imports for different features, either "secret"
or "vanilla"
. The imports are conditionally compiled using #[cfg(feature = "…")]
attributes. The "secret"
feature uses the secret_std
crate, whereas the "vanilla"
feature uses the cosmwasm_std
crate.
For more information about the feature differences of Standard CosmWasm vs the secret version please visit TLDR: No raw queries, iterators and upgradeability
Contract.rs defines several functions:
instantiate
: This function initializes the contract and sets the initial vote count for both options to 0. In this instance, it is the same for both Secret and vanilla CosmWasm.
execute
: This function processes the ExecuteMsg
, which supports voting. It calls the vote_impl
function to perform the vote.
vote_impl
: This function is implemented twice, once for each feature ("secret"
and "vanilla"
). It checks whether the voter has already voted, and if not, it updates the tally for the chosen option.
query
: This function processes QueryMsg
, which supports two queries: getting the vote tallies (Tally
) and getting a list of voters (Voters
). For the Tally
query, it returns the vote count for both options. For the Voters
query, it calls the voters_query_impl
function.
voters_query_impl
: This function is also implemented twice for each feature. It retrieves the voters list based on the provided page number.
Let's examine the differences in vote_impl
based on which type of CosmWasm we are using. The overall structure and logic of the function are the same, but there are differences in the specific methods called on the VOTERS
, OPTION_1
, and OPTION_2
objects. These differences arise from the different crates used for the "secret" and "vanilla" features.
In the "secret" version of vote_impl
:
VOTERS.contains(deps.storage, &info.sender)
is used to check if the voter already exists in the storage.
VOTERS.insert(deps.storage, &info.sender, &1)
is used to insert the voter into the storage.
In the "vanilla" version of vote_impl
:
VOTERS.has(deps.storage, info.sender.clone())
is used to check if the voter already exists in the storage.
VOTERS.save(deps.storage, info.sender, &1)
is used to save the voter into the storage.
The rest of the function, including the match statement for updating the vote tally, is the same between the two versions. Now let's examine the difference in querying functions between the two versions.
Vanilla CosmWasm Iterators are not supported on Secret Network because users cannot iterate over data stored by different users as there is no access to their dedicated encryption key. However, iteration is still possible on Secret Network through the use of the secret_toolkit
for storage in place of cosmwasm_std
.
In the "secret" version of voters_query_impl
:
The VOTERS.paging_keys(deps.storage, page, PAGE_SIZE as u32)?
method is used to retrieve a list of voters corresponding to the requested page. This method is specific to the secret_toolkit
storage API and directly handles pagination.
In the "vanilla" version of voters_query_impl
:
The VOTERS.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
method is used to retrieve an iterator over all the keys (voters) in the storage. Pagination is implemented manually in the function.
The main difference between the two implementations is that the Secret version relies on a secret_toolkit
pagination method (paging_keys
), whereas the Vanilla version manually implements pagination using iterator methods.
The contract uses state_secret
or state_vanilla
modules for managing the state, depending on the selected feature. The state management includes saving the vote counts for each option and managing the list of voters. Let's examine the differences between state_secret.rs
and state_vanilla.rs
.
In the Secret version:
The secret_std::Addr
is used as the address type.
The secret_toolkit::storage::Item
and secret_toolkit::storage::Keymap
types are used for storage management.
The storage objects are initialized with Keymap::new
and Item::new
methods, passing the byte representation of the corresponding prefixes.
In the Vanilla version:
The cosmwasm_std::Addr
is used as the address type.
The cw_storage_plus::Item
and cw_storage_plus::Map
types are used for storage management.
The storage objects are initialized with the Map::new
and Item::new
methods, passing the corresponding prefixes directly.
Thus, the Secret version relies on the secret_std
and secret_toolkit
crates, while the Vanilla version uses the cosmwasm_std
and cw_storage_plus
crates. However, the overall purpose of the state management objects (VOTERS, OPTION_1, and OPTION_2) remains the same in both versions.
Since the V1.11 Upgrade Secret now supports native migrateability for contracts
The Secret Network's version of the CosmWasm runtime disables the ability to natively migrate contracts. Data written to storage using some contract can only ever be accessed by that same contract. This is done for trust and security reasons. Once a user sends some information to a secret contract, there should not be an easy way for anyone to change the code that has access to this information. If it was possible, then the new code would be able to leak any information entrusted by the user to the old code.
That being said, contracts are fully programmable, and "not easy" is not impossible. If it was, we wouldn't even be here 😉. In some cases, for example when wanting to deliver support to users and deploy bug fixes, it makes sense to have the ability to change the code. This is especially true for services which wish to change algorithms, add features, etc. For these use cases, contracts can be written to allow various modes of migration. Doing so means that the anyone inspecting the source code will be able to know that such a migration is possible and will require that users have a higher level of trust in the contract administrators, as they effectively install a backdoor in their product.
The contracts in this repository show various migration strategies that can be used and adapted in real-world contract. These should not be considered production-ready examples, and some applications may have more specific or creative migration paths.
For examples of implementations of the techniques described here see:
Did you know? Contract migration/upgradability in a privacy-preserving way is on the roadmap for Secret Network
The idea of a handoff architecture is that the original deployed contract is written in a way that allows a contract admin to give a new contract permission to directly collect private information from it. In order to add a basic level of protection, the reference in this repository makes sure that the information can only be collected by a contract, and not just a regular user.
This is done by exchanging a secret with a contract through a callback. The admin would provide the first contract with the address and hash of the new contract. The first contract would then generate a secret based on some source entropy (internal or external) which it would send to the second contract (A malicious admin can still write a contract that would leek this secret, but we already decided we have to trust the admin). The second contract can then query the first contract, either once or with some paging, to retrieve and store all the previously hidden information. An important detail shown in the example is that the first contract essentially gets locked as soon as the migration starts. This is done in order to make sure the data stored in it doesn't change anymore. new interactions should be with the new contract, after the admin completes the migration.
In theory, one could write a contract that only forwards messages to another contract that is configured by the contract admin. Users would then only interact with the facade, rather than the implementation contracts. You can imagine that it would define a small, reserved interface of messages that do not get forwarded, and would only be seen by the facade. Messages such as ChangeOwner
, ChangeImplemntation
, etc. This would allow great flexibility for contract authors to just deploy a new version of a contract, and point the facade to the new implementation. Paired with the technique, you could have a complete change of implementation, with only a short downtime. (The facade contract can even be written in a way that informs users that the service is temporarily down, and that they should retry in a couple minutes)
Unfortunately, this is currently not feasible, at least not in a pretty way, because of limitations of the available json parsing libraries. The only one i'm aware of that can run on an environment that forbids floats is serde-json-wasm
, but it doesn't support parsing unstructured messages (like e.g., serde_json::Value
). A workaround then, is to include the message to the "backend" contract as a serialized JSON in a string field of the message to the facade. It's not as pretty but can be implemented without having to start rewriting ecosystem packages.
Another, less flexible alternative, is to define the full API that you will commit to for the rest of the application's lifetime. Then. you can just parse-reserialize messages that aren't meant for the facade itself.
This is more of a migration technique for products rather than specific contracts, but it's worth mentioning.
The idea is that if your application needs to deploy a new contract every time it serves customers (e.g., game rooms, bids, etc.), then you can write a factory contract that handles some things for you. The factory contract can be used for many things, from providing one authentication method for queries to multiple contracts, for aggregating usage statistics on-chain, for sharing information between contracts in a structured way, and more. Another use for it, is changing the implementation of new contract that will be deployed in the future.
You can think of the factory as a sort of facade, so it can also include a handle that would allow the admin to reconfigure the code-id, and maybe even init parameters, used to open new contracts (game rooms, bids, etc.).
A SNIP-20 contract can also implement the Receive
interface and allow users of one token to Send
a swap request to the new token. The new token will lock those funds in its custody for the rest of time, and mint new tokens, with some exchange rate, to the sender address. This technique will require zero preparation by the original token contract.
A similar technique can be used by applications with similar ideas to SNIP-20.
In case of emergencies, it can be useful to have certain admin functions to alter the functionality of the contract in some way that are only usable by the admin. For example, if your contract receives funds and forwards them to a different address, but you lose the keys to the to that address, you want to ensure their is a function implemented that will enable you to change it. Or possibly there may be some situation where you want to deactivate the contract (aside from the admin function to reactivate it).
It's important to consider what possibly go wrong with the contract and what can be done to fix it ahead of time, and implement those fixes. On the flip side, making too many functions alterable by the admin may make your dapp more centralized and users may become more cautious to use it. Balance these two sides carefully during dapp creation.
In some cases, you may want to write a new updated smart contract. This can be for multiple reasons, such as fixing bugs or adding new features. However, because you cannot just change the code of a contract already uploaded on the chain, the upgrade process becomes significantly more complex than web2 and there are many things you will need to consider.
Due to the expense of sending large amounts of data, it would be unreasonable or impossible to send all data to a new contract in a single transaction if you store a significant amount, such as in the case of a token contract which may have tens of thousands of users. You will have to set up functions to transfer data strategically and in manageable chunks.
If a single admin calls a function that would transfer over all user data to a new contract, in many cases that would also mean they could simply read the data themselves by transferring it to a contract they have total read access to. To prevent this risk to user privacy, it is wise to only give your users permission to transfer over their own data. Using the example of a token contract once again, allow only the users permission to transfer their token balance to the upgraded token contract. Otherwise there is a risk of an admin reading their balance without their permission.
In some cases, you may want to ensure once the data is transferred it is immediately deleted, or otherwise prevented from being accessed again from the old contract. This is to prevent the possibility of a user taking advantage of their data existing on two contracts simultaneously. For example, if an NFT contract was updated and the NFTs were moved over to the new contract, but the old contract did not remove them after transferring, you could risk your users selling both the old and new copy their NFTs. Your specific contract may not require data deletion like this, but it is important to consider all possibilities and think from the perspective of potential attackers to decide if this is a risk you need to prevent.
Optionally perform state migrations of Secret Network smart contracts
Contracts on Secret Network can be initialized as migratable, which allows the contract administrator to upload a new version of the contract and then send a migrate
message to move to the new code, which "migrates" storage from a previous contract to the new contract.
There are two key components to a migratable contract:
A designated admin
whose address will be allowed to perform migrations.
The availability of a MigrateMsg
transaction.
On , the contract creator can specify an admin
address, which can be the contract creator's wallet address, an external account, or a governance contract, etc. Whatever the address is, this admin
gains the ability to migrate the contract to a new codeId
and codeHash
, and can also update or clear the admin
address. When the admin
invokes the MigrateMsg
message, the migrate()
function is called on the new contract, where the new contract can optionally perform state migrations from an old contract.
Once the migrate()
function is invoked, the contract address now points to the new code, and anybody contacting that address will reach the new code. The old code becomes unreachable.
Performing a contract migration is a three step process:
Write a newer version of the contract you wish to update
Upload the new smart contract, but don’t instantiate it
Use a dedicated transaction to point the new contract to the code you wish to migrate
When migrating, the new contract must have a migrate
function as an entry_point.
The migrate
function provides the ability to make any desired changes to the contract's state, similar to a database migration.
If the migrate
function returns an error, the transaction will abort and no state changes will occur.
Secret contracts instantiated prior to the v1.11 network upgrade can be migrated via a SCRT governance signaling proposal. If approved for migration, the contracts' code hashes will be matched to a chosen admin key and added to a list which is hardcoded inside the enclave for reference.
In order for a Secret contract instantiated after the v1.11 network upgrade to be migrated, the contract must be instantiated with an admin. To query whether or not a Secret contract was instantiated with an admin
, use the contractInfo
method:
The query will return the admin address, if there is one:
The method is designed to retrieve the history of a contract, specifically its code changes over time. The method returns an object containing an array of ContractCodeHistoryEntry
items.
, which contains the proper_initialization()
test and tests whether or not the counter contract is properly instantiated. Let's break this function down line-by-line so we have a thorough understanding of each piece of the code.
The use super::*;
line imports all the modules from the parent module ( of the contract.rs
file). This allows the test module to access contract.rs
's imports and test its functionality.
Remember, , which is why this struct is required to instantiate the contract. If there was no instantiation struct with a starting count, the test would fail.
With this information, examine the other testing functions in the counter contract. You should have all of the resources you need to start writing your very own unit tests! Now let's learn about Multitests!
An example of this technique being used in production is .
To query if a Secret contract was migrated successfully, use the method:
Secret supports IBC-Go v4 and several additional tools and middleware. ICA is supported but functions are not yet enabled nor is the host module live, if you need this please reach out to the Devrel team via discord or Telegram. Tools supported:
IBC hooks by Osmosis Labs - Secret - v1.11
WASM hooks: allows ICS-20 token transfers to initiate contract calls, serving various use cases.
Example: Sending tokens to Secret and immediately wrapping them as SNIP-20 token. For example, ATOM on Hub -> ATOM on Secret -> sATOMS on Secret
(2 transactions on 2 chains) now becomes ATOM on Hub -> sATOM on Secret
(1 transaction).
Example: Cross-chain swaps. Using IBC Hooks, an AMM on Secret can atomically swap tokens that originated on a different chain and are headed to Secret. The AMM can also send those tokens back to the originating chain.
Axelar GMP: Using IBC Hooks, a contract on Ethereum can call a contract on Secret and get a response back.
Ack callbacks: allow non-IBC contracts that send an IbcMsg::Transfer
to listen for the ack/timeout of the token transfer. This allows these contracts to definitively know whether the transfer was successful or not and act accordingly (refund if failed, continue if succeeded). See usage example here.
Packet forward middleware (PMF) by Strangelove - Secret v1.9
Other chains are able to more easily route SCRT in the interchain. For example, sending SCRT from Osmosis to Hub now becomes a single transaction from Osmosis -> Secret
rather than a transaction from Osmosis -> Secret
, then a transaction from Secret -> Hub
.
We recommend when writing Secret Contracts to leverage a CI platform to create a continuous integration flow for your contract.
The two types of tests to focus on:
Unit Tests - tests that are executed only in the context of the contract itself
Integration Tests - tests that include executing contract flows on a running chain (LocalSecret or Secret testnet)
Our platform of choice is GitHub Actions. GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository, or deploy merged pull requests to production.
Here are examples of how to implement these flows in your own contracts:
Integration Tests - https://github.com/scrtlabs/SecretJack/blob/master/.github/workflows/Integration.yml\
Note that in this example the unit test flow executes the main command make unit-test
, while the integration tests initialize a LocalSecret instance, and execute a Typescript file that handles deployment, initialization and execution of the test logic. You can find that file here.
Uint128
is a data structure designed to work with usigned 128-bit integers.
If you are familiar with Rust, you might know that it has it's own native primitive - u128
. Uint128
differs from u128
in that it is a string encoded integer, rather than the traditional little/big-endian.
Simliarly, cosmwasm-std
also has Uint64
and Uint256
and all of the following applies there as well.
Uint128
is a thin wrapper around u128
that uses strings for JSON encoding/decoding, such that the full u128
range can be used for clients that convert JSON numbers to floats, like JavaScript and jq (source).
If you are familiar with Messages, you already know that most of the time we will use serde to deserialize them from JSON (if not, you should read on contract entrypoints and the concept of Messages). Output will often be serialized in the same way.
In general, JSON implementations usually accept [-(2^53)+1,(2^53)-1]
as an acceptable range for numbers. So if we need more than that (for example for 64(unsigned), 128 and 256 numbers) we'll want to use a string-encoded numbers. That's why we'll prefer to use Uint128
in entrypoint messages, for example:
Depends on the needs of your contract, you can choose to use either Uint128
or u128
.
As a rule of thumb, most of the time you will want to store numbers as u128
rather than Uint128
.
More specifically, since Uint128
is a string encoded number the storage space it'll consume will depend on the number of digits of the number you are storing. u128
on the other hand will always take a constant amount of storage space. That's why Uint128
will be more efficient for very small numbers (and then, why use 128-bit integer to begin with?), while u128
will be more efficient for most use cases.
Example:
Floating points are a big no-no in blockchain. The reason being, and without diving into too much detail, that floating point operations might be non-deterministic, so different nodes in the blockchain might get different results and not reach consensus.
That being said, there are different ways to overcome this.
Sometimes you can absorb some lack of precision, and you can use integer division. For example, if you want to divide 1 million tokens between three addresses:
Note - integer division in Rust will always round down towards zero (source).
You can often increase integer division's precision by enlarging your inputs by some factor. When you are done with the calculations, you can shrink the number to the original scale.
Let's look at an example calculation with the following inputs:
Not scaling up before the calculation causes loss of precision:
Instead, we can first scale up the inputs by a constant SCALE_FACTOR
, and shrink them back down at the end:
Scale factors can be as big as possible, provided they don't cause overflows.
If you still need decimals in your code, a fixed-point decimals library can assist you.
There are several Rust libraries that implement fixed-point decimals, but you'd probably be best to use Cosmwasm's own Decimal
library.
Keep in mind that using fixed-point decimals comes with an overhead (both efficiency and ease of use), so you would prefer to avoid it if possible.
Sometimes even when you don't use floats directly, one of your contract's dependencies do. In that case you'd want to turn off the feature that using the floats or just replace the library altogether.
But the hard part is to identify what causes the problem to begin with. It might get pretty complicated, and probably a bit too involved for this doc, but there's this greate article that was published in the Cosmwasm blog that is very helpful for this.
A step-by-step tutorial of how to use Secret Network's randomness API to generate a coin flip
In this tutorial, you will learn how to access the randomness feature and use it with a smart contract that returns a truly random coin flip 🎉
For a detailed feature explainer head to the network technical documentation
In your Cargo.toml file, add secret-toolkit-storage 0.10.1:
What follows is a step-by-step tutorial of how to use Secret Network's randomness API to generate a coin flip (returning either 0 or 1) with true randomness. You can follow along and/or view the completed code in this repo.
To consume the random number, you need to import the necessary dependencies in your contract.rs
file in order to access the random number from the env parameter.
In your contract, import the necessary dependencies (these are already imported in the cloned repo):
In the contract's entry point (e.g., execute, instantiate, or query), you can access the random number from the env
parameter:
The env and block_info structures are defined as:
Where random
is 32 bytes and base64 encoded.
Below is a simple coin flip function that uses the randomness feature:
try_flip()
uses the config
function to update the state of the smart contract by flipping a coin and storing the result in the flip
field in the state
variable. Specifically, it generates a random number using the random
field of the env.block
object, which is an optional value representing the most recent block's metadata, and takes the modulo 2 to obtain a value of either 0 or 1. It then updates the flip
field of the state
variable to this value.
Now, let's compile, upload, instantiate, and execute the contract to see it in action!
To compile your contract, run make build-mainnet-reproducible
This returns the optimized contract wasm file, ie contract.wasm.gz
Upload and instantiate your contract to Secret Network testnet with the upload script here.
If you would like to use your own wallet addres, be sure to update the mnemonic.
Now that you have a contract address you can execute the coin flip with the randomness feature!
To flip the coin, update the contract address and code hash with your parameters and run:
And to query that it was successful, update the contract address and code hash with your parameters and run:
You might have to execute the flip function a few times to see the queried flip change, since there is a 50% chance the flip will return the same number :D
Congrats! In this step-by-step tutorial on creating a coin flip contract, you learned how to compile, upload, instantiate, and execute a contract on Secret testnet using Secret Network's randomness API to generate random numbers 🎉 For documentation on Secret VRF in a contract on another IBC-connected chain, click here.
Step-by-step guide on how to execute Secret Network smart contracts that communicate with each other
In previous sections, we explored the creation of isolated smart contracts within the Secret Network ecosystem. To construct more intricate systems using smart contracts, it is essential to establish effective communication between them. This documentation will delve into the implementation of secure and efficient communication between secret contracts, allowing you to build powerful, interconnected applications on Secret Network.
In this tutorial, we will be designing a simple smart contract that can execute a counter smart contract that is already deployed to the Secret Testnet. Thus, we are working with two smart contracts:
Manager Contract - which executes the Counter Contract
Counter Contract - which is executed by the Manager Contract
By the end of this tutorial, you will understand how to implement the Increment()
function on a Manager smart contract, which increments a counter smart contract by one every time the Increment function is called.
To follow along with this tutorial step-by-step, clone down the Secret Network counter template repo here 😊
We will be designing a Manager Smart Contract which can execute a Counter Contract that is deployed to the Secret testnet. Let's start by creating the message that we want to execute on the counter smart contract. In the src directory (which currently contains contract.rs
, lib.rs
, msg.rs
and state.rs
), create a counter.rs
file and add the following:
CounterExecuteMsg
contains a single function Increment{}.
This is the function we will call to increment the counter contract once we have completed designing our Manager smart contract.
Now, navigate to the msg.rs
file. Replace the existing code with the following:
Here we have an empty InstantiateMsg
, as well as an enum ExecuteMsg
, with a single variant, IncrementCounter
, which contains two strings: contract
and code_hash.
This is the contract address and code hash of the counter contract, which we will be calling from the Manager contract.
What is the code hash?
Unlike other Cosmos chains, Secret Network requires the hash of the smart contract in addition to the address when executing calls to smart contracts.
Contract hashes are what binds a transaction to the specific contract code being called. Otherwise, it would be possible to perform a replay attack in a forked chain with a modified contract that decrypts and prints the input message.
Now, navigate to the contract.rs
file. Replace the execute entry point with the following:
When the IncrementCounter
variant is matched, the function calls the try_increment
function, which contains two fields, contract
and code_hash
. The contract
field is the address of the contract to be incremented, and the code_hash
field is the code hash of the contract. This function contains the logic for incrementing the counter in our counter contract.
Remember the counter.rs file that we created earlier with the CounterExecuteMsg
enum containing Increment{}
? 🤔 Now, in the try_increment()
function, we are going to use a WasmMsg to call Increment{}
in order to increment the counter contract.
In CosmWasm, a WasmMsg
is an enum used to perform actions such as instantiating a new contract, executing a function on an existing contract, or sending tokens to a contract. Here are the main variants of WasmMsg
:
Instantiate
: This variant is used to create a new instance of a Wasm smart contract. It contains the following fields:
code_id
: The ID of the Wasm code to instantiate.
msg
: The message to initialize the contract (usually a JSON object).
funds
: The amount of tokens to send to the contract upon instantiation.
label
: A human-readable label for the new contract instance.
admin
: An optional address that, if provided, will have administrative privileges over the contract instance (e.g., for migration or update purposes).
Execute
: This variant is used to execute a function on an existing Wasm smart contract (and is the variant that we are calling in our contract) It contains the following fields:
contract_addr
: The address of the contract to execute the function on.
msg
: The message to be processed by the contract (usually a JSON object).
funds
: The amount of tokens to send to the contract along with the execution message.
Migrate
: This variant is used to migrate an existing Wasm smart contract to a new code version. It contains the following fields:
contract_addr
: The address of the contract to migrate.
new_code_id
: The ID of the new Wasm code to migrate the contract to.
msg
: The message to initialize the new code version (usually a JSON object).
In the counter.rs file, comment out or delete the existing execute functions and add the following:
The try_increment
function creates a WasmMsg::Execute
message to increment the counter in the specified smart contract and returns a Response
object containing the message and an attribute. When the Response
object is returned by the smart contract's execute
function, the blockchain will execute the WasmMsg::Execute
message, effectively incrementing the counter in the target contract.
Here's a breakdown of the code inside the function:
let exec_msg = CounterExecuteMsg::Increment {};
: This line creates a new CounterExecuteMsg
enum instance with the Increment
variant, which represents a message to increment the counter.
let cosmos_msg = WasmMsg::Execute { ... };
: This block creates a WasmMsg
enum instance with the Execute
variant. The Execute
variant is used to execute our Counter smart contract. The contract_addr
field is set to the contract
parameter, the code_hash
field is set to the code_hash
parameter, the msg
field is set to the binary representation of exec_msg
, and the funds
field is set to an empty vector, indicating no funds are sent with this message.
Ok(Response::new() ... )
: This block constructs a new Response
object with the following:
.add_message(cosmos_msg)
: The cosmos_msg
(a WasmMsg::Execute
instance) is added to the response as a message to be executed after the current contract execution finishes.
.add_attribute("action", "increment")
: An attribute with key "action" and value "increment" is added to the response. Attributes are used to store additional information about the operation and can be useful for indexing and querying transactions.
You can view the completed code repo here which contains the upload, instantiation, and execution functions using secret.js.
Let's focus on how to write the increment function as seen below:
The secret.js function increase_count
sends an increment_counter
message (which is the message we defined in msg.rs
) to our Counter smart contract. This message increments the counter contract via the WasmMsg that we defined in the increase_count
function.
You can call this function by commenting out the other functions in index.js and running node index.js
in the terminal (make sure you are in the secret-messages/manager/node
directory).
Now, in your terminal, navigate to the secret/counter/node
directory. Call the query_count
function to see the current count of the counter contract. After using the manager contract to increment, it should now be incremented by one!
Congratulations, you have now created a Manager smart contract that interacts with a Counter smart contract on Secret Network! By designing a Multi-Contract System, you can enable secure and efficient communication between secret contracts, allowing for more intricate and interconnected decentralized applications.