LogoLogo
  • Welcome
    • About ENI
  • Getting Started
    • Quickstart
      • Account Structure
      • Token Standards
      • Gas
    • Divergence from Ethereum
    • Transactions
      • Creating Transaction
    • Governance
      • Proposals
    • Oracles
  • Build
    • Setup and Installation
    • Smart Contracts
      • EVM (General)
      • EVM (CLI)
      • Querying State
    • Frontend Development
      • Overview
      • How to Deploy Your First dApp
    • Ecosystem
      • Tools and Resources
      • Resources
  • Node
    • Getting Started
    • Node Operators Guide
    • Validator Operations Guide
    • Advanced Configuration & Monitoring
    • Technical Reference
  • Reference
    • Overview
    • enid
    • CLI
      • enid add-genesis-account
      • enid blocktest
      • enid collect-gentxs
      • enid compact
      • enid config
      • enid debug
      • enid export
      • enid gentx
      • enid help
      • enid init
      • enid keys
        • enid keys delete
        • enid keys add
        • enid keys export
        • enid keys import
        • enid keys list
        • enid keys mnemonic
        • enid keys parse
        • enid keys show
      • enid latest_version
      • enid migrate
      • enid prune
      • enid query
        • enid query accesscontrol
        • enid query upgrade
        • enid query account
        • enid query auth
        • enid query bank
        • enid query block
        • enid query authz
        • enid query distribution
        • enid query epoch
        • enid query evidence
        • enid query evm
        • enid query feegrant
        • enid query ibc-transfer
        • enid query gov
        • enid query ibc
        • enid query mint
        • enid query oracle
        • enid query params
        • enid query slashing
        • enid query staking
        • enid query tendermint-validator-set
        • enid query tokenfactory
        • enid query tx
        • enid query txs
      • enid rollback
      • enid start
      • enid status
      • enid tendermint
      • enid tools
      • enid tx
      • enid validate-genesis
      • enid version
Powered by GitBook
On this page
  • Overview
  • Smart Contract Languages
  • Deploying EVM Contracts on ENI
  • Calling the Contract from a JS Client
  1. Build
  2. Smart Contracts

EVM (General)

Overview

The Ethereum Virtual Machine (EVM) is the runtime environment for smart contracts, supporting compatibility with Ethereum-based decentralized applications (dApps). ENI is an EVM-compatible blockchain. ENI’s parallelized EVM ensures high performance and efficiency.

Here are some key points about the EVM:

  1. Turing Completeness: The EVM is Turing complete, meaning it can execute any computable function. This allows developers to write complex smart contracts.

  2. Gas: Transactions and contract executions on EVM-compatible networks consume gas. Gas is a unit of measure for computational work, and users pay for gas on the ENI network using ueni. Gas ensures that malicious or inefficient code does not overload the network.

  3. Bytecode Execution: Smart contracts are compiled into bytecode (low-level, machine-readable instructions) and deployed to EVM-compatible networks. The EVM executes this bytecode.

Smart Contract Languages

The two most popular languages for developing smart contracts on the EVM are Solidity and Vyper.

Solidity

  • An object-oriented, high-level language for implementing smart contracts.

  • A curly-brace language most heavily influenced by C++.

  • Statically typed (variable types are known at compile time).

  • Supports:

    • Inheritance (can extend other contracts).

    • Libraries (reusable code can be created and called from different contracts—similar to static functions in static classes in other object-oriented programming languages).

    • Complex user-defined types.

Solidity Contract Example

// SPDX-License-Identifier: GPL-3.0
pragma solidity >= 0.7.0;

contract Coin {
    // The keyword "public" makes variables
    // accessible from other contracts
    address public minter;
    mapping (address => uint) public balances;

    // Events allow clients to react to specific
    // contract changes you declare
    event Sent(address from, address to, uint amount);

    // Constructor code is only run when the contract
    // is created
    constructor() {
        minter = msg.sender;
    }

    // Sends an amount of newly created coins to an address
    // Can only be called by the contract creator
    function mint(address receiver, uint amount) public {
        require(msg.sender == minter);
        require(amount < 1e60);
        balances[receiver] += amount;
    }

    // Sends an amount of existing coins
    // from any caller to an address
    function send(address receiver, uint amount) public {
        require(amount <= balances[msg.sender], "Insufficient balance.");
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        emit Sent(msg.sender, receiver, amount);
    }
}

Vyper

  • A Pythonic programming language.

  • Strong typing.

  • Small and understandable compiler code.

  • Efficient bytecode generation.

  • Deliberately has fewer features than Solidity with the aim of making contracts more secure and easier to audit. Vyper does not support:

    • Modifiers

    • Inheritance

    • Inline assembly

    • Function overloading

    • Operator overloading

    • Recursive calling

    • Infinite-length loops

    • Binary fixed points

Example Vyper Contract

# Open Auction

# Auction params
# Beneficiary receives money from the highest bidder
beneficiary: public(address)
auctionStart: public(uint256)
auctionEnd: public(uint256)

# Current state of auction
highestBidder: public(address)
highestBid: public(uint256)

# Set to true at the end, disallows any change
ended: public(bool)

# Keep track of refunded bids so we can follow the withdraw pattern
pendingReturns: public(HashMap[address, uint256])

# Create a simple auction with `_bidding_time`
# seconds bidding time on behalf of the
# beneficiary address `_beneficiary`.
@external
def __init__(_beneficiary: address, _bidding_time: uint256):
    self.beneficiary = _beneficiary
    self.auctionStart = block.timestamp
    self.auctionEnd = self.auctionStart + _bidding_time

