All Articles

Coding comparison between Cosmwasm and Solidity

dapp

Cosmwasm is a smart contract platform.

I developed the fundraiser dApp on Terra blockchain with Cosmwasm. fundraiser-dapp-on-terra

The fundraiser application is originally written in the book below. The original one is developed in Solidity. The detail of this application is written in the book, and I highly recommend reading this book below if you are a beginner of smart contracts or DApp development. Hands-On Smart Contract Development with Solidity and Ethereum

In the official Cosmwasm doc, there is a comparison doc with Solidity. This doc describes mainly a comparison from a concept and a security perspective. On the other hand, I will compare the Cosmwasm with Solidity from a coding perspective based on the fundraiser application code in the article.

  1. State management
  2. Execute contracts
  3. Execute another contract from contract
  4. Standard functions/messages
  5. Call contract functions

1. State management

Solidity

In dApp, state modeling is one of the most important aspects. You need to decide which variables are stored on the blockchain or not. In Solidity, state variables can be simply written as same as general programming. By using some keywords such as memory and storage, you can control whether variables are stored on the blockchain or not. Moreover, Solidity has some standard types such as mapping and array so that you can flexibly define your variables.

contract Fundraiser is Ownable {
    struct Donation {
        uint256 value;
        uint256 date;
    }
    mapping(address => Donation[]) private _donations;

    uint256 public totalDonations;
    uint256 public donationsCount;
}

These variables can be easily updated same as variables other programming languages have.

function donate() public payable {
    Donation memory donation = Donation({
        value: msg.value,
        date: block.timestamp
    });
    _donations[msg.sender].push(donation);
    totalDonations = totalDonations.add(msg.value);
    donationsCount++;

    emit DonationReceived(msg.sender, msg.value);
}

Cosmwasm

In Cosmwasm, you need to use storage objects when you want to store states in the blockchain. There are some storages such as Map and Item implemented in cosmwasm-storage and cw-storage-plus.

Some explanations about storage are written here. Compared with the above states of Fundraiser contract in Solidity, the ones in Cosmwasm are defined like below.

pub struct FundraiserContract<'a> {
    pub donation: Map<'a, Addr, Vec<Donation>>,
    pub total_donations: Item<'a, Uint128>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Donation {
    pub value: Uint128,
    pub date: Timestamp,
}

An example advanced usage such as using the IndexedMap is written in cw721-base.

These storage objects have some functions to update these.

self.total_donations
    .update(deps.storage, |total| -> Result<_, ContractError> {
        Ok(total + payment)
    })?;

self.donation.update(
    deps.storage,
    info.sender,
    |donation_opt: Option<Vec<Donation>>| -> Result<_, ContractError> {
        match donation_opt {
            Some(mut donation_map) => {
                donation_map.push(donation);
                Ok(donation_map)
            }
            None => Ok(vec![donation]),
        }
    },
)?;

2. Execute Contracts

Solidity

constructor enables to intialize Contract like other languages such as Java.

constructor(
    string memory _name,
    string memory _url,
    string memory _imageURL,
    string memory _description,
    address payable _beneficiary,
    address _custodian
) public {
    name = _name;
    url = _url;
    imageURL = _imageURL;
    description = _description;
    beneficiary = _beneficiary;
    transferOwnership(_custodian);
}

Functions in Solidity are categorised mainly into three.

  1. only read data from the blockchain (view function)

Functions declared view can read the data from the blockchain but can’t write data. Gas is not consumed when this function is called by external. But if this function is called by internal functions without the ‘view’ keyword, gas is consumed.

function myDonationsCount() public view returns(uint256) {
    return _donations[msg.sender].length;
}
  1. normal functions which don’t read and write any data from the blockchain (pure function)

Functions declared pure can read the data from the blockchain but can’t write data. Gas is not consumed when this function is called, even if it’s called by internal functions.

function _sum(uint a, uint b) private pure returns (uint) {
    return a + b;
}
  1. write data in the blockchain

Functions without the pure or view can write data into blockchain.

function setBeneficiary(address payable _beneficiary) public onlyOwner {
    beneficiary = _beneficiary;
}

Cosmwasm

The actor model is the basic model in CosmWasm.

There are mainly three messages in Cosmwasm

  1. InstantiateMsg

This message is called when initialize contracts. It’s same as contructor of Solidity

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
    pub name: String,
    pub url: String,
    pub image_url: String,
    pub description: String,
    pub beneficiary: String,
    pub custodian: String,
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    FundraiserContract::default().instantiate(deps, env, info, msg)
}
  1. ExecuteMsg

This message is called when writes the data in the blockchain like functions without pure and view function of Solidity

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
    SetBeneficiary { beneficiary: String },
    Donate {},
    Withdraw {},
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    FundraiserContract::default().execute(deps, env, info, msg)
}

pub fn execute(
    &self,
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::SetBeneficiary { beneficiary } => {
            self.set_beneficiary(deps, info, beneficiary)
        }
        ExecuteMsg::Donate {} => self.donate(deps, env, info),
        ExecuteMsg::Withdraw {} => self.withdraw(deps, env, info),
    }
}
  1. QueryMsg

This message is isent when reads the data from the blockchain like view function of Solidity.

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    GetFundraiser {},
    DonationAmount { address: String },
    MyDonations { address: String },
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    FundraiserContract::default().query(deps, env, msg)
}

pub fn query(&self, deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::GetFundraiser {} => to_binary(&self.query_fundraiser(deps)?),
        QueryMsg::DonationAmount { address } => {
            to_binary(&self.query_donation_amount(deps, address)?)
        }
        QueryMsg::MyDonations { address } => {
            to_binary(&self.query_my_donations(deps, address)?)
        }
    }
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    GetFundraiser {},
    DonationAmount { address: String },
    MyDonations { address: String },
}

