Smart Contract Development

Master the art of building secure, efficient smart contracts on Cosmos EVM. From your first line of Solidity to production deployment with cross-chain capabilities.

Smart contracts are self-executing programs that run on the blockchain. They automatically enforce agreements without intermediaries, making them perfect for DeFi, governance, and cross-chain applications.

Quick Start

1

Write Your First Contract

Deploy a simple “Hello World” contract to understand the basics

2

Set Up Development Tools

Configure Hardhat or Foundry with essential plugins and libraries

3

Test and Deploy

Write comprehensive tests and deploy to testnet, then mainnet

Your First Contract

Start with a simple contract that demonstrates core concepts:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract HelloWorld {
    string public message;

    event MessageUpdated(string newMessage, address updatedBy);

    constructor() {
        message = "Hello, Cosmos EVM!";
    }

    function setMessage(string memory newMessage) public {
        message = newMessage;
        emit MessageUpdated(newMessage, msg.sender);
    }
}

Development Environment

Essential Tools

Visual Studio Code

The most popular IDE for Solidity development. Install the “Hardhat Solidity” extension for syntax highlighting and error detection.

Development Framework

Choose between Hardhat (JavaScript/TypeScript) or Foundry (Solidity-native). Both are excellent—pick based on your background.

OpenZeppelin Contracts

Battle-tested smart contract components. Never reinvent security-critical code when you can use proven libraries.

Quick Setup

# Create new project
mkdir cosmos-evm-project && cd cosmos-evm-project
npm init -y

# Install Hardhat
npm install --save-dev hardhat

# Initialize project
npx hardhat init

# Install OpenZeppelin
npm install @openzeppelin/contracts

Building a Token Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    uint256 public constant MAX_SUPPLY = 1000000 * 10**18;

    constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
        _mint(msg.sender, MAX_SUPPLY);
    }

    function mint(address to, uint256 amount) public onlyOwner {
        require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
        _mint(to, amount);
    }
}

Best Practice: Always use battle-tested libraries like OpenZeppelin for standard functionality. Focus your custom code on unique features.

Security & Best Practices

Core Security Patterns

Input Validation

Always validate inputs to prevent unexpected behavior and attacks

CEI Pattern

Checks-Effects-Interactions prevents reentrancy vulnerabilities

Access Control

Use modifiers and role-based permissions for sensitive functions

Fail-Safe Defaults

Design contracts to fail safely with emergency pause mechanisms

Gas Optimization

// Efficient struct packing
struct User {
    uint128 balance;      // Slot 1
    uint128 lastUpdate;   // Slot 1 (packed)
    address wallet;       // Slot 2
    bool isActive;        // Slot 2 (packed)
    uint8 tier;          // Slot 2 (packed)
}

// Cache storage reads
function processUser(uint256 userId) public {
    User memory user = users[userId]; // Single SLOAD
    // Use 'user' multiple times without additional storage reads
}

Gas Optimization Trade-offs: Always balance gas optimization with code readability and security. Premature optimization can introduce bugs.

Testing Strategies

Comprehensive Testing Approach

Unit Tests

Test individual functions in isolation. Start here to catch basic logic errors.

Integration Tests

Test how different contracts work together. Critical for complex systems.

Fuzz Testing

Throw random inputs at your functions to find edge cases you never considered.

Security Tests

Specifically test for common vulnerabilities like reentrancy and overflow attacks.

Testing Examples

describe("Token Core Functions", function () {
    it("Should handle edge cases", async function () {
        const Token = await ethers.getContractFactory("MyToken");
        const token = await Token.deploy();

        // Test zero amount
        await expect(
            token.transfer(addr1.address, 0)
        ).to.be.revertedWith("Amount must be positive");

        // Test address(0)
        await expect(
            token.transfer(ethers.constants.AddressZero, 100)
        ).to.be.revertedWith("Invalid recipient");
    });
});

Test Coverage Goal: Aim for 100% line coverage, but remember that high coverage doesn’t guarantee bug-free code. Focus on testing edge cases and attack vectors.

Deployment Process

Deployment Pipeline

1

Local Testing

Run comprehensive test suite on local Hardhat/Anvil network

2

Testnet Deployment

Deploy to Cosmos EVM testnet for real-world testing

3

Security Audit

Get professional audit for contracts handling significant value

4

Mainnet Deployment

Deploy to mainnet with proper monitoring and incident response plan

Network Configuration

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

module.exports = {
  solidity: {
    version: "0.8.24",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    cosmosEvmTestnet: {
      url: process.env.TESTNET_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      chainId: 9000 // Replace with actual testnet chain ID
    },
    cosmosEvmMainnet: {
      url: process.env.MAINNET_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
      chainId: 9001 // Replace with actual mainnet chain ID
    }
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY
  }
};

Deployment Scripts

const hre = require("hardhat");
const { verify } = require("./verify");

