Skip to main content

Comparison with solidity contracts

CosmWasm follows a different approach compared to Ethereum for deploying and executing smart contracts. It involves three phases to have a smart contract up and running:

  1. Upload Code - In this step, you only upload some optimized wasm code. Unlike smart contracts on Ethereum, in this step, you don't need to care about either the state or contract address.
  2. Instantiate Contract - In this step, you instantiate a code reference with an initial state and create a contract address.
  3. Execute Contract - Once your contract is live, it can support different calls depending on your specific contract design.

Similarly to Ethereum:

  • Contract instantiation and execution require gas.
  • Both instantiation and execution allow the signer to send some tokens to the contract alongside the message.

Differently from Ethereum:

  • Sending tokens directly to a contract (i.e., via SendMsg) does not trigger any contract code. As contract execution needs to be explicitly requested. This CosmWasm design decision helps reduce possible attack vectors. For example in Ethereum, when tokens are transferred to a contract, the contract's fallback function is activated automatically. If this fallback function is not well-implemented or has security vulnerabilities, it can leave the contract or even the entire system susceptible to different kinds of attacks, including reentrancy attacks.

Smart contract instantiation

In CosmWasm, the upload of a contract's code and the instantiation of a contract are regarded as separate events (unlike on Ethereum). This is to allow a small set of vetted contract archetypes to exist as multiple instances sharing the same base code but to still be configured with different parameters.

A vesting contract is a type of smart contract that locks up tokens and releases them gradually over a specified period of time. By using different parameters during instantiation, you can create multiple instances of the same vesting contract template to suit various use cases.

For example if a vesting contract allowed for the following parameters:

::highlight-card

// VestingContract.wasm
{
"vesting_start_time": "1616678400", // Unix timestamp for vesting start time
"vesting_duration": "31536000", // Vesting duration in seconds (1 year)
"total_tokens": "1000000", // Total tokens to be vested
"beneficiary_address": "terp1xyz" // Address of the beneficiary
}

::

By instantiating this vesting contract with different parameter values, you can create multiple instances that cater to various vesting scenarios. For example:

Instance A: Employee A receives 1,000,000 tokens vested over three years.
Instance B: Employee B receives 500,000 tokens vested over two years.
Instance C: Advisor A receives 250,000 tokens vested over one year with a six-month cliff.

By using a single vesting contract template and customizing the parameters during instantiation, you can create unique vesting contracts for different beneficiaries and vesting conditions.

Avoiding reentrancy attacks

Reentrancy attacks are avoided by design. This is an important issue, as a large number of exploits in Ethereum are based on reentrancy attacks. To illustrate how reentrancy attacks work in Ethereum smart contracts, consider the following scenario:

In the middle of the execution of a function on Contract A, an implicit or explicit call is made. This call transfers execution control to contract B (which can now execute code) and calls back into Contract A again. As there are now two copies of Contract A running, you need to be extremely careful regarding managing the state before executing any remote contract (or make very strict gas limits in sub-calls). Reentrancy attacks can trigger undefined behavior in Contract A, setting up the basis for exploits (similar to the Ethereum DAO hack).

Cosmwasm avoids reentrancy attacks completely by preventing any contract from directly calling another one. As we want to allow composition while avoiding inline function calls to malicious code, CosmWasm allows any contract to return a list of messages to be executed in the same transaction. This means that a contract needs to finish its execution before being able to perform a call to another contract. If the future messages fail, then the entire transaction reverts, including updates to the contract's state. This CosmWasm approach allows for atomic composition.

Resource limits

Another attack vector for smart contracts is denial of service attacks (DoS). For example, a malicious actor could upload a contract that ran an infinite loop to halt the chain or write tons of data to fill storage capabilities. As Web Assembly provides a tight sandbox with no default access to the OS, we only need to worry about providing tight resource limits for the smart contracts. All developers should be aware of these limits.

