智能合約介紹

翻譯原文
date:20170612

一個簡單的智能合約

讓我們中最簡單的例子開始∮ぃ現(xiàn)在對所有這一切不了解都沒有關(guān)系负蚊。我們會在后續(xù)的文章中介紹他們的點點滴滴神妹。

存儲(storage)
pragma solidity ^0.4.0;

contract SimpleStorage {
    uint storedData;

    function set(uint x) {
        storedData = x;
    }

    function get() constant returns (uint) {
        return storedData;
    }
}

第一行說明了這份代碼的運行環(huán)境是Solidity的0.4.0版本或者兼容這個版本的后續(xù)版本(可以到0.5.0,但是不包括0.5.0)家妆。這保證了在不同版本的編譯器里鸵荠,代碼的執(zhí)行效果都是一樣的。prama關(guān)鍵詞用來指示編譯器如何編譯代碼(例如c和c++里的#pragma once)伤极。
用Solidity編寫的合約是一個代碼和數(shù)據(jù)的集合蛹找。合約保存在以太坊區(qū)塊鏈中一個特定的地址中。uint storedData;這行代碼聲明了一個名為storedData的哨坪、類型為uint(256位的無符號整形數(shù)據(jù))的變量庸疾。你可以想像成在數(shù)據(jù)庫中的一個小小的數(shù)據(jù)片,我們可以通過調(diào)用函數(shù)來查詢和改變這個值齿税。在以太坊中彼硫,這些函數(shù)總是在各自合約中的函數(shù)(?In the case of Ethereum,this is always the owning contract.)凌箕。在這個例子中拧篮,setget函數(shù)可以用來改變和取出變量storedData的值。
使用變量牵舱,我們不需要加this.前綴串绩,這一點和其他的語言相同。
這個合約并沒有做很多事情(很多基礎(chǔ)的事情芜壁,以太坊平臺已經(jīng)幫你完成了),除了實現(xiàn)這樣的功能:允許任何人存儲一個數(shù)據(jù)和獲取數(shù)據(jù)礁凡。任何人都可以調(diào)用set函數(shù)來覆蓋你寫入的數(shù)據(jù)高氮,但是你之前的數(shù)據(jù)已經(jīng)寫入到區(qū)塊鏈中了。稍后顷牌,我們給合約將增加限制訪問的功能剪芍。這樣一來,那就只有你才能改變這個數(shù)字了窟蓝。

子貨幣例子(subcurrency example)

以下的合約將實現(xiàn)一個簡單的加密貨幣罪裹。憑空產(chǎn)生貨幣是可能的(?It is possible to generate coins out of thin air)运挫,但是只有生成合約的人才能這樣做状共。這是一種狹隘的實現(xiàn)發(fā)行計劃。(谁帕?it is trivial to implement a different issuance scheme)
而且峡继,所有人都可以發(fā)送貨幣給任何人,而不需要用戶名密碼注冊匈挖。你所需要的只是是以太坊的密鑰對碾牌。

pragma solidity ^0.4.0;

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.
    function Coin() {
        minter = msg.sender;
    }

    function mint(address receiver, uint amount) {
        if (msg.sender != minter) return;
        balances[receiver] += amount;
    }

    function send(address receiver, uint amount) {
        if (balances[msg.sender] < amount) return;
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        Sent(msg.sender, receiver, amount);
    }
}

這個合約中將會引入新的概念。我們來一一介紹关划。
address public minter;這一行聲明了一個公有的address類型的變量小染。address類型是一個160位的值翘瓮,它不允許進行任何的算數(shù)運算贮折。它合適用于存儲合同的地址或者其他人的鍵值對。關(guān)鍵詞public自動的生成一個函數(shù)用于獲取這個變量的值资盅。如果沒有這個關(guān)鍵詞调榄,那其他合同將無法獲取到這個變量。自動生成的函數(shù)如下所示:

function minter() returns (address) { return minter; }

