Giter VIP home page Giter VIP logo

cairo-workshop's Introduction

Getting Started with Cairo (workshop) πŸ‘ΆπŸ»βœ¨

Along with the slides (πŸ‡¬πŸ‡§eng / πŸ‡ͺπŸ‡Έspa), this repository works as an introductory guide to develop StarkNet smart contracts with the Cairo programming language, the OpenZeppelin Contracts for Cairo library, and the Nile development environment.

Resources:

1. Installation

First time?

Before installing Cairo on your machine, you need to install gmp:

sudo apt install -y libgmp3-dev # linux
brew install gmp # mac

If you have any troubles installing gmp on your Apple M1 computer, here’s a list of potential solutions.

Set up your project

Create a directory for your project, then cd into it and create a Python virtual environment.

mkdir cairo-workshop
cd cairo-workshop
python3 -m venv env
source env/bin/activate

Install the Nile development environment and the OpenZeppelin Contracts.

pip install cairo-lang cairo-nile openzeppelin-cairo-contracts==0.4.0b

Note that we're installing Contracts v0.4.0b. This is a beta release that works with the new Cairo 0.10 syntax.

Run init to kickstart a new project. Nile will create the project directory structure and install dependencies such as the Cairo language, a local network, and a testing framework.

nile init

Now let's run a local StarkNet testing node so we can work locally, and leave it running for the rest of the steps:

nile node

2. Use a preset

Rename contracts/contract.cairo to contracts/UwuToken.cairo and replace its contents with:

%lang starknet

from openzeppelin.token.erc20.presets.ERC20 import (
    constructor,
    name,
    symbol,
    totalSupply,
    decimals,
    balanceOf,
    allowance,
    transfer,
    transferFrom,
    approve,
    increaseAllowance,
    decreaseAllowance,
)

That's it! That's our ERC20 contract. What this does is to import the ERC20 basic preset and re-exporting it.

Let's try to compile it:

(env) ➜  nile compile

πŸ“ Creating artifacts/abis to store compilation artifacts
πŸ€– Compiling all Cairo contracts in the contracts directory
πŸ”¨ Compiling contracts/UwuToken.cairo
βœ… Done

Magic ✨

3. Deploy it (with a script!)

Let's now try to deploy our contract. Although we could simply use nile deploy like this:

nile deploy UwuToken <name> <symbol> <decimals> <initial_supply> <recipient> --alias uwu_token

Truth is that there's still some representation issues to overcome:

  • strings (name and symbol) need to be converted to an integer representation first
  • uint256 values such as initial_supply need to be represented by two felts since they're just 252bits

To overcome this issues, it's easier to write a deployment script instead of using the CLI directly. Therefore we need to create a scripts/ directory and create a deploy.py file in it:

Note: you can find this script already written in this repo

# scripts/deploy.py
import time
from nile.utils import *

ALIAS = "uwu_token"
decimals = 18

def run(nre):
    account_a = nre.get_or_deploy_account("ACCOUNT_A")

    name = str_to_felt("UwuToken")
    symbol = str_to_felt("UWU")
    initial_supply = to_uint(to_decimals(1337))
    recipient = int(account_a.address, 16)

    arguments = [
        name,
        symbol,
        decimals,                         
        *initial_supply,
        recipient
    ]

    token_address, _ = nre.deploy("UwuToken", arguments, alias=ALIAS)
    print("UwuToken deployed at", token_address)

    wait = 1 # seconds
    print(f"Waiting {wait} seconds for it to get confirmed")
    time.sleep(wait)

    supply = from_hex(nre.call("uwu_token", "totalSupply")[0])
    print("total supply:", from_decimals(supply))

    name = nre.call("uwu_token", "name")[0]
    print("token name:", felt_to_str(name))

    symbol = nre.call("uwu_token", "symbol")[0]
    print("token symbol:", felt_to_str(symbol))


def from_decimals(x):
    return x / (10 ** decimals)

def to_decimals(x):
    return x * (10 ** decimals)

def from_hex(x):
    return int(x, 16)

There's a few things to note in here:

  • The script attempts to find or deploy an account controlled by the private key stored in the ACCOUNT_A environmental variable (see below).

Create a .env file to store your private keys so the script can find them:

ACCOUNT_A=207965718267142127099503064836527205057
ACCOUNT_B=671421270995030648365272050552079657182

That's it! We're ready to run:

nile run scripts/deploy.py

You should see something like this:

UwuToken deployed at 0x02cc100da2779fbf2d9f86281c3e7d459167deb372fb21c2e0e8527fdb0812ea
total supply: 1337.0
token name: UwuToken
token symbol: UWU

4. Interact with it

We can go one step further and write a script to transfer funds between accounts:

# scripts/transfer.py
from nile.utils import *

ALIAS = "uwu_token"
decimals = 18

def run(nre):
    account_a = nre.get_or_deploy_account("ACCOUNT_A")
    account_b = nre.get_or_deploy_account("ACCOUNT_B")
    token_address, _ = nre.get_deployment(ALIAS)
    
    print_balance(nre, account_a.address, 'a')
    print_balance(nre, account_b.address, 'b')

    recipient = from_hex(account_b.address)
    amount = to_uint(to_decimals(0.5))

    print(f"transfer {from_decimals(from_uint(amount))} to {account_b.address}")
    account_a.send(token_address, 'transfer', [recipient, *amount], max_fee=0)

    print_balance(nre, account_a.address, 'a')
    print_balance(nre, account_b.address, 'b')


def get_balance(nre, address):
    balance = nre.call(ALIAS, "balanceOf", [from_hex(address)])[0]
    return from_hex(balance)

def print_balance(nre, address, alias):
    balance = get_balance(nre, address)
    print(f"balance {alias}", from_decimals(balance))

def from_decimals(x):
    return x / (10 ** decimals)

def to_decimals(x):
    return x * (10 ** decimals)

def from_hex(x):
    return int(x, 16)

And again, we run:

nile run scripts/transfer.py

5. Write a custom contract (i.e. extend a library)

Without inheritance or another language native extensibility system, we need to come up with our own rules to safely extend existing modules to e.g. build our own custom ERC20 based on a standard library one.

To do this we follow our own Extensibility pattern (recommended reading), which extends library modules like this pausable transfer function:

%lang starknet

from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.uint256 import Uint256
from openzeppelin.security.pausable.library import Pausable
from openzeppelin.token.erc20.library import ERC20

(...)

@external
func transfer{
        syscall_ptr : felt*,
        pedersen_ptr : HashBuiltin*,
        range_check_ptr
    }(recipient: felt, amount: Uint256) -> (success: felt):
    Pausable.assert_not_paused()
    ERC20.transfer(recipient, amount)
    return (TRUE)
end

The main problem with this is that we need to manually re-export every function in order to make it available (transfer, transferFrom, approve, etc) even if we don't want to extend or make any changes to it.

Luckily, we have Wizard

With it, we can just add a name, symbol, premint amount and any features we want to our contract. In this example, I'll be creating the UwuToken and make it Pausable. Then I can copy to clipboard and paste it into contracts/UwuTokenPausable.cairo

Wizard for Cairo

Deploy to a public network

To deploy to a public network like goerli or mainnet, contracts need to be declared first:

nile declare UwuToken --network goerli

Now we can run our deployment script against the goerli testnet:

nile run scripts/deploy.py --network goerli

Extra mile

Develop your own custom contract using the OpenZeppelin Contracts for Cairo library!

cairo-workshop's People

Contributors

martriay avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.