contract Coin { // The keyword "public" makes those variables // readable from outside. address public minter; mapping (address => uint) public balances;
// Events allow light clients to react on // changes efficiently. event Sent(address from, address to, uint amount);
// This is the constructor whose code is // run only when the contract is created. functionCoin() public { minter = msg.sender; }
functionmint(address receiver, uint amount) public { if (msg.sender != minter) return; balances[receiver] += amount; }
一个合约决定有多少它的剩余 gas 需要被伴随内部消息调用发送,有多少它想要保留。如果一个 out-of-gas 异常在内部调用发生了(或者任何其他异常),这件事会以一个被放在栈上的错误值作为信号。在这种情况下,只有伴随着这个调用的 gas 被用尽了(按:其它的 gas 没有被用尽)。在 Solidity 中,在这种情况下,调用方合约引发一个手动异常(按:下面传上来的是错误码,在这里才 raise 异常),所以异常状况就在调用栈上被冒泡上去了。
/// @title Voting with delegation. contract Ballot { // This declares a new complex type which will // be used for variables later. // It will represent a single voter. struct Voter { uint weight; // weight is accumulated by delegation bool voted; // if true, that person already voted address delegate; // person delegated to uint vote; // index of the voted proposal }
// This is a type for a single proposal. struct Proposal { bytes32 name; // short name (up to 32 bytes) uint voteCount; // number of accumulated votes }
address public chairperson;
// This declares a state variable that // stores a `Voter` struct for each possible address. mapping(address =>Voter) public voters;
// A dynamically-sized array of `Proposal` structs. Proposal[] public proposals;
/// Create a new ballot to choose one of `proposalNames`. functionBallot(bytes32[] proposalNames) public { chairperson = msg.sender; // 主席有投票权 voters[chairperson].weight = 1;
// For each of the provided proposal names, // create a new proposal object and add it // to the end of the array. for (uint i = 0; i < proposalNames.length; i++) { // `Proposal({...})` creates a temporary // Proposal object and `proposals.push(...)` // appends it to the end of `proposals`. proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); } }
// Give `voter` the right to vote on this ballot. // May only be called by `chairperson`. functiongiveRightToVote(address voter) public { // If the argument of `require` evaluates to `false`, // it terminates and reverts all changes to // the state and to Ether balances. It is often // a good idea to use this if functions are // called incorrectly. But watch out, this // will currently also consume all provided gas // (this is planned to change in the future). // require 类似断言,会反转所有的区块链 state,但还是会消耗 gas(这点未来会被修改),这比 return 好吗? require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0)); // 某个投票者地址获得了投票权 voters[voter].weight = 1; }
/// Delegate your vote to the voter `to`. functiondelegate(address to) public { // assigns reference // 第一个出现的显式 storage,这证明读成员变量赋给局部变量总是要求 storage 的 Voter storage sender = voters[msg.sender]; require(!sender.voted);
// Self-delegation is not allowed. require(to != msg.sender);
// Forward the delegation as long as // `to` also delegated. // In general, such loops are very dangerous, // because if they run too long, they might // need more gas than is available in a block. // In this case, the delegation will not be executed, // but in other situations, such loops might // cause a contract to get "stuck" completely. // 这里使用了一个可以比较零地址的例子,证明不可以直接 != 0来比较 // 在现实中这样的循环可能很耗 gas while (voters[to].delegate != address(0)) { to = voters[to].delegate;
// We found a loop in the delegation, not allowed. // 内置 required 断言 require(to != msg.sender); }
// Since `sender` is a reference, this // modifies `voters[msg.sender].voted` // 把被委托者的旧值作废掉 sender.voted = true; sender.delegate = to; Voter storage delegate = voters[to]; if (delegate.voted) { // If the delegate already voted, // directly add to the number of votes proposals[delegate.vote].voteCount += sender.weight; } else { // If the delegate did not vote yet, // add to her weight. delegate.weight += sender.weight; } }
// 每一个合约都是 /// Give your vote (including votes delegated to you) /// to proposal `proposals[proposal].name`. functionvote(uint proposal) public { Voter storage sender = voters[msg.sender]; require(!sender.voted); sender.voted = true; sender.vote = proposal;
// If `proposal` is out of the range of the array, // this will throw automatically and revert all // changes. proposals[proposal].voteCount += sender.weight; }
/// @dev Computes the winning proposal taking all /// previous votes into account. functionwinningProposal() public view returns (uint winningProposal) { // solidity 的 dummy 数值对象就是这样写的了 uint winningVoteCount = 0; // 注意,这个 proposals 是数组,winner 其实只是这个数组的一个索引 for (uint p = 0; p < proposals.length; p++) { if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal = p; } } }
// Calls winningProposal() function to get the index // of the winner contained in the proposals array and then // returns the name of the winner functionwinnerName() public view returns (bytes32 winnerName) { winnerName = proposals[winningProposal()].name; } }
contract SimpleAuction { // 卖东西的人 // Parameters of the auction. Times are either // absolute unix timestamps (seconds since 1970-01-01) // or time periods in seconds. address public beneficiary; // 用整数来表示时间 uint public auctionEnd;
// 当前最高投标者的地址 // Current state of the auction. address public highestBidder; // 最高的投标 uint public highestBid;
// Set to true at the end, disallows any change bool ended;
// Events that will be fired on changes. event HighestBidIncreased(address bidder, uint amount); event AuctionEnded(address winner, uint amount);
// 三斜杠的是提示符评论,可以提示用户是否确认一个事务 // The following is a so-called natspec comment, // recognizable by the three slashes. // It will be shown when the user is asked to // confirm a transaction.
/// Create a simple auction with `_biddingTime` /// seconds bidding time on behalf of the /// beneficiary address `_beneficiary`. functionSimpleAuction( uint _biddingTime, address _beneficiary ) public { beneficiary = _beneficiary; // 看来 now 也是秒数的意思。换言之,没有毫秒数。 auctionEnd = now + _biddingTime; }
/// Bid on the auction with the value sent /// together with this transaction. /// The value will only be refunded if the /// auction is not won. // payable 就是拿来修饰 function 的,所以它是 payble 的。 functionbid() public payable { // No arguments are necessary, all // information is already part of // the transaction. The keyword payable // is required for the function to // be able to receive Ether.
// Revert the call if the bidding // period is over. require(now <= auctionEnd);
// If the bid is not higher, send the // money back. // 这个 payable 的合约也可以被断言所中断 require(msg.value > highestBid);
// 最高投标者的地址的另一种比对方法 if (highestBidder != 0) { // Sending back the money by simply using // highestBidder.send(highestBid) is a security risk // because it could execute an untrusted contract. // It is always safer to let the recipients // withdraw their money themselves. pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; highestBid = msg.value; HighestBidIncreased(msg.sender, msg.value);
/// Withdraw a bid that was overbid. // 这个退款函数是谁来调用 ? functionwithdraw() public returns (bool) { uint amount = pendingReturns[msg.sender]; if (amount > 0) { // It is important to set this to zero because the recipient // can call this function again as part of the receiving call // before `send` returns. pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) { // No need to call throw here, just reset the amount owing pendingReturns[msg.sender] = amount; returnfalse; } } returntrue; }
/// End the auction and send the highest bid /// to the beneficiary. functionauctionEnd() public { // 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 require(now >= auctionEnd); // auction did not yet end require(!ended); // this function has already been called
/// Modifiers are a convenient way to validate inputs to /// functions. `onlyBefore` is applied to `bid` below: /// The new function body is the modifier's body where /// `_` is replaced by the old function body. // modifier 有点像装饰器,结合了 require,_指的就是被修饰方法的 body modifier onlyBefore(uint _time) { require(now < _time); _; } modifier onlyAfter(uint _time) { require(now > _time); _; }
// keccak 函数是 SHA-3函数的别名,被以太坊更名过的函数。SHA-256实际上是 SHA-2 的一个子类型,keccak 算法是下一代的 SHA-3算法,是最强的散列算法。http://www.atool.org/hash.php // keccak 是个松散参数函数,所有的输入都是散列的种子,会被函数直接拼接起来,类似分段签名相加的思路。 /// Place a blinded bid with `_blindedBid` = keccak256(value, /// fake, secret). /// The sent ether is only refunded if the bid is correctly /// revealed in the revealing phase. The bid is valid if the /// ether sent together with the bid is at least "value" and /// "fake" is not true. Setting "fake" to true and sending /// not the exact amount are ways to hide the real bid but /// still make the required deposit. The same address can /// place multiple bids. // 一个地址可以做多个投标。只有被正确亮出的投标值才会被返还(其他的投标值就不返还了吗?)。只有附带的投标值大于等于value(这是什么意思?)而且 fake 不是 true。设置 fake 为 true 而且发送不准确的数额是隐藏真实投标但满足要求的锁定存款的方法。 functionbid(bytes32 _blindedBid) public payable onlyBefore(biddingEnd) { bids[msg.sender].push(Bid({ blindedBid: _blindedBid, deposit: msg.value })); }
// 亮标。用户可以得到所有被正确掩盖的错标,和除了最高标以外的所有标。 /// Reveal your blinded bids. You will get a refund for all /// correctly blinded invalid bids and for all bids except for /// the totally highest. functionreveal( uint[] _values, bool[] _fake, bytes32[] _secret ) public onlyAfter(biddingEnd) onlyBefore(revealEnd) { uint length = bids[msg.sender].length; require(_values.length == length); require(_fake.length == length); require(_secret.length == length);
uint refund; for (uint i = 0; i < length; i++) { var bid = bids[msg.sender][i]; var (value, fake, secret) = (_values[i], _fake[i], _secret[i]); if (bid.blindedBid != keccak256(value, fake, secret)) { // Bid was not actually revealed. // Do not refund deposit. continue; } refund += bid.deposit; // deposit 是实际存入的锁定价值,value 是用来做散列种子的声称价值 if (!fake && bid.deposit >= value) { if (placeBid(msg.sender, value)) refund -= value; } // 因为以太坊可以被认为是序列化的事务隔离级别的,所以在这里置零可以抗并发问题 // Make it impossible for the sender to re-claim // the same deposit. bid.blindedBid = bytes32(0); } msg.sender.transfer(refund); }
// internal 的第一个用法,internal 函数类似 private,只能在合约内部使用了。 // This is an "internal" function which means that it // can only be called from the contract itself (or from // derived contracts). functionplaceBid(address bidder, uint value) internal returns (bool success) { // 不中标。所以在这里 place 就是放标的意思? if (value <= highestBid) { returnfalse; } if (highestBidder != 0) { // 先把之前的最高标放进退款名单里面 // Refund the previously highest bidder. pendingReturns[highestBidder] += highestBid; } // 替换最高标 highestBid = value; highestBidder = bidder; returntrue; }
/// Withdraw a bid that was overbid. functionwithdraw() public { uint amount = pendingReturns[msg.sender]; if (amount > 0) { // It is important to set this to zero because the recipient // can call this function again as part of the receiving call // before `transfer` returns (see the remark above about // conditions -> effects -> interaction). pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount); } }
// 在拍卖结束的时候把投标款转走。 /// End the auction and send the highest bid /// to the beneficiary. functionauctionEnd() public onlyAfter(revealEnd) { require(!ended); AuctionEnded(highestBidder, highestBid); ended = true; beneficiary.transfer(highestBid); } }
contract Purchase { uint public value; address public seller; address public buyer; enum State { Created, Locked, Inactive } State public state;
// 确认价值是偶数(为什么?)。由卖方初始化合约。这是第一个构造函数也是 payable 的合约。初始化合约的 value,大致等于一个买卖的押金。 // Ensure that `msg.value` is an even number. // Division will truncate if it is an odd number. // Check via multiplication that it wasn't an odd number. functionPurchase() public payable { seller = msg.sender; value = msg.value / 2; require((2 * value) == msg.value); }
/// Abort the purchase and reclaim the ether. /// Can only be called by the seller before /// the contract is locked. functionabort() public onlySeller inState(State.Created) { Aborted(); state = State.Inactive; // 这个 balance 就是合约自带的所有 balance 了,而不是我们自定义的 value 一类的成员变量。 seller.transfer(this.balance); }
/// Confirm the purchase as buyer. /// Transaction has to include `2 * value` ether. /// The ether will be locked until confirmReceived /// is called. functionconfirmPurchase() public inState(State.Created) condition(msg.value == (2 * value)) payable { // 发出购买锁定事件 PurchaseConfirmed(); buyer = msg.sender; state = State.Locked; }
/// Confirm that you (the buyer) received the item. /// This will release the locked ether. functionconfirmReceived() public onlyBuyer inState(State.Locked) { // 发出事件 ItemReceived(); // It is important to change the state first because // otherwise, the contracts called using `send` below // can call in again here. state = State.Inactive;
// NOTE: This actually allows both the buyer and the seller to // block the refund - the withdraw pattern should be used. // 把押金转给买家,把差额交给卖家。为什么要这样设计?为了避免空口无凭的卖家浪费合约价值? buyer.transfer(value); seller.transfer(this.balance); } }
这个语句制造了一个新的全局符号,类似名字空间,所有该文件名下的符号,都是这个新的全局符号的成员。它和import "filename" as symbolName;等价。
1
import {symbol1 asalias, symbol2} from "filename";
部分引用和别名机制。
路径
.和..的机制同 Unix 系统。
要引用同一个文件夹下的文件,用这样的语法mport "./x" as x;。如果你使用了import "x" as x;,一个全局的“include directory”里的同名文件夹会被引用。
重映射问题
太无聊,直接看文档吧。
注释问题
注意看下面的文档,展示了如何写注释,也展示了如何写多返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
pragma solidity ^0.4.0;
/** @title Shape calculator. */ contract shapeCalculator { /** @dev Calculates a rectangle's surface and perimeter. * @param w Width of the rectangle. * @param h Height of the rectangle. * @return s The calculated surface. * @return p The calculated perimeter. */ functionrectangle(uint w, uint h)returns(uint s, uint p){ s = w * h; p = 2 * (w + h); } }
function setGoStraight() public { choice = ActionChoices.GoStraight; }
// Since enum types are not part of the ABI, the signature of "getChoice" // will automatically be changed to "getChoice() returns (uint8)" // forall matters externalto Solidity. The integertype used is just // large enough to hold all enum values, i.e. if you have more values, // `uint16` will be used and so on. function getChoice() publicviewreturns (ActionChoices) { return choice; }
function getDefaultChoice() public pure returns (uint) { return uint(defaultChoice); } }
library ArrayUtils { // 同在一个上下文里的内部函数调用。 // internal functions can be used in internal library functions because // they will be part of the same code context functionmap(uint[] memory self, function(uint) pure returns (uint) f) internal pure returns(uint[] memory r) { r = newuint[](self.length); for (uint i = 0; i < self.length; i++) { r[i] = f(self[i]); } } functionreduce( uint[] memory self, function(uint, uint) pure returns (uint) f ) internal pure returns(uint r) { r = self[0]; for (uint i = 1; i < self.length; i++) { r = f(r, self[i]); } } functionrange(uint length)internalpurereturns(uint[] memory r){ r = newuint[](length); for (uint i = 0; i < r.length; i++) { r[i] = i; } } }
contract Pyramid { using ArrayUtils for *; functionpyramid(uint l)publicpurereturns(uint){ return ArrayUtils.range(l).map(square).reduce(sum); } functionsquare(uint x)internalpurereturns(uint){ return x * x; } functionsum(uint x, uint y)internalpurereturns(uint){ return x + y; } }
pragma solidity ^0.4.20; // should actually be 0.4.21
contract Oracle { struct Request { bytes data; function(bytes memory) external callback; } Request[] requests; event NewRequest(uint); function query(bytes data, function(bytes memory) external callback) public { requests.push(Request(data, callback)); emit NewRequest(requests.length - 1); } function reply(uint requestID, bytes response) public { // Here goes the check that the reply comes from a trusted source requests[requestID].callback(response); } }
contract OracleUser { Oracle constant oracle = Oracle(0x1234567); // known contract function buySomething() { oracle.query("USD", this.oracleResponse); } function oracleResponse(bytes response) public { require(msg.sender == address(oracle)); // Use the data } }
contract C { uint[] x; // the data location of x is storage
// the data location of memoryArray is memory function f(uint[] memoryArray) public { x = memoryArray; // works, copies the whole array to storage vary = x; // works, assigns a pointer, data location of y is storage y[7]; // fine, returns the 8th element // 局部 storage 变量修改了状态 storage 变量。 y.length = 2; // fine, modifies x through y // 清除数组的方法 deletex; // fine, clears the array, also modifies y // 可以把实参赋给状态变量,把局部变量指向状态变量,却不能跳过第一步。状态变量都是“静态分配”的。 // The following does not work; it would need to create a new temporary / // unnamed array in storage, but storage is "statically" allocated: // y = memoryArray; // This does not work either, since it would "reset" the pointer, but there // is no sensible location it could point to. // delete y; g(x); // calls g, handing over a reference to x h(x); // calls h and creates an independent, temporary copy in memory }
function g(uint[] storage storageArray) internal {} function h(uint[] memoryArray) public {} }
可以用 new 来创建内存数组。memory数组不可以通过.length 来修改数组尺寸,但storage数组可以。
1 2 3 4 5 6 7 8 9 10
pragma solidity ^0.4.16;
contract C { function f(uint len) publicpure { uint[] memory a = newuint[](7); bytes memory b = new bytes(len); // Here we have a.length == 7 and b.length == len a[6] = 8; } }
数组字面量/内联数组
1 2 3 4 5 6 7 8 9 10 11
pragma solidity ^0.4.16;
contract C { function f() publicpure { g([uint(1), 2, 3]); } // 这也是个定长数组的例子 function g(uint[3] _data) publicpure { // ... } }
contract ArrayContract { uint[2**20] m_aLotOfIntegers; // Note that the following is not a pair of dynamic arrays but a // dynamic array of pairs (i.e. of fixed size arrays of length two). bool[2][] m_pairsOfFlags; // newPairs is stored in memory - the default for function arguments
function setAllFlagPairs(bool[2][] newPairs) public { // assignment to a storage array replaces the complete array m_pairsOfFlags = newPairs; }
function setFlagPair(uint index, bool flagA, bool flagB) public { // access to a non-existing index will throw an exception m_pairsOfFlags[index][0] = flagA; m_pairsOfFlags[index][17] = flagB; }
function changeFlagArraySize(uint newSize) public { // if the new size is smaller, removed array elements will be cleared m_pairsOfFlags.length = newSize; }
function clear() public { // these clear the arrays completely delete m_pairsOfFlags; delete m_aLotOfIntegers; // identical effect here m_pairsOfFlags.length = 0; }
bytes m_byteData;
function byteArrays(bytes data) public { // byte arrays ("bytes") are different as they are stored without padding, // but can be treated identical to "uint8[]" m_byteData = data; m_byteData.length += 7; m_byteData[3] = byte(8); delete m_byteData[2]; }
function addFlag(bool[2] flag) public returns (uint) { return m_pairsOfFlags.push(flag); }
function createMemoryArray(uint size) publicpure returns (bytes) { // Dynamic memory arrays are created using `new`: uint[2][] memory arrayOfPairs = newuint[2][](size); // Create a dynamic byte array: bytes memory b = new bytes(200); for (uint i = 0; i < b.length; i++) b[i] = byte(i); return b; } }
function newCampaign(address beneficiary, uint goal) publicreturns (uint campaignID) { campaignID = numCampaigns++; // campaignID isreturn variable // Creates new struct and saves instorage. We leave out the mappingtype. // 这里构造函数生成的数据类型是 memory,而 campaigns[campaignID]的数据位置是 storage,只有这种方式可以隐式自动转化这两种 data location。 campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); }
function contribute(uint campaignID) public payable { Campaign storage c = campaigns[campaignID]; // Creates a newtemporary memory struct, initialised with the given values // and copies it overtostorage. // Note that you can also use Funder(msg.sender, msg.value) to initialise. c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); c.amount += msg.value; }
function checkGoalReached(uint campaignID) publicreturns (bool reached) { Campaign storage c = campaigns[campaignID]; if (c.amount < c.fundingGoal) returnfalse; uint amount = c.amount; c.amount = 0; c.beneficiary.transfer(amount); returntrue; } }
function f() public { // 注意!这里 x 拷贝了 data,而不是把一个指针指向了 data。 uint x = data; delete x; // sets x to0, does not affect data delete data; // sets data to0, does not affect x which still holds a copy uint[] storage y = dataArray; delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also // y is affected which is an aliasto the storageobject // On the other hand: "delete y" isnotvalid, as assignments tolocal variables // referencing storage objects can only be made from existing storage objects. } }
addmod(uint x, uint y, uint k) returns (uint): compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256. Assert that k != 0 starting from version 0.5.0.
mulmod(uint x, uint y, uint k) returns (uint): compute (x y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2*256. Assert that k != 0 starting from version 0.5.0.
keccak256(…) returns (bytes32): compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
sha256(…) returns (bytes32): compute the SHA-256 hash of the (tightly packed) arguments
sha3(…) returns (bytes32): alias to keccak256
ripemd160(…) returns (bytes20): compute RIPEMD-160 hash of the (tightly packed) arguments
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address): recover the address associated with the public key from elliptic curve signature or return zero on error (example usage)
function f() public pure returns (uint, bool, uint) { return (7, true, 2); }
function g() public { // Declares and assigns the variables. Specifying the type explicitly is not possible. var (x, b, y) = f(); // Assigns to a pre-existing variable. (x, y) = (2, 7); // Common trick to swap values -- does not work for non-value storage types. (x, y) = (y, x); // Components can be left out (also for variable declarations). // If the tuple ends in an empty component, // the rest of the values are discarded. (data.length,) = f(); // Sets the length to 7 // The same can be done on the left side. // If the tuple begins in an empty component, the beginning values are discarded. (,data[3]) = f(); // Sets data[3] to 2 // Components can only be left out at the left-hand-side of assignments, with // one exception: (x,) = (1,); // (1,) is the only way to specify a 1-component tuple, because (1) is // equivalent to 1. } }
数组和结构体的复杂点
数组和结构体等非值类型赋值语义稍显复杂。
赋值给一个状态变量总是会产生一个独立拷贝。
从状态变量赋数组和结构体给局部变量是传引用,再赋值还是传引用。
作用域和声明
一个变量(包括在函数内)被声明的时候就会拥有字节零值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
pragma solidity ^0.4.0;
contract C { function foo() publicpure returns (uint) { // baz is implicitly initialized as 0 uint bar = 5; if (true) { bar += baz; } else { uint baz = 10;// never executes } return bar;// returns 5 } }
函数内的变量里还是没有块级作用域的。
从 0.5.0 开始就有作用域了
块作用域有块生命周期。 for 相关变量只有 for 块的生命周期。
1 2 3 4 5 6 7 8 9 10 11 12 13
pragma solidity ^0.4.0; pragma experimental "v0.5.0"; contract C { function minimalScoping() purepublic { { uint same2 = 0; }
{ uint same2 = 0; } } }
1 2 3 4 5 6 7 8 9 10 11 12
pragma solidity ^0.4.0; pragma experimental "v0.5.0"; contract C { function f() purepublic returns (uint) { uint x = 1; { x = 2; // this will assign to the outer variable uint x; } return x; // x has value 2 } }
contract OwnedToken { // TokenCreator is a contract type that is defined below. // It is fine to reference it as long as it isnot used // tocreate a new contract. TokenCreator creator; address owner; bytes32 name;
// This is the constructor which registers the // creator and the assigned name. function OwnedToken(bytes32 _name) public { // internal 变量不需要 this 访问,反正这时候合约还不存在呢 // State variables are accessed via their name // andnot via e.g. this.owner. This also applies // tofunctionsand especially in the constructors, // you can onlycall them like that ("internally"), // because the contract itself does not exist yet. owner = msg.sender; // We do an explicit typeconversionfrom `address` // to `TokenCreator` and assume that the typeof // the calling contract is TokenCreator, there is // noreal way tocheck that. creator = TokenCreator(msg.sender); name = _name; }
function changeName(bytes32 newName) public { // Only the creator can alter the name-- // the comparison is possible since contracts // are implicitly convertible to addresses. if (msg.sender == address(creator)) name = newName; }
function transfer(address newOwner) public { // Only the currentowner can transfer the token. if (msg.sender != owner) return; // We also want to ask the creator if the transfer // is fine. Note that this calls a functionof the // contract defined below. If the call fails (e.g. // due toout-of-gas), the execution here stops // immediately. if (creator.isTokenTransferOK(owner, newOwner)) owner = newOwner; } }
contract TokenCreator { function createToken(bytes32 name) public returns (OwnedToken tokenAddress) { // Create a new Token contract andreturn its address. // From the JavaScript side, the returntypeis simply // `address`, as this is the closest type available in // the ABI. returnnew OwnedToken(name); }
function changeName(OwnedToken tokenAddress, bytes32 name) public { // Again, the externaltypeof `tokenAddress` is // simply `address`. tokenAddress.changeName(name); }
function f(uint a) privatereturns(uint b) { return a + 1; } function setData(uint a) public { data = a; } function getData() publicreturns(uint) { return data; } function compute(uint a, uint b) internalreturns (uint) { return a+b; } }
contract D { function readData() public { C c = new C(); uint local = c.f(7); // error: member `f` is not visible c.setData(3); local = c.getData(); local = c.compute(3, 5); // error: member `compute` is not visible } }
// 通过继承,实现了代码复用 contract E is C { function g() public { C c = new C(); uint val = compute(3, 5); // access to internal member (from derived to parent contract) } }
Getter
getter 是自动生成的,一个调用的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13
pragma solidity ^0.4.0;
contract C { uintpublic data = 42; }
contract Caller { C c = new C(); function f() public { // 换言之,用函数来访问公共变量,不要用点操作符。 uint local = c.data(); } }
内外访问与内部访问:
1 2 3 4 5 6 7 8 9
pragma solidity ^0.4.0;
contract C { uint publicdata; function x() public { data = 3; // internal access uint val = this.data(); // external access } }
contract owned { function owned() public { owner = msg.sender; } address owner;
// This contract only defines a modifier but does not use // it: it will be used in derived contracts. // The function body is inserted where the special symbol // `_;` in the definition of a modifier appears. // This means that if the owner calls this function, the // function is executed and otherwise, an exception is // thrown. modifier onlyOwner { require(msg.sender == owner); _; } }
contract mortal is owned { // This contract inherits the `onlyOwner` modifier from // `owned` and applies it to the `close` function, which // causes that calls to `close` only have an effect if // they are made by the stored owner. function close() public onlyOwner { selfdestruct(owner); } }
function Register(uint initialPrice) public { price = initialPrice; }
// 如果没有这个关键字,合约会自动拒绝被发送过来的以太币 // It is important to also provide the // `payable` keyword here, otherwise the function will // automatically reject all Ether sent to it. function register() public payable costs(price) { registeredAddresses[msg.sender] = true; }
function changePrice(uint _price) public onlyOwner { price = _price; } }
/// This function is protected by a mutex, which means that /// reentrant calls from within `msg.sender.call` cannot call `f` again. /// The `return 7` statement assigns 7 to the return value but still /// executes the statement `locked = false` in the modifier. function f() public noReentrancy returns (uint) { require(msg.sender.call()); return7; } }
contract Test { // This function is called for all messages sent to // this contract (there is no other function). // Sending Ether to this contract will cause an exception, // because the fallback function does not have the `payable` // modifier. function() public { x = 1; } uint x; }
// This contract keeps all Ether sent to it with no way // to get it back. contract Sink { function() public payable { } }
contract Caller { function callTest(Test test) public { test.call(0xabcdef01); // hash does not exist // results in test.x becoming == 1.
// The following will not compile, but even // if someone sends ether to that contract, // the transaction will fail and reject the // Ether. //test.send(2 ether); } }
函数重载
大部分情况下同其他重载一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// This will not compile pragma solidity ^0.4.16;
contract A { function f(B _in) publicpure returns (B out) { out = _in; }
function f(address _in) publicpure returns (address out) { out = _in; } }
function deposit(bytes32 _id) public payable { // Events are emitted using `emit`, followed by // the name of the event and the arguments // (if any) in parentheses. Any such invocation // (even deeply nested) can be detected from // the JavaScript API by filtering for `Deposit`. emit Deposit(msg.sender, _id, msg.value); } }
var abi = /* abi as generated by the compiler */; var ClientReceipt = web3.eth.contract(abi); var clientReceipt = ClientReceipt.at("0x1234...ab67"/* address */);
var event = clientReceipt.Deposit();
// watch for changes event.watch(function(error, result){ // result will contain various information // including the argumets given to the `Deposit` // call. if (!error) console.log(result); });
// Or pass a callback to start watching immediately var event = clientReceipt.Deposit(function(error, result) { if (!error) console.log(result); });
低级日志设施
1 2 3 4 5 6 7 8 9 10 11 12 13
pragma solidity ^0.4.10;
contract C { functionf() public payable { bytes32 _id = 0x420042; log3( bytes32(msg.value), bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), bytes32(msg.sender), _id ); } }
// 只有 private 不会被继承下来 // Use `is` to derive from another contract. Derived // contracts can access all non-private members including // internal functions and state variables. These cannot be // accessed externally via `this`, though. contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }
// 没有函数体的函数。其合约是抽象的。完全没有函数体的合约只能当接口用。 // These abstract contracts are only provided to make the // interface known to the compiler. Note the function // without body. If a contract does not implement all // functions it can only be used as an interface. contract Config { function lookup(uint id) publicreturns (address adr); }
contract NameReg { function register(bytes32 name) public; function unregister() public; }
// 多继承类似 C++ 的虚继承,不会有多份重复成员。 // Multiple inheritance is possible. Note that `owned` is // also a base class of `mortal`, yet there is only a single // instance of `owned` (as for virtual inheritance in C++). contract named is owned, mortal { function named(bytes32 name) { Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); NameReg(config.lookup(1)).register(name); }
// Functions can be overridden by another function with the same name and // the same number/types of inputs. If the overriding function has different // types of output parameters, that causes an error. // Both local and message-based function calls take these overrides // into account. function kill() public { if (msg.sender == owner) { Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); NameReg(config.lookup(1)).unregister(); // It is still possible to call a specific // overridden function. mortal.kill(); } } }
// 这样调用显式构造函数就讨厌了 // If a constructor takes an argument, it needs to be // provided in the header (or modifier-invocation-style at // the constructor of the derived contract (see below)). contract PriceFeed is owned, mortal, named("GoldFeed") { function updateInfo(uint newInfo) public { if (msg.sender == owner) info = newInfo; }
function get() public view returns(uint r) { return info; }
library Set { // We define a new struct datatype that will be used to // hold its data in the calling contract. struct Data { mapping(uint => bool) flags; }
// Note that the first parameter is of type "storage // reference" and thus only its storage address and not // its contents is passed as part of the call. This is a // special feature of library functions. It is idiomatic // to call the first parameter `self`, if the function can // be seen as a method of that object. functioninsert(Data storage self, uint value) public returns(bool) { if (self.flags[value]) returnfalse; // already there self.flags[value] = true; returntrue; }
functionremove(Data storage self, uint value) public returns(bool) { if (!self.flags[value]) returnfalse; // not there self.flags[value] = false; returntrue; }
functionregister(uint value)public{ // The library functions can be called without a // specific instance of the library, since the // "instance" will be the current contract. require(Set.insert(knownValues, value)); } // In this contract, we can also directly access knownValues.flags, if we want. }
库也有限制:
不能有状态变量
不能继承其他也不能被继承
不能接收以太币
Using For
using A for B; 表明,把一个库的函数添加到一个指定类型上。这导致了这些库函数多了一个(隐式的)消息接受者对象作为第一个参数,这类似 Python 里的 self。
using A for *;表明 A 的库函数被附着给任意类型。这可能也是为什么库不能有自己的 state 的原因吧。
// 全部都是 pure 函数 function fromUint(uint x) internal pure returns (bigint r) { r.limbs = new uint[](1); r.limbs[0] = x; }
functionadd(bigint _a, bigint _b) internal pure returns (bigint r) { r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length)); uint carry = 0; for (uint i = 0; i < r.limbs.length; ++i) { uint a = limb(_a, i); uint b = limb(_b, i); r.limbs[i] = a + b + carry; if (a + b < a || (a + b == uint(-1) && carry > 0)) carry = 1; else carry = 0; } if (carry > 0) { // too bad, we have toadd a limb uint[] memory newLimbs = new uint[](r.limbs.length + 1); for (i = 0; i < r.limbs.length; ++i) newLimbs[i] = r.limbs[i]; newLimbs[i] = carry; r.limbs = newLimbs; } }
function limb(bigint _a, uint _limb) internal pure returns (uint) { return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; }
function max(uint a, uint b) private pure returns (uint) { return a > b ? a : b; } }
contract C { usingBigIntforBigInt.bigint;
function f() public pure { // 这里的三个变量类型都是 BigInt.bigint,但增加了库函数 var x = BigInt.fromUint(7); var y = BigInt.fromUint(uint(-1)); var z = x.add(y); } }
// This is the same code asbefore, just without comments library Set { struct Data { mapping(uint => bool) flags; }
functioninsert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) returnfalse; // already there self.flags[value] = true; returntrue; }
function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) returnfalse; // not there self.flags[value] = false; returntrue; }
function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; } }
contract C { usingSetforSet.Data; // this is the crucial change // 用名字空间来访问内部类型 Set.Data knownValues;
function register(uint value) public { // Here, all variables oftypeSet.Data have // corresponding member functions. // The followingfunctioncallis identical to // `Set.insert(knownValues, value)` // 从第二个参数传起 require(knownValues.insert(value)); } }
// THIS CONTRACT CONTAINS A BUG - DO NOT USE contract Fund { /// Mapping of ether shares of the contract. mapping(address => uint) shares; /// Withdraw your share. functionwithdraw() public { if (msg.sender.send(shares[msg.sender])) shares[msg.sender] = 0; } }
1 2 3 4 5 6 7 8 9 10 11 12
pragma solidity ^0.4.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE contract Fund { /// Mapping of ether shares of the contract. mapping(address => uint) shares; /// Withdraw your share. functionwithdraw() public { if (msg.sender.call.value(shares[msg.sender])()) shares[msg.sender] = 0; } }
contract Fund { /// Mapping of ether shares of the contract. mapping(address => uint) shares; /// Withdraw your share. functionwithdraw() public { var share = shares[msg.sender]; shares[msg.sender] = 0; msg.sender.transfer(share); } }
contract WithdrawalContract { address public richest; uintpublic mostSent;
mapping (address => uint) pendingWithdrawals;
function WithdrawalContract() 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; returntrue; } else { returnfalse; } }
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); } }
contract AccessRestriction { // These will be assigned at the construction // phase, where `msg.sender` is the account // creating this contract. address public owner = msg.sender; uint public creationTime = now;
// Modifiers can be used to change // the body of a function. // If this modifier is used, it will // prepend a check that only passes // if the function is called from // a certain address. modifier onlyBy(address _account) { require(msg.sender == _account); // Do not forget the "_;"! It will // be replaced by the actual function // body when the modifier is used. _; }
/// Make `_newOwner` the new owner of this /// contract. functionchangeOwner(address _newOwner) public onlyBy(owner) { owner = _newOwner; }
/// Erase ownership information. /// May only be called 6 weeks after /// the contract has been created. functiondisown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) { delete owner; }
// This modifier requires a certain // fee being associated with a function call. // If the caller sent too much, he or she is // refunded, but only after the function body. // This was dangerous before Solidity version 0.4.0, // where it was possible to skip the part after `_;`. modifier costs(uint _amount) { require(msg.value >= _amount); _; if (msg.value > _amount) msg.sender.send(msg.value - _amount); }
functionforceOwnerChange(address _newOwner) public costs(200 ether) { owner = _newOwner; // just some example condition if (uint(owner) & 0 == 1) // This did not refund for Solidity // before version 0.4.0. return; // refund overpaid fees } }
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 { }
function h() public timedTransitions atStage(Stages.AreWeDoneYet) transitionNext { }
function i() public timedTransitions atStage(Stages.Finished) { } }