1
0
mirror of https://github.com/adambard/learnxinyminutes-docs.git synced 2025-01-17 13:38:38 +01:00

Remove cryptocurrency promotion (#4912)

This commit is contained in:
Boris Verkhovskiy 2024-04-28 18:11:27 -07:00 committed by GitHub
parent 2c0eb04886
commit d2efe1c0f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 0 additions and 2675 deletions

View File

@ -1,864 +0,0 @@
---
language: Cairo
filename: learnCairo.sol
contributors:
- ["Darlington Nnam", "https://github.com/Darlington02"]
---
# Cairo
Cairo is a Turing-complete language that allows you write provable programs
(where one party can prove to another that a certain computation was executed
correctly) on StarkNet.
## StarkNet
StarkNet is a decentralized ZK-rollup that operates as an Ethereum layer 2
chain.
In this document, we are going to be going in-depth into understanding Cairo's
syntax and how you could create and deploy a Cairo smart contract on StarkNet.
**NB: As at the time of this writing, StarkNet is still at v0.10.3, with Cairo
1.0 coming soon. The ecosystem is young and evolving very fast, so you might
want to check the [official docs](https://www.cairo-lang.org/docs) to confirm
this document is still up-to-date. Pull requests are welcome!**
## Setting Up A Development Environment
Before we get started writing codes, we will need to setup a Cairo development
environment, for writing, compiling and deploying our contracts to StarkNet.
For the purpose of this tutorial we are going to be using the
[Protostar Framework](https://github.com/software-mansion/protostar).
Installation steps can be found in the docs
[here](https://docs.swmansion.com/protostar/docs/tutorials/installation).
Note that Protostar supports just Mac and Linux OS, Windows users might need to
use WSL, or go for other alternatives such as the Official
[StarkNet CLI](https://www.cairo-lang.org/docs/quickstart.html) or
[Nile from Openzeppelin](https://github.com/OpenZeppelin/nile)
Once you're done with the installations, run the command `protostar -v` to
confirm your installation was successful. If successful, you should see your
Protostar version displayed on the screen.
## Initializing a new project
Protostar similar to Truffle for solidity development can be installed once and
used for multiple projects. To initialize a new Protostar project, run the
following command:
```
protostar init
```
It would then request the project's name and the library's directory name,
you'd need to fill in this, and a new project will be initialized successfully.
## Compiling, Declaring, Deploying and Interacting with StarkNet Contracts
Within the `src` folder you'll find a boilerplate contract that comes with
initializing a new Protostar project, `main.cairo`. We are going to be
compiling, declaring and deploying this contract.
### Compiling Contracts
To compile a Cairo contract using Protostar, ensure a path to the contract is
specified in the `[contracts]` section of the `protostar.toml` file. Once
you've done that, open your terminal and run the command:
```
protostar build
```
And you should get an output similar to what you see below, with a `main.json`
and `main_abi.json` files created in the `build` folder.
<img src="./images/cairo/build.png" alt="building your contract">
### Declaring Contracts
With the recent StarkNet update to 0.10.3, the DEPLOY transaction was
deprecated and no longer works. To deploy a transaction, you must first declare
a Contract to obtain the class hash, then deploy the declared contract using the
[Universal Deployer Contract](https://community.starknet.io/t/universal-deployer-contract-proposal/1864).
Before declaring or deploying your contract using Protostar, you should set the
private key associated with the specified account address in a file, or in the
terminal. To set your private key in the terminal, run the command:
```
export PROTOSTAR_ACCOUNT_PRIVATE_KEY=[YOUR PRIVATE KEY HERE]
```
Then to declare our contract using Protostar run the following command (for
visual clarity, the backslash sign symbolizes the continuing line):
```
protostar declare ./build/main.json \
--network testnet \
--account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 \
--max-fee auto
```
where `network` specifies the network we are deploying to, `account` specifies
account whose private key we are using, `max-fee` specifies the maximum fee to
be paid for the transaction. You should get the class hash outputted as seen
below:
<img src="./images/cairo/declare.png" alt="declaring your contract">
### Deploying Contracts
After obtaining our class hash from declaring, we can now deploy using the
command below:
```
protostar \
deploy 0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc \
--network testnet \
--account 0x0691622bBFD29e835bA4004e7425A4e9630840EbD11c5269DE51C16774585b16 \
--max-fee auto
```
where `0x02a5de1b145e18dfeb31c7cd7ff403714ededf5f3fdf75f8b0ac96f2017541bc` is
the class hash of our contract.
<img src="./images/cairo/deploy.png" alt="deploying your contract">
### Interacting with Contracts
To interact with your deployed contract, we will be using `Argent X`
(alternative: `Braavos`), and `Starkscan` (alternative: `Voyager`). To install
and setup `Argent X`, see this
[guide](https://www.argent.xyz/learn/how-to-create-an-argent-x-wallet/).
Copy your contract address, displayed on screen from the previous step, and
head over to [Starkscan](https://testnet.starkscan.co/) to search for the
contract. Once found, you can make write calls to the contract in the following
sequence:
+ click on the "connect wallet" button,
<img src="./images/cairo/connect.png" alt="connect wallet">
+ select `Argent X` and approve the connection
<img src="./images/cairo/connect2.png" alt="connect to argentX">
+ you can now make read and write calls easily.
## Let's learn Cairo
First let's look at a default contract that comes with Protostar which allows
you to set balance on deployment, increase, and get the balance.
```
// Language directive - instructs compiler its a StarkNet contract
%lang starknet
// Library imports from the Cairo-lang library
from starkware.cairo.common.math import assert_nn
from starkware.cairo.common.cairo_builtins import HashBuiltin
// @dev Storage variable that stores the balance of a user.
// @storage_var is a decorator that instructs the compiler the function
// below it is a storage variable.
@storage_var
func balance() -> (res: felt) {}
// @dev Constructor writes the balance variable to 0 on deployment
// Constructors sets storage variables on deployment. Can accept arguments too.
@constructor
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() {
balance.write(0);
return();
}
// @dev increase_balance updates the balance variable
// @param amount the amount you want to add to balance
// @external is a decorator that specifies the func below it is an external
// function.
@external
func increase_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(amount: felt){
with_attr error_message("Amount must be positive. Got: {amount}.") {
assert_nn(amount);
}
let (res) = balance.read();
balance.write(res + amount);
return ();
}
// @dev returns the balance variable
// @view is a decorator that specifies the func below it is a view function.
@view
func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() -> (res: felt) {
let (res) = balance.read();
return (res,);
}
```
Before proceeding to the main lessons, try to build, deploy and interact with
this contract.
NB: You should be at `main.cairo` if you are using Protostar.
### 1. The Felt data type
Unlike solidity, where you have access to various data types, Cairo comes with
just a single data type `..felts`. Felts stands for Field elements, and are a
252 bit integer in the range `0<=x<=P` where `P` is a prime number. You can
create a `Uint256` in Cairo by utlizing a struct of two 128 bits felts.
```
struct Uint256 {
low: felt, // The low 128 bits of the value.
high: felt, // The high 128 bits of the value.
}
```
To avoid running into issues with divisions, it's safer to work with the
`unsigned_div_rem` method from Cairo-lang's library.
### 2. Lang Directive and Imports
To get started with writing a StarkNet contract, you must specify the directive:
```
%lang starknet
```
This directive informs the compiler you are writing a contract and not a
program. The difference between both is contracts have access to StarkNet's
storage, programs don't and as such are stateless.
There are important functions you might need to import from the official
Cairo-lang library or Openzeppelin's, e.g.
```
from starkware.cairo.common.cairo_builtins import HashBuiltin
from cairo_contracts.src.openzeppelin.token.erc20.library import ERC20
from starkware.cairo.common.uint256 import Uint256
from starkware.cairo.common.bool import TRUE
```
### 3. Data Structures
+ Storage variables: Cairo's storage is a map with `2^251` slots, where each
slot is a felt which is initialized to `0`. You create one using the
`@storage_var` decorator.
```
@storage_var
func names() -> (name: felt) {}
```
+ Storage mappings: Unlike Solidity where mappings have a separate keyword, in
Cairo you create mappings using storage variables.
```
@storage_var
func names(address: felt) -> (name: felt) {}
```
+ Structs: are a means to create custom data types in Cairo. A `struct` has a
size, which is the sum of the sizes of its members. The size can be
retrieved using `MyStruct.SIZE`. You create a struct in Cairo using the
`struct` keyword.
```
struct Person {
name: felt,
age: felt,
address: felt,
}
```
+ Constants: Constants are fixed and as such can't be altered after being set.
They evaluate to an integer (field element) at compile time. To create a
constant in Cairo, you use the `const` keyword. It's proper practice to
capitalize constant names.
```
const USER = 0x01C6cfC1DB2ae90dACEA243F0a8C2F4e32560F7cDD398e4dA2Cc56B733774E9b
```
+ Arrays: Arrays can be defined as a `pointer(felt*)` to the first element of
the array. As an array is populated, its elements take up contigous memory
cells. The `alloc` keyword can be used to dynamically allocate a new memory
segment, which can be used to store an array:
```
let (myArray: felt*) = alloc ();
assert myArray[0] = 1;
assert myArray[1] = 2;
assert myArray[3] = 3;
```
You can also use the `new` operator to create fixed-size arrays using
tuples. The new operator is useful as it enables you allocate memory and
initialize the object in one instruction
```
func foo() {
tempvar arr: felt* = new (1, 1, 2, 3, 5);
assert arr[4] = 5;
return ();
}
```
+ Tuples: A tuple is a finite, ordered, unchangeable list of elements. It is
represented as a comma-separated list of elements enclosed by parentheses.
Their elements may be of any combination of valid types.
```
local tuple0: (felt, felt, felt) = (7, 9, 13);
```
+ Events: Events allows a contract emit information during the course of its
execution, that can be used outside of StarkNet. An event can be created,
subsequently emitted:
```
@event
func name_stored(address, name) {}
name_stored.emit(address, name);
```
### 4. Constructors, External and View functions
+ Constructors: Constructors are a way to intialize state variables on
contract deployment. You create a constructor using the `@constructor`
decorator.
```
@constructor
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(_name: felt) {
let (caller) = get_caller_address();
names.write(caller, _name);
return ();
}
```
+ External functions: External functions are functions that modifies the state
of the network. You create an external function using the `@external`
decorator:
```
@external
func store_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(_name: felt){
let (caller) = get_caller_address();
names.write(caller, _name);
stored_name.emit(caller, _name);
return ();
}
```
+ View functions: View functions do not modify the state of the blockchain.
You can create a view function using the `@view` decorator.
```
@view
func get_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(_address: felt) -> (name: felt){
let (name) = names.read(_address);
return (name,);
}
```
NB: Unlike Solidity, Cairo supports just External and View function types.
You can alternatively also create an internal function by not adding any
decorator to the function.
### 5. Decorators
All functions in Cairo are specified by the `func` keyword, which can be
confusing. Decorators are used by the compiler to distinguish between these
functions.
Here are the most common decorators you'll encounter in Cairo:
+ `@storage_var` — used for specifying state variables.
+ `@constructor` — used for specifying constructors.
+ `@external` — used for specifying functions that write to a state variable.
+ `@event` — used for specifying events
+ `@view` — used to specify functions reading from a state variable
+ `@contract_interface` — used for specifying function interfaces.
+ `@l1_handler` — used for specifying functions that processes message sent from
an L1 contract in a messaging bridge.
### 6. BUILTINS, HINTS & IMPLICIT Arguments
+ `BUILTINS` are predefined optimized low-level execution units, which are
added to Cairos CPU board. They help perform predefined computations like
pedersen hashing, bitwise operations etc, which are expensive to perform in
Vanilla Cairo. Each builtin in Cairo is assigned a separate memory location,
accessible through regular Cairo memory calls using implicit parameters. You
specify them using the `%builtins` directive
Here is a list of available builtins in Cairo:
+ `output` — the output builtin is used for writing program outputs
+ `pedersen` — the pedersen builtin is used for pedersen hashing
computations
+ `range_check` — This builtin is mostly used for integer comparisons,
and facilitates check to confirm that a field element is within a range
`[0, 2^128)`
+ `ecdsa` — the ecdsa builtin is used for verifying ECDSA signatures
+ `bitwise` — the bitwise builtin is used for carrying out bitwise
operations on felts
+ `HINTS` are pieces of Python codes, which contains instructions that only
the prover sees and executes. From the point of view of the verifier these
hints do not exist. To specify a hint in Cairo, you need to encapsulate it
within `%{` and `%}`. It is good practice to avoid using hints as much as
you can in your contracts, as hints are not added to the bytecode, and thus
do not count in the total number of execution steps.
```
%{
# Python hint goes here
%}
```
+ `IMPLICIT ARGUMENTS` are not restricted to the function body, but can be
inherited by other functions calls that require them. Implicit arguments are
passed in between curly bracelets, like you can see below:
```
func store_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(_name: felt){
let (caller) = get_caller_address();
names.write(caller, _name);
stored_name.emit(caller, _name);
return ();
}
```
### 7. Error Messages and Access Controls
You can create custom errors in Cairo which is outputted to the user upon failed
execution. This can be very useful for implementing checks and proper access
control mechanisms. An example is preventing a user to call a function except
user is `admin`.
```
// imports
from starkware.starknet.common.syscalls import get_caller_address
// create an admin constant
const ADMIN = 0x01C6cfC1DB2ae90dACEA243F0a8C2F4e32560F7cDD398e4dA2Cc56B733774E9b
// implement access control
with_attr error_message("You do not have access to make this action!"){
let (caller) = get_caller_address();
assert ADMIN = caller;
}
// using an assert statement throws if condition is not true, thus
// returning the specified error.
```
### 8. Contract Interfaces
Contract interfaces provide a means for one contract to invoke or call the
external function of another contract. To create a contract interface, you use
the `@contract_interface` keyword:
```
@contract_interface
namespace IENS {
func store_name(_name: felt) {
}
func get_name(_address: felt) -> (name: felt) {
}
}
```
Once a contract interface is specified, any contract can make calls to that
contract passing in the contract address as the first parameter like this:
```
IENS.store_name(contract_address, _name);
```
Note that Interfaces exclude the function body/logic and the implicit
arguments.
### 9. Recursions
Due to the unavailability of loops, Recursion is the go-to for similar
operations. In simple terms, a recursive function is one which calls itself
repeatedly.
A good example to demonstrate this is writing a function for getting the nth
fibonacci number:
```
@external
func fibonacci{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(n : felt) -> (result : felt){
alloc_locals;
if (n == 0){
return (0);
}
if (n == 1){
return (1);
}
let (local x) = fibonacci(n - 1);
let (local y) = fibonacci(n - 2);
return (result=(x + y));
}
```
The nth fibonacci term is the sum of the `nth - 1` and the `nth - 2` numbers,
that's why we get these two as `(x,y)` using recursion.
NB: when implementing recursive functions, always remember to implement a base
case (`n==0`, `n==1` in our case), to prevent stack overflows.
### 10. Registers
Registers holds values that may change over time. There are 3 major types of
registers:
+ `ap` (allocation pointer) points to a yet unused memory. Temporary variables
created using `let`, `tempvar` are held here, and thus susceptible to being
revoked.
+ `fp` (frame pointer) points to the frame of the current function. The address
of all the function arguments and local variables are relative to this
register and as such can never be revoked.
+ `pc` (program counter) points to the current instruction.
### 11. Revoked References
Revoked references occur when there is a call instruction to another function,
between the definition of a reference variable that depends on `ap` (temp
variables) and its usage. This occurs as the compiler may not be able to compute
the change of `ap` (as one may jump to the label from another place in the
program, or call a function that might change ap in an unknown way).
Here is an example to demonstrate what I mean:
```
@external
func get_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() -> (res: felt) {
return (res=100);
}
@external
func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() -> (res: felt) {
let multiplier = 2;
let (balance) = get_balance();
let new_balance = balance * multiplier;
return (res=new_balance);
}
```
If you run that code, you'll run into the revoked reference error as we are
trying to access the `multiplier` variable after calling the `get_balance`
function.
In simple cases you can resolve revoked references by adding the keyword
`alloc_locals` within function scopes. In more complex cases you might need to
create a local variable to resolve it.
```
// resolving the `double_balance` function:
@external
func double_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}() -> (res: felt) {
alloc_locals;
let multiplier = 2;
let (balance) = get_balance();
let new_balance = balance * multiplier;
return (res=new_balance);
}
```
### 12. Understanding Cairo's Punctuations
+ `;` (semicolon). Used at the end of each instruction
+ `()` (parentheses). Used in a function declaration, if statements, and in a
tuple declaration
+ `{}` (curly braces). Used in a declaration of implicit arguments and to define
code blocks.
+ `[]` (square brackets). Standalone brackets represent the value at a
particular address location (such as the allocation pointer, `[ap]`). Brackets
following a pointer or a tuple act as a subscript operator, where `x[2]`
represents the element with index `2` in `x`.
+ `*` (single asterisk). Refers to the pointer of an expression.
+ `%` (percent sign). Appears at the start of a directive, such as `%builtins`
or `%lang`.
+ `%{` and `%}` represent Python hints.
+ `_` (underscore). A placeholder to handle values that are not used, such as an
unused function return value.
## Full Contract Example
Below is a simple automated market maker contract example that implements most
of what we just learnt! Re-write, deploy, have fun!
```
%lang starknet
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.hash import hash2
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.math import (assert_le, assert_nn_le,
unsigned_div_rem)
from starkware.starknet.common.syscalls import (get_caller_address,
storage_read, storage_write)
// CONSTANTS
//
// @dev the maximum amount of each token that belongs to the AMM
const BALANCE_UPPER_BOUND = 2 ** 64;
const TOKEN_TYPE_A = 1;
const TOKEN_TYPE_B = 2;
// @dev Ensure the user's balances are much smaller than the pool's balance
const POOL_UPPER_BOUND = 2 ** 30;
const ACCOUNT_BALANCE_BOUND = 1073741; // (2 ** 30 / 1000)
// STORAGE VARIABLES
//
// @dev A map from account and token type to corresponding balance
@storage_var
func account_balance(account_id: felt, token_type: felt) -> (balance: felt) {}
// @dev a map from token type to corresponding pool balance
@storage_var
func pool_balance(token_type: felt) -> (balance: felt) {}
// GETTERS
//
// @dev returns account balance for a given token
// @param account_id Account to be queried
// @param token_type Token to be queried
@view
func get_account_token_balance{syscall_ptr: felt*, pedersen_ptr:
HashBuiltin*, range_check_ptr}(
account_id: felt, token_type: felt
) -> (balance: felt) {
return account_balance.read(account_id, token_type);
}
// @dev return the pool's balance
// @param token_type Token type to get pool balance
@view
func get_pool_token_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(
token_type: felt
) -> (balance: felt) {
return pool_balance.read(token_type);
}
// EXTERNALS
//
// @dev set pool balance for a given token
// @param token_type Token whose balance is to be set
// @param balance Amount to be set as balance
@external
func set_pool_token_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(
token_type: felt, balance: felt
) {
with_attr error_message("exceeds maximum allowed tokens!"){
assert_nn_le(balance, BALANCE_UPPER_BOUND - 1);
}
pool_balance.write(token_type, balance);
return ();
}
// @dev add demo token to the given account
// @param token_a_amount amount of token a to be added
// @param token_b_amount amount of token b to be added
@external
func add_demo_token{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(
token_a_amount: felt, token_b_amount: felt
) {
alloc_locals;
let (account_id) = get_caller_address();
modify_account_balance(account_id=account_id, token_type=TOKEN_TYPE_A,
amount=token_a_amount);
modify_account_balance(account_id=account_id, token_type=TOKEN_TYPE_B,
amount=token_b_amount);
return ();
}
// @dev intialize AMM
// @param token_a amount of token a to be set in pool
// @param token_b amount of token b to be set in pool
@external
func init_pool{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(
token_a: felt, token_b: felt
) {
with_attr error_message("exceeds maximum allowed tokens!"){
assert_nn_le(token_a, POOL_UPPER_BOUND - 1);
assert_nn_le(token_b, POOL_UPPER_BOUND - 1);
}
set_pool_token_balance(token_type=TOKEN_TYPE_A, balance=token_a);
set_pool_token_balance(token_type=TOKEN_TYPE_B, balance=token_b);
return ();
}
// @dev swaps token between the given account and the pool
// @param token_from token to be swapped
// @param amount_from amount of token to be swapped
// @return amount_to the token swapped to
@external
func swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
token_from: felt, amount_from: felt
) -> (amount_to: felt) {
alloc_locals;
let (account_id) = get_caller_address();
// verify token_from is TOKEN_TYPE_A or TOKEN_TYPE_B
with_attr error_message("token not allowed in pool!"){
assert (token_from - TOKEN_TYPE_A) * (token_from - TOKEN_TYPE_B) = 0;
}
// check requested amount_from is valid
with_attr error_message("exceeds maximum allowed tokens!"){
assert_nn_le(amount_from, BALANCE_UPPER_BOUND - 1);
}
// check user has enough funds
let (account_from_balance) =
get_account_token_balance(account_id=account_id, token_type=token_from);
with_attr error_message("insufficient balance!"){
assert_le(amount_from, account_from_balance);
}
let (token_to) = get_opposite_token(token_type=token_from);
let (amount_to) = do_swap(account_id=account_id, token_from=token_from,
token_to=token_to, amount_from=amount_from);
return (amount_to=amount_to);
}
// INTERNALS
//
// @dev internal function that updates account balance for a given token
// @param account_id Account whose balance is to be modified
// @param token_type Token type to be modified
// @param amount Amount Amount to be added
func modify_account_balance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(
account_id: felt, token_type: felt, amount: felt
) {
let (current_balance) = account_balance.read(account_id, token_type);
tempvar new_balance = current_balance + amount;
with_attr error_message("exceeds maximum allowed tokens!"){
assert_nn_le(new_balance, BALANCE_UPPER_BOUND - 1);
}
account_balance.write(account_id=account_id, token_type=token_type,
value=new_balance);
return ();
}
// @dev internal function that swaps tokens between the given account and
// the pool
// @param account_id Account whose tokens are to be swapped
// @param token_from Token type to be swapped from
// @param token_to Token type to be swapped to
// @param amount_from Amount to be swapped
func do_swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*,
range_check_ptr}(
account_id: felt, token_from: felt, token_to: felt, amount_from: felt
) -> (amount_to: felt) {
alloc_locals;
// get pool balance
let (local amm_from_balance) = get_pool_token_balance(token_type =
token_from);
let (local amm_to_balance) = get_pool_token_balance(token_type=token_to);
// calculate swap amount
let (local amount_to, _) = unsigned_div_rem((amm_to_balance *
amount_from), (amm_from_balance + amount_from));
// update token_from balances
modify_account_balance(account_id=account_id, token_type=token_from,
amount=-amount_from);
set_pool_token_balance(token_type=token_from, balance=(amm_from_balance
+ amount_from));
// update token_to balances
modify_account_balance(account_id=account_id, token_type=token_to,
amount=amount_to);
set_pool_token_balance(token_type=token_to, balance=(amm_to_balance -
amount_to));
return (amount_to=amount_to);
}
// @dev internal function to get the opposite token type
// @param token_type Token whose opposite pair needs to be gotten
func get_opposite_token(token_type: felt) -> (t: felt) {
if(token_type == TOKEN_TYPE_A) {
return (t=TOKEN_TYPE_B);
} else {
return (t=TOKEN_TYPE_A);
}
}
```
## Additional Resources
+ [Official documentation](https://www.cairo-lang.org/docs/)
+ [Starknet EDU](https://medium.com/starknet-edu)
+ [Journey through Cairo](https://medium.com/@darlingtonnnam/journey-through-cairo-i-setting-up-protostar-and-argentx-for-local-development-ba40ae6c5524)
+ [Demystifying Cairo whitepaper](https://medium.com/@pban/demystifying-cairo-white-paper-part-i-b71976ad0108)
+ [Learn about StarkNet with Argent](https://www.argent.xyz/learn/tag/starknet/)
## Development Frameworks
+ [Protostar](https://docs.swmansion.com/protostar/docs/tutorials/installation)
+ [Nile](https://github.com/OpenZeppelin/nile)
+ [StarkNet CLI](https://www.cairo-lang.org/docs/quickstart.html)
## Helpful Libraries
+ [Cairo-lang](https://github.com/starkware-libs/cairo-lang)
+ [Openzeppelin](https://github.com/OpenZeppelin/cairo-contracts)
## Educational Repos
+ [StarkNet Cairo 101](https://github.com/starknet-edu/starknet-cairo-101)
+ [StarkNet ERC721](https://github.com/starknet-edu/starknet-erc721)
+ [StarkNet ERC20](https://github.com/starknet-edu/starknet-erc20)
+ [L1 -> L2 Messaging](https://github.com/starknet-edu/starknet-messaging-bridge)
+ [StarkNet Debug](https://github.com/starknet-edu/starknet-debug)
+ [StarkNet Accounts](https://github.com/starknet-edu/starknet-accounts)
+ [Min-Starknet](https://github.com/Darlington02/min-starknet)
## Security
+ [Amarna static analysis for Cairo programs](https://blog.trailofbits.com/2022/04/20/amarna-static-analysis-for-cairo-programs/)
+ [Cairo and StarkNet security by Ctrl03](https://ctrlc03.github.io/)
+ [How to hack almost any Cairo smart contract](https://medium.com/ginger-security/how-to-hack-almost-any-starknet-cairo-smart-contract-67b4681ac0f6)
+ [Analyzing Cairo code using Armana](https://dic0de.substack.com/p/analyzing-cairo-code-using-amarna?sd=pf)
## Future TO-DOs
Update tutorial to fit Cairo 1.0

View File

@ -1,338 +0,0 @@
---
language: FunC
filename: learnFunC.fc
contributors:
- ["Ivan Romanovich", "https://t.me/ton_learn"]
- ["Kirill Malev", "https://fslabs.io"]
---
The FunC language is used to program smart contracts on the [The Open Network](https://ton.org) blockchain. Contract logic is executed in TVM, the stack-based TON Virtual Machine.
FunC is a statically typed, which is similar to C.
# Basic syntax, the first Smart Contract — Data Types, Storage, Functions
```c
;; Single line comment
{- This is a multi-line comment
{- this is a comment in the comment -}
-}
(int) sum(int a, int b) {
;; This is a function that gets two integer parameters
;; and return integer result
return a + b;
;; All integers are signed and are 257 bit long. Overflow throws exception
;; expressions must end with a semicolon
}
() f(int i, cell c, slice s, builder b, cont c, tuple t) {
;; FunC has 7 atomic types:
;; int - 257 bit signed integers,
;; cell - basic for TON opaque data structure,
;; which contains up to 1,023 bits and up to 4 references to other cells,
;; slice and builder - special objects to read from and write to cells,
;; continuation - another flavor of cell that contains
;; ready-to-execute TVM byte-code.
;; tuple is an ordered collection of up to 255 components,
;; having arbitrary value types, possibly distinct.
;; Finally tensor type (A,B, ...) is an ordered collection ready for
;; mass assigning like: (int, int) a = (3, 5);
;; Special case of tensor type is the unit type ().
;; It represents that a function doesnt return any value,
;; or has no arguments.
}
;; During execution, the contract has read access to local context:
;; its storage, balance, time, network config, etc.
;; Contract may change its storage and code,
;; and also may send messages to other contracts
;; Lets write a counter smart contract that gets a number
;; from an incoming message,
;; adds to already stored numbers and stores result in “storage”
;; For handling special events, smart contracts have reserved methods:
;; recv_internal() handles internal messages from other smart contracts
;; recv_external() handles external messages from the outside world —
;; e.g., from a user.
() recv_internal(slice in_msg_body) {
;; Cells play the role of memory in the stack-based TVM.
;; A cell can be transformed into a slice,
;; and then the data bits and references to
;; other cells from the cell can be obtained
;; by loading them from the slice.
;; Data bits and references to other cells can be stored
;; into a builder, and then the builder can be finalized into a new cell.
;; recv_internal gets the slice
;; with incoming message data as an argument.
;; As everything else on TON, permanent storage data is stored as a cell.
;; It can be retrieved via the get_data() method
;; begin_parse - converts a cell with data into a readable slice
slice ds = get_data().begin_parse();
;; `.` is a syntax sugar: a.b() is equivalent to b(a)
;; load_uint is a function from the FunC standard library;
;; it loads an unsigned n-bit integer from a slice
int total = ds~load_uint(64); ;; `~` is a "modifying" method:
;; essentially, it is a syntax sugar: `r = a~b(x)`
;; is equivalent to (a,r) = b(a,x)
;; Now lets read the incoming value from the message body slice
int n = in_msg_body~load_uint(32);
total += n;
;; integers support usual +-*/ operations as well as (+-*/)= syntax sugar
;; In order to keep a store integer value, we need to do four things:
;; create a Builder for the future cell - begin_cell()
;; write a value to total - store_uint(value, bit_size)
;; create a Cell from the Builder - end_cell()
;; write the resulting cell into permanent storage - set_data()
set_data(begin_cell().store_uint(total, 64).end_cell());
}
;; The FunC program is essentially a list of
function declarations/definitions and global variable declarations.
;; Any function in FunC matches the following pattern:
;; [<forall declarator>] <return_type> <function_name>(<comma_separated_function_args>) <specifiers>
;; Specifiers:
;; The impure specifier indicates that
;; function calls should not be optimized
;; (whether its result is used or not)
;; it is important for methods that change the smart contract data
;; or send messages
;; The method_id specifier allows you to call a GET function by name
;; For instance, we can create a get method for the contract above
;; to allow outside viewers to read counter
int get_total() method_id {
slice ds = get_data().begin_parse();
int total = ds~load_uint(64);
;; Note that (int) and int is the same,
;; thus brackets in the function declaration
;; and in the return statement are omitted.
return total;
}
;; Now any observer can read get_total value via lite-client or explorer
```
# Messages
The actor model is a model of concurrent computation and is at the heart of TON smart contracts. Each smart contract can process one message at a time, change its own state, or send one or several messages. Processing of the message occurs in one transaction, that is, it cannot be interrupted. Messages to one contract are processed consequently one by one. As a result, the execution of each transaction is local and can be parallelized at the blockchain level, which allows for on-demand throughput horizontal scaling and hosting an unlimited number of users and transactions.
```c
;; For normal internal message-triggered transactions,
;; before passing control to recv_internal TVM puts the following
;; elements on stack.
;;;; Smart contract balance (in nanoTons)
;;;; Incoming message balance (in nanoTons)
;;;; Cell with an incoming message
;;;; Incoming message body, slice type
;; In turn, recv_internal may use only
;; the required number of fields (like 1 in the example above or 4 below)
;; Lets dive into message sending
() recv_internal (
int balance, int msg_value, cell in_msg_full, slice in_msg_body) {
;;
;; Every message has a strict layout, thus by parsing it,
;; we can get the senders address
;; first, we need to read some tech flags and
;; then take the address using load_msg_addr
;; function from FunC standard library - ()
var cs = in_msg_full.begin_parse();
var flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
;; if we want to send a message, we first need to construct it
;; message serialization in most cases may be reduced to
var msg = begin_cell()
.store_uint(0x18, 6) ;; tech flags
.store_slice(addr) ;; destination address
.store_coins(amount) ;; attached value
.store_uint(0, 107) ;; more tech flags :)
.store_slice(in_msg_body) ;; just put some payload here
.end_cell();
;; to send messages, use send_raw_message from the standard library.
;; it accepts two arguments message and mode
send_raw_message(msg, 64);
;; mode parameter specifies how to process the funds passed into
;; the smart contract with the message and the smart contract funds
;; 64 means send everything from the incoming message —
;; whats left after the commission is deducted
;; Exceptions can be thrown by conditional primitives throw_if and
;; throw_unless and by unconditional throw
;; by default, it will automatically cause a bounce message with 64 mode
var some = 7;
throw_if(102, some == 10);
;; Throw exception with code 102 conditionally
throw_unless(103, some != 10);
;; Throw exception with code 103 conditionally
throw(101); ;; Throw exception with code 101 unconditionally
}
```
# Flow control: Conditional Statements and Loops; Dictionaries
```c
;; FunC, of course, supports if statements
;;;; usual if-else
if (flag) {
;;do_something();
}
else {
;;do_alternative();
}
;; If statements are often used as an operation identifier
;; for a smart contract, for example:
() recv_internal (
int balance, int msg_value, cell in_msg_full, slice in_msg_body) {
int op = in_msg_body~load_int(32);
if (op == 1) {
;; smth here
} else {
if (op == 2) {
;; smth here
} else {
;; smth here
}
}
}
;; Loops
;; FunC supports repeat, while and do { ... } until loops.
;; for loop is not supported.
;; repeat
int x = 1;
repeat(10) {
x *= 2;
}
;; x = 1024
;; while
int x = 2;
while (x < 100) {
x = x * x;
}
;; x = 256
;; until loops
int x = 0;
do {
x += 3;
} until (x % 17 == 0);
;; x = 51
;; In practice, loops in TON smart contracts are often used to work with
;; dictionaries, or as they are also called in TON hashmaps
;; A hashmap is a data structure represented by a tree.
;; Hashmap maps keys to values of arbitrary type so that
;; quick lookup and modification are possible.
;; udict_get_next? from FunC standard library in combination with
;; the loop will help, go through the dictionary
int key = -1;
do {
(key, slice cs, int f) = dic.udict_get_next?(256, key);
} until (~ f);
;; udict_get_next? - Calculates the minimum key k in the dictionary dict
;; that is greater than some given value and returns k,
;; the associated value, and a flag indicating success.
;; If the dictionary is empty, returns (null, null, 0).
```
# Functions
```c
;; Most useful functions are slice reader and builder writer primitives,
;; storage handlers and sending messages
;; slice begin_parse(cell c) - Converts a cell into a slice
;; (slice, int) load_int(slice s, int len) -
;; Loads a signed len-bit integer from a slice.
;; (slice, int) load_uint(slice s, int len) -
;; Loads a unsigned len-bit integer from a slice.
;; (slice, slice) load_bits(slice s, int len) -
;; Loads the first 0 ≤ len ≤ 1023 bits from slice into a separate slice.
;; (slice, cell) load_ref(slice s) - Loads the reference cell from the slice.
;; builder begin_cell() - Creates a new empty builder.
;; cell end_cell(builder b) - Converts a builder into an ordinary cell.
;; builder store_int(builder b, int x, int len) -
;; Stores a signed len-bit integer x into b for 0 ≤ len ≤ 257.
;; builder store_uint(builder b, int x, int len) -
;; Stores an unsigned len-bit integer x into b for 0 ≤ len ≤ 256.
;; builder store_slice(builder b, slice s) - Stores slice s into builder b.
;; builder store_ref(builder b, cell c) -
;; Stores a reference to cell c into builder b.
;; cell get_data() - Returns the persistent contract storage cell.
;; () set_data(cell c) - Sets cell c as persistent contract data.
;; () send_raw_message(cell msg, int mode) -
;; put message msg into sending queue with mode.
;; Note, that message will be sent after a successful execution
;; of the whole transaction
;; Detailed descriptions of all standard functions can be found
;; in docs https://ton.org/docs/#/func/stdlib
;;
```
## Additional resources
- [FunC Lessons](https://github.com/romanovichim/TonFunClessons_Eng)
- [TON Development Onboarding](https://www.tonspace.co)
- [TON Documentation](https://ton.org/docs/#/)
- [FunC Documentation](https://ton.org/docs/#/func/overview)
- [TON Smart Contracts examples](https://github.com/ton-blockchain/ton/tree/master/crypto/smartcont)
- [Community portal](https://society.ton.org)
- [Blockchain portal](https://ton.org)
- [Stackoverflow](https://stackoverflow.com/questions/tagged/ton)
## Social
- [Developer community](https://t.me/tondev_eng)
- [TON Learn](https://t.me/ton_learn)
- [FunC Lessons Channel](https://github.com/romanovichim/TonFunClessons_Eng)
- [FunC onboarding](https://t.me/func_guide)
- [Tondev News](https://t.me/tondevnews)
## Useful blogposts
- [Setting up a TON Development Environment](https://society.ton.org/setting-up-a-ton-development-environment)
- [Hello World on TON](https://society.ton.org/ton-hello-world-step-by-step-guide-for-writing-your-first-smart-contract-in-func)
## Future To Dos
- Add smart contracts examples
- Add more posts
This file is mostly copied from [TonFunClessons 15 minutes intro](https://github.com/romanovichim/TonFunClessons_Eng/blob/main/13lesson/15min.md).
P.S. If by any chance you're familiar with [Forth](https://learnxinyminutes.com/docs/forth/), you can also take a look at [Fift](https://ton-blockchain.github.io/docs/fiftbase.pdf).

View File

@ -1,602 +0,0 @@
---
language: Tact
filename: tact.tc
contributors:
- ["Tal Kol", "https://www.orbs.com/"]
- ["Kirill Malev", "https://fslabs.io"]
- ["Yash Garg", "https://github.com/yash0501"]
---
Tact language is used to program smart contracts on the
[The Open Network](https://ton.org) blockchain. Contract logic is executed in
TVM, the stack-based TON Virtual Machine.
Tact is a statically typed, but language was designed to be friendly for
developers with JS and Python background.
This page is based on [Tact-by-Example](https://tact-by-example.org/).
You can use this resource to play around with contracts and check out
the interactive features.
# Basic syntax, function definition
```c
// Single line comment
// This is a multi-line comment
// this is a comment in the comment
get fun greeting(): String {
// This is a function that returns "hello world" message
// Return type is specified after a colon :
return "hello world";
}
```
# A Simple Counter contract
This is a simple counter contract that allows users to increment its value.
This contract has a state variable `val` that persists between contract calls
- the counter value. When persisted, this variable is encoded as `uint32` -
a 32-bit unsigned integer. Contracts pay rent in proportion to the amount
of persistent space they consume, so compact representations are encouraged.
State variables should be initialized in `init()` that runs on deployment of
the contract.
## Messages
The actor model is a model of concurrent computation and is at the heart of TON
smart contracts. Each smart contract can process one message at a time, change
its own state, or send one or several messages. Processing of the message
occurs in one transaction, that is, it cannot be interrupted. Messages to one
contract are processed consequently one by one. As a result, the execution of
each transaction is local and can be parallelized at the blockchain level,
which allows for on-demand throughput horizontal scaling and hosting an
unlimited number of users and transactions.
## Receiving messages
This contract can receive messages from users. Unlike getters that are just
read-only, messages can do write operations and change the contract's
persistent state. Incoming messages are processed in receive() methods as
transactions and cost gas for the sender.
After deploying the contract, send the increment message by pressing the Send
increment button in order to increase the counter value by one. Afterwards,
call the getter value() to see that the value indeed changed.
```c
contract Counter {
// Tact allows to create a contract
// persistent state variable of type Int to hold the counter value
val: Int as uint32;
// initialize the state variable when contract is deployed
init() {
self.val = 0;
}
// handler for incoming increment messages that change the state
receive("increment") {
self.val = self.val + 1;
}
// read-only getter for querying the counter value
get fun value(): Int {
return self.val;
}
}
```
# The Deployable Trait
Tact doesn't support classical class inheritance, but contracts can implement
traits. One of the commonly used traits is `Deployable`. It implements a simple
receiver for the Deploy message which helps deploy contracts in a standard way.
All contracts are deployed by sending them a message. This can be any message,
but best practice is to designate the special `Deploy`
message for this purpose.
This message has a single field, `queryId`, which is provided by the deployer
(normally zero). If the deploy succeeds, the contract will reply with the
message `DeployOk` and echo the same `queryId` in the response.
If you're using Tact's [auto-generated](https://docs.tact-lang.org/tools/typescript#tact-contract-in-typescript) TypeScript
classes to deploy, sending the deploy message should look like:
```c
const msg = { $$type: "Deploy", queryId: 0n };
await contract.send(sender, { value: toNano(1) }, msg);
```
You can see the implementation of the trait [here](https://github.com/tact-lang/tact/blob/main/stdlib/libs/deploy.tact).
Notice that the file deploy.tact needs to be imported from the standard
library using the import keyword.
```c
// this trait has to be imported
import "@stdlib/deploy";
// the Deployable trait adds a default receiver for the "Deploy" message
contract Counter with Deployable {
val: Int as uint32;
init() {
self.val = 0;
}
receive("increment") {
self.val = self.val + 1;
}
get fun value(): Int {
return self.val;
}
}
```
# Integers
Tact supports a number of primitive data types that are tailored for
smart contract use.
`Int` is the primary number type. Math in smart contracts is always done
with integers and never with floating points since floats are [unpredictable](https://learn.microsoft.com/en-us/cpp/build/why-floating-point-numbers-may-lose-precision).
The runtime type `Int` is always 257-bit signed, so all runtime calculations
are done at 257-bit. This should be large enough for pretty much anything you
need as it's large enough to hold the number of atoms in the universe.
Persistent state variables can be initialized inline or inside `init()`.
If you forget to initialize a state variable, the code will not compile.
## State costs
When encoding `Int` to persistent state, we will usually use smaller
representations than 257-bit to reduce storage cost.
The persistent state size is specified in every declaration of
a state variable after the `as` keyword.
Storing 1000 257-bit integers in state [costs](https://ton.org/docs/develop/smart-contracts/fees#how-to-calculate-fees) about
0.184 TON per year. Storing 1000 32-bit integers only costs
0.023 TON per year by comparison.
```c
import "@stdlib/deploy";
contract Integers with Deployable {
// contract persistent state variables
// integers can be persisted in state in various sizes
// range -2^256 to 2^256 - 1 (takes 257 bit = 32 bytes + 1 bit)
i1: Int as int257 = 3001;
i2: Int as uint256; // range 0 to 2^256 - 1 (takes 256 bit = 32 bytes)
// range -2^255 to 2^255 - 1 (takes 256 bit = 32 bytes)
i3: Int as int256 = 17;
i4: Int as uint128; // range 0 to 2^128 - 1 (takes 128 bit = 16 bytes)
// range -2^127 to 2^127 - 1 (takes 128 bit = 16 bytes)
i5: Int as int128;
i6: Int as coins; // range 0 to 2^120 - 1 (takes 120 bit = 15 bytes)
// range 0 to 18,446,744,073,709,551,615 (takes 64 bit = 8 bytes)
i7: Int as uint64 = 0x1c4a;
// range -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
// (takes 64 bit = 8 bytes)
i8: Int as int64 = -203;
i9: Int as uint32 = 0; // range 0 to 4,294,967,295 (takes 32 bit = 4 bytes)
// range -2,147,483,648 to 2,147,483,647 (takes 32 bit = 4 bytes)
i10: Int as int32 = 0;
i11: Int as uint16 = 0; // range 0 to 65,535 (takes 16 bit = 2 bytes)
i12: Int as int16 = 0; // range -32,768 to 32,767 (takes 16 bit = 2 bytes)
i13: Int as uint8 = 0; // range 0 to 255 (takes 8 bit = 1 byte)
i14: Int as int8 = 0; // range -128 to 127 (takes 8 bit = 1 byte)
init() {
// we can define numbers in hex (base 16)
self.i2 = 0x83dfd552e6372;
self.i4 = 1507998500293440234999; // we can define numbers in decimal
self.i5 = pow(10, 9); // this is 10^9 = 1,000,000,000
self.i6 = ton("1.23"); // easy to read coin balances
// (coins type is nano-tons, like cents, just with 9 decimals)
}
receive("show all") {
dump(self.i1);
dump(self.i2);
dump(self.i3);
dump(self.i4);
dump(self.i5);
dump(self.i6);
dump(self.i7);
dump(self.i8);
}
get fun result(): Int {
return self.i1;
}
}
```
## Bools, Addresses, Strings, Operators and Constants
### Bool
Bool can be used for boolean variables
```js
b1: Bool = true;
b2: Bool = false;
```
### Address
Address is another primitive data type. It represents standard addresses on
the TON blockchain.
TON is divided into multiple chains called workchains. One of the internal
fields of the address is the workchain id:
0 - The standard workchain, for regular users. Your contracts will be here.
-1 - The masterchain, usually for validators.
```js
// bouncable (same foundation wallet)
a1: Address = address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");
// non-bounceable (same foundation wallet)
a2: Address = address("UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI");
```
### String
Tact has basic support for strings. Strings support unicode and don't
have any special escape characters like \n.
Strings are immutable. Once a sequence of characters is created, this
sequence cannot be modified.
If you need to concatenate strings in run-time, you can use a StringBuilder.
This object handles gas efficiently and supports append() of various types to
the string.
```js
s1: String = "hello world";
sb: StringBuilder = beginString();
sb.append(self.s1);
```
### Integer Operations
Addition, subtraction, multiplication, division, modulo,
shift left and right, minimum and maximum numbers, absolute value
```js
i: Int = -12; // temporary variable, runtime Int type is always int257
i = i1 * 3 + (i2 - i); // basic math expressions
i = i1 % 10; // modulo (remainder after division), 3001 % 10 = 1
i = i1 / 1000; // integer division (truncation toward zero), 3001 / 1000 = 3
i = i1 >> 3; // shift right (multiply by 2^n)
i = i1 << 2; // shift left (divide by 2^n)
i = min(i2, 11); // minimum between two numbers
i = max(i2, 66); // maximum between two numbers
i = abs(-1 * i2); // absolute value
```
### Constants
Unlike variables, constants cannot change. Their values are
calculated in compile-time and cannot change during execution.
```js
const StateUnpaid: Int = 0;
```
## Getters, Receivers and Messages
### Getters
Getters are special contract functions that allow users to query
information from the contract.
Contract methods starting with the prefix get fun are all getters.
Calling getters is free and does not cost gas.
Getters are read-only, they cannot change the contract persistent state.
A contract cannot execute a getter of another contract. Getters are only
executable by end-users off-chain.
```js
count: Int as uint32 = 17;
get fun counter(): Int {
return self.count;
}
```
### Receivers
Contract methods named receive() are the handlers that process
each incoming message type.
Tact will automatically route every incoming message to the correct receiver
listening for it according to its type. A message is only handled by one receiver.
Handler for "increment" textual message - this is a textual string message,
these cannot carry input arguments
```js
receive("increment") {
self.val = self.val + 1;
}
```
### Messages
Messages are defined using the message keyword. They can carry input
arguments. For integers, you must define the encoding size, just like in
state variables.
Handler for the "Add" message - this is a binary message that has an input
argument (amount)
```js
receive(msg: Add) {
self.val = self.val + msg.amount;
}
```
## Structs
Structs allow you to combine multiple primitives together in a more semantic way.
Structs can define complex data types that contain multiple fields of
different types. They can also be nested.
```js
// Normal struct
struct Point {
x: Int as int64;
y: Int as int64;
}
// Nested struct
struct Params {
name: String = "Satoshi"; // default value
age: Int? = null; // optional field
point: Point; // nested structs
}
```
## Message Sender and Throwing Errors
### Message Sender
Every incoming message is sent from some contract that has
an address. You can query the address of the message sender by calling sender()
```js
deployer: Address = sender();
```
### Errors
When an error is thrown, the transaction reverts. By writing a
require() on a condition that isn't met
```js
require(self.val < 5, "Counter is too high");
```
## Messages Between Contracts, Sending and Receiving TON Coins
### Messages Between Contracts
Different contracts can only communicate with
each other by sending each other messages.
This example sends a message to the to address with value of 1 TON and body
of a comment with a string "Hello, World!".
SendIgnoreErrors means that even when error occurs during message sending
next messages would be sent anyway.
```js
let to: Address = ...;
let value: Int = ton("1");
send(SendParameters{
to: to, // address of receiver
value: value, // amount of TON you want to send
mode: SendIgnoreErrors, // 8-bit flag configuring how to send message
bounce: true, // if set to true (default) then message
// will be bounced back to sender
body: "Hello, World!".asComment() // message body as Cell
});
```
### Receiving TONs
You can query the contract balance with myBalance() - note
that the value is in nano-tons (like cents, just with 9 decimals). The balance
already contains the incoming message value.
You can also get the incoming TON balance with context().value
```js
val: Int as int64 = myBalance()
// or
// print how much TON coin were sent with this message
dump(context().value);
```
### Sending TONs
We can send any amount of TON to any address just like we created
a send call between different contracts
Send mode SendRemainingValue will add to the outgoing value any excess left
from the incoming message after all gas costs are deducted from it.
```js
amount: Int as coins = ton("1");
send(SendParameters{
to: sender(),
bounce: true,
value: amount,
mode: SendRemainingValue + SendIgnoreErrors
});
```
## If/Else statements and Loops
### If
Tact supports if statements in a similar syntax to most programming
languages. Curly braces are required.
We can have the else and else if similar to other programming languages.
```js
if (val > 1000) {
dump("larger than 1000");
} else if (val > 500) {
dump("between 500 and 1000");
} else {
dump("smaller than 500");
}
```
### Loops
Tact does not support traditional 'for' loops, 'break' and 'continue'
statements in loops.
The repeat loop statement input number must fit within an int32.
```js
// repeat exactly 10 times
repeat (10) {
i = i + 1;
sum = sum + i;
}
// While loop
let x: Int = 10;
while(x > 0) {
x = x - 1;
}
// do-until loop
let x: Int = 10;
do {
x = x - 1;
} until (x <= 0);
```
## Functions
Functions in Tact start with the fun keyword. Functions can receive multiple
input arguments and can optionally return a single output value. You can
return a struct if you want to return multiple values.
```js
fun average(a: Int, b: Int): Int {
return (a + b) / 2;
}
```
## Maps and Arrays
### Maps
Maps are a dictionary type that can hold an arbitrary number of items,
each under a different key.
The keys in maps can either be an Int type or an Address type.
You can check if a key is found in the map by calling the get() method.
Replace the value under a key by calling the set() method.
```js
mi1: map<Int, TokenInfo>; // maps with Int as key
ma1: map<Address, TokenInfo>; // maps with Address as key
```
### Arrays
To create an array, define a map with 'Int' type as key as well as value.
```js
arr: map<Int, Int>; // this is our array implemented with a map
```
## Ownable Standard Library
The Ownable trait allows the contract to set an owner role, which can have
higher priviliges from everybody else.
For this you would need to import the "@stdlib/ownable" library and inherit
it in your contract
- Use the self.requireOwner() call to verify that the person making that
function call is the owner of contract
- 'ChangeOwner{newOwner: Address}' message which allows the owner to
transfer ownership.
- Define state variables named 'owner: Address' and 'stopped: Bool' and
call 'self.requireNotStopped()' on actions that should be stopped.
- Define state variables named 'owner: Address' and "stopped: Bool' and
call 'self.requireNotStopped()' on actions that should be stopped.
```js
import "@stdlib/ownable";
import "@stdlib/deploy";
contract Counter with Deployable, Ownable {
owner: Address;
init() { // initialize a contract with default values like 'constructor'
self.owner = sender(); // we can initialize owner to any value we want, the deployer in this case
self.val = 0;
}
// this message in only available to the owner
receive("double") {
self.requireOwner();
self.val = self.val * 2;
}
// this message will only work until the contract was stopped
receive("increment") {
self.requireNotStopped();
self.val = self.val + 1;
}
// this message will only work as long as the contract is not stopped
receive("increment2") {
self.requireNotStopped();
self.val = self.val + 1;
}
}
```
## Additional resources
- [TON Documentation](https://ton.org/docs/#/)
- [Tact Docs](https://docs.tact-lang.org/)
- [Tact by Example](https://tact-by-example.org/)
- [Community portal](https://society.ton.org)
- [Blockchain portal](https://ton.org)
- [Stackoverflow](https://stackoverflow.com/questions/tagged/ton)
## Social
- [Tact community](https://t.me/tactlang)
- [Developer community](https://t.me/tondev_eng)
- [TON Learn](https://t.me/ton_learn)
- [Tondev News](https://t.me/tondevnews)
- [TON Technical Updates](https://t.me/thetontech)
## Useful blogposts
- [Setting up a TON Development Environment](https://society.ton.org/setting-up-a-ton-development-environment)
- [Hello World on TON](https://society.ton.org/ton-hello-world-step-by-step-guide-for-writing-your-first-smart-contract-in-func)
## Future To Dos
- Add smart contracts examples
- Add more links to documentations
This file is based on [Tact By Example](https://tact-by-example.org).
P.S. If by any chance you're familiar with [Forth](https://learnxinyminutes.com/docs/forth/),
you can also take a look at [Fift](https://ton-blockchain.github.io/docs/fiftbase.pdf).

View File

@ -1,871 +0,0 @@
---
language: Vyper
filename: learnVyper.vy
contributors:
- ["Kenny Peluso", "kennyp.herokuapp.com"]
---
> The content of this document is largely inspired by ["Learn Solidity in Y Minutes"](https:#github.com/adambard/learnxinyminutes-docs/blob/master/solidity.html.markdown)
Vyper lets you program on [Ethereum](https:#www.ethereum.org/), a
blockchain-based virtual machine that allows the creation and
execution of smart contracts, without requiring centralized or trusted parties. It was
designed to improve upon Solidity, another smart contract language for Ethereum, by
limiting unsafe practices and enhancing readability; Vyper seeks to optimize the
security and auditability of smart contracts.
Vyper is an experimental, statically typed, contract programming language meant to
resemble Python. Like objects in OOP, each contract contains state variables, functions,
and common data types. Contract-specific features include event notifiers for listeners,
and custom global variables, global constants.
Some Ethereum contract examples include crowdfunding, voting, and blind auctions.
---
## Table of Contents
- Intro
- Example
1. Data types and associated methods
2. Data structures
3. Simple operators
4. Global variables of note
5. Functions and more
a. functions
b. events
6. Branching and loops
7. Objects/contracts
a. calling external contracts
b. ERC20 built-in
c. following an interface
8. Other keywords
a. selfdestruct
9. Contract design notes
a. obfuscation
b. storage optimization
c. data access in blockchain
d. cron job
e. observer pattern
10. Security
11. Style notes
12. Natspec comments
- Other documents
---
## Intro
From [the docs](https://media.readthedocs.org/pdf/vyper/latest/vyper.pdf)
the foundational tenants of Vyper are:
1. *Security*
2. *Language and compiler simplicity*
3. *Auditability*
This allows for the following features:
1. *Bounds and overflow checking*
- On the arithmetic and array level
- There are no dynamic arrays in Vyper
2. *Support for signed integers and decimal fixed point numbers*
3. *Decidability* - You can always compute precise upper bound on gas cost
4. *Strong typing* - for built-in and custom types
5. *Small and understandable compiler code*
6. *Limited support for pure functions*
- Anything marked `@constant` is not allowed to change the state
Following the principles and goals, Vyper does not provide the following features:
1. *Modifiers* (defining parts of functions elsewhere)
2. *Class inheritance*
3. *Inline assembly*
4. *Function overloading*
5. *Operator overloading*
6. *Recursive calling*
7. *Infinite-length loops*
8. *Binary fixed point* (decimal fixed point is used for its exactness)
WITH THE RAPID CHANGES IN ETHEREUM, THIS DOCUMENT IS UNLIKELY TO STAY UP TO
DATE, SO YOU SHOULD FOLLOW THE LATEST VYPER DOCS AND ETHEREUM BLOG FOR THE LATEST.
ALL CODE HERE IS PROVIDED AS IS, WITH SUBSTANTIAL RISK OF ERRORS OR DEPRECATED CODE
PATTERNS.
This document primarily discusses syntax, and so excludes many
popular design patterns.
As Vyper and Ethereum are under active development, experimental or beta
features are typically marked, and subject to change. Pull requests welcome.
This document describes Vyper version `0.1.0-beta.8`.
*All of the following code exists for educational purposes only!*
*None of the following code should be used in production as-is!*
## Example
```python
# First, a simple todo list contract
# Implements CRUD operations for tasks
# todo.vy (note .vy extension)
### **** START EXAMPLE **** ###
# Start with Natspec comment
# used for documentation
# @title SimpleBank v1
# @author kennyp
# @notice This is a simple bank.
# Vyper contracts must obey a particular order:
# struct -> interface -> events -> globals and constants -> functions
# Additionally, like Python, Vyper functions must be defined in the file
# before they're called.
# Structs
struct Task:
done: bool
deleted: bool
task: string[100]
metadata: bytes32
# Interfaces
contract AnotherContract():
def fetch() -> bytes32: constant
def inform(_taskId: uint256, _status: uint256) -> bool: modifying
# Events
# Events - publicize actions to external listeners
# `indexed` means that it's easier to search/filter on this field
TaskStatus: event({_taskId: indexed(uint256), _status: uint256})
# Global Variables
# State variables are values which are permanently stored in contract storage
# State vars consist of any value persisting beyond any function's scope
# and are permanently stored in contract storage
# You can define your own, custom, unmutable constants
CREATED: constant(uint256) = 0
COMPLETED: constant(uint256) = 1
DELETED: constant(uint256) = 2
# The `public` built-in allows for this address to be read externally
# without defining a `get()` constant function
owner: public(address)
other: public(address)
# uint256 means "unsigned positive integer between 0 and 2^256 - 1"
# Overflow protection is built-in to Vyper
taskCount: uint256
tasks: map(uint256, Task) # dictionary: key=uint256, value: Task struct
# Private Functions
# Start each function with Pythonic decorators
# These decorators resemble Natspec but are actually enforced by Vyper's compiler
# These decorators are:
# @public XOR @private (either one or the other)
# @public (if any contract/user can call it)
# @private (if only internal functions can call it)
# @payable (if the function is payable i.e. accepting ETH)
# @constant (if the function is not modifying anything on-chain)
@private
def _changeTaskStatus( \
_sender: address, \
_taskId: uint256, \
_status: uint256, \
):
# backslashes (\) allow for multi-line code
# Natspec comments are particularly helpful for documentation and readability
# Natspec can be included using familiar Pythonic docstring syntax
"""
@notice
@dev `_sender` MUST be `self.owner`
@param _sender Who is triggering this function
@param _task The description of the task (only useful when task added)
"""
# NOTE: Private functions do not have access to `msg.sender`
# SIDE NOTE: `msg.sender` refers to whoever immediately called the function of
# immediate scope. In other words, if I call a function that calls another
# in-contract, public function, then `msg.sender` turns from my address to
# the address of the current contract.
assert _sender == self.owner # failed assertions cause calls/transactions to fail
# Note that unlike Solidity, `self.` is required to query the contract's state
# Control flow is Pythonic, as is much of Vyper:
_task: string[100] # initialized to default value
_data: bytes32 = sha3(convert(_sender, bytes32)) # owner is obfuscated (but still visible in logs)
if _status == CREATED: # control flow mimics python
# How a new struct is instantiated:
self.tasks[_taskId] = Task({ \
done: False, deleted: False, task: _task, metadata: _data \
})
elif _status == COMPLETED:
# Modifying an existing struct:
self.tasks[_taskId].done = True
elif _status == DELETED:
self.tasks[_taskId].deleted = True
AnotherContract(self.other).inform(_taskId, _status) # modifying external call
log.TaskStatus(_taskId, _status) # emit an event
# Public Functions
# Pythonic constructor - can receive none or many arguments
@public
def __init__(_owner: address, _other_contract: address):
"""
@dev Called once and only upon contract depoyment
"""
self.owner = _owner
self.other = _other_contract
# NOTE: Pythonic whitespace rules are mandated in Vyper
@public
def addTask(_task: string[100]) -> uint256:
"""
@notice Adds a task to contract
@param _task Description of task
@return Id of newly minted task
"""
# msg.sender gives the address of who/what contract is calling this function
self._changeTaskStatus(msg.sender, self.taskCount, CREATED)
self.tasks[self.taskCount].task = _task
self.taskCount += 1
return self.taskCount - 1
@public
def addSpecialTask(_task: string[100]) -> uint256:
"""
@notice Adds a task with metadata pulled from elsewhere
@param _task Description of task
@return Id of newly minted task
"""
self._changeTaskStatus(msg.sender, self.taskCount, CREATED)
self.tasks[self.taskCount].task = _task
self.tasks[self.taskCount].metadata = AnotherContract(self.other).fetch()
self.taskCount += 1
return self.taskCount - 1
@public
def completeTask(_taskId: uint256):
"""
@notice Marks a task as "completed"
@param _taskId Id of task to complete
"""
self._changeTaskStatus(msg.sender, _taskId, COMPLETED)
@public
def deleteTask(_taskId: uint256):
"""
@notice Adds a task to contract
@param _taskId Id of task to delete
"""
self._changeTaskStatus(msg.sender, _taskId, DELETED)
@public
@constant # allows function to run locally/off blockchain
def getTask(_taskId: uint256) -> string[100]:
"""
@notice Getter for a task's description
@param _taskId Id of task with desired description
@return Description of task
"""
return self.tasks[_taskId].task
### **** END EXAMPLE **** ###
# Now, the basics of Vyper
# ---
# 1. DATA TYPES AND ASSOCIATED METHODS
# uint256 used for currency amount and for dates (in unix time)
x: uint256
# int of 128 bits, cannot be changed after contract deployment
# with 'constant', compiler replaces each occurrence with actual value
a: constant(int128) = 5
# All state variables (those outside a function)
# are by default 'internal' and accessible inside contract
# Need to explicitly set to 'public' to allow external contracts to access
# A getter is automatically created, but NOT a setter
# Can only be called in the contract's scope (not within functions)
# Add 'public' field to indicate publicly/externally accessible
a: public(int128)
# No random functions built in, use other contracts for randomness
# Type casting is limited but exists
b: int128 = 5
x: uint256 = convert(b, uint256)
# Types of accounts:
# Contract Account: f(creator_addr, num_transactions)=address set on contract creation
# External Account: (person/external entity): f(public_key)=address
# Addresses - An address type can hold an Ethereum address which
# equates to 20 bytes or 160 bits. It returns in hexadecimal notation
# with a leading 0x. No arithmetic allowed
owner: public(address)
# Members can be invoked on all addresses:
owner.balance # returns balance of address as `wei_value`
owner.codesize # returns code size of address as `int128`
owner.is_contract # `True` if Contract Account
# All addresses can be sent ether via `send()` built-in
@public
@payable
def sendWei(any_addr: address):
send(any_addr, msg.value)
# Bytes available
a: bytes[2]
b: bytes[32]
c: bytes32
# `b` and `c` are 2 different types
# Bytes are preferable to strings since Vyper currently offers better
# support for bytes i.e. more built-ins to deal with `bytes32`, `bytes32`
# can be returned from functions and strings[] can't be, UTF8 (string encoding)
# uses more storage, etc.
# There are no dynamically sized bytes, similar to how there are no
# dynamic arrays
# Fixed-size byte arrays (Strings)
a: string[100]
b: string[8]
c: string[108] = concat(a, b) # check the latest docs for more built-ins
# Time
t1: timedelta
t2: timestamp
# Both types are built-in "custom type" variants of `uint256`
# `timedelta` values can be added but not `timestamp` values
# Money
m: wei_value
# Also has the base type `uint256` like `timestamp` and `timedelta`
# 1 unit of WEI (a small amount of ETH i.e. ether)
# Custom types
# specify units used in the contract:
units: {
cm: "centimeter",
km: "kilometer"
}
# usage:
a: int128(cm)
b: uint256(km)
# BY DEFAULT: all values are set to 0 on instantiation
# `clear()` can be called on most types
# Does NOT destroy value, but sets value to 0, the initial value
# ---
# 2. DATA STRUCTURES
# Arrays
bytes32[5] nicknames; # static array
bytes32[] names; # dynamic array
uint newLength = names.push("John"); # adding returns new length of the array
# Length
names.length; # get length
names.length = 1; # lengths can be set (for dynamic arrays in storage only)
# Multidimensional Arrays
# At initialization, array dimensions must be hard-coded or constants
# Initialize a 10-column by 3-row, multidimensional fixed array
ls: (uint256[10])[3] # parentheses are optional
@public
def setToThree():
# Multidimensional Array Access and Write
# access indices are reversed
# set element in row 2 (3rd row) column 5 (6th column) to 3
self.ls[2][5] = 3
# Dictionaries (any simple type to any other type including structs)
theMap: map(uint256, bytes32)
theMap[5] = sha3("charles")
# theMap[255] result is 0, all non-set key values return zeroes
# To make read public, make a getter that accesses the mapping
@public
def getMap(_idx: uint256) -> bytes32:
"""
@notice Get the value of `theMap` at `_idx`
"""
return self.theMap[_idx]
self.getMap(5) # returns sha3("charles") in bytes32
# Nested mappings
aMap: map(address, map(address, uint256))
# NOTE: Mappings are only allowed as state variables
# NOTE: Mappings are not iterable; can only be accessed
# To delete (reset the mapping's value to default at a key)
clear(balances["John"])
clear(balances); # sets all elements to 0
# Unlike other languages, CANNOT iterate through all elements in
# mapping, without knowing source keys - can build data structure
# on top to do this
# Structs
struct Struct:
owner: address
_balance: uint256 # balance is a reserved keyword, is a member for addresses
exampleStruct: Struct
@public
def foo() -> uint256:
self.exampleStruct = Struct({owner: msg.sender, _balance: 5})
self.exampleStruct._balance = 10
self.exampleStruct._balance = 5 # set to new value
clear(self.exampleStruct._balance)
clear(self.exampleStruct)
return self.exampleStruct._balance
# Data locations: Memory vs. storage vs. calldata - all complex types (arrays,
# structs) have a data location
# 'memory' does not persist, 'storage' does
# Default is 'storage' for local and state variables; 'memory' for func params
# stack holds small local variables
# for most types, can explicitly set which data location to use
# ---
# 3. SIMPLE OPERATORS
# Comparisons, bit operators and arithmetic operators are provided
# exponentiation: **
# modulo: %
# maximum: max(x, y)
# AND: bitwise_and(x, y)
# bitwise shift: shift(x, _shift)
# where x,y are uint256
# _shift is int128
# 4. GLOBAL VARIABLES OF NOTE
# ** self **
self # address of contract
# often used at end of contract life to transfer remaining balance to party:
self.balance # balance of current contract
self.someFunction() # calls func externally via call, not via internal jump
# ** msg - Current message received by the contract **
# Ethereum programmers take NOTE: this `msg` object is smaller than elsewhere
msg.sender # address of sender
msg.value # amount of ether provided to this contract in wei, the function should be marked `@payable`
msg.gas # remaining gas
# ** tx - This transaction **
# Ethereum programmers take NOTE: this `tx` object is smaller than elsewhere
tx.origin # address of sender of the transaction
# ** block - Information about current block **
block.timestamp # time at current block (uses Unix time)
# Note that `block.timestamp` can be manipulated by miners, so be careful
block.number # current block number
block.difficulty # current block difficulty
# ** storage - Persistent storage hash **
storage['abc'] = 'def'; # maps 256 bit words to 256 bit words
# ---
# 5. FUNCTIONS AND MORE
# A. FUNCTIONS
# Simple function
function increment(uint x) returns (uint) {
x += 1;
return x;
}
# Functions can return many arguments
@public
@constant
def increment(x: uint256, y: uint256) -> (uint256, uint256):
x += 1
y += 1
return (x, y)
# Call previous function
@public
@constant
def willCall() -> (uint256, uint256):
return self.increment(1,1)
# One should never have to call a function / hold any logic outside
# outside the scope of a function in Vyper
# '@constant'
# indicates that function does not/cannot change persistent vars
# Constant function execute locally, not on blockchain
y: uint256
@public
@constant
def increment(x: uint256) -> uint256:
x += 1
y += 1 # this line would fail
# y is a state variable => can't be changed in a constant function
# 'Function Decorators'
# Used like python decorators but are REQUIRED by Vyper
# @public - visible externally and internally (default for function)
# @private - only visible in the current contract
# @constant - doesn't change state
# @payable - receive ether/ETH
# @nonrentant(<unique_key>) - Function can only be called once, both externally
# and internally. Used to prevent reentrancy attacks
# Functions hare not hoisted
# Functions cannot be assigned to a variable
# Functions cannot be recursive
# All functions that receive ether must be marked 'payable'
@public
@payable
def depositEther():
self.balances[msg.sender] += msg.value
# B. EVENTS
# Events are notify external parties; easy to search and
# access events from outside blockchain (with lightweight clients)
# typically declare after contract parameters
# Declare
LogSent: event({_from: indexed(address), address: indexed(_to), _amount: uint256})
# Call
log.LogSent(from, to, amount)
/**
For an external party (a contract or external entity), to watch using
the Web3 Javascript library:
# The following is Javascript code, not Vyper code
Coin.LogSent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins were sent from " + result.args.from +
" to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
}
}
**/
# Common paradigm for one contract to depend on another (e.g., a
# contract that depends on current exchange rate provided by another)
# ---
# 6. BRANCHING AND LOOPS
# All basic logic blocks from Python work - including if/elif/else, for,
# while, break, continue, return - but no switch
# Syntax same as Python, but no type conversion from non-boolean
# to boolean (comparison operators must be used to get the boolean val)
# REMEMBER: Vyper does not allow resursive calls or infinite loops
# ---
# 7. OBJECTS/CONTRACTS
# REMEMBER: Vyper does not allow for inheritance or imports
# A. CALLING EXTERNAL CONTRACTS
# You must define an interface to an external contract in the current contract
contract InfoFeed():
def getInfo() -> uint256: constant
info: uint256
@public
def __init__(_source: address):
self.info = InfoFeed(_source).getInfo()
# B. ERC20 BUILT-IN
# Using the `ERC20` keyword implies that the contract at the address
# follows the ERC20 token standard, allowing you to safely call
# functions like `transfer()`, etc.
tokenAddress: address(ERC20)
@public
def transferIt(_to: address, _amt: uint256(wei)):
self.tokenAddress.transfer(_to, _amt)
# C. FOLLOWING AN INTERFACE
# Vyper is experimenting with using the following syntax at the top of
# a `.vy` file to specify what interfaces are followed by the contract
# This allows interfaces to be better organized, registered, and recognized
import interfaces.some_interface as SomeInterface
implements: SomeInterface
# <rest of contract>
# ---
# 8. OTHER KEYWORDS
# A. selfdestruct()
# selfdestruct current contract, sending funds to address (often creator)
selfdestruct(SOME_ADDRESS);
# removes storage/code from current/future blocks
# helps thin clients, but previous data persists in blockchain
# Common pattern, lets owner end the contract and receive remaining funds
@public
def endItAll() {
assert msg.sender == self.creator # Only let the contract creator do this
selfdestruct(self.creator) # Makes contract inactive, returns funds
# May want to deactivate contract manually, rather than selfdestruct
# (ether sent to selfdestructed contract is lost)
# B. sha3()
# Encrypts strings and other data
# Very important on the blockchain
# Takes 1 argument, `concat()` can be called beforehand
# All strings passed are concatenated before hash action
sha3(concat("ab", "cd")) # returns bytes32
# ---
# 9. CONTRACT DESIGN NOTES
# A. Obfuscation
# All variables are publicly viewable on blockchain, so anything
# that is private needs to be obfuscated (e.g., hashed w/secret)
# Oftentimes, a "commit-reveal" scheme is employed
# Step 1. Commit
# Place a commitment by sending output of `sha3()`
sha3("a secret"); # bytes32 commit
sha3(concat("secret", "other secret", "salt")); # commit multiple things
# The `sha3()` calculation should occur off-chain, only the bytes32
# output should be inputted into some `commit()` function
commits: map(address, bytes32)
@public
def commit(commitment: bytes32):
self.commits[msg.sender] = commitment
# Step 2. Reveal
# Send your previously committed data so the contract can check
# if your commitment was honest
@public
def reveal(_secret: string[100], _salt: string[100]) -> bool:
return sha3(concat(_secret, _salt)) == self.commits[msg.sender]
# B. Storage optimization
# Writing to blockchain can be expensive, as data stored forever; encourages
# smart ways to use memory (eventually, compilation will be better, but for now
# benefits to planning data structures - and storing min amount in blockchain)
# Cost can often be high for items like multidimensional arrays
# (cost is for storing data - not declaring unfilled variables)
# C. Data access in blockchain
# Cannot restrict human or computer from reading contents of
# transaction or transaction's state
# While 'private' prevents other *contracts* from reading data
# directly - any other party can still read data in blockchain
# All data to start of time is stored in blockchain, so
# anyone can observe all previous data and changes
# D. Cron Job
# Contracts must be manually called to handle time-based scheduling;
# can create external code to regularly ping or provide incentives
# (ether) for others to ping
# E. Observer Pattern
# An Observer Pattern lets you register as a subscriber and
# register a function which is called by the oracle (note, the oracle
# pays for this action to be run)
# Some similarities to subscription in Pub/sub
# This is an abstract contract, both client and server classes import,
# the client should implement
### **** START EXAMPLE **** ###
contract SomeOracleCallback():
def oracleCallback(_value: uint256, _time: timestamp, _info: bytes32): modifying
MAX_SUBS: constant(uint256) = 100
numSubs: public(uint256) # number of subscribers
subs: map(uint256, address) # enumerates subscribers
@public
def addSub(_sub: address) -> uint256:
"""
@notice Add subscriber
@param _sub Address to add
@return Id of newly added subscriber
"""
self.subs[self.numSubs] = _sub
self.numSubs += 1
return self.numSubs - 1
@private
def notify(_value: uint256, _time: timestamp, _info: bytes32) -> bool:
"""
@notice Notify all subscribers
@dev Check `numSubs` first; Watch out for gas costs!
@param _value whatever
@param _time what have you
@param _info what else
@return True upon successful completion
"""
j: uint256
for i in range(MAX_SUBS):
j = convert(i, uint256) # `i` is int128 by default
if j == self.numSubs:
return True
SomeOracleCallback(self.subs[j]).oracleCallback(_value, _time, _info)
@public
def doSomething():
"""
@notice Do something and notify subscribers
"""
# ...something...
whatever: uint256 = 6
what_have_you: timestamp
what_else: bytes32 = sha3("6")
self.notify(whatever, what_have_you, what_else)
# Now, your client contract can addSubscriber by importing SomeOracleCallback
# and registering with Some Oracle
### **** END EXAMPLE **** ###
# ---
# 10. SECURITY
# Bugs can be disastrous in Ethereum contracts - and even popular patterns in
# Vyper may be found to be antipatterns
# See security links at the end of this doc
# ---
# 11. STYLE NOTES
# Based on Python's PEP8 style guide
# Full Style guide: http:#solidity.readthedocs.io/en/develop/style-guide.html
# Quick summary:
# 4 spaces for indentation
# Two lines separate contract declarations (and other top level declarations)
# Avoid extraneous spaces in parentheses
# Can omit curly braces for one line statement (if, for, etc)
# else should be placed on own line
# Specific to Vyper:
# arguments: snake_case
# events, interfaces, structs: PascalCase
# public functions: camelCase
# private functions: _prefaceWithUnderscore
# ---
# 12. NATSPEC COMMENTS
# used for documentation, commenting, and external UIs
# Contract natspec - always above contract definition
# @title Contract title
# @author Author name
# Function natspec
# Should include in docstring of functions in typical Pythonic fashion
# @notice Information about what function does; shown when function to execute
# @dev Function documentation for developer
# Function parameter/return value natspec
# @param someParam Some description of what the param does
# @return Description of the return value
```
## Additional resources
- [Installation](https://vyper.readthedocs.io/en/latest/installing-vyper.html)
- [Vyper Docs](https://media.readthedocs.org/pdf/vyper/latest/vyper.pdf)
- [Vyper GitHub (under active dev)](https://github.com/ethereum/vyper)
- [Tools and Resources](https://github.com/ethereum/vyper/wiki/Vyper-tools-and-resources)
- [Online Compiler](https://vyper.online/)
## Sample contracts
- [Uniswap](https://github.com/Uniswap/contracts-vyper)
- [Generalized Governance](https://github.com/kpeluso/gdg)
- [Dynamic Arrays](https://github.com/kpeluso/vyper-dynamic-array)
## Security
Vyper is secure by design, but it may be helpful to understand what Vyper is
protecting you from.
- [Thinking About Smart Contract Security](https:#blog.ethereum.org/2016/06/19/thinking-smart-contract-security/)
- [Smart Contract Security](https:#blog.ethereum.org/2016/06/10/smart-contract-security/)
- [Hacking Distributed Blog](http:#hackingdistributed.com/)
## Style
- [Vyper Style Guide WIP](https://github.com/ethereum/vyper/issues/905)
- Heavily derived from [Solidity's style guide](http:#solidity.readthedocs.io/en/latest/style-guide.html) ...
- ... which, in turn, is heavily derived from Python's [PEP 8](https:#www.python.org/dev/peps/pep-0008/) style guide.
## Editors
- [Vyper for VS Code (alpha)](https://github.com/p-/vscode-vyper)
## Future To Dos
- Update to current Vyper release
- List of common design patterns
*Feel free to send a pull request with any edits - or email* `pelusoken -/at-/ gmail`