mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-27 03:08:52 +00:00
6e0ce5f976
Multidimensional array syntax was wrong, now fixed
873 lines
26 KiB
Markdown
873 lines
26 KiB
Markdown
---
|
|
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 readibility
|
|
# 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
|
|
|
|
exampleStuct: Struct
|
|
|
|
@public
|
|
def foo() -> uint256:
|
|
self.exampleStuct = Struct({owner: msg.sender, _balance: 5})
|
|
self.exampleStuct._balance = 10
|
|
self.exampleStuct._balance = 5 # set to new value
|
|
clear(self.exampleStuct._balance)
|
|
clear(self.exampleStuct)
|
|
return self.exampleStuct._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 functon
|
|
@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"); # btyes32 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`
|
|
|