# First Smart Contract
In lab01, we learned how to run a local geth
node
and make simple transactions that sends Ether to an account or call methods.
In this tutorial, we will learn the basic concept of smart contracts that run on Ethereum. There are many different ways (opens new window) to learn how to write a smart contract, but we will introduce two popular development tools, namely Remix (opens new window) and Brownie (opens new window). Remix (opens new window) is a simple web-based IDE, which provides an easy, quick way to learn the concept of smart contracts using Solidity (opens new window), a popular language to write an Ethereum smart contract. Brownie (opens new window) is a Python-based framework that provides full-fledged development features like deployment and testing.
You can complete Lab02 by using only Remix but we would like to start using Brownie and Brownie-like frameworks such as Hardhat (opens new window) and Truffle (opens new window). These framework embeds basic "Web3" libraries like Web3.js (opens new window), Ether.js (opens new window) or Web3.py (opens new window) as their foundation along with own local nodes (opens new window) and testing tools for your convenience.
# Remix
We will use the hosted version of Remix (opens new window).
Let's take a look at 1_Storage.sol
(under "contracts" / "default_workspace").
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256) {
return number;
}
}
The basic elements of Solidity will be introduced in the lecture and we will
use this as a running example to deploy, interact and debug smart
contracts in Remix. The Storage
contract provides a store(uint256)
and
retrieve()
API method that stores and retrieve a number
from the blockchain.
If you click the green triangle on the top (or press Ctrl+S
),
Remix compiles the current contract and makes it ready to deploy.
It works like any IDE that you are familiar with, so take some time
and tinker with it for a while: e.g., make it to spit
some compilation errors. Most features should be pretty intuitive.
Remix compiles the contract with solc (opens new window)
with a version specified in the contract (e.g., pragma solidity >=0.7.0 <0.9.0
)
and prepares a binary blob (or artifact) for deployment.
The "Environment" tab shows
various Ethereum providers,
which help you interact with the Ethereum network,
like geth
nodes, MetaMask, or test nodes provided by each framework.
The "Remix VM (London)" under the tab
shows a local EVM for the London fork (opens new window),
and helps you test your contract
without connecting to an external provider.
Let's click "Deploy" to deploy the compiled "Storage" to the "Remix VM".
Once deployed,
the console shows the basic information about the transaction
and you can interact with via store
and retrieve
buttons
under the "Deployed Contracts".
Under the hood,
Remix reads the ABI
generated by solc
,
and populates the UI accordingly.
You can now store
and retrieve
a number to/from the local EVM!
Once you test the contract,
please deploy it to the ledger
by simply changing the provider:
either "Injected Provider" (MetaMask)
or "External Http Provider" (geth
).
It is also a great time to
explore a few examples in here (opens new window)!
To know more about Remix, please refer to these Remix documents: Deploy & Run (opens new window) Deploy & Run (part 2) (opens new window).
Task
Your job is to write a Fibonacci
contract that calculates
a Fibonacci number (opens new window).
The contract should implement the function below:
function calculate(uint number) external returns (uint);
Deploy the Fibonacci contract and submit it via (in the dev console):
contract.submitFibonacci(address)
# Brownie
First, install Brownie by following this tutorial (opens new window), and then check out its Quickstart (opens new window)!
You can find some tools (sctips/pwn.py
) to interact with our labs:
$ git pull
$ ./setup.sh
$ brownie run scripts/pwn.py -I
...
>>> help()
load_abi(name): Load ABI/json of the label instance
load_main(): Load the Main contract
load_gamedata(): Fetch the gamedata from the website
get_instance_abi(name): Get the name of the instance's ABI
new_level(name): Create a new level instance
load_level(name): Load an existing instance
submit_level(address): Submit the level instance for grading
set_default_account(): Set a default account
e.g., lab02 = new_level("lab02")
submit_level(lab02.address)
Note that -I
at the end of the command spawns an interactive console
after executing the script.
Task
Do you remember you transferred some ether to the Lab01 instance? Let's withdraw the ether back to your account.
>>> lab01 = load_level("lab01")
>>> lab01
<lab01 Contract '0xcDC3E4D34f0A36D47b4E7FdF3d3384B500cFf6e7'>
>>> lab01.balance()
2000000000
>>> lab01.withdraw()
Transaction sent: 0xfd6a07e3295dd9caedfaa78911f941dd1b3ed8c7cacbfd357a16038d41b295a2
Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 620
Transaction confirmed Block: 627 Gas used: 22816 (0.19%)
<Transaction '0xfd6a07e3295dd9caedfaa78911f941dd1b3ed8c7cacbfd357a16038d41b295a2'>
>>> lab01.balance()
0
You can submit the lab01 instance like below in Brownie:
>>> lab02 = load_level("lab02")
>>> lab02
<lab02 Contract '0x6c52BBd5767f4c4780B3301483f6F9FcE94B94CE'>
>>> lab02.completed2()
False
>>> lab02.submitLab01(lab01.address)
Transaction sent: 0x0226b6b6215a0bbd34c5048e49699ffc998c55398a8aa7b66fc8df23339f85ae
Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 625
Transaction confirmed Block: 632 Gas used: 28351 (0.24%)
<Transaction '0x0226b6b6215a0bbd34c5048e49699ffc998c55398a8aa7b66fc8df23339f85ae'>
>>> lab02.completed2()
True
You can script your tasks, of course: simply, put your python code under scripts/
and import pwn.py
(i.e., from scripts.pwn import *
) in your script.
Task
Similar to lab01, transfer "1 gwei" to the lab2 instance in Brownie!
>>> lab02.balance()
0
>>> tx = a[0].transfer(lab02.address, "1 gwei")
Transaction sent: 0xe1c82323275c5674d07c7c1393d5b22e25395fda5e5239de7a49c12004527631
Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 627
Transaction confirmed Block: 634 Gas used: 21055 (0.18%)
<Transaction '0xe1c82323275c5674d07c7c1393d5b22e25395fda5e5239de7a49c12004527631'>
>>> tx.info()
Transaction was Mined
---------------------
Tx Hash: 0xe1c82323275c5674d07c7c1393d5b22e25395fda5e5239de7a49c12004527631
From: 0xfed61D4a212A14143DC51f21d8D6617072e7a621
To: 0x6c52BBd5767f4c4780B3301483f6F9FcE94B94CE
Value: 1000000000
Block: 634
Gas Used: 21055 / 12000000 (0.2%)
>>> lab02.balance()
1000000000
In Brownie, you can simply put a solidity contract under /contracts/
for
compilation and deployment. For example, the Storage
contract
(/contracts/Storage.sol
) will be compiled and loaded to the console when
launched. If you'd like to deploy the Storage
contract, simply
try Storage.deploy()
!
Task
The last task is to write a simple contract that invokes
submitMagicNumber()
with 0xdeadbeef
as an augment
along with 1 gwei
.
interface Lab02 {
function submitMagicNumber(uint _magic) payable external;
}
...
function exploit(address addr) public payable {
Lab02(addr).submitMagicNumber{value: 1 gwei}(0xdeadbeef);
}
If you are successfully done, you can invoke lab02.completed()
to see if it is
indeed completed! It's a time to submit your instance for grade. In the Brownie console,
submit_level(lab02.address)
will submit your instance!