mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 17:41:41 +00:00
Create vyper.html.markdown
This commit is contained in:
parent
47fe9495ca
commit
35c8d77d01
867
vyper.html.markdown
Normal file
867
vyper.html.markdown
Normal file
@ -0,0 +1,867 @@
|
||||
---
|
||||
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 fixed array
|
||||
ls: (uint256[10])[3] # note the parentheses
|
||||
@public
|
||||
def setToThree():
|
||||
self.ls[5][3] = 3 # multidimensional array access
|
||||
|
||||
# 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`
|
||||
|
Loading…
Reference in New Issue
Block a user