如果直接添加這樣的函數(shù)是行不通的呵扛,因為函數(shù)和變量的名稱相同每庆。但是別擔(dān)心,編譯器會自動完成這個操作的,把兩者區(qū)分開來今穿。
下一行缤灵,mapping (address => uint) public balances;同樣是生成一個公有變量,但是有著更加復(fù)雜的數(shù)據(jù)類型蓝晒。這個類型將地址映射成為一個無符號整形腮出。該映射可以看作是一個哈希表,這個表里羅列了所有可能的鍵芝薇,并且對應(yīng)于一個字節(jié)表示為全零的值胚嘲。(?Mappings can be seen as hash tables which are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is all zeros.)但是這個邏輯是行不通的洛二,因為不可能列舉完所有的鍵馋劈,所有的值攻锰。所以要么留意下你映射的類型(最好使用更加高級的類型),要么在不需要的地方使用它妓雾,就像這個例子一樣娶吞。(?So either keep in mind (or better, keep a list or use a more advanced data type) what you added to the mapping or use it in a context where this is not needed, like this one. )(ps:這段話有些難以理解械姻。寝志。。)這個例子由于public關(guān)鍵字而生成的getter函數(shù)會比較復(fù)雜些策添,如下所示:

function balances(address _account) returns (uint) {
    return balances[_account];
}

如你所見材部,你可以使用這個函數(shù)很方便的查詢到余額。
下一行唯竹,event Sent(address from, address to, uint amount);聲明了所謂的“事件”乐导,這個事件通過最后一行的send函數(shù)觸發(fā)。接口(服務(wù)器程序)能夠很容易的監(jiān)聽到這些事件的觸發(fā)浸颓。當(dāng)事件觸發(fā)的時候物臂,監(jiān)聽函數(shù)將會接收到fromto产上,amount參數(shù)棵磷。為了能夠監(jiān)聽這個事件,代碼應(yīng)該這么寫:

Coin.Sent().watch({}, '', function(error, result) {
    if (!error) {
        console.log("Coin transfer: " + result.args.amount +
            " coins were sent from " + result.args.from +
            " to " + result.args.to + ".");
        console.log("Balances now:\n" +
            "Sender: " + Coin.balances.call(result.args.from) +
            "Receiver: " + Coin.balances.call(result.args.to));
    }
})

這里注意下晋涣,接口是如何調(diào)用自動生成的函數(shù)balance仪媒。
下面說下比較特殊的函數(shù),Coin函數(shù)是合約的構(gòu)造函數(shù)谢鹊,它在合約創(chuàng)建的時候執(zhí)行算吩,并且不會延遲執(zhí)行。它會固定的存儲創(chuàng)建人的addressmsg(還有txblock)是一個全局變量佃扼,包含了一些允許進入?yún)^(qū)塊鏈的屬性信息偎巢。msg.sender的值恒為函數(shù)調(diào)用者的address
最后兼耀,這些函數(shù)實現(xiàn)了合約压昼,并且可以被用戶使用來履行mintsend約定。如果mint被非創(chuàng)建者調(diào)用瘤运,那么不會進行任何操作窍霞。另一方面,send可以被其他任何有貨幣的用戶調(diào)用尽超,來進行轉(zhuǎn)賬操作官撼。需要注意的是,如果你通過這個合約給一個address轉(zhuǎn)賬似谁,在區(qū)塊鏈瀏覽器中你在這個address中將看不到任何有關(guān)這個操作的信息傲绣。因為這些轉(zhuǎn)賬信息掠哥,余額信息都是存在這個特定的合約當(dāng)中。通過使用事件秃诵,你可以輕易的實現(xiàn)該貨幣的區(qū)塊鏈瀏覽器续搀,來記錄該貨幣的轉(zhuǎn)賬和余額信息。

區(qū)塊鏈基礎(chǔ)

區(qū)塊鏈對于程序員來說并不是難于理解的一個概念菠净。因為很多難題(挖礦禁舷,哈希,橢圓曲線加密毅往,p2p網(wǎng)絡(luò)等)都是為了提供一系列的功能和承諾牵咙。一旦你接受了這些功能,你就不會擔(dān)心難于理解技術(shù)問題了攀唯。難道你必須理解阿里云怎么提供服務(wù)之后才能使用它嗎洁桌?

交易(transactions)

