summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKenny P <17100641+kpeluso@users.noreply.github.com>2019-03-20 01:16:40 -0400
committerGitHub <noreply@github.com>2019-03-20 01:16:40 -0400
commit35c8d77d01b573207297774c3b0c3c7ce7b62b8c (patch)
treecc8a590f2137ee52b85402e5dbb1b2e74fdaf67e
parent47fe9495ca6ad680ed1e5cf5f94326322dfb2e9b (diff)
Create vyper.html.markdown
-rw-r--r--vyper.html.markdown867
1 files changed, 867 insertions, 0 deletions
diff --git a/vyper.html.markdown b/vyper.html.markdown
new file mode 100644
index 00000000..2c654797
--- /dev/null
+++ b/vyper.html.markdown
@@ -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`
+