Memory Usage - When instantiating a Wasm VM, it is provided with 32MB of RAM by default. This is to store the bytecode as well as all memory used by running the process (stack and heap). This should be plenty large for almost any contract, but maybe some complex zero-knowledge circuits would hit limits there. It is also small enough to ensure that contracts have a minimal impact on the memory usage of the blockchain.

CPU Usage - The Wasmer Runtime that we use can inject metering logic into the wasm code. It calculates prices for various operations, charges, and checks limits before every jump statement (loop, function call, etc.), to produce a deterministic gas price regardless of CPU speed, platform, etc. Before executing a contract, a wasm gas limit is set based on the remaining Cosmos SDK gas, and gas is deducted at the end of the contract (there is a constant multiplier to convert, currently 100 wasm gas to 1 SDK gas). This puts a hard limit on any CPU computations as you must pay for the cycles used.

Disk Usage - All disk access is via reads and writes on the KVStore. The Cosmos SDK already enforces gas payments for KVStore access. Since all disk access in the contracts is made via callbacks into the SDK, it is charged there. If one were to integrate CosmWasm into another runtime, one would have to ensure to charge for access there as well.

Lessons learned from ethereum

In order to produce robust smart contract functionalities, it is useful to learn from both Ethereum's successes and shortcomings. Therefore, we can look into the most common Ethereum attack vectors along with mitigation strategies, so that we can compare how much of this list applies to Cosmwasm. You will find that many of these attack vectors are closed by design, while a number remain, and a section is planned on avoiding the remaining issues.

1. Reentrancy

As discussed before, in CosmWasm, we return messages to execute other contracts in the same atomic operation, but after the contract has finished its execution. This is based on the actor model and avoids the possibility of reentrancy attacks. More specifically, there is never a volatile state when a contract is called.

2. Arithmetic under/overflows

Rust allows you to simply set overflow-checks = true in the Cargo manifest to abort the program if any overflow is detected. There is no way to opt out of safe math.

3. Delegate Call

In CosmWasm, there is no Delegate Call logic. You can import modules, but they are linked together at compile time, which allows them to be tested as a whole, and there are no subtle entry points inside a contract's logic.

4. Default Visibilities

Rather than auto-generating entry points for every function/method in your code (or worse, assuming public if not specified), the developer must clearly define a list of messages to be handled and dispatch them to the proper functions. This way, it is impossible to accidentally expose a function.

5. Short Address/Parameter Attack

This is an exploit that takes advantage of the RLP encoding mechanism and the fixed 32-byte stack size. It does not apply to CosmWasm's type-checking JSON parser.

6. Unchecked CALL Return Values

CosmWasm does not allow calling other contracts directly, but instead, sent messages will be dispatched by a router. The router will check the result of all messages, and if any message in the chain returns an error, the entire transaction is aborted, and state changes are rolled back. This allows you to safely focus on the success case when scheduling calls to other contracts, knowing all states will be rolled back if it does not go as planned.

7. Uninitialized Storage Pointers

CosmWasm doesn't automatically fill in variables, as you must explicitly load them from storage. Rust does not allow uninitialized variables anywhere, making storage explicit rather than implicit.

8. Floating Points and Precision

Both Solidity and CosmWasm have no support for floating-point operations due to possible non-determinism in rounding, which is CPU dependent. Solidity has no alternative to doing integer math, and many developers hand-roll integer approximations to decimal numbers, which may introduce rounding errors. In CosmWasm, you can import any Rust package and simply pick an appropriate package and use it internally. For example, you can use rust_decimal, "a Decimal implementation written in pure Rust suitable for financial calculations that require significant integral and fractional digits with no round-off errors," or it must be fixed to provide fixed-point decimal math. It supports up to 128-bit numbers, which is enough for 18 digits before the decimal and 18 afterward, which should be enough for any use case.

  1. Tx.Origin Authentication CosmWasm doesn't expose tx.origin, but only the contract or user directly calling the contract as params.message.signer. This means it is impossible to rely on the wrong authentication, as there is only one value to compare.