3. Execute another contract from contract

Sometimes one contract calls another contract. In the fundraiser DApp, the FundraiserFactory contract calls the constructor of the Fundraiser contract to initialize it.

Solidity

You can call another contract like calling other classes in Java.

contract FundraiserFactory {
    Fundraiser[] private _fundraisers;

    event FundraiserCreated(Fundraiser indexed fundraiser, address indexed owner);

    function createFundraiser(
        string memory name,
        string memory url,
        string memory imageURL,
        string memory description,
        address payable beneficiary
    ) public {
        Fundraiser fundraiser = new Fundraiser(
            name,
            url,
            imageURL,
            description,
            beneficiary,
            msg.sender
        );
        _fundraisers.push(fundraiser);
        emit FundraiserCreated(fundraiser, fundraiser.owner());
    }
}

Cosmwasm

As I said, Cosmos is based on the Actor model. So, the communication between contracts is also executed via messages. In the fundraiser-dapp-on-terra, the FundraiserFactory contract creates a WasmMsg and adds it as SubMessage in the Response. This SubMessage will be called, and the Cosmos SDK ensures the transaction.

A transaction may consist of multiple messages and each one is executed in turn under the same context and same gas limit. If all messages succeed, the context will be committed to the underlying blockchain state and the results of all messages will be stored in the TxResult. If one message fails, all later messages are skipped and all state changes are reverted. This is very important for atomicity. That means Alice and Bob can both sign a transaction with 2 messages: Alice pays Bob 1000 ATOM, Bob pays Alice 50 ETH, and if Bob doesn’t have the funds in his account, Alice’s payment will also be reverted. This is just like a DB Transaction typically works. sdk-context

let fundraiser_instantiate_msg = fundraiser::msg::InstantiateMsg {
     name: name.clone(),
     url,
     image_url,
     description,
     beneficiary,
     custodian: info.sender.to_string(),
 };

 Ok(Response::new()
     .add_attribute("action", "create_fundraiser")
     .add_attribute("name", name)
     .add_submessages(vec![SubMsg::reply_on_success(
         WasmMsg::Instantiate {
             code_id: config.fundraiser_code_id,
             funds: vec![],
             admin: None,
             label: "".to_string(),
             msg: to_binary(&fundraiser_instantiate_msg)?,
         },
         1,
     )]))

Reply message

When you need to use reponses of calling WasmMsg , reply message enables it.

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result<Response, ContractError> {
    FundraiserFactoryContract::default().reply(deps, env, msg)
}

fn handle_instantiate_fundraiser(
    &self,
    deps: DepsMut,
    msg: Reply,
) -> Result<Response, ContractError> {
    let res: MsgInstantiateContractResponse = Message::parse_from_bytes(
        msg.result.unwrap().data.unwrap().as_slice(),
    )
    // do whatever you want with the res value
}

4. Standard functions/libraries

There are some useful standard functions and libraries in Solidity and CosmWasm.

Solidity

For example, transfer function enables to send the Ether to the address.

function withdraw() public onlyOwner {
    uint256 balance = address(this).balance;
    beneficiary.transfer(balance);
    emit Withdraw(balance);
}

OpenZeppelin is one of the most popular Solidity libraries. This library provides many useful base Contracts.

Cosmwasm

For example, the BankMsg enables to send amount to the address.

let send = BankMsg::Send {
    to_address: fundraiser.beneficiary.to_string(),
    amount,
};

Ok(Response::new()
    .add_message(send)
    .add_attribute("action", "with_draw")
    .add_attribute("owner", fundraiser.owner.to_string())
    .add_attribute("fundraiser", fundraiser.name))

cw-plus is one of the most popular libraries for Cosmos SDK. This library provides many useful base Contracts, storages and funtions.

5. Call contract functions

The functions of Smart contract are called by external such as front-end application.

Solidity

web3.js is a Javascript API for Ethereum.

It enables to initialize an instance and execute a smart contract. You can call the function name as defined.

await contract.methods.createFundraiser(
  name,
  url,
  imageURL,
  description,
  beneficiary
).send({ from: accounts[0] })

You can also call the value of the state directly from the frontend if this state is declared as public state.

const totalDonations = await instance.methods.totalDonations().call();

Cosmwasm

terra.js is a Javascript API for Terra.

Messages are sent as JSON.

// ExecuteMsg
const msg = {
    set_beneficiary: {
        beneficiary: beneficiary,
    }
};

const result = await wallet.post({
     fee,
     msgs: [
         new MsgExecuteContract(
             wallet.walletAddress,
             contractAddress,
             msg,
             coins
         ),
     ],
     gas: 'auto'
 });

// QueryMsg
await lcd.wasm.contractQuery<FundraiserResponse>(contractAddress, { get_fundraiser: {}});

The schema of messages are created in the contract repository by running cargo schema.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "ExecuteMsg",
  "oneOf": [
    {
      "type": "object",
      "required": [
        "set_beneficiary"
      ],
      "properties": {
        "set_beneficiary": {
          "type": "object",
          "required": [
            "beneficiary"
          ],
          "properties": {
            "beneficiary": {
              "type": "string"
            }
          }
        }
      },
      "additionalProperties": false
    },
    ...
  ]
}

Conclusion

In this article, I briefly compared the Cosmwasm with Solidity from a coding perspective based on the fundraiser application code in the article. There are more things that have to be taken into account when developing DApp, but I hope this article is helpful for developing DApp with CosmWasm.