async function main() {
    console.log("Deploying contracts...");

    const [deployer] = await ethers.getSigners();
    console.log("Deploying with:", deployer.address);

    // Check balance
    const balance = await deployer.getBalance();
    console.log("Account balance:", ethers.utils.formatEther(balance));

    // Deploy contract
    const Contract = await ethers.getContractFactory("MyToken");
    const contract = await Contract.deploy();
    await contract.deployed();

    console.log("Contract deployed to:", contract.address);
    console.log("Deployment tx:", contract.deployTransaction.hash);

    // Wait for confirmations
    console.log("Waiting for confirmations...");
    await contract.deployTransaction.wait(5);

    // Verify contract
    await verify(contract.address, []);

    // Save deployment info
    const deploymentInfo = {
        address: contract.address,
        txHash: contract.deployTransaction.hash,
        deployer: deployer.address,
        network: hre.network.name,
        timestamp: new Date().toISOString()
    };

    console.log("Deployment complete!", deploymentInfo);
}

main().catch((error) => {
    console.error(error);
    process.exit(1);
});

Pre-Deployment Checklist

Contract Verification

Why Verification Matters

Transparency

Users can read and verify your contract’s source code on block explorers

Trust

Verified contracts build user confidence through code transparency

Integration

Other developers can easily integrate with verified contracts

Debugging

Easier to debug issues with readable source code in production

Verification Methods

// scripts/verify.js
async function verify(address, constructorArgs) {
    console.log("Verifying contract...");
    try {
        await run("verify:verify", {
            address: address,
            constructorArguments: constructorArgs,
        });
        console.log("Contract verified!");
    } catch (e) {
        if (e.message.toLowerCase().includes("already verified")) {
            console.log("Contract already verified!");
        } else {
            console.error(e);
        }
    }
}

module.exports = { verify };

Complete Example: Governance Contract

Let’s build a production-ready governance contract that demonstrates all concepts:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Governance is ReentrancyGuard, Pausable, Ownable {
    struct Proposal {
        string description;
        uint256 forVotes;
        uint256 againstVotes;
        uint256 startTime;
        uint256 endTime;
        bool executed;
        mapping(address => bool) hasVoted;
    }

    uint256 public constant VOTING_PERIOD = 3 days;
    uint256 public constant MIN_QUORUM = 100 * 10**18; // 100 tokens

    mapping(uint256 => Proposal) public proposals;
    uint256 public proposalCount;

    event ProposalCreated(
        uint256 indexed proposalId,
        string description,
        uint256 startTime,
        uint256 endTime
    );

    event VoteCast(
        uint256 indexed proposalId,
        address indexed voter,
        bool support,
        uint256 weight
    );

    event ProposalExecuted(uint256 indexed proposalId);

    constructor() Ownable(msg.sender) {}

    function createProposal(string calldata description)
        external
        whenNotPaused
        returns (uint256)
    {
        uint256 proposalId = proposalCount++;
        Proposal storage proposal = proposals[proposalId];

        proposal.description = description;
        proposal.startTime = block.timestamp;
        proposal.endTime = block.timestamp + VOTING_PERIOD;

        emit ProposalCreated(
            proposalId,
            description,
            proposal.startTime,
            proposal.endTime
        );

        return proposalId;
    }

    function vote(uint256 proposalId, bool support)
        external
        nonReentrant
        whenNotPaused
    {
        Proposal storage proposal = proposals[proposalId];

        require(
            block.timestamp >= proposal.startTime,
            "Voting not started"
        );
        require(
            block.timestamp <= proposal.endTime,
            "Voting ended"
        );
        require(
            !proposal.hasVoted[msg.sender],
            "Already voted"
        );

        uint256 weight = getVotingPower(msg.sender);
        require(weight > 0, "No voting power");

        proposal.hasVoted[msg.sender] = true;

        if (support) {
            proposal.forVotes += weight;
        } else {
            proposal.againstVotes += weight;
        }

        emit VoteCast(proposalId, msg.sender, support, weight);
    }

    function executeProposal(uint256 proposalId)
        external
        nonReentrant
    {
        Proposal storage proposal = proposals[proposalId];

        require(
            block.timestamp > proposal.endTime,
            "Voting still active"
        );
        require(!proposal.executed, "Already executed");
        require(
            proposal.forVotes > proposal.againstVotes,
            "Proposal failed"
        );
        require(
            proposal.forVotes >= MIN_QUORUM,
            "Quorum not reached"
        );

        proposal.executed = true;

        // Execute proposal logic here
        _executeProposal(proposalId);

        emit ProposalExecuted(proposalId);
    }

    function getVotingPower(address voter)
        public
        view
        returns (uint256)
    {
        // Implement your voting power logic
        // Could be based on token balance, NFT ownership, etc.
        return 100 * 10**18; // Placeholder
    }

    function _executeProposal(uint256 proposalId) private {
        // Implement proposal execution logic
    }

    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }
}

Advanced Topics

Next Learning Steps

Upgradeable Contracts

Implement proxy patterns for contract upgradeability while maintaining decentralization

Cross-Chain DApps

Build applications that leverage IBC for seamless cross-chain communication

DeFi Protocols

Create lending, DEX, and yield farming protocols with advanced financial logic

Security Patterns

Master advanced security patterns and formal verification techniques

Cosmos EVM Specific Features

Access native Cosmos SDK modules directly from Solidity:

  • Staking: Delegate, undelegate, and claim rewards
  • Governance: Create and vote on proposals
  • IBC: Send cross-chain messages and tokens
  • Bank: Query balances and send native tokens

Resources & Community

Start Building Today: The best way to learn is by doing. Start with simple contracts, iterate quickly, and don’t be afraid to ask for help in the community.