Vyper -サンプルより学ぶ-
Vyperの公式ホームページでは、サンプルコードが5つ紹介されています。
本ブログでは、それらからどのようにVyperでコーディングをするのか、学んでいきたいと思います。
紹介するコード
- Simple Open Auction
- Safe Remote Purchase
- Crowdfund
- Voting
- Company Stock
1. Simple Open Auction
# Open Auction # Auction params # Beneficiary receives money from the highest bidder beneficiary: public(address) auction_start: public(timestamp) auction_end: public(timestamp) # Current state of auction highest_bidder: public(address) highest_bid: public(wei_value) # Set to true at the end, disallows any change ended: public(bool) # Create a simple auction with `_bidding_time` # seconds bidding time on behalf of the # beneficiary address `_beneficiary`. @public def __init__(_beneficiary: address, _bidding_time: timedelta): self.beneficiary = _beneficiary self.auction_start = block.timestamp self.auction_end = self.auction_start + _bidding_time # Bid on the auction with the value sent # together with this transaction. # The value will only be refunded if the # auction is not won. @public @payable def bid(): # Check if bidding period is over. assert block.timestamp < self.auction_end # Check if bid is high enough assert msg.value > self.highest_bid if not self.highest_bid == 0: # Sends money back to the previous highest bidder send(self.highest_bidder, self.highest_bid) self.highest_bidder = msg.sender self.highest_bid = msg.value # End the auction and send the highest bid # to the beneficiary. @public def end_auction(): # It is a good guideline to structure functions that interact # with other contracts (i.e. they call functions or send Ether) # into three phases: # 1. checking conditions # 2. performing actions (potentially changing conditions) # 3. interacting with other contracts # If these phases are mixed up, the other contract could call # back into the current contract and modify the state or cause # effects (Ether payout) to be performed multiple times. # If functions called internally include interaction with external # contracts, they also have to be considered interaction with # external contracts. # 1. Conditions # Check if auction endtime has been reached assert block.timestamp >= self.auction_end # Check if this function has already been called assert not self.ended # 2. Effects self.ended = True # 3. Interaction send(self.beneficiary, self.highest_bid)
変数の設定方法
変数名: アクセス修飾子(型)
例
beneficiary: public(address)
publicを記述することで、自動的にゲッターが設定される
コンストラクター
initを定義する
例
@public def __init__(_beneficiary: address, _bidding_time: timedelta): self.beneficiary = _beneficiary self.auction_start = block.timestamp self.auction_end = self.auction_start + _bidding_time
- 関数定義の前に "@public" を記述することで、外部からの呼びだしが可能になる
- 自身のステート変数に値を設定する場合、
self.valueble = ...
のように、”self”を記述する - "timestamp"型の差分は、”timedelta"型を使わないとエラー
その他
@public @payable def bid(): # Check if bidding period is over. assert block.timestamp < self.auction_end # Check if bid is high enough assert msg.value > self.highest_bid if not self.highest_bid == 0: # Sends money back to the previous highest bidder send(self.highest_bidder, self.highest_bid) self.highest_bidder = msg.sender self.highest_bid = msg.value
- 関数定義の前に "@payable" を記述することで、コントラクトに関数を通してetherを受け取ることが可能になる
- assertで、状態のチェックをすることができる。Trueの場合、次の行に進める
- assert notで、Falseの場合、次の行に進める
- send(address, wei_value)で、etherをaddressに送信できる
2. Safe Remote Purchase
#Safe Remote Purchase #Originally from #https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst #ported to vyper and optimized #Rundown of the transaction: #1. Seller posts item for sale and posts safety deposit of double the item value. # Balance is 2*value. #(1.1. Seller can reclaim deposit and close the sale as long as nothing was purchased.) #2. Buyer purchases item (value) plus posts an additional safety deposit (Item value). # Balance is 4*value. #3. Seller ships item. #4. Buyer confirms receiving the item. Buyer's deposit (value) is returned. #Seller's deposit (2*value) + items value is returned. Balance is 0. value: public(wei_value) #Value of the item seller: public(address) buyer: public(address) unlocked: public(bool) #@constant #def unlocked() -> bool: #Is a refund possible for the seller? # return (self.balance == self.value*2) @public @payable def __init__(): assert (msg.value % 2) == 0 self.value = msg.value / 2 #The seller initializes the contract by #posting a safety deposit of 2*value of the item up for sale. self.seller = msg.sender self.unlocked = True @public def abort(): assert self.unlocked #Is the contract still refundable? assert msg.sender == self.seller #Only the seller can refund # his deposit before any buyer purchases the item. selfdestruct(self.seller) #Refunds the seller and deletes the contract. @public @payable def purchase(): assert self.unlocked #Is the contract still open (is the item still up for sale)? assert msg.value == (2 * self.value) #Is the deposit the correct value? self.buyer = msg.sender self.unlocked = False @public def received(): assert not self.unlocked #Is the item already purchased and pending confirmation # from the buyer? assert msg.sender == self.buyer send(self.buyer, self.value) #Return the buyer's deposit (=value) to the buyer. selfdestruct(self.seller) #Return the seller's deposit (=2*value) # and the purchase price (=value) to the seller.s
コントラクトからetherの送信と、コントラクトの破壊
3. Crowdfund
# Setup private variables (only callable from within the contract) funders: {sender: address, value: wei_value}[int128] nextFunderIndex: int128 beneficiary: address deadline: timestamp goal: wei_value refundIndex: int128 timelimit: timedelta # Setup global variables @public def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): self.beneficiary = _beneficiary self.deadline = block.timestamp + _timelimit self.timelimit = _timelimit self.goal = _goal # Participate in this crowdfunding campaign @public @payable def participate(): assert block.timestamp < self.deadline nfi: int128 = self.nextFunderIndex self.funders[nfi] = {sender: msg.sender, value: msg.value} self.nextFunderIndex = nfi + 1 # Enough money was raised! Send funds to the beneficiary @public def finalize(): assert block.timestamp >= self.deadline and self.balance >= self.goal selfdestruct(self.beneficiary) # Not enough money was raised! Refund everyone (max 30 people at a time # to avoid gas limit issues) @public def refund(): assert block.timestamp >= self.deadline and self.balance < self.goal ind: int128 = self.refundIndex for i in range(ind, ind + 30): if i >= self.nextFunderIndex: self.refundIndex = self.nextFunderIndex return send(self.funders[i].sender, self.funders[i].value) self.funders[i] = None self.refundIndex = ind + 30
構造体・配列
構造体の配列を1行で定義することができる
例
funders: {sender: address, value: wei_value}[int128]
配列へのアクセス
rangeを用いる
例
for i in range(ind, ind + 30): if i >= self.nextFunderIndex: self.refundIndex = self.nextFunderIndex return send(self.funders[i].sender, self.funders[i].value) self.funders[i] = None self.refundIndex = ind + 30
- for index in range(from, to) fromから、to番までの配列の中身を順次取り出す
- コントラクトに紐づけられているether量は、self.balanceでチェックする
4.Voting
# Voting with delegation. # Information about voters voters: public({ # weight is accumulated by delegation weight: int128, # if true, that person already voted (which includes voting by delegating) voted: bool, # person delegated to delegate: address, # index of the voted proposal, which is not meaningful unless `voted` is True. vote: int128 }[address]) # This is a type for a list of proposals. proposals: public({ # short name (up to 32 bytes) name: bytes32, # int128ber of accumulated votes vote_count: int128 }[int128]) voter_count: public(int128) chairperson: public(address) int128_proposals: public(int128) @public @constant def delegated(addr: address) -> bool: # equivalent to # self.voters[addr].delegate != 0x0000000000000000000000000000000000000000 return not not self.voters[addr].delegate @public @constant def directly_voted(addr: address) -> bool: # not <address> equivalent to # == 0x0000000000000000000000000000000000000000 return self.voters[addr].voted and not self.voters[addr].delegate # Setup global variables @public def __init__(_proposalNames: bytes32[2]): self.chairperson = msg.sender self.voter_count = 0 for i in range(2): self.proposals[i] = { name: _proposalNames[i], vote_count: 0 } self.int128_proposals += 1 # Give a `voter` the right to vote on this ballot. # This may only be called by the `chairperson`. @public def give_right_to_vote(voter: address): # Throws if the sender is not the chairperson. assert msg.sender == self.chairperson # Throws if the voter has already voted. assert not self.voters[voter].voted # Throws if the voter's voting weight isn't 0. assert self.voters[voter].weight == 0 self.voters[voter].weight = 1 self.voter_count += 1 # Used by `delegate` below, and can be called by anyone. @public def forward_weight(delegate_with_weight_to_forward: address): assert self.delegated(delegate_with_weight_to_forward) # Throw if there is nothing to do: assert self.voters[delegate_with_weight_to_forward].weight > 0 target: address = self.voters[delegate_with_weight_to_forward].delegate for i in range(4): if self.delegated(target): target = self.voters[target].delegate # The following effectively detects cycles of length <= 5, # in which the delegation is given back to the delegator. # This could be done for any int128ber of loops, # or even infinitely with a while loop. # However, cycles aren't actually problematic for correctness; # they just result in spoiled votes. # So, in the production version, this should instead be # the responsibility of the contract's client, and this # check should be removed. assert target != delegate_with_weight_to_forward else: # Weight will be moved to someone who directly voted or # hasn't voted. break weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight self.voters[delegate_with_weight_to_forward].weight = 0 self.voters[target].weight += weight_to_forward if self.directly_voted(target): self.proposals[self.voters[target].vote].vote_count += weight_to_forward self.voters[target].weight = 0 # To reiterate: if target is also a delegate, this function will need # to be called again, similarly to as above. # Delegate your vote to the voter `to`. @public def delegate(to: address): # Throws if the sender has already voted assert not self.voters[msg.sender].voted # Throws if the sender tries to delegate their vote to themselves or to # the default address value of 0x0000000000000000000000000000000000000000 # (the latter might not be problematic, but I don't want to think about it). assert to != msg.sender and not not to self.voters[msg.sender].voted = True self.voters[msg.sender].delegate = to # This call will throw if and only if this delegation would cause a loop # of length <= 5 that ends up delegating back to the delegator. self.forward_weight(msg.sender) # Give your vote (including votes delegated to you) # to proposal `proposals[proposal].name`. @public def vote(proposal: int128): # can't vote twice assert not self.voters[msg.sender].voted # can only vote on legitimate proposals assert proposal < self.int128_proposals self.voters[msg.sender].vote = proposal self.voters[msg.sender].voted = True # transfer msg.sender's weight to proposal self.proposals[proposal].vote_count += self.voters[msg.sender].weight self.voters[msg.sender].weight = 0 # Computes the winning proposal taking all # previous votes into account. @public @constant def winning_proposal() -> int128: winning_vote_count: int128 = 0 winning_proposal: int128 = 0 for i in range(2): if self.proposals[i].vote_count > winning_vote_count: winning_vote_count = self.proposals[i].vote_count winning_proposal = i return winning_proposal # Calls winning_proposal() function to get the index # of the winner contained in the proposals array and then # returns the name of the winner @public @constant def winner_name() -> bytes32: return self.proposals[self.winning_proposal()].name
状態を変更しない
@public @constant def winning_proposal() -> int128: winning_vote_count: int128 = 0 winning_proposal: int128 = 0 for i in range(2): if self.proposals[i].vote_count > winning_vote_count: winning_vote_count = self.proposals[i].vote_count winning_proposal = i return winning_proposal
- @constantで、状態を変更しないことを明記する
- "-> 型"で、返り値を設定する。returnで値を返す
5.Company Stock
units: { currency_value: "Currency Value" } # Financial events the contract logs Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256(currency_value)}) Buy: event({_buyer: indexed(address), _buy_order: uint256(currency_value)}) Sell: event({_seller: indexed(address), _sell_order: uint256(currency_value)}) Pay: event({_vendor: indexed(address), _amount: wei_value}) # Initiate the variables for the company and it's own shares. company: public(address) total_shares: public(uint256(currency_value)) price: public(uint256 (wei / currency_value)) # Store a ledger of stockholder holdings. holdings: uint256(currency_value)[address] # Set up the company. @public def __init__(_company: address, _total_shares: uint256(currency_value), initial_price: uint256(wei / currency_value) ): assert _total_shares > 0 assert initial_price > 0 self.company = _company self.total_shares = _total_shares self.price = initial_price # The company holds all the shares at first, but can sell them all. self.holdings[self.company] = _total_shares @public @constant def stock_available() -> uint256(currency_value): return self.holdings[self.company] # Give some value to the company and get stock in return. @public @payable def buy_stock(): # Note: full amount is given to company (no fractional shares), # so be sure to send exact amount to buy shares buy_order: uint256(currency_value) = msg.value / self.price # rounds down # Check that there are enough shares to buy. assert self.stock_available() >= buy_order # Take the shares off the market and give them to the stockholder. self.holdings[self.company] -= buy_order self.holdings[msg.sender] += buy_order # Log the buy event. log.Buy(msg.sender, buy_order) # Find out how much stock any address (that's owned by someone) has. @public @constant def get_holding(_stockholder: address) -> uint256(currency_value): return self.holdings[_stockholder] # Return the amount the company has on hand in cash. @public @constant def cash() -> wei_value: return self.balance # Give stock back to the company and get money back as ETH. @public def sell_stock(sell_order: uint256(currency_value)): assert sell_order > 0 # Otherwise, this would fail at send() below, # due to an OOG error (there would be zero value available for gas). # You can only sell as much stock as you own. assert self.get_holding(msg.sender) >= sell_order # Check that the company can pay you. assert self.cash() >= (sell_order * self.price) # Sell the stock, send the proceeds to the user # and put the stock back on the market. self.holdings[msg.sender] -= sell_order self.holdings[self.company] += sell_order send(msg.sender, sell_order * self.price) # Log the sell event. log.Sell(msg.sender, sell_order) # Transfer stock from one stockholder to another. (Assume that the # receiver is given some compensation, but this is not enforced.) @public def transfer_stock(receiver: address, transfer_order: uint256(currency_value)): assert transfer_order > 0 # This is similar to sell_stock above. # Similarly, you can only trade as much stock as you own. assert self.get_holding(msg.sender) >= transfer_order # Debit the sender's stock and add to the receiver's address. self.holdings[msg.sender] -= transfer_order self.holdings[receiver] += transfer_order # Log the transfer event. log.Transfer(msg.sender, receiver, transfer_order) # Allow the company to pay someone for services rendered. @public def pay_bill(vendor: address, amount: wei_value): # Only the company can pay people. assert msg.sender == self.company # Also, it can pay only if there's enough to pay them with. assert self.cash() >= amount # Pay the bill! send(vendor, amount) # Log the payment event. log.Pay(vendor, amount) # Return the amount in wei that a company has raised in stock offerings. @public @constant def debt() -> wei_value: return (self.total_shares - self.holdings[self.company]) * self.price # Return the cash holdings minus the debt of the company. # The share debt or liability only is included here, # but of course all other liabilities can be included. @public @constant def worth() -> wei_value: return self.cash() - self.debt()
イベント
- eventは、以下のように記述する
Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256(currency_value)})
- 呼び出す時は、log." イベント名"を呼び出す。
log.Transfer(msg.sender, receiver, transfer_order)
単位
- unitsを用いることで、単位を設定できる
units: {
currency_value: "Currency Value"
}
- 設定した単位の使用方法は以下のようにする
total_shares: public(uint256(currency_value))
まとめ
サンプルから、標準的な実装方法を学ぶことが可能ですね。 ただ、ビルトイン関数は他に多くあり、これらからは学ぶことができませんでした。 次回のブログでは、その関数を確認してみたいと思います。