# Bid on the auction with the value sent
# together with this transaction.
# The value will only be refunded if the
# auction is not won.
@external
@payable
def bid():
    # Check if bidding period is over.
    assert block.timestamp < self.auctionEnd
    # Check if bid is high enough
    assert msg.value > self.highestBid
    # Track the refund for the previous high bidder
    self.pendingReturns[self.highestBidder] += self.highestBid
    # Track new high bid
    self.highestBidder = msg.sender
    self.highestBid = msg.value

# Withdraw a previously refunded bid. The withdraw pattern is
# used here to avoid a security issue. If refunds were directly
# sent as part of bid(), a malicious bidding contract could block
# those refunds and thus block new higher bids from coming in.
@external
def withdraw():
    pending_amount: uint256 = self.pendingReturns[msg.sender]
    self.pendingReturns[msg.sender] = 0
    send(msg.sender, pending_amount)

# End the auction and send the highest bid
# to the beneficiary.
@external
def endAuction():
    # It is a good guideline to structure functions that interact
    # with other contracts (i.e. they call functions or send ether)
    # into three phases:
    # 1. checking conditions
    # 2. performing actions (potentially changing conditions)
    # 3. interacting with other contracts
    # If these phases are mixed up, the other contract could call
    # back into the current contract and modify the state or cause
    # effects (ether payout) to be performed multiple times.
    # If functions called internally include interaction with external
    # contracts, they also have to be considered interaction with
    # external contracts.

    # 1. Conditions
    # Check if auction endtime has been reached
    assert block.timestamp >= self.auctionEnd
    # Check if this function has already been called
    assert not self.ended

    # 2. Effects
    self.ended = True

    # 3. Interaction
    send(self.beneficiary, self.highestBid)

Deploying EVM Contracts on ENI

Also, ensure you have a wallet on the ENI network.

After the project is created, adjust the contract code by adding a getCount function as shown below:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }

    function getCount() public view returns (uint256) {
        return number;
    }
}

And update the test code to the following:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract CounterTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
        counter.setNumber(0);
    }

    function test_Increment() public {
        counter.increment();
        assertEq(counter.number(), 1);
    }

    function testFuzz_SetNumber(uint256 x) public {
        counter.setNumber(x);
        assertEq(counter.number(), x);
    }

    function test_GetCount() public {
        uint256 initialCount = counter.getCount();
        counter.increment();
        assertEq(counter.getCount(), initialCount + 1);
    }
}

Run the tests with the following command:

$ forge test

If the tests pass, deploy the contract to the ENI chain using the following command:

$ forge create --rpc-url $ENI_NODE_URI --mnemonic $MNEMONIC src/Counter.sol:Counter
[⠒] Compiling...
No files changed, compilation skipped
Deployer: $0X_DEPLOYER_ADDRESS
Deployed to: $0X_CONTRACT_ADDRESS
Transaction hash: $0X_TX_HASH

Let’s query the contract using the cast command:

$ cast call $0X_CONTRACT_ADDRESS "getCount()(uint256)" --rpc-url $ENI_NODE_URI

This command should return 0, the initial value of the counter.

Now, use the cast command to call the increment function:

$ cast send $0X_CONTRACT_ADDRESS "increment()" --mnemonic $MNEMONIC --rpc-url $ENI_NODE_URI

If the command succeeds, you will receive the transaction hash and other information.

Now call the getCount function again; this time it should return 1.

Calling the Contract from a JS Client

To call the contract from a frontend, you can use ethers, for example:

import {ethers} from "ethers";

const privateKey = <Your Private Key>;
const evmRpcEndpoint = <Your Evm Rpc Endpoint>
const provider = new ethers.JsonRpcProvider(evmRpcEndpoint);
const signer = new ethers.Wallet(privateKey, provider);

if (!signer) {
    console.log('No signer found');
    return;
}
const abi = [
 {
      "type": "function",
      "name": "setNumber",
      "inputs": [
        {
          "name": "newNumber",
          "type": "uint256",
          "internalType": "uint256"
        }
      ],
      "outputs": [],
      "stateMutability": "nonpayable"
    },
    {
        "type": "function",
        "name": "getCount",
        "inputs": [],
        "outputs": [
            {
                "name": "",
                "type": "int256",
                "internalType": "int256"
            }
        ],
        "stateMutability": "view"
    },
    {
        "type": "function",
        "name": "increment",
        "inputs": [],
        "outputs": [],
        "stateMutability": "nonpayable"
    }
];

// Define the address of the deployed contract
const contractAddress = 0X_CONTRACT_ADDRESS;

// Create a new instance of the ethers.js Contract object
const contract = new ethers.Contract(contractAddress, abi, signer);

// Call the contract's functions
async function getCount() {
    const count = await contract.getCount();
    console.log(count.toString());
}

async function increment() {
    const txResponse = await contract.increment();
    const mintedTx = await txResponse.wait();
    console.log(mintedTx);
}

await increment();
await getCount();
PreviousSmart ContractsNextEVM (CLI)

Last updated 2 months ago

Since ENI is an EVM-compatible chain, existing EVM tools such as , , or others can be reused.

In this example, we will use the .

Install by following the .

Create a new project by following the .

Where $ENI_NODE_URI is the URI of the ENI node, and $MNEMONIC is the mnemonic phrase of the account deploying the contract. If you are running a local ENI node, the address will be http://localhost:8545; otherwise, you can obtain the evm_rpc URL from the . If the deployment is successful, you will see the EVM contract address in the output.

Hardhat
Foundry Forge
Foundry tools
Foundry tools
installation guide
new project guide
registry