In this workshop, you will learn how to create a simple Starknet smart contract, implement public functions, and events, access external contracts, and use OpenZeppelin's Ownable contract.
After completing each step, run the associated script to verify it has been implemented correctly.
Use the Cairo book and the Starknet docs as a reference.
Clone this repository and choose whether you prefer using Docker to manage global dependencies or not in the following steps:
- Install
asdf(instructions) - Install Scarb
2.8.0viaasdf(instructions) - Install Starknet Foundry
0.27.0viaasdf(instructions) - Install the Cairo 1.0 extension for VSCode (marketplace)
- Make sure Docker is installed and running
- Install the Dev Containers extension for VSCode (marketplace)
- Launch an instance of VSCode inside of the container by going to View -> Command Palette -> Dev Containers: Rebuild and Reopen in Container
Note: All the commands shown from this point on will assume that you are using the integrated terminal of a VSCode instance running inside the container. If you want to run the tests on a different terminal you'll need to use the command
docker compose run test.
Switch to the step1 branch to enable the verification tests:
git checkout -b step1 origin/step1Initialize the project structure within the cloned repository by using the Scarb package manager and enable compilation of Starknet Contracts.
- When initializing the project with
Scarb, name the package asworkshop - Create a new Cairo file under the
srcdirectory namedcounter.cairo, and add the following starting code:#[starknet::contract] pub mod counter_contract { #[storage] struct Storage {} }
- In the
lib.cairofile remove the code and define thecountermodule
Note: Using any other name will disrupt upcoming steps.
When completed, build your project by running the following command:
scarb build- Check out the
scarb initcommand to initialize a project. In case you want to initialize the project with a specific name, you can use thescarb init --name PACKAGE_NAMEcommand. - Refer to the Cheat Sheet for essential
Scarbcommands - To enable Starknet Contract compilation:
- Target
starknet-contract. - Specify the Cairo version in the
Scarb.toml. - Learn more in the Starknet Contract Target documentation.
- Target
Switch to the step2 branch to enable the verification tests:
git checkout -b step2 origin/step2Add snforge as a dependency within your Scarb.toml file to allow execution of tests with Starknet Foundry.
- In your
Scarb.toml, declare thesnforge_stdpackage as your project dependency and enablecasmcontract class generation - In your
Scarb.toml, define a script namedtestto be able to runsnforge testcommand - In your
Scarb.toml, set youreditionto target2023_01
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- Specify the version of Starknet Foundry that the project currently uses
- Refer to the Starknet Foundry Documention for more information.
- Refer to the Scarb Running Scripts Documentation for more information.
edition = "2024_07"is a default configuration from Scarb that targets the July 2024 version of Cairo prelude. However, in our workshop we will work with2023_01for simplicity. Refer to the Prelude Documentation for more information.
Switch to the step3 branch to enable the verification tests:
git checkout -b step3 origin/step3Implement the constructor function to initialize an input number and store a variable named counter within the contract.
- Store a variable named
counterasu32type in theStoragestruct. - Implement the constructor function that initializes the
countervariable with a given input value. - The input variable of the constructor function should be named
initial_value
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- Storage variables are the most common way to interact with your contract storage. You can read more about it in Chapter 14 - Contract Storage.
- The constructor function is a special type of function that runs only once. You can read more about it in Chapter 14 - Constructors.
Switch to the step4 branch to enable the verification tests:
git checkout -b step4 origin/step4Implement an interface for the contract which contains the get_counter() function. This function should return the value of the stored counter variable within the contract.
- Implement an interface for a function named
get_counter()which returns the value of thecountervariable. - The
get_counter()function must be within the contract's interface namedICounter.
Note: Any other given name to the contract's interface would break the test, be sure to have to correct name!
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- To create a contract interface, you will need to define a trait with the name
ICounter(otherwise the tests will fail) and mark the trait with the[starknet::interface]attribute. You can read more about it in Chapter 13 Anatomy of a Simple Contract. - The
get_counter()function should only be able to read the state of the contract and not modify it. You can read more about it in Chapter 14 - View Functions.
Switch to the step5 branch to enable the verification tests:
git checkout -b step5 origin/step5Within the same interface created in the previous step, implement a function called increase_counter() that can increment the current value of the counter by 1 each time it is invoked.
- Implement a function named
increase_counter()which increments thecountervalue by1. - The
increase_counter()function must be within the contract's interface namedICounter.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- The
increase_counter()function should be able to modify the state of the contract (also called an external function) and update thecountervalue within theStorage. You can read more about it in Chapter 14 - External Functions.
Switch to the step6 branch to enable the verification tests:
git checkout -b step6 origin/step6Implement an event named CounterIncreased that emits the current value of the counter variable, every time the value is increased.
- Define a variant named
CounterIncreasedin theEventenum. - Defining the
valuevariable within theCounterIncreasestruct. - Emit the event in the
increase_counter()function with the new value, once thecountervalue has been incremented.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- Events are custom data structures that are emitted by a contract. More information about Events can be found in Chapter 14 - Contract Events.
- To emit an event, you can use the
self.emit()function as show here.
Note: CHECKPOINT Reached ⛳️! Switch to the
step15-jsbranch to get a deployment script based on starknet.js.git checkout -b step15-js origin/step15-js
Switch to the step7 branch to enable the verification tests:
git checkout -b step7 origin/step7In this step, we will introduce an external smart contract that acts as a kill switch for a specific function. Your task is to add the external KillSwitch contract as a dependency within your project.
Note: The
KillSwitchcontract can be found here.
- In your
Scarb.tomlfile, declare thekill_switchpackage as your project dependency under the[dependencies]section. - In your
Scarb.tomlfile, to allow compilation of external contracts for Starknet Foundry, add the following line under the[[target.starknet-contract]]section.build-external-contracts = ["kill_switch::KillSwitch"]
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- Refer to the Scarb Managing Dependencies Documention for more information.
- Refer to the Compiling External Cotnract for more information.
Switch to the step8 branch to enable the verification tests:
git checkout -b step8 origin/step8Initialize the KillSwitch contract by storing the contract's address given as an input variable in the constructor function.
- Store a variable named
kill_switchas typeContractAddress. - Update the constructor function to initialize the
kill_switchvariable.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- The task is similar to Step 3. Refer to it for more information.
Switch to the step9 branch to enable the verification tests:
git checkout -b step9 origin/step9Implement the KillSwitch mechanism in the increase_counter() by calling the is_active() function from the KillSwitch contract.
- If the function
is_active()from theKillSwitchcontract returnsfalse, then allow theincrease_counter()function to increment the value; otherwise, return without incrementing.
Note: Analyze the
KillSwitchcode to understand the interface and the contract structure from here.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- You need to import the
DispatcherandDispatcherTraitof theKillSwitchcontract. These dispatchers are automatically created and exported by the compiler. More information about Contract Dispatcher can be found in Chapter 15.2 - Contract Dispatcher. - You can access the
is_active()function from yourKillSwitchcontract dispatcher. - You can use an
ifexpression to implement the mechanism. Refer to the Cairo Book to learn more.
Note: If you want to deploy the
Countercontract, you can use the following deployedKillSwitchcontract address.Contract Address:
0x05f7151ea24624e12dde7e1307f9048073196644aa54d74a9c579a257214b542
Switch to the step10 branch to enable the verification tests:
git checkout -b step10 origin/step10Protect the increase_counter() function by reverting the transaction if KillSwitch mechanism is enabled.
- Create the condition to revert the transaction if the
KillSwithcontract is enabled - Revert the transaction with the following message
Kill Switch is active
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- You can stop and revert a transaction with an error message using the
assert!()macro. Refer to the Cairo Book documentation to learn more. - You can replace the
ifexpression with theassert!()macro instead.
Switch to the step11 branch to enable the verification tests:
git checkout -b step11 origin/step11Add the external OpenZeppelin contracts as a dependency within your project.
Note: The
OpenZeppelincontracts can be found here.
- In your
Scarb.tomlfile, declare theopenzeppelinpackage as your project dependency under the[dependencies]section.
When completed, execute the test suite to verify you've met all the requirements for this section.
$ scarb test- Specify the OpenZeppelin
tagversion asv0.16.0inScarb.toml. - Refer to the OZ Contracts for Cairo Documention for more information.
Switch to the step12 branch to enable the verification tests:
git checkout -b step12 origin/step12Initialize the Ownable component from the OpenZeppelin contracts.
Before working on this step, make sure to read Chapter 16.2: Composability and Components and see how Components work.
- Declare the component inside your contract using the
component!()macro. - Add the path to the component's storage and events to the contract's
StorageandEvent. - Embed the component's logic into your contract by creating an instance of the component's generic implementation with a specific
ContractState.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- Refer to the Using Components Inside a Contract documentation to learn how to implement a component within a contract.
Switch to the step13 branch to enable the verification tests:
git checkout -b step13 origin/step13Modify the constructor function to call the initializer() function within the Ownable component to initialize the owner.
- The input variable of the constructor function should be named
initial_owner
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- To call the
initializer()function you can look at what functionsself.ownableexposes. - Refer to the Ownable Component to learn more about the accessible function.
Switch to the step14 branch to enable the verification tests
:
git checkout -b step14 origin/step14Protect the increase_counter() function so that only the owner of the contract can call this.
- Use the
assert_only_owner()function from the Ownable Component.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test- To call the
assert_only_owner()function you can look at what functionsself.ownableexposes. - Check out the
assert_only_owner()function from the Ownable Component for more information.
Switch to the step15-js branch to get a deployment script based on starknet.js.
git checkout -b step15-js origin/step15-jsTo deploy your account contract to Starknet's testnet using the deploy.ts script found in the scripts folder.
Run the command below from the project's root folder to install the deployment script dependencies.
npm installCreate a wallet that the script can use to pay for the declaration of your account contract.
- Create a wallet on Starknet testnet using the Argent X or Braavos browser extension.
- Fund the wallet by using the Faucet or the Bridge.
- Create a file in the project's root folder called
.env - Export the private key of the funded wallet and paste it into the
.envfile using the keyDEPLOYER_PRIVATE_KEY.
DEPLOYER_PRIVATE_KEY=<YOUR_FUNDED_TESTNET_WALLET_PRIVATE_KEY>- Export the public key of the funded wallet and paste it into the
.envfile using the keyDEPLOYER_ADDRESS
DEPLOYER_ADDRESS=<YOUR_FUNDED_TESTNET_WALLET_PUBLIC_ADDRESS>To successfully deploy the contract with the script on the Starknet Testnet, you will need to provide an RPC URL. For our workshop, we will use Blast's Public RPC Endpoint.
Add the following line in your .env file:
RPC_ENDPOINT=https://starknet-sepolia.public.blastapi.io/Refer to Blast to learn more about their Starknet RPC Endpoints.
Run the script that will declare and deploy your smart contract on the Starknet Testnet and ensure that you adjust the constructor inputs in your deploy.ts file appropriately.
Note: If you are deploying the smart contract from the CHECKPOINT, in the
deploy.tsfile, you will only need theinitial_counterin theconstructorvariable. Ensure that you remove or comment out thekill_switch_addressandinitial_onwervariables.Additionally, ensure that the variable names in the
deploy.tsconstructor are the same as in thecounter.cairoconstructor function.
const constructor = myCallData.compile("constructor", {
initial_counter: 100,
address: "0x05f7151ea24624e12dde7e1307f9048073196644aa54d74a9c579a257214b542",
initial_owner: process.env.DEPLOYER_ADDRESS,
});- From the project's root folder run
npm run deploy - Follow the instructions from the terminal
If the script finishes successfully your smart contract is ready to be used on Starknet testnet. Congratulations!