Solidity PAtterns
@tomusdrw
Tomasz Drwięga
Parity Technologies
Title Text
contract SimpleToken { // TODO Use SafeMath!
uint256 constant rate = 1000;
event Transfer(address indexed _from, address indexed _to, uint _value);
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => uint256) public lockTime;
function buyTokens() payable {
var tokens = msg.value * rate;
var unlockAt = block.number + 256 * 24; // 24h
totalSupply += tokens;
balanceOf[msg.sender] += tokens;
lockTime[msg.sender] = unlockAt;
}
function burnTokens(uint256 _value) {
var tokens = _value * rate;
require(block.number >= lockTime[msg.sender]);
require(balanceOf[msg.sender] >= tokens);
balanceOf[msg.sender] -= tokens;
totalSupply -= tokens;
delete lockTime[msg.sender];
msg.sender.transfer(_value);
}
function transfer(address _to, uint _value) returns (bool success) {
if (block.number < lockTime[msg.sender]) return false;
if (balanceOf[msg.sender] < _value) return false;
if (_value == 0 || balanceOf[_to] + _value <= balanceOf[_to]) return false;
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
}
}
Token Contract
Token Standard (ERC20)
https://theethereum.wiki/w/index.php/ERC20_Token_Standard
// https://github.com/ethereum/EIPs/issues/20
contract ERC20 {
function totalSupply()
constant returns (uint totalSupply);
function balanceOf(address _owner)
constant returns (uint balance);
function transfer(address _to, uint _value)
returns (bool success);
function transferFrom(address _from, address _to, uint _value)
returns (bool success);
function approve(address _spender, uint _value)
returns (bool success);
function allowance(address _owner, address _spender)
constant returns (uint remaining);
event Transfer(
address indexed _from, address indexed _to, uint _value
);
event Approval(
address indexed _owner, address indexed _spender, uint _value
);
}
Questions to Discuss
How much is one token worth?
Does the lock period change anything?
What if you can do something ONLY if you own that token?
Some More Practical Examples?
Designing contracts is designing economic models around them.
"What are the incentives?"
Remote Purchase
Done wrong
contract Purchase {
uint public value;
address public seller;
address public buyer;
function Purchase(uint _value) {
seller = msg.sender;
value = _value;
}
/// Confirm the purchase as buyer.
function confirmPurchase() payable {
require(msg.value == value);
buyer = msg.sender;
}
/// Confirm that you (the buyer) received the item.
function confirmReceived() {
require(msg.sender == buyer);
seller.transfer(this.balance);
}
}
Safe Remote Purchase
(Simplified)
contract Purchase {
uint public value;
address public seller;
address public buyer;
// Seller needs to deposit double the value of the item.
function Purchase() payable {
seller = msg.sender;
value = msg.value / 2;
require(value * 2 == msg.value);
}
function abort() {
require(msg.sender == seller);
require(buyer == 0);
seller.transfer(this.balance);
}
/// Confirm the purchase as buyer. Deposit double the value of the item.
function confirmPurchase() payable {
require(msg.value == 2 * value);
buyer = msg.sender;
}
/// Confirm that you (the buyer) received the item.
function confirmReceived() {
require(msg.sender == buyer);
buyer.transfer(value); // transfer half of the deposit
seller.transfer(this.balance); // transfer the entire deposit
delete value;
}
}
Simple Auction
Done slightly wrong
contract Auction {
address public beneficiary;
uint public endBlock;
bool public ended;
address public highestBidder;
uint public highestBid;
mapping(address => uint) pendingReturns;
function Auction(uint _time) {
beneficiary = msg.sender; endBlock = block.number + _time;
}
function bid() payable {
require(block.number < endBlock);
require(highestBid < msg.value);
if (highestBidder != 0) { pendingReturns[highestBidder] += highestBid; }
highestBidder = msg.sender;
highestBid = msg.value;
}
function endAuction() {
require(endBlock <= block.number);
require(!ended);
ended = true;
beneficiary.transfer(highestBid);
}
function withdraw() {
uint amount = pendingReturns[msg.sender];
delete pendingReturns[msg.sender];
msg.sender.transfer(amount);
}
}
Why is it wrong?
Because Auction Theory
https://en.wikipedia.org/wiki/Auction_theory
"There are many possible designs for an auction and typical issues studied by auction theorists include the efficiency of a given auction design, optimal and equilibrium bidding strategies, and revenue comparison."
Solution?
Blind Auction
https://en.wikipedia.org/wiki/Auction#Primary
https://en.wikipedia.org/wiki/Commitment_scheme
"In this type of auction all bidders simultaneously submit sealed bids so that no bidder knows the bid of any other participant. The highest bidder pays the price they submitted."
address public beneficiary;
uint public bidEndBlock;
uint public revealEndBlock;
bool public ended;
address public highestBidder;
uint public highestBid;
mapping(address => uint) pendingReturns;
mapping(address => bytes32) bids;
mapping(address => uint) deposits;
function Auction(uint _time) {
beneficiary = msg.sender;
bidEndBlock = block.number + _time;
revealEndBlock = bidEndBlock + _time;
}
function bid(bytes32 _blindedBid) payable {
require(block.number < bidEndBlock);
bids[msg.sender] = _blindedBid;
deposits[msg.sender] = msg.value;
}
function endAuction() {
require(revealEndBlock <= block.number);
require(!ended);
ended = true;
beneficiary.transfer(highestBid);
}
function withdraw() {
uint amount = pendingReturns[msg.sender];
delete pendingReturns[msg.sender];
msg.sender.transfer(amount);
}
function reveal(bytes32 _secret, uint _value) {
require(bidEndBlock <= block.number);
require(block.number < revealEndBlock);
require(deposits[msg.sender] > _value);
require(
bids[msg.sender] == keccak256(_value, _secret)
);
var isHighest = placeBid(msg.sender, _value);
var deposit = deposits[msg.sender];
delete bids[msg.sender];
delete deposits[msg.sender];
if (!isHighest) {
msg.sender.transfer(deposit);
} else {
msg.sender.transfer(deposit - _value);
}
}
function placeBid(address _bidder, uint _value)
internal returns (bool success)
{
if (highestBid < _value) return false;
if (highestBidder != 0) pendingReturns[highestBidder] += highestBid;
highestBidder = _bidder;
highestBid = _value;
return true;
}
Blind auction (simplified)
Withdraw Pattern
pragma solidity ^0.4.11;
contract KingOfEthereum {
address public richest;
uint public mostSent;
function KingOfEthereum() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
// This line can cause problems
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
}
Withdraw Pattern
pragma solidity ^0.4.11;
contract KingOfEthereum {
address public richest;
uint public mostSent;
mapping (address => uint) pendingWithdrawals;
function KingOfEthereum() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
State Machine
pragma solidity ^0.4.11;
contract StateMachine {
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
// This is the current stage.
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(stage == _stage);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// Perform timed transitions. Be sure to mention
// this modifier first, otherwise the guards
// will not take the new stage into account.
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();
// The other stages transition by transaction
_;
}
// Order of the modifiers matters here!
function bid()
public
payable
timedTransitions
atStage(Stages.AcceptingBlindedBids)
{
// We will not implement that here
}
function reveal()
public
timedTransitions
atStage(Stages.RevealBids)
{}
// This modifier goes to the next stage
// after the function is done.
modifier transitionNext()
{
_;
nextStage();
}
function g()
public
timedTransitions
atStage(Stages.AnotherStage)
transitionNext
{
}
}
Modifier Anti-patterns?
State Machine
pragma solidity ^0.4.11;
contract StateMachine {
enum Stages {
AcceptingBlindedBids,
RevealBids,
AnotherStage,
AreWeDoneYet,
Finished
}
// This is the current stage.
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
modifier atStage(Stages _stage) {
require(stage == _stage);
_;
}
function nextStage() internal {
stage = Stages(uint(stage) + 1);
}
// Perform timed transitions.
// Anyone can call this function.
function timedTransition() public {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
nextStage();
}
// Order of the modifiers DOES NOT matter!
function bid()
public
payable
atStage(Stages.AcceptingBlindedBids)
{
// We will not implement that here
}
function reveal()
public
atStage(Stages.RevealBids)
{}
function g()
public
atStage(Stages.AnotherStage)
{
// just manually progress to next stage
nextStage();
}
}
Modifier fixed.
Inspired to write some contracts?
Learn Game Theory basics first.
And please read this:
"Building a Fun Ethereum Game" by Conrad Barski
http://www.cointagion.com/2016/08/24/part-i/
References
- Dan Finlay's Intro to how Ethereum works https://youtu.be/-SMliFtoPn8
- Solidity docs https://solidity.readthedocs.io
- ERC20 https://theethereum.wiki/w/index.php/ERC20_Token_Standard
- Browser Solidity / Remix https://ethereum.github.io/browser-solidity/
- Parity https://github.com/paritytech/parity
- Safe Remote Purchase http://solidity.readthedocs.io/en/develop/solidity-by-example.html#safe-remote-purchase
- Auction Theory https://en.wikipedia.org/wiki/Auction_theory
- Game Theory https://en.wikipedia.org/wiki/Game_theory
-
Commitment Scheme https://en.wikipedia.org/wiki/Commitment_scheme
- Building a Fun Ethereum Game http://www.cointagion.com/2016/08/24/part-i/
some other features of Solidity
That were not covered here
- Libraries
- Inheritance
- Calling other contracts
- Memory
- Encapsulation (visibility modifiers)
https://www.meetup.com/Wroclaw-Blockchain-Meetup/
Tuesday, 27th June 2017
Smart Contracts (part 2)
By Tomasz Drwięga
Smart Contracts (part 2)
- 533