New documentation setup (#1708)
* initial docsite setup * switch from pushd to cd * install and set up solidity-docgen * use the docsite branch next for now * make it clear that env var is a repository * add a clarifying comment about a relative path * change relative to absolute path in docsite script * add docgen script * add first few READMEs for contract documentation * update solidity-docgen * add docsite as dependency and adjust script * update openzeppelin-docsite * update solidity-docgen * remove dummy text * update docgen and docsite * update openzeppelin-docsite * add netlify.toml * udpate tokens guide for 2.2 * add DOCUMENTATION.md * Update docs/learn-about-utilities.md Co-Authored-By: frangio <frangio.1@gmail.com> * fix PaymentSplitter docs wording * update solidity-docgen * add missing ERC20 contracts * update solidity-docgen * trigger deploy with cleared cache * update solidity-docgen * update openzeppelin-docsite * remove travis docs setup * update openzeppelin-docsite * switch to published solidity-docgenpull/1725/head
parent
412cdfd0be
commit
ed5652b0aa
@ -0,0 +1,19 @@ |
||||
We're building an improved documentation website. It's still in development and |
||||
contributions will be really appreciated. |
||||
|
||||
All of the content for the site is in this repository. The guides are in the |
||||
[docs](/docs) directory, and the API Reference is extracted from comments in |
||||
the source code. If you want to help improve the content, this is the |
||||
repository you should be contributing to. |
||||
|
||||
[`solidity-docgen`](https://github.com/OpenZeppelin/solidity-docgen/tree/next) is the |
||||
program that extracts the API Reference from source code. |
||||
|
||||
The [`openzeppelin-docsite`](https://github.com/OpenZeppelin/openzeppelin-docsite/tree/next) |
||||
repository hosts the configuration for Docusaurus, the static site generator |
||||
that we use. |
||||
|
||||
To run the docsite locally you should run `npm run docsite start` on this |
||||
repository. This will live reload as the guides are edited, but not with |
||||
changes to the source code comments, for that you need to restart the server. |
||||
This should be improved eventually (contributions welcome!). |
@ -0,0 +1,7 @@ |
||||
--- |
||||
sections: |
||||
- title: Library |
||||
contracts: |
||||
- Roles |
||||
- subdirectory: roles |
||||
--- |
@ -0,0 +1,11 @@ |
||||
--- |
||||
title: Crowdsales |
||||
sections: |
||||
- title: Core |
||||
contracts: |
||||
- Crowdsale |
||||
- subdirectory: emission |
||||
- subdirectory: price |
||||
- subdirectory: validation |
||||
- subdirectory: distribution |
||||
--- |
@ -0,0 +1,18 @@ |
||||
--- |
||||
sections: |
||||
- title: Interfaces |
||||
contracts: |
||||
- IERC20 |
||||
- title: Contracts |
||||
contracts: |
||||
- ERC20 |
||||
- ERC20Detailed |
||||
- ERC20Mintable |
||||
- ERC20Burnable |
||||
- ERC20Capped |
||||
- ERC20Pausable |
||||
- title: Utilities |
||||
contracts: |
||||
- SafeERC20 |
||||
- TokenTimelock |
||||
--- |
@ -0,0 +1,51 @@ |
||||
--- |
||||
id: get-started |
||||
title: Get Started |
||||
--- |
||||
|
||||
OpenZeppelin can be installed directly into your existing node.js project with `npm install --save-exact openzeppelin-solidity`, but OpenZeppelin also integrates directly with [Truffle](https://github.com/ConsenSys/truffle), an Ethereum development environment, which we'll use to get started. |
||||
|
||||
Please install Truffle and initialize your project: |
||||
|
||||
```sh |
||||
$ npm install -g truffle |
||||
$ mkdir myproject && cd myproject |
||||
$ truffle init |
||||
``` |
||||
|
||||
To install the OpenZeppelin library, run the following in your Solidity project root directory: |
||||
```sh |
||||
$ npm init -y |
||||
$ npm install --save-exact openzeppelin-solidity |
||||
``` |
||||
|
||||
**Note that OpenZeppelin only lightly follows semantic versioning.** You may encounter breaking changes upon a minor version bump. We recommend pinning the version of OpenZeppelin you use by using the `--save-exact` option. |
||||
|
||||
After that, you'll get all the library's contracts in the `node_modules/openzeppelin-solidity/contracts` folder. Because Truffle and other Ethereum development toolkits understand `node_modules`, you can use the contracts in the library like so: |
||||
|
||||
```js |
||||
import 'openzeppelin-solidity/contracts/ownership/Ownable.sol'; |
||||
|
||||
contract MyContract is Ownable { |
||||
... |
||||
} |
||||
``` |
||||
|
||||
## Next Steps |
||||
|
||||
After installing OpenZeppelin, check out the rest of the guides in the sidebar to learn about the different contracts that OpenZeppelin provides and how to use them. |
||||
|
||||
- [Learn About Access Control](/api/docs/learn-about-access-control.html) |
||||
- [Learn About Crowdsales](/api/docs/learn-about-crowdsales.html) |
||||
- [Learn About Tokens](/api/docs/learn-about-tokens.html) |
||||
- [Learn About Utilities](/api/docs/learn-about-utilities.html) |
||||
|
||||
You may also want to take a look at the guides which cover several common use cases and good practices: https://blog.zeppelin.solutions/guides/home |
||||
|
||||
For example, [The Hitchhiker’s Guide to Smart Contracts in Ethereum](https://blog.zeppelin.solutions/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05) will help you get an overview of the various tools available for smart contract development, and help you set up your environment. |
||||
|
||||
[A Gentle Introduction to Ethereum Programming, Part 1](https://blog.zeppelin.solutions/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094) provides very useful information on an introductory level, including many basic concepts from the Ethereum platform. |
||||
|
||||
For a more in-depth dive, you may read the guide [Designing the architecture for your Ethereum application](https://blog.zeppelin.solutions/designing-the-architecture-for-your-ethereum-application-9cec086f8317), which discusses how to better structure your application and its relationship to the real world. |
||||
|
||||
You may also ask for help or follow OpenZeppelin's progress in the community [forum](https://forum.zeppelin.solutions), or read OpenZeppelin's full API on this website. |
@ -0,0 +1,104 @@ |
||||
--- |
||||
id: learn-about-access-control |
||||
title: Learn About Access Control |
||||
--- |
||||
|
||||
Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. The access control of your contract governs who can mint tokens, who can vote on proposals, who can [`selfdestruct()`](https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7) the contract, and more, so it's very important to understand how you implement it. |
||||
|
||||
## Ownership & Ownable.sol |
||||
|
||||
The most common and basic form of access control is the concept of _ownership_: there's one account that is the `owner` and can do administrative tasks on contracts. This approach is perfectly reasonable for contracts that only have a single administrative user. |
||||
|
||||
OpenZeppelin provides [contracts/ownership/Ownable.sol](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol) for implementing ownership in your contracts. |
||||
|
||||
```solidity |
||||
import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; |
||||
|
||||
contract MyContract is Ownable { |
||||
|
||||
function normalThing() |
||||
public |
||||
{ |
||||
// anyone can call this normalThing() |
||||
} |
||||
|
||||
function specialThing() |
||||
public |
||||
onlyOwner |
||||
{ |
||||
// only the owner can call specialThing()! |
||||
} |
||||
} |
||||
``` |
||||
|
||||
By default, the `owner` of an `Ownable` contract is the `msg.sender` of the contract creation transaction, which is usually exactly what you want. |
||||
|
||||
Ownable also lets you: |
||||
+ `transferOwnership(address newOwner)` to transfer ownership from one account to another |
||||
+ `renounceOwnership()` to remove the owner altogether, useful for decentralizing control of your contract. **⚠ Warning!** Removing the owner altogether will mean that administrative tasks that are protected by `onlyOwner` will no longer be callable! |
||||
|
||||
|
||||
Note that any contract that supports sending transactions can also be the owner of a contract; the only requirement is that the owner has an Ethereum address, so it could be a Gnosis Multisig or Gnosis Safe, an Aragon DAO, an ERC725/uPort identity contract, or a totally custom contract that _you_ create. |
||||
|
||||
In this way you can use _composability_ to add additional layers of access control complexity to your contracts. Instead of having a single Ethereum Off-Chain Account (EOA) as the owner, you can replace them with a 2/3 multisig run by your project leads, for example. |
||||
|
||||
### Examples in OpenZeppelin |
||||
|
||||
You'll notice that none of the OpenZeppelin contracts use Ownable, though! This is because there are more flexible ways of providing access control that are more in-line with our reusable contract philosophy. For most contracts, We'll use `Roles` to govern who can do what. There are some cases, though—like with `Escrow`—where there's a direct relationship between contracts. In those cases, we'll use [`Secondary`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Secondary.sol) to create a "secondary" contract that allows a "primary" contract to manage it. |
||||
|
||||
Let's learn about Role-Based Access Control! |
||||
|
||||
## Role-Based Access Control & Roles.sol |
||||
|
||||
An alternative to single-concern `Ownable` is role based access control (RBAC), which, instead of keeping track of a single entity with "admin" level privileges, keeps track of multiple different entities with a variety of roles that inform the contract about what they can do. |
||||
|
||||
For example, a `MintableToken` could have a `minter` role that decides who can mint tokens (which could be assigned to a Crowdsale). It could also have a `namer` role that allows changing the name or symbol of the token (for whatever reason). RBAC gives you much more flexibility over who can do what and is generally recommended for applications that need more configurability. If you're experienced with web development, the vast majority of access control systems are role-based: some users are normal users, some are moderators, and some can be company employee admins. |
||||
|
||||
OpenZeppelin provides [contracts/access/Roles.sol](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/access/Roles.sol) for implementing role-based access control. |
||||
|
||||
Here's an example of using `Roles` in our token example above, we'll use it to implement a token that can be minted by `Minters` and renamed by `Namers`: |
||||
|
||||
```solidity |
||||
import "openzeppelin-solidity/contracts/access/Roles.sol"; |
||||
|
||||
contract MyToken is DetailedERC20, StandardToken { |
||||
using Roles for Roles.Role; |
||||
|
||||
Roles.Role private minters; |
||||
Roles.Role private namers; |
||||
|
||||
constructor( |
||||
string name, |
||||
string symbol, |
||||
uint8 decimals, |
||||
address[] minters, |
||||
address[] namers, |
||||
) |
||||
DetailedERC20(name, symbol, decimals) |
||||
Standardtoken() |
||||
public |
||||
{ |
||||
namers.addMany(namers); |
||||
minters.addMany(minters); |
||||
} |
||||
|
||||
function mint(address to, uint256 amount) |
||||
public |
||||
{ |
||||
// only allow minters to mint |
||||
require(minters.has(msg.sender), "DOES_NOT_HAVE_MINTER_ROLE"); |
||||
_mint(to, amount); |
||||
} |
||||
|
||||
function rename(string name, string symbol) |
||||
public |
||||
{ |
||||
// only allow namers to name |
||||
require(namers.has(msg.sender), "DOES_NOT_HAVE_NAMER_ROLE"); |
||||
name = name; |
||||
symbol = symbol; |
||||
} |
||||
} |
||||
``` |
||||
|
||||
So clean! You'll notice that the role associations are always the last arguments in the constructor; this is a good pattern to follow to keep your code more organized. |
@ -0,0 +1,245 @@ |
||||
--- |
||||
id: learn-about-crowdsales |
||||
title: Learn About Crowdsales |
||||
--- |
||||
|
||||
Crowdsales are a popular use for Ethereum; they let you allocate tokens to network participants in various ways, mostly in exchange for Ether. They come in a variety of shapes and flavors, so let's go over the various types available in OpenZeppelin and how to use them. |
||||
|
||||
Crowdsales have a bunch of different properties, but here are some important ones: |
||||
- Price & Rate Configuration |
||||
- Does your crowdsale sell tokens at a fixed price? |
||||
- Does the price change over time or as a function of demand? |
||||
- Emission |
||||
- How is this token actually sent to participants? |
||||
- Validation — Who is allowed to purchase tokens? |
||||
- Are there KYC / AML checks? |
||||
- Is there a max cap on tokens? |
||||
- What if that cap is per-participant? |
||||
- Is there a starting and ending time frame? |
||||
- Distribution |
||||
- Does distribution of funds happen in real-time or after the crowdsale? |
||||
- Can participants receive a refund if the goal is not met? |
||||
|
||||
To manage all of the different combinations and flavors of crowdsales, OpenZeppelin provides a highly configurable [`Crowdsale.sol`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/Crowdsale.sol) base contract that can be combined with various other functionalities to construct a bespoke crowdsale. |
||||
|
||||
## Crowdsale Rate |
||||
|
||||
Understanding the rate of a crowdsale is super important, and mistakes here are a common source of bugs. |
||||
|
||||
✨ **HOLD UP FAM THIS IS IMPORTANT** ✨ |
||||
|
||||
Firstly, **all currency math is done in the smallest unit of that currency and converted to the correct decimal places when _displaying_ the currency**. |
||||
|
||||
This means that when you do math in your smart contracts, you need to understand that you're adding, dividing, and multiplying the smallest amount of a currency (like wei), _not_ the commonly-used displayed value of the currency (Ether). |
||||
|
||||
In Ether, the smallest unit of the currency is wei, and `1 ETH === 10^18 wei`. In tokens, the process is _very similar_: `1 TKN === 10^(decimals) TKNbits`. |
||||
|
||||
- The smallest unit of a token is "bits" or `TKNbits`. |
||||
- The display value of a token is `TKN`, which is `TKNbits * 10^(decimals)` |
||||
|
||||
What people usually call "one token" is actually a bunch of TKNbits, displayed to look like `1 TKN`. This is the same relationship that Ether and wei have. And what you're _always_ doing calculations in is **TKNbits and wei**. |
||||
|
||||
So, if you want to issue someone "one token for every 2 wei" and your decimals are 18, your rate is `0.5e17`. Then, when I send you `2 wei`, your crowdsale issues me `2 * 0.5e17 TKNbits`, which is exactly equal to `10^18 TKNbits` and is displayed as `1 TKN`. |
||||
|
||||
If you want to issue someone "`1 TKN` for every `1 ETH`", and your decimals are 18, your rate is `1`. This is because what's actually happening with the math is that the contract sees a user send `10^18 wei`, not `1 ETH`. Then it uses your rate of 1 to calculate `TKNbits = rate * wei`, or `1 * 10^18`, which is still `10^18`. And because your decimals are 18, this is displayed as `1 TKN`. |
||||
|
||||
One more for practice: if I want to issue "1 TKN for every dollar (USD) in Ether", we would calculate it as follows: |
||||
|
||||
- assume 1 ETH == $400 |
||||
- therefore, 10^18 wei = $400 |
||||
- therefore, 1 USD is `10^18 / 400`, or `2.5 * 10^15 wei` |
||||
- we have a decimals of 18, so we'll use `10 ^ 18 TKNbits` instead of `1 TKN` |
||||
- therefore, if the participant sends the crowdsale `2.5 * 10^15 wei` we should give them `10 ^ 18 TKNbits` |
||||
- therefore the rate is `2.5 * 10^15 wei === 10^18 TKNbits`, or `1 wei = 400 TKNbits` |
||||
- therefore, our rate is `400` |
||||
|
||||
(this process is pretty straightforward when you keep 18 decimals, the same as Ether/wei) |
||||
|
||||
|
||||
## Token Emission |
||||
|
||||
One of the first decisions you have to make is "how do I get these tokens to users?". This is usually done in one of three ways: |
||||
|
||||
- (default) — The Crowdsale contract owns tokens and simply transfers tokens from its own ownership to users that purchase them. |
||||
- [MintedCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/emission/MintedCrowdsale.sol) — The Crowdsale mints tokens when a purchase is made. |
||||
- [AllowanceCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/emission/AllowanceCrowdsale.sol) — The Crowdsale is granted an allowance to another wallet (like a Multisig) that already owns the tokens to be sold in the crowdsale. |
||||
|
||||
### Default Emission |
||||
|
||||
In the default scenario, your crowdsale must own the tokens that are sold. You can send the crowdsale tokens through a variety of methods, but here's what it looks like in Solidity: |
||||
|
||||
```solidity |
||||
IERC20(tokenAddress).transfer(CROWDSALE_ADDRESS, SOME_TOKEN_AMOUNT); |
||||
``` |
||||
|
||||
Then when you deploy your crowdsale, simply tell it about the token |
||||
|
||||
```solidity |
||||
new Crowdsale( |
||||
1, // rate in TKNbits |
||||
MY_WALLET, // address where Ether is sent |
||||
TOKEN_ADDRESS // the token contract address |
||||
); |
||||
``` |
||||
|
||||
### Minted Crowdsale |
||||
|
||||
To use a `MintedCrowdsale`, your token must also be a `ERC20Mintable` token that the crowdsale has permission to mint from. This can look like: |
||||
|
||||
```solidity |
||||
contract MyToken is ERC20, ERC20Mintable { |
||||
// ... see "Learn About Tokens" for more info |
||||
} |
||||
|
||||
contract MyCrowdsale is Crowdsale, MintedCrowdsale { |
||||
constructor( |
||||
uint256 rate, // rate in TKNbits |
||||
address wallet, |
||||
ERC20 token |
||||
) |
||||
MintedCrowdsale() |
||||
Crowdsale(rate, wallet, token) |
||||
public |
||||
{ |
||||
|
||||
} |
||||
} |
||||
|
||||
contract MyCrowdsaleDeployer { |
||||
constructor() |
||||
public |
||||
{ |
||||
// create a mintable token |
||||
ERC20Mintable token = new MyToken(); |
||||
|
||||
// create the crowdsale and tell it about the token |
||||
Crowdsale crowdsale = new MyCrowdsale( |
||||
1, // rate, still in TKNbits |
||||
msg.sender, // send Ether to the deployer |
||||
address(token) // the token |
||||
); |
||||
// transfer the minter role from this contract (the default) |
||||
// to the crowdsale, so it can mint tokens |
||||
token.addMinter(address(crowdsale)); |
||||
token.renounceMinter(); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
### AllowanceCrowdsale |
||||
|
||||
Use an `AllowanceCrowdsale` to send tokens from another wallet to the participants of the crowdsale. In order for this to work, the source wallet must give the crowdsale an allowance via the ERC20 `approve(...)` method. |
||||
|
||||
```solidity |
||||
contract MyCrowdsale is AllowanceCrowdsale, Crowdsale { |
||||
constructor( |
||||
uint256 rate, |
||||
address wallet, |
||||
ERC20 token, |
||||
address tokenWallet // <- new argument |
||||
) |
||||
AllowanceCrowdsale(tokenWallet) // <- used here |
||||
Crowdsale(rate, wallet, token) |
||||
public |
||||
{ |
||||
|
||||
} |
||||
} |
||||
``` |
||||
|
||||
Then after the crowdsale is created, don't forget to approve it to use your tokens! |
||||
|
||||
```solidity |
||||
IERC20(tokenAddress).approve(CROWDALE_ADDRESS, SOME_TOKEN_AMOUNT); |
||||
``` |
||||
|
||||
## Validation |
||||
|
||||
There are a bunch of different validation requirements that your crowdsale might be a part of: |
||||
|
||||
- [CappedCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/validation/CappedCrowdsale.sol) — adds a cap to your crowdsale, invalidating any purchases that would exceed that cap |
||||
- [IndividuallyCappedCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol) — caps an individual's contributions. |
||||
- [WhitelistedCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/validation/WhitelistedCrowdsale.sol) — only allow whitelisted participants to purchase tokens. this is useful for putting your KYC / AML whitelist on-chain! |
||||
- [TimedCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/validation/TimedCrowdsale.sol) — adds an `openingTime` and `closingTime` to your crowdsale |
||||
|
||||
Simply mix and match these crowdsale flavors to your heart's content: |
||||
|
||||
```solidity |
||||
contract MyCrowdsale is CappedCrowdsale, TimedCrowdsale, Crowdsale { |
||||
|
||||
constructor( |
||||
uint256 rate, // rate, in TKNbits |
||||
address wallet, // wallet to send Ether |
||||
ERC20 token, // the token |
||||
uint256 cap, // total cap, in wei |
||||
uint256 openingTime, // opening time in unix epoch seconds |
||||
uint256 closingTime // closing time in unix epoch seconds |
||||
) |
||||
CappedCrowdsale(cap) |
||||
TimedCrowdsale(openingTime, closingTime) |
||||
Crowdsale(rate, wallet, token) |
||||
public |
||||
{ |
||||
// nice, we just created a crowdsale that's only open |
||||
// for a certain amount of time |
||||
// and stops accepting contributions once it reaches `cap` |
||||
} |
||||
} |
||||
``` |
||||
|
||||
## Distribution |
||||
|
||||
There comes a time in every crowdsale's life where it must relinquish the tokens it's been entrusted with. It's your decision as to when that happens! |
||||
|
||||
The default behavior is to release tokens as participants purchase them, but sometimes that may not be desirable. For example, what if we want to give users a refund if we don't hit a minimum raised in the sale? Or, maybe we want to wait until after the sale is over before users can claim their tokens and start trading them, perhaps for compliance reasons? |
||||
|
||||
OpenZeppelin is here to make that easy! |
||||
|
||||
### PostDeliveryCrowdsale |
||||
|
||||
The [PostDeliveryCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/distribution/PostDeliveryCrowdsale.sol), as its name implies, distributes tokens after the crowdsale has finished, letting users call `withdrawTokens()` in order to claim the tokens they've purchased. |
||||
|
||||
```solidity |
||||
contract MyCrowdsale is PostDeliveryCrowdsale, TimedCrowdsale, Crowdsale { |
||||
|
||||
constructor( |
||||
uint256 rate, // rate, in TKNbits |
||||
address wallet, // wallet to send Ether |
||||
ERC20 token, // the token |
||||
uint256 openingTime, // opening time in unix epoch seconds |
||||
uint256 closingTime // closing time in unix epoch seconds |
||||
) |
||||
PostDeliveryCrowdsale() |
||||
TimedCrowdsale(startTime, closingTime) |
||||
Crowdsale(rate, wallet, token) |
||||
public |
||||
{ |
||||
// nice! this Crowdsale will keep all of the tokens until the end of the crowdsale |
||||
// and then users can `withdrawTokens()` to get the tokens they're owed |
||||
} |
||||
} |
||||
``` |
||||
|
||||
### RefundableCrowdsale |
||||
|
||||
The [RefundableCrowdsale](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/distribution/RefundableCrowdsale.sol) offers to refund users if a minimum goal is not reached. If the goal is not reached, the users can `claimRefund()` to get their Ether back. |
||||
|
||||
|
||||
```solidity |
||||
contract MyCrowdsale is RefundableCrowdsale, Crowdsale { |
||||
|
||||
constructor( |
||||
uint256 rate, // rate, in TKNbits |
||||
address wallet, // wallet to send Ether |
||||
ERC20 token, // the token |
||||
uint256 goal // the minimum goal, in wei |
||||
) |
||||
RefundableCrowdsale(goal) |
||||
Crowdsale(rate, wallet, token) |
||||
public |
||||
{ |
||||
// nice! this crowdsale will, if it doesn't hit `goal`, allow everyone to get their money back |
||||
// by calling claimRefund(...) |
||||
} |
||||
} |
||||
``` |
@ -0,0 +1,138 @@ |
||||
--- |
||||
id: learn-about-tokens |
||||
title: Learn About Tokens |
||||
--- |
||||
|
||||
Ah, the "token": the world's most powerful and most misused tool. In this section we'll learn to harness the power of native units of account for good and world peace! |
||||
|
||||
## But First, ~~Coffee~~ a Primer on Tokens |
||||
|
||||
Simply put, a token _isn't anything special_. In Ethereum, pretty much _everything_ is a contract, and that includes what we call tokens. "Sending a token" is the same as "calling a method on a smart contract that someone wrote and deployed". And, at the end of the day, a token is just a mapping of addresses to balances and some nice methods to add and subtract from those balances. |
||||
|
||||
That's it! These balances could be considered money, or they could be voting rights or they could be experience points in your game. |
||||
|
||||
Even though the concept of a token is simple, they have a variety of complexities in the implementation. Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of **standards** (called EIPs or ERCs) for documenting how a contract can interoperate with other contracts. |
||||
|
||||
You've probably heard of the **ERC20** standard, and that's why you're here. |
||||
|
||||
## ERC20 |
||||
|
||||
An ERC20 token is a contract that keeps track of a `mapping(address => uint256)` that represents a user's balance. These tokens are _fungible_ in that any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC20 useful for things like a medium of exchange currency, general voting rights, staking, and more. |
||||
|
||||
OpenZeppelin provides a few different ERC20-related contracts. Here are the core contracts you'll almost definitely be using: |
||||
|
||||
- [IERC20](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/IERC20.sol) — defines the interface that all ERC20 token implementations should conform to |
||||
- [ERC20](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20.sol) — the base implementation of the ERC20 interface |
||||
- [ERC20Detailed](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20Detailed.sol) — the `name()`, `symbol()`, and `decimals()` getters are optional in the original standard, so `ERC20Detailed` adds that information to your token. |
||||
|
||||
|
||||
After that, OpenZeppelin provides a few extra properties that you may want depending on your use-case: |
||||
|
||||
- [ERC20Mintable](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20Mintable.sol) — `ERC20Mintable` allows users with the [`MinterRole`](/api/docs/learn-about-access-control.html) to call the `mint()` function and mint tokens to users. Minting can also be finished, locking the `mint()` function's behavior. |
||||
- [ERC20Burnable](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20Burnable.sol) — if your token can be burned (aka, it can be destroyed), include this one |
||||
- [ERC20Capped](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20Capped.sol) — `ERC20Capped` is a type of `ERC20Mintable` that enforces a maximum cap on tokens; this is really useful if you want to ensure network participants that there will always be a maximum number of tokens, and is useful for making sure that multiple different minting methods don't accidentally create more tokens than you expected. |
||||
- [ERC20Pausable](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20Pausable.sol) — `ERC20Pausable` allows anyone with the Pauser role to pause the token, freezing transfers to and from users. This is useful if you want to stop trades until the end of a crowdsale, or if you want to have an emergency switch for freezing your tokens in the event of a large bug. Note that there are inherent decentralization tradeoffs when using a pausable token; users may not expect that their unstoppable money can be frozen by a single address! |
||||
|
||||
Finally, if you're working with ERC20 tokens, OpenZeppelin provides some utility contracts: |
||||
|
||||
- [SafeERC20](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/SafeERC20.sol) — provides `safeTransfer`, `safeTransferFrom`, and `safeApprove` that are helpful wrappers around the normal ERC20 functions. Using `SafeERC20` forces transfers and approvals to succeed, or the entire transaction is reverted. |
||||
- [TokenTimelock](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/TokenTimelock.sol) — is an escrow contract for ERC20 tokens that will release some tokens after a specified timeout. This is useful for simple vesting schedules like "advisors get all of their tokens after 1 year". For a better vesting schedule, though, see [`TokenVesting`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/drafts/TokenVesting.sol) |
||||
|
||||
### Constructing a Nice ERC20 Token |
||||
|
||||
Now that we know what all of the contracts do (you should read the code! It's open source!), we can make our ERC20 token that will revolutionize dogsitting by reducing human beings to organic machines that act entirely based on rational monetary incentives, fueled by the DOGGO token. |
||||
|
||||
Here's what a good DOGGO token might look like. |
||||
|
||||
```solidity |
||||
contract DoggoToken is ERC20, ERC20Detailed, ERC20Mintable, ERC20Burnable { |
||||
|
||||
constructor( |
||||
string memory name, |
||||
string memory symbol, |
||||
uint8 decimals |
||||
) |
||||
ERC20Burnable() |
||||
ERC20Mintable() |
||||
ERC20Detailed(name, symbol, decimals) |
||||
ERC20() |
||||
public |
||||
{} |
||||
} |
||||
``` |
||||
|
||||
`ERC20Mintable` allows to add minters via `addMinter(addr)`, so they (like the DOGGO Network multisig) can mint tokens to the dogsitters in exchange for watching the nice doggos while their owners leave for vacation. The token is `ERC20Burnable` we want to have the ability to stake DOGGO tokens on our reputation—if the dogsitter does a bad job, their tokens get burned! |
||||
|
||||
### A Note on `decimals` |
||||
|
||||
You might remember from the previous chapter about crowdsales about how math is performed in financial situations: **all currency math is done in the smallest unit of that currency**. |
||||
|
||||
That means that the `totalSupply` of a token is actually in what we call `TKNbits`, not what you see as `TKN`. So if my total supply is `1` and we have `5` decimals in the token, that's actually `1 TKNbit` and will be displayed as `0.00001 TKN`. |
||||
|
||||
You probably want to use a decimals of `18`, just like Ether, unless you have a special reason not to, so when you're minting tokens to people or transferring them around, you're actually sending the number `numTKN * 10^(decimals)`. So if I'm sending you `5` tokens using a token contract with 18 decimals, the method I'm executing actually looks like `transfer(yourAddress, 5 * 10^18)`. |
||||
|
||||
## ERC721 |
||||
|
||||
We've discussed how you can make a _fungible_ token using ERC20, but what if not all tokens are alike? This comes up in situations like company stock; some stock is common stock and some stock is investor shares, etc. It also comes up in a bunch of other places like in-game items, time, property, and so on. |
||||
|
||||
[ERC721](https://eips.ethereum.org/EIPS/eip-721) is a standard for representing ownership that is **non-fungible** aka, each token has unique properties. |
||||
|
||||
Let's see what contracts OpenZeppelin provides for helping us work with ERC721: |
||||
|
||||
- The `IERC721`, `IERC721Metadata`, `IERC721Enumerable` interfaces are part of the [IERC721.sol](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/IERC721.sol) file, which document the interfaces. |
||||
- [ERC721](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol) — is the full implementation of ERC721, and the contract you'll most likely be inheriting from. |
||||
- [IERC721Receiver](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/IERC721Receiver.sol) — in some cases, it's beneficial to be 100% certain that a contract knows how to handle ERC721 tokens (imagine sending an in-game item to an exchange address that can't send it back!). When using `safeTransferFrom()`, the contract checks to see that the receiver is an `IERC721Receiver`, which implies that it knows how to handle ERC721 tokens. If you're writing a contract that accepts 721 tokens, you'll want to implement this interface. |
||||
- [ERC721Mintable](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721Mintable.sol) — like the ERC20 version, ERC721Mintable allows addresses with the `Minter` role to mint tokens. |
||||
- [ERC721Pausable](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721Pausable.sol) — like the ERC20 version, ERC721Pausable allows addresses with the `Pauser` role to freeze transfers of tokens. |
||||
|
||||
|
||||
We'll use these contracts to tokenize the time of our dogsitters: when a dogsitter wants to sell an hour of their time to watch a dog, they can mint an ERC721 token that represents that hour slot and then sell this token on an exchange. Then they'll go to the owner's house at the right time to watch their doggos. |
||||
|
||||
Here's what tokenized dogsitter timeframes might look like: |
||||
|
||||
```solidity |
||||
contract DoggoTime is ERC721Full { |
||||
using Counters for Counters.Counter; |
||||
Counters.Counter private tokenId; |
||||
|
||||
constructor( |
||||
string memory name, |
||||
string memory symbol |
||||
) |
||||
ERC721Full(name, symbol) |
||||
public |
||||
{} |
||||
|
||||
function createDoggoTimeframe( |
||||
string memory tokenURI |
||||
) |
||||
public |
||||
returns (bool) |
||||
{ |
||||
tokenId.increment(); |
||||
uint256 doggoTokenId = tokenId.current(); |
||||
_mint(msg.sender, doggoTokenId); |
||||
_setTokenURI(doggoTokenId, tokenURI); |
||||
return true; |
||||
} |
||||
}``` |
||||
|
||||
Now anyone who wants to sell their time in exchange for DOGGO tokens can call: |
||||
|
||||
```solidity |
||||
DoggoTime(doggoTimeAddress).createDoggoTimeframe("https://example.com/doggo.json") |
||||
``` |
||||
|
||||
where the tokenURI should resolve to a json document that might look something like: |
||||
|
||||
```json |
||||
{ |
||||
"name": "Alex's DOGGO Dogsitting Time — 1 Hour on Thursday the 5th at 6pm", |
||||
"description": "Alex agrees to dog sit for 1 hour of her time on Thursday the 5th at 6pm.", |
||||
"image": "https://example.com/doggo-network.png" |
||||
} |
||||
``` |
||||
|
||||
For more information about tokenURI metadata, check out the [finalized ERC721 spec](https://eips.ethereum.org/EIPS/eip-721). |
||||
|
||||
_Note: you'll also notice that the date information is included in the metadata, but that information isn't on-chain! So Alex the dogsitter could change the time and scam some people out of their money! If you'd like to put the dates of the dogsitting hours on-chain, you can extend ERC721 to do so. You could also leverage IPFS to pin the tokenURI information, which lets viewers know if Alex has changed the metadata associated with her tokens, but these techniques are out of the scope of this overview guide._ |
@ -0,0 +1,94 @@ |
||||
--- |
||||
id: learn-about-utilities |
||||
title: Learn About Utilities |
||||
--- |
||||
|
||||
OpenZeppelin provides a ton of useful utilities that you can use in your project. Here are some of the more popular ones: |
||||
|
||||
## Cryptography |
||||
|
||||
- [ECDSA.sol](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/cryptography/ECDSA.sol) — provides functions for recovering and managing Ethereum account ECDSA signatures: |
||||
- to use it, declare: `using ECDSA for bytes32;` |
||||
- signatures are tightly packed, 65 byte `bytes` that look like `{v (1)} {r (32)} {s (32)}` |
||||
- this is the default from `web3.eth.sign` so you probably don't need to worry about this format |
||||
- recover the signer using `myDataHash.recover(signature)` |
||||
- if you are using `eth_personalSign`, the signer will hash your data and then add the prefix `\x19Ethereum Signed Message:\n`, so if you're attempting to recover the signer of an Ethereum signed message hash, you'll want to use `toEthSignedMessageHash` |
||||
|
||||
|
||||
Use these functions in combination to verify that a user has signed some information on-chain: |
||||
|
||||
```solidity |
||||
keccack256( |
||||
abi.encodePacked( |
||||
someData, |
||||
moreData |
||||
) |
||||
) |
||||
.toEthSignedMessageHash() |
||||
.recover(signature) |
||||
``` |
||||
|
||||
- [MerkleProof.sol](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/cryptography/MerkleProof.sol) — provides `verify(...)` for verifying merkle proofs. |
||||
|
||||
|
||||
## Introspection |
||||
|
||||
In Solidity, it's frequently helpful to know whether or not a contract supports an interface you'd like to use. ERC165 is a standard that helps do runtime interface detection. OpenZeppelin provides some helpers, both for implementing ERC165 in your contracts and querying other contracts: |
||||
|
||||
- [IERC165](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/introspection/IERC165.sol) — this is the ERC165 interface that defines `supportsInterface(...)`. When implementing ERC165, you'll conform to this interface. |
||||
- [ERC165](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/introspection/ERC165.sol) — inherit this contract if you'd like to support interface detection using a lookup table in contract storage. You can register interfaces using `_registerInterface(bytes4)`: check out example usage as part of the ERC721 implementation. |
||||
- [ERC165Checker](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/introspection/ERC165Checker.sol) — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about. |
||||
- include with `using ERC165Checker for address;` |
||||
- `myAddress.supportsInterface(bytes4)` |
||||
- `myAddress.supportsInterfaces(bytes4[])` |
||||
|
||||
|
||||
```solidity |
||||
contract MyContract { |
||||
using ERC165Checker for address; |
||||
|
||||
bytes4 private InterfaceId_ERC721 = 0x80ac58cd; |
||||
|
||||
/** |
||||
* @dev transfer an ERC721 token from this contract to someone else |
||||
*/ |
||||
function transferERC721( |
||||
address token, |
||||
address to, |
||||
uint256 tokenId |
||||
) |
||||
public |
||||
{ |
||||
require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN"); |
||||
IERC721(token).transferFrom(address(this), to, tokenId); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
## Math |
||||
|
||||
The most popular math related library OpenZeppelin provides is [SafeMath](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol), which provides mathematical functions that protect your contract from overflows and underflows. |
||||
|
||||
Include the contract with `using SafeMath for uint256;` and then call the functions: |
||||
|
||||
- `myNumber.add(otherNumber)` |
||||
- `myNumber.sub(otherNumber)` |
||||
- `myNumber.div(otherNumber)` |
||||
- `myNumber.mul(otherNumber)` |
||||
- `myNumber.mod(otherNumber)` |
||||
|
||||
Easy! |
||||
|
||||
## Payment |
||||
|
||||
Want to split some payments between multiple people? Maybe you have an app that sends 30% of art purchases to the original creator and 70% of the profits to the current owner; you can build that with [`PaymentSplitter`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/payment/PaymentSplitter.sol)!. |
||||
|
||||
In solidity, there are some security concerns with blindly sending money to accounts, since it allows them to execute arbitrary code. You can read up on these security concerns in the [Ethereum Smart Contract Best Practices](https://consensys.github.io/smart-contract-best-practices/) website. One of the ways to fix reentrancy and stalling problems is, instead of immediately sending Ether to accounts that need it, you can use [`PullPayment`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/payment/PullPayment.sol), which offers an `asyncSend` function for sending money to something and requesting that they `withdraw()` it later. |
||||
|
||||
If you want to Escrow some funds, check out [`Escrow`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/payment/escrow/Escrow.sol) and [`ConditionalEscrow`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/payment/escrow/ConditionalEscrow.sol) for governing the release of some escrowed Ether. |
||||
|
||||
### Misc |
||||
|
||||
Want to check if an address is a contract? Use [`Address`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/utils/Address.sol) and `Address#isContract()`. |
||||
|
||||
Want to keep track of some numbers that increment by 1 every time you want another one? Check out [`Counter`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.1.2/contracts/drafts/Counter.sol). This is especially useful for creating incremental ERC721 tokenIds like we did in the last section. |
@ -0,0 +1,17 @@ |
||||
{ |
||||
"Overview": [ |
||||
"get-started" |
||||
], |
||||
"Guides": [ |
||||
"learn-about-access-control", |
||||
"learn-about-crowdsales", |
||||
"learn-about-tokens", |
||||
"learn-about-utilities" |
||||
], |
||||
"API Reference": [ |
||||
{ |
||||
"type": "directory", |
||||
"directory": "api" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,3 @@ |
||||
[build] |
||||
command = "npm run docsite build" |
||||
publish = "docsite-build" |
File diff suppressed because it is too large
Load Diff
@ -1,32 +0,0 @@ |
||||
#!/bin/bash |
||||
# |
||||
# Trigger the job that will update the documentation website. |
||||
# Argument: |
||||
# version: the version of the new release. This should be a tag in the |
||||
# https://github.com/OpenZeppelin/openzeppelin-solidity repository. |
||||
|
||||
set -ev |
||||
|
||||
if [ "$#" -lt 1 ]; then |
||||
echo "Usage: $0 <version>" |
||||
exit 1 |
||||
fi |
||||
|
||||
readonly VERSION="$1" |
||||
|
||||
readonly BODY="{ |
||||
\"request\": { |
||||
\"branch\": \"master\", |
||||
\"config\": { |
||||
\"env\": [\"VERSION=${VERSION}\"] |
||||
} |
||||
} |
||||
}" |
||||
|
||||
curl -s -X POST \ |
||||
-H "Content-Type: application/json" \ |
||||
-H "Accept: application/json" \ |
||||
-H "Travis-API-Version: 3" \ |
||||
-H "Authorization: token ${DOCS_TRAVIS_API_TOKEN}" \ |
||||
-d "${BODY}" \ |
||||
https://api.travis-ci.com/repo/OpenZeppelin%2Fopenzeppelin-docs/requests |
@ -0,0 +1,6 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
OUTDIR=docs/api |
||||
|
||||
rm -rf "$OUTDIR" |
||||
solidity-docgen -o "$OUTDIR" -i contracts/mocks -i contracts/examples |
@ -0,0 +1,8 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
# usage: npm run docsite [build|start] |
||||
|
||||
set -o errexit |
||||
|
||||
npm run docgen |
||||
npx openzeppelin-docsite-run "$1" |
Loading…
Reference in new issue