一條區(qū)塊鏈?zhǔn)且粋€共享的交易數(shù)據(jù)庫。這意味著所有加入網(wǎng)絡(luò)的人都可以訪問數(shù)據(jù)侯嘀。如果你想要對這個數(shù)據(jù)庫做些改變另凌,那么你必須要進行能夠被別人認(rèn)可的交易。交易這個詞說明了要么你完成改變數(shù)據(jù)(假設(shè)你要同時修改兩個值)戒幔,要么什么也沒有發(fā)生吠谢。另外,當(dāng)你的交易被數(shù)據(jù)庫接受之后诗茎,其他的交易不會改變它工坊。
舉個例子,想象一個表格错沃,它列舉了一個電子貨幣里所有賬戶的余額栅组。如果發(fā)起一個轉(zhuǎn)賬操作,將一筆錢從一個賬戶打到另一個賬戶枢析,這個交易性質(zhì)的數(shù)據(jù)庫保證了,一個賬戶余額減少刃麸,另一個賬戶一定多出相應(yīng)的金額醒叁。如果一個賬戶無法收錢,那么原賬戶并不會少錢泊业。
另外把沼,一個交易總是被發(fā)起者(創(chuàng)建者)加密。這直觀的保護了要修改的數(shù)據(jù)吁伺。在電子貨幣這個例子中饮睬,簡單的驗證保證了只有擁有key的人才能從中轉(zhuǎn)賬。

區(qū)塊

在比特幣項目中篮奄,需要克服的一個比較大的問題是所謂的“雙花攻擊”捆愁。雙花攻擊就是網(wǎng)絡(luò)中有兩筆交易都想清空一個賬戶割去,而引發(fā)沖突。
對這個問題比較抽象的解答就是昼丑,你無需關(guān)心呻逆。會自動為你確認(rèn)交易順序。交易寫入?yún)^(qū)塊菩帝,然后執(zhí)行咖城、分發(fā)給網(wǎng)絡(luò)中所有參與的節(jié)點。如果兩筆交易相互矛盾呼奢,那么后一筆交易會駁回宜雀,不會寫入?yún)^(qū)塊。
這些區(qū)塊根據(jù)時間握础,串在一起州袒,形成”區(qū)塊鏈“。這就是區(qū)塊鏈的由來弓候。區(qū)塊按照特定的時間加入到區(qū)塊鏈中郎哭。在以太坊平臺中,這個時間是17秒左右菇存。
由于順序選擇機制(所謂的挖礦)夸研,頂端的區(qū)塊可能經(jīng)常的撤回。越多的區(qū)塊寫入?yún)^(qū)塊鏈中依鸥,你的交易越不容易撤回亥至。

以太坊虛擬機(EVM)

概覽

以太坊虛擬機(EVM)是以太坊平臺智能合約的運行環(huán)境。它并不是一個沙盒贱迟,但是是完全獨立的姐扮,這意味著EVM不能訪問網(wǎng)絡(luò),文件系統(tǒng)或者其他進程衣吠。有些智能合約也被約束為不能訪問其他的智能合約茶敏。

賬號

以太坊平臺有兩種賬號,但是公用一個地址空間:對外賬號——被鍵值對控制著——和合約賬號——與賬號一同保存的代碼缚俏。
對外賬號的address由公鑰決定惊搏,而合約賬號的地址由合約創(chuàng)建的時候確認(rèn)(它根據(jù)創(chuàng)建者的地址、交易的數(shù)目忧换,組成所謂的“nonce”——“Number used once“——臨時數(shù)據(jù))恬惯。
無論賬號中是否有代碼,EVM都是相同對待的亚茬。
每個賬號都有一個固定的鍵值對酪耳,對應(yīng)于256比特字到256比特字的存儲。(?Every account has a persistent key-value store mapping 256-bit words to 256-bit words called storage.)
另外刹缝,每個賬號都有以太幣余額碗暗,它可以通過進行比特幣交易來改變颈将。

交易

交易是一種從一個賬號發(fā)送到另一個賬號(可能是同個賬號,或者0賬號)的消息讹堤。它包含二進制數(shù)據(jù)(負(fù)載)和以太幣吆鹤。
如果目標(biāo)賬號包含代碼,那么執(zhí)行代碼洲守,二進制負(fù)載數(shù)據(jù)作為代碼的輸入疑务。
如果目標(biāo)賬號是0賬號(address為0),交易會生成一個新的合約梗醇。之前提到過的知允,合約賬號是由發(fā)送者的address和轉(zhuǎn)賬金額生成的,絕對是非0賬號叙谨。這類創(chuàng)建合約交易的負(fù)載成為EVM的字節(jié)碼并且執(zhí)行温鸽。執(zhí)行的輸出被永久保存在合約代碼中。這意味著手负,要創(chuàng)建一個合同涤垫,你并不發(fā)送合同的實際代碼,而是返回合同代碼的代碼竟终。(ps:有點繞蝠猬,可以看原文)

燃料(gas)

在創(chuàng)建的時候,每個交易都會收取特定的gas统捶,這個目的在于榆芦,限制交易執(zhí)行的工作量并且對這個執(zhí)行交易收費。當(dāng)EVM執(zhí)行交易喘鸟,gas按照特定的規(guī)則緩慢的消耗掉了匆绣。
gas的價格在創(chuàng)建交易的時候就設(shè)定了,必須在交易之前支付gas價格 * gas數(shù)目的費用。如果執(zhí)行完畢之后還有g(shù)as剩余什黑,會原路返回到賬戶中崎淳。
如果gas在任何時候消耗完畢(例如成了負(fù)數(shù)),就會觸發(fā)gas不足的異常兑凿,將會撤銷本次交易的所有修改凯力。

storage,內(nèi)存和堆棧

每個賬號都有一個永久的存儲區(qū)域礼华,稱為storage。storage有一個鍵值存儲拗秘,對應(yīng)于256比特字到256比特字的映射圣絮。例舉一個合同里的storage是不可能的。讀取或者其他修改storage的操作都是代價昂貴的雕旨。合約只能修改自己的storage扮匠,不能修改其他的捧请。
內(nèi)存區(qū)域被稱為內(nèi)存(memory),每次執(zhí)行的時候會獲取到一個清除干凈的內(nèi)存棒搜。內(nèi)存是線性的疹蛉,可以實現(xiàn)byte尋址。但是讀取必須是256位的力麸,寫入可以是8位或者256位的可款。當(dāng)接觸到(讀取或者寫入)之前為觸及的內(nèi)存字的時候,內(nèi)存以256位的字長擴展克蚂。當(dāng)擴展的時候闺鲸,必須支付gas。所以內(nèi)存消耗越大埃叭,花費更大摸恍。
EVM不是一種存儲器機,而是一種堆棧機赤屋。所以所有的計算都在堆棧上執(zhí)行立镶。最大的是1024個元素并包含256位的字。訪問stack的深度有如下的限制規(guī)則:可以將頂端下的16個棧中拷貝出一個元素放置在頂端类早,或者可以將頂端的元素與下面的16個元素中的任意一個互換媚媒。所有其他的操作將距離頂端的兩個(或者一個,或者多個莺奔,全看操作需要)取出計算欣范,將結(jié)果放置在棧頂端。當(dāng)然也可以將堆棧元素放置在storage或者內(nèi)存里令哟。但是不移除棧頂?shù)臅r候是無法再繼續(xù)深入訪問其他元素的恼琼。

指令集

EVM的指令集一直保持最小化,來避免導(dǎo)致一致性問題的可能性屏富。所有指令操作都基于基本的數(shù)據(jù)類型晴竞,256位的字長。允許算術(shù)操作狠半,位操作噩死,邏輯操作以及比較操作∩衲辏可以條件跳轉(zhuǎn)和非條件跳轉(zhuǎn)已维。另外,合約可以訪問當(dāng)前區(qū)塊的相關(guān)屬性已日,例如序號和時間戳垛耳。

消息調(diào)用(message call)

合約可以調(diào)用其他合約或者可以通過消息調(diào)用發(fā)送以太幣到非合約賬號。消息調(diào)用和jiao交易非常相似,有原始地址堂鲜,目的地址栈雳,數(shù)據(jù)負(fù)載,以太幣缔莲,gas和返回值哥纫。實際上,每個交易都是由高級的消息調(diào)用組成痴奏。所謂高級蛀骇,就是可以生成其他消息調(diào)用。
一個合約可以決定發(fā)送多少gas抛虫,保留多少松靡。如果一個gas不足異常(或者其他異常)在內(nèi)部的消息調(diào)用中生成的時候,將會在堆棧頂標(biāo)記一個錯誤的值建椰。在這個例子中雕欺,當(dāng)且僅當(dāng)與消息一起發(fā)送的gas使用完畢時。在solidity中棉姐,調(diào)用合約引起一個認(rèn)為異常屠列,導(dǎo)致調(diào)用棧上升。(伞矩?理解的稀里糊涂笛洛,推薦看原文)
就像之前所說,被調(diào)用的合約(可以是調(diào)用者自身)能夠得到一塊嶄新的內(nèi)存乃坤,并且可以訪問到負(fù)載(在單獨的區(qū)域中提供苛让,稱之為calldata區(qū)域)。在執(zhí)行結(jié)束之后湿诊,它可以返回數(shù)據(jù)狱杰,并存儲到調(diào)用者的內(nèi)存中。
調(diào)用的最大深度是1024厅须,這意味著更復(fù)雜的調(diào)用可以采用遞歸仿畸,而不是循環(huán)。

代理調(diào)用/調(diào)用代碼 和庫(Delegatcall/Callcode and Libraries)

存在一種消息調(diào)用的變種朗和,稱為代理調(diào)用错沽。代理調(diào)用就是說目的地址中的代碼在當(dāng)前調(diào)用者的上下文中運行,msg.sendermsg.value不會改變眶拉,其他方面跟一般的消息調(diào)用一致千埃。
這就意味著一個合約可以在運行時動態(tài)的從不同地址中調(diào)用代碼。storage忆植,當(dāng)前地址和余額都是引用當(dāng)前的調(diào)用合約镰禾,僅僅只是代碼從調(diào)用地址中獲取的皿曲,其他的都是當(dāng)前合約的唱逢。
這就可以實現(xiàn)類庫的功能:可以復(fù)用的代碼可以放在合約的storage中吴侦,來實現(xiàn)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。

日志

可以將數(shù)據(jù)保存在特殊的索引數(shù)據(jù)結(jié)構(gòu)中坞古,該結(jié)構(gòu)與區(qū)塊一一對應(yīng)备韧。這個功能稱為日志功能,不需要實現(xiàn)事件(event)痪枫。在日志創(chuàng)建之后织堂,合約就不能訪問這些數(shù)據(jù)了。但是這些數(shù)據(jù)可以在區(qū)塊鏈之外的地方輕松訪問到奶陈。由于一些日志數(shù)據(jù)用bloom filter算法保存易阳,所以可以搞笑的安全的方式訪問到數(shù)據(jù),而且節(jié)點也不需要下載所有的區(qū)塊鏈(輕客戶端)吃粒,也能找到這些日志潦俺。

創(chuàng)建

合約可以用特殊的代碼(不是簡單的調(diào)用0地址)創(chuàng)建其他的合約。創(chuàng)建調(diào)用和其他一般的消息調(diào)用不同的一點是徐勃,創(chuàng)建調(diào)用的負(fù)載是可以執(zhí)行的事示,并且返回值是代碼,調(diào)用者(創(chuàng)建者)可以獲取到新合約的地址僻肖。

自我銷毀

代碼從區(qū)塊鏈中移除的唯一可能就是合約調(diào)用了selfdestruct操作肖爵。該地址剩余的以太幣將會返回到設(shè)定的賬戶中。storage和代碼都從狀態(tài)中移除了臀脏。

警告:即便合同中不包含selfdistruct代碼劝堪,它依舊可以通過delegatecall或者callcode來完成自毀

注意:除去老舊合同的功能也許會,也許不會出現(xiàn)在以太坊客戶端中揉稚。另外秒啦,檔案節(jié)點可以選擇是否永久保留代碼和storage。

注意:目前外部賬號不能移除

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窃植,一起剝皮案震驚了整個濱河市帝蒿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巷怜,老刑警劉巖葛超,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異延塑,居然都是意外死亡绣张,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門关带,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侥涵,“玉大人沼撕,你說我怎么就攤上這事∥咂” “怎么了务豺?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嗦明。 經(jīng)常有香客問我笼沥,道長,這世上最難降的妖魔是什么娶牌? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任奔浅,我火速辦了婚禮,結(jié)果婚禮上诗良,老公的妹妹穿的比我還像新娘汹桦。我一直安慰自己,他們只是感情好鉴裹,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布舞骆。 她就那樣靜靜地躺著,像睡著了一般壹罚。 火紅的嫁衣襯著肌膚如雪葛作。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天猖凛,我揣著相機與錄音赂蠢,去河邊找鬼。 笑死辨泳,一個胖子當(dāng)著我的面吹牛虱岂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菠红,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼第岖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了试溯?” 一聲冷哼從身側(cè)響起蔑滓,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遇绞,沒想到半個月后键袱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡摹闽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年蹄咖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片付鹿。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡澜汤,死狀恐怖蚜迅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俊抵,我是刑警寧澤谁不,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站务蝠,受9級特大地震影響拍谐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜馏段,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望践瓷。 院中可真熱鬧院喜,春花似錦、人聲如沸晕翠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淋肾。三九已至硫麻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間樊卓,已是汗流浹背拿愧。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碌尔,地道東北人浇辜。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像唾戚,于是被迫代替她去往敵國和親柳洋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容