10 زبان برتر برنامه نویسی قرارداد هوشمند در سال 2023
قبل از معرفی زبان های برنامه نویسی قرارداد هوشمند ابتدا باید ببینیم قرارداد هوشمند چیست و چگونه ابداع شد؟ در این مقاله، پس از معرفی تاریخچه قرارداد هوشمند، ابتدا با مفهوم قراردادهای هوشمند آشنا خواهید شد و سپس خواهیم دید با کدام زبان های برنامه نویسی می توان قرارداد هوشمند نوشت. هر کدام از زبان های برنامه نویسی قرارداد هوشمند از جمله Solidity, Vyper, Yul, Rust, JavaScript, C++, C# , Java. Python, Go را بررسی خواهیم کرد و با مزایا و معایب هر کدام از آنها آشنا خواهیم شد.
تاریخچه قرارداد هوشمند
1997 : قرارداد هوشمند (Smart Contract) برای اولین بار توسط نیک زابو (Nick Szabo) دانشمند کامپیوتر آمریکایی به جهان معرفی شد. نیک زابو قرارداد هوشمند را به اینصورت تعریف کرد: “قراردادهای هوشمند عبارت است از پروتکل های داد و ستد کامپیوتری قابل اجرا به صورت قرارداد”.
2009 : بیت کوین (Bitcoin) با هدف حذف بانک ها معرفی شد و مفهوم ارز دیجیتال و بلاک چین (به عنوان تکنولوژی زیرین ارزهای دیجیتال) وارد بازارهای مالی شد.
2013 : اتریوم (Ethereum) ویژگی جالب پول قابل برنامه نویسی (Programmable Money) که امروزه آنرا با عنوان قرارداد هوشمند (Smart Contract) می شناسیم، را معرفی کرد. با معرفی این نوآوری، بلاک چین ابعاد گسترده تری پیدا کرد و وارد فاز جدیدی شد و ثابت کرد که با تکنولوژی قرارداد هوشمند نه تنها بانک، بلکه حذف تمام واسطه ها نیز امکانپذیر است.
قرارداد هوشمند چیست؟
اسمارت کانترکت در واقع برنامه کامپیوتری است که توافق مستقیم و بدون واسطه بین خریدار و فروشنده را به صورت کدنویسی پیاده سازی کرده است. از آنجا که قراردادهای هوشمند قابلیت اجرای خودکار دارند به آنها هوشمند (Smart) گفته می شود.
ویژگی های جالب سیستم قرارداد هوشمند:
- هوشمند (Smart) : بدون دخالت انسان قابل اجرا می باشد.
- شفافیت (Transparency) : قرارداد هوشمند، کاملاً شفاف و اوپن سورس بوده و مشاهده کد قراردادها برای عموم امکانپذیر است.
- ناشناس ماندن افراد (Anonymity) : طرفین قرارداد میتوانند با هویت پنهان و ناشناس توافق و داد و ستد مطمئن و معتبری داشته باشند.
- غیرمتمرکز (Decentralized) : نیازی به نهاد مرکزی، مکانیزم های بازدارنده خارجی یا سیستم های قانونی و حقوقی دیگری نیست .
- غیرقابل برگشت بودن (Irreversiblity) : قرارداد هوشمند و تراکنش های انجام شده، پس از ذخیره روی بلاک چین، قابل تغییر نخواهد بود.
- قابل رهگیری بودن (Traceablity) : تمام بندهای قرارداد و همچنین تراکنش های انجام شده در بلاک چین ثبت شده و قابل مشاهده برای عموم است.
زبان های برنامه نویسی قرارداد هوشمند
هرچند زبان سالیدیتی، به عنوان بهترین زبان برای برنامه نویسی قرارداد هوشمند شناخته شده است و پیاده سازی قرارداد هوشمند با زبان Solidity، در بلاک چین های متعدد (evm compatible) امکانپذیر است ولی برخی بلاک چین ها به کاربران خود حق انتخاب می دهند تا از بین چند زبان، یکی را به دلخواه برای توسعه قرارداد هوشمند انتخاب نمایند. بنابراین بهتر است با زبان های دیگر نیز آشنا باشیم. در این بخش زبان های برنامه نویسی که می توان برای برنامه نویسی قرارداد هوشمند از آنها استفاده کرد را معرفی خواهیم کرد.
1. Solidity
اولین زبانی که بررسی می کنیم، زبان سالیدیتی (Solidity) است. این زبان مبتنی بر قرارداد توسط Gavin Wood و سایر بنیانگذاران اتریوم توسعه داده شد. زبان سالیدیتی محبوب ترین زبان برای برنامه نویسی قراردادهای هوشمند پروژه های بلاک چین محسوب می شود. سالیدیتی یک زبان سطح بالا از نوع تایپ ایستا (statically typed) است که سینتکس آن شبیه زبان پایتون، ++C و جاوا اسکریپت می باشد. زبان برنامه نویسی سالیدیتی، شی گرا بوده و از ساختار کلاس و وراثت (inheritance) پشتیبانی می کند.
زبان سالیدیتی برای اجرا روی معماری ماشین مجازی اتریوم (Ethereum Virtual Machine) طراحی شد و در ابتدا به طور اختصاصی جهت برنامه نویسی قرارداد هوشمند روی بلاک چین اتریوم استفاده می شد. ولی موفقیت معماری EVM و رشد شگفت انگیز محبوبیت و جامعیت بلاک چین اتریوم، امروزه زبان سالیدیتی را به زبان اصلی برنامه نویسی قرارداد هوشمند تبدیل کرده و اکثر بلاک چین های دیگر نیز سعی دارند تا شبکه بلاک چین خودشان را سازگار با پلتفرم EVM و قراردادهای هوشمند سالیدیتی طراحی کنند. به شبکه های بلاک چین که منطبق با معماری EVM طراحی شده باشند شبکه های EVM Compatible گفته می شود و قراردادهای هوشمند نوشته شده برای شبکه اتریوم، به سادگی روی این شبکه ها قابل اجرا می باشد.
قرارداد هوشمند توکن ERC20 به زبان سالیدیتی (Solidity)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <=0.8.13; contract SimpleToken { uint private initialSupply; uint private _totalSupply; address private owner; string public name; string public symbol; uint8 public decimals; mapping(address => uint) public balances; mapping(address => mapping(address => uint)) public allowances; event Transfer(address indexed _from, address indexed _to, uint _amount); event Approval(address indexed _owner, address indexed _spender, uint _amount); constructor(uint _initialSupply, string memory _name, string memory _symbol, uint8 _decimals) { owner = msg.sender; initialSupply = _initialSupply; decimals = _decimals; name = _name; symbol = _symbol; mint(initialSupply); } function mint(uint amount) public { require(msg.sender == owner, "only owner can mint!"); balances[owner] = amount; _totalSupply += amount; emit Transfer(address(0), owner, amount); } function transfer(address _to, uint _amount) public returns(bool) { require(balances[msg.sender] >= _amount, "Transfer: Not enough balance!"); balances[msg.sender] -= _amount; balances[_to] += _amount; emit Transfer(msg.sender, _to, _amount); return true; } function approve(address _spender, uint _amount) public returns(bool) { allowances[msg.sender][_spender] = _amount; emit Approval(msg.sender, _spender, _amount); return true; } function transferFrom(address _from, address _to, uint _amount) public returns(bool) { uint _allowance = allowances[_from][msg.sender]; require(_allowance >= _amount, "transferFrom: insufficient allowance!"); require(balances[_from] >= _amount, "transferFrom: Not enough balance!"); balances[_from] -= _amount; balances[_to] += _amount; allowances[_from][msg.sender] -= _amount; emit Transfer(_from, _to, _amount); return true; } function balanceOf(address _owner) public view returns(uint) { return balances[_owner]; } function allowance(address _owner, address _spender) public view returns(uint) { return allowances[_owner][_spender]; } function totalSupply() public view returns(uint) { return _totalSupply; } } |
شبکه های بلاک چین که از سالیدیتی پشتیبانی می کنند:
- اتریوم (Ethereum)
- بایننس اسمارت چین (BSC)
- ترون (Tron)
- پالیگان (Polygon)
- و تمام شبکه های بلاک چین سازگار با EVM
2. Vyper
وایپر یک زبان برنامه نویسی قرارداد هوشمند مشابه پایتون (Python) است که در سال 2017 معرفی شد. زبان وایپر نیز مانند سالیدیتی سورس کد قرارداد هوشمند را به بایت کدهای قابل اجرا روی evm کامپایل می کند.
توسعه دهندگان زبان وایپر، بعد از بررسی زبان سالیدیتی سعی کرده اند، زبان جدیدی ارائه دهند که بهینه تر، ساده تر و امن تر باشد و کدهای نوشته شده با آن قابلیت خوانایی (Readability) و قابلیت بازنگری (Audit) بیشتری داشته باشد. هرگاه امنیت قرارداد هوشمند اهمیت بالاتری داشته باشد و نیاز به نوشتن کدهایی با امنیت بالاتر باشد، می توان به جای سالیدیتی از زبان وایپر استفاده کرد.
وایپر نمی تواند جایگزین سالیدیتی باشد. چراکه تعداد پروژه های نوشته شده با وایپر در گیت هاب حدود 1000 پروژه است و این عدد در مقابل پروژه های سالیدیتی (740,000 پروژه) بسیار ناچیز است. همین موضوع باعث می شود منابع و مراجع یادگیری و مثال های قرارداد هوشمند به زبان وایپر بسیار کمتر از سالیدیتی باشد.
قرارداد هوشمند توکن ERC20 به زبان وایپر
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
from vyper.interfaces import ERC20 from vyper.interfaces import ERC20Detailed implements: ERC20 implements: ERC20Detailed event Transfer: sender: indexed(address) receiver: indexed(address) value: uint256 event Approval: owner: indexed(address) spender: indexed(address) value: uint256 name: public(String[32]) symbol: public(String[32]) decimals: public(uint8) balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) totalSupply: public(uint256) minter: address @external def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) self.name = _name self.symbol = _symbol self.decimals = _decimals self.balanceOf[msg.sender] = init_supply self.totalSupply = init_supply self.minter = msg.sender log Transfer(ZERO_ADDRESS, msg.sender, init_supply) @external def transfer(_to : address, _value : uint256) -> bool: self.balanceOf[msg.sender] -= _value self.balanceOf[_to] += _value log Transfer(msg.sender, _to, _value) return True @external def transferFrom(_from : address, _to : address, _value : uint256) -> bool: self.balanceOf[_from] -= _value self.balanceOf[_to] += _value self.allowance[_from][msg.sender] -= _value log Transfer(_from, _to, _value) return True @external def approve(_spender : address, _value : uint256) -> bool: self.allowance[msg.sender][_spender] = _value log Approval(msg.sender, _spender, _value) return True @external def mint(_to: address, _value: uint256): assert msg.sender == self.minter assert _to != ZERO_ADDRESS self.totalSupply += _value self.balanceOf[_to] += _value log Transfer(ZERO_ADDRESS, _to, _value) @internal def _burn(_to: address, _value: uint256): assert _to != ZERO_ADDRESS self.totalSupply -= _value self.balanceOf[_to] -= _value log Transfer(_to, ZERO_ADDRESS, _value) @external def burn(_value: uint256): self._burn(msg.sender, _value) @external def burnFrom(_to: address, _value: uint256): self.allowance[_to][msg.sender] -= _value self._burn(_to, _value) |
3. Yul
زبان yul یک زبان واسط (Intermediate) برای اتریوم محسوب می شود. این زبان از هر دو پلتفرم EVM و Ewasm پشتیبانی می کند. زبان Yul از نوع تایپ ایستا (statically typed) است و به همین دلیل کار کردن با مفاهیمی مانند value و reference در زبان Yul گیج کننده نخواهد بود.
اهداف اصلی زبان Yul
- سورس برنامه باید خوانا باشد
- جریان (flow) برنامه باید قابل درک باشد
- ترجمه از زبان Yul به بایت کد باید در حد امکان ساده باشد
- زبان Yul باید برای بهینه سازی کل برنامه (whole-program) مناسب باشد
قرارداد هوشمند توکن ERC20 به زبان Yul
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
object "Token" { code { // Store the creator in slot zero. sstore(0, caller()) // Deploy the contract datacopy(0, dataoffset("runtime"), datasize("runtime")) return(0, datasize("runtime")) } object "runtime" { code { // Protection against sending Ether require(iszero(callvalue())) // Dispatcher switch selector() case 0x70a08231 /* "balanceOf(address)" */ { returnUint(balanceOf(decodeAsAddress(0))) } case 0x18160ddd /* "totalSupply()" */ { returnUint(totalSupply()) } case 0xa9059cbb /* "transfer(address,uint256)" */ { transfer(decodeAsAddress(0), decodeAsUint(1)) returnTrue() } case 0x23b872dd /* "transferFrom(address,address,uint256)" */ { transferFrom(decodeAsAddress(0), decodeAsAddress(1), decodeAsUint(2)) returnTrue() } case 0x095ea7b3 /* "approve(address,uint256)" */ { approve(decodeAsAddress(0), decodeAsUint(1)) returnTrue() } case 0xdd62ed3e /* "allowance(address,address)" */ { returnUint(allowance(decodeAsAddress(0), decodeAsAddress(1))) } case 0x40c10f19 /* "mint(address,uint256)" */ { mint(decodeAsAddress(0), decodeAsUint(1)) returnTrue() } default { revert(0, 0) } function mint(account, amount) { require(calledByOwner()) mintTokens(amount) addToBalance(account, amount) emitTransfer(0, account, amount) } function transfer(to, amount) { executeTransfer(caller(), to, amount) } function approve(spender, amount) { revertIfZeroAddress(spender) setAllowance(caller(), spender, amount) emitApproval(caller(), spender, amount) } function transferFrom(from, to, amount) { decreaseAllowanceBy(from, caller(), amount) executeTransfer(from, to, amount) } function executeTransfer(from, to, amount) { revertIfZeroAddress(to) deductFromBalance(from, amount) addToBalance(to, amount) emitTransfer(from, to, amount) } /* ---------- calldata decoding functions ----------- */ function selector() -> s { s := div(calldataload(0), 0x100000000000000000000000000000000000000000000000000000000) } function decodeAsAddress(offset) -> v { v := decodeAsUint(offset) if iszero(iszero(and(v, not(0xffffffffffffffffffffffffffffffffffffffff)))) { revert(0, 0) } } function decodeAsUint(offset) -> v { let pos := add(4, mul(offset, 0x20)) if lt(calldatasize(), add(pos, 0x20)) { revert(0, 0) } v := calldataload(pos) } /* ---------- calldata encoding functions ---------- */ function returnUint(v) { mstore(0, v) return(0, 0x20) } function returnTrue() { returnUint(1) } /* -------- events ---------- */ function emitTransfer(from, to, amount) { let signatureHash := 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef emitEvent(signatureHash, from, to, amount) } function emitApproval(from, spender, amount) { let signatureHash := 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 emitEvent(signatureHash, from, spender, amount) } function emitEvent(signatureHash, indexed1, indexed2, nonIndexed) { mstore(0, nonIndexed) log3(0, 0x20, signatureHash, indexed1, indexed2) } /* -------- storage layout ---------- */ function ownerPos() -> p { p := 0 } function totalSupplyPos() -> p { p := 1 } function accountToStorageOffset(account) -> offset { offset := add(0x1000, account) } function allowanceStorageOffset(account, spender) -> offset { offset := accountToStorageOffset(account) mstore(0, offset) mstore(0x20, spender) offset := keccak256(0, 0x40) } /* -------- storage access ---------- */ function owner() -> o { o := sload(ownerPos()) } function totalSupply() -> supply { supply := sload(totalSupplyPos()) } function mintTokens(amount) { sstore(totalSupplyPos(), safeAdd(totalSupply(), amount)) } function balanceOf(account) -> bal { bal := sload(accountToStorageOffset(account)) } function addToBalance(account, amount) { let offset := accountToStorageOffset(account) sstore(offset, safeAdd(sload(offset), amount)) } function deductFromBalance(account, amount) { let offset := accountToStorageOffset(account) let bal := sload(offset) require(lte(amount, bal)) sstore(offset, sub(bal, amount)) } function allowance(account, spender) -> amount { amount := sload(allowanceStorageOffset(account, spender)) } function setAllowance(account, spender, amount) { sstore(allowanceStorageOffset(account, spender), amount) } function decreaseAllowanceBy(account, spender, amount) { let offset := allowanceStorageOffset(account, spender) let currentAllowance := sload(offset) require(lte(amount, currentAllowance)) sstore(offset, sub(currentAllowance, amount)) } /* ---------- utility functions ---------- */ function lte(a, b) -> r { r := iszero(gt(a, b)) } function gte(a, b) -> r { r := iszero(lt(a, b)) } function safeAdd(a, b) -> r { r := add(a, b) if or(lt(r, a), lt(r, b)) { revert(0, 0) } } function calledByOwner() -> cbo { cbo := eq(owner(), caller()) } function revertIfZeroAddress(addr) { require(addr) } function require(condition) { if iszero(condition) { revert(0, 0) } } } } } |
از زبان Yul می توان به صورت inline assembly داخل کدهای سالیدیتی نیز استفاده کرد.
1 2 3 4 5 6 7 8 |
{ "language": "Yul", "sources": { "input.yul": { "content": "{ sstore(0, 1) }" } }, "settings": { "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }, "optimizer": { "enabled": true, "details": { "yul": true } } } } |
4. DAML
زبان DAML یا Digital Asset Modelling Language یک زبان ساده، امن و بهینه برای برنامه نویسی قرارداد هوشمند و لجر توزیع شده است. وقتی برنامه نویس از DAML استفاده میکند، به جای تمرکز روی چگونگی تبدیل ایده به کد، روی بیزینس لاجیک (Business Logic) تمرکز میکند. ازآنجایی که اپلیکیشن های توزیع شده از روندهای نوظهور بوده و توسعه آنها نیز به سادگی برنامه های رایج نیست، زبان های رایج برای توسعه این نوع برنامه ها، انتخاب خوبی نخواهند بود. daml زبان اوپن سورسی است که انتخاب مناسبی برای توسعه سریع و دقیق اپلیکیشن های توزیع شده می باشد.
نقاط قوت زبان DAML عبارتند از:
- زبان Contract است
- زبان Functional است
- زبان توسعه Ledger های توزیع شده
- Privacy
زبان DAML علی رغم زبان سالیدیتی که در نوشتن قراردادهای هوشمند برای شبکه های عمومی کاربرد دارد، برای نوشتن قراردادهای هوشمند برای شبکه های خصوصی مثل هایپرلجر و همچنین برنامه نویسی لجرهای توزیع شده کاربرد دارد.
شبکه های بلاک چین که از زبان DAML پشتیبانی می کنند:
- هایپرلجر Sawtooth
- بلاک چین NEO
قرارداد هوشمند توکن ERC20 به زبان DAML برای شبکه NEO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
daml 1.2 module Ethereum.ERC20Contract where import Ethereum.Call import Ethereum.Transaction import Ethereum.Utils type ERC20ContractCid = ContractId ERC20Contract template ERC20Contract with operator : Party user : Party name : Text symbol : Text decimals : Int address : Text where signatory operator ensure decimals >= 0 controller user can nonconsuming ERC20Contract_TotalSupply : CallRequestCid do create CallRequest with name = "totalSupply" args = [] returns = [abiUint256] optFrom = None to = address .. nonconsuming ERC20Contract_BalanceOf : CallRequestCid with owner : Text do let abiOwner = ABIValue with abiType = ABIAddress abiValue = owner balance = abiUint256 create CallRequest with name = "balanceOf" args = [abiOwner] returns = [balance] optFrom = None to = address .. nonconsuming ERC20Contract_Allowance : CallRequestCid with owner : Text spender : Text do let abiOwner = ABIValue with abiType = ABIAddress abiValue = owner abiSpender = ABIValue with abiType = ABIAddress abiValue = spender remaining = abiUint256 create CallRequest with name = "allowance" args = [abiOwner, abiSpender] returns = [remaining] optFrom = None to = address .. nonconsuming ERC20Contract_Transfer : TransactionRequestCid with to : Text value : Text requester : Text optGas : Optional Int optGasPrice : Optional EtherUnits do let abiTo = ABIValue with abiType = ABIAddress abiValue = to abiValue = ABIValue with abiType = abiUint256 abiValue = value success = ABIBool create TransactionRequest with name = "transfer" args = [abiTo, abiValue] returns = [success] from = requester optValue = None .. nonconsuming ERC20Contract_Approve : TransactionRequestCid with spender : Text value : Text requester : Text optGas : Optional Int optGasPrice : Optional EtherUnits do let abiSpender = ABIValue with abiType = ABIAddress abiValue = spender abiValue = ABIValue with abiType = abiUint256 abiValue = value success = ABIBool create TransactionRequest with name = "approve" args = [abiSpender, abiValue] returns = [success] from = requester to = address optValue = None .. nonconsuming ERC20Contract_TransferFrom : TransactionRequestCid with from : Text to : Text value : Text requester : Text optGas : Optional Int optGasPrice : Optional EtherUnits do let abiFrom = ABIValue with abiType = ABIAddress abiValue = from abiTo = ABIValue with abiType = ABIAddress abiValue = from abiValue = ABIValue with abiType = abiUint256 abiValue = value success = ABIBool create TransactionRequest with name = "transferFrom" args = [abiFrom, abiTo, abiValue] returns = [success] from = requester to = address optValue = None .. nonconsuming ERC20Contract_Name : CallRequestCid do create CallRequest with name = "name" args = [] returns = [ABIString] optFrom = None to = address .. nonconsuming ERC20Contract_Symbol : CallRequestCid do create CallRequest with name = "symbol" args = [] returns = [ABIString] optFrom = None to = address .. nonconsuming ERC20Contract_Decimals : CallRequestCid do create CallRequest with name = "decimals" args = [] returns = [abiUint8] optFrom = None to = address .. |
5. Rust
طبق آمارهای گیت هاب و استک اورفلو (Stack Overflow)، زبان Rust یکی از محبوب ترین زبان های برنامه نویسی بین برنامه نویسان است. زبان Rust برای امنیت و کارآیی، بخصوص امنیت همزمانی (Concurrency) طراحی شده است. از لحاظ سینتکس، این زبان شبیه زبان C++ است. محبوبیت زبان Rust برای نوشتن قرارداد هوشمند از این جهت است که Rust زبان برنامه نویسی قراردادهای هوشمند برای شبکه بلاک چین سولانا (Solana) است. سولانا یکی از شبکه های بلاک چین با TPS (transactions per second) بالا است و به خاطر همین سرعت بالای تراکنش سولانا به تازگی محبوبیت بالایی را بین شبکه هی بلاک چین بدست آورده است. ازآنجا که برنامه نویسان Rust معمولاً برنامه نویس های حرفه ای و با تجربه ای هستند، سورس کدهای نوشته شده با این زبان، Bug کمتر دارد.
زبان Rust در مورد مدیریت حافظه بسیار عالی است ولی معایبی نیز دارد. یکی از معایب زبان Rust زمان بالای کامپایل است به طوری که زمان کامپایل پروژه های بزرگ حتی به 10 دقیقه هم میرسد. نقطه ضعف دوم آن یادگیری زبان Rust است که برای یک فرد نوآموز، یادگیری، خواندن و نوشتن کدهای Rust نسبتاً پیچیده است. ضمناً مرجع آموزش Rust و سورس کدها و نمونه کد Rust بسیار محدود است.
شبکه های بلاک چین که از زبان Rust پشتیبانی می کنند:
- سولانا (Solana)
- بلاک چین Near
- پولکادات (Polkadot)
قرارداد هوشمند توکن ERC20 به زبان Rust
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
#![no_std] extern crate tiny_keccak; use tiny_keccak::Keccak; static TOTAL_SUPPLY_KEY: H256 = H256([2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); static OWNER_KEY: H256 = H256([3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); fn read_balance_of(owner: &Address) -> U256 { owasm_ethereum::read(&balance_key(owner)).into() } fn read_allowance(key: &H256) -> U256 { owasm_ethereum::read(key).into() } fn write_allowance(key: &H256, value: U256) { owasm_ethereum::write(key, &value.into()) } fn allowance_key(owner: &Address, spender: &Address) -> H256 { let mut keccak = Keccak::new_keccak256(); let mut res = H256::new(); keccak.update("allowance_key".as_ref()); keccak.update(owner.as_ref()); keccak.update(spender.as_ref()); keccak.finalize(&mut res); res } fn balance_key(address: &Address) -> H256 { let mut key = H256::from(address); key[0] = 1; key } #[owasm_abi_derive::contract] trait TokenContract { fn constructor(&mut self, total_supply: U256) { let sender = owasm_ethereum::sender(); // Set up the total supply for the token owasm_ethereum::write(&TOTAL_SUPPLY_KEY, &total_supply.into()); // Give all tokens to the contract owner owasm_ethereum::write(&balance_key(&sender), &total_supply.into()); // Set the contract owner owasm_ethereum::write(&OWNER_KEY, &H256::from(sender).into()); } #[constant] fn balanceOf(&mut self, owner: Address) -> U256 { read_balance_of(&owner) } #[constant] fn totalSupply(&mut self) -> U256 { owasm_ethereum::read(&TOTAL_SUPPLY_KEY).into() } fn transfer(&mut self, to: Address, amount: U256) -> bool { let sender = owasm_ethereum::sender(); let senderBalance = read_balance_of(&sender); let recipientBalance = read_balance_of(&to); if amount == 0.into() || senderBalance < amount || to == sender { false } else { let new_sender_balance = senderBalance - amount; let new_recipient_balance = recipientBalance + amount; // TODO: impl From<U256> for H256 makes convertion to big endian. Could be optimized owasm_ethereum::write(&balance_key(&sender), &new_sender_balance.into()); owasm_ethereum::write(&balance_key(&to), &new_recipient_balance.into()); true } } fn approve(&mut self, spender: Address, value: U256) -> bool { write_allowance(&allowance_key(&owasm_ethereum::sender(), &spender), value); true } fn allowance(&mut self, owner: Address, spender: Address) -> U256 { read_allowance(&allowance_key(&owner, &spender)) } fn transferFrom(&mut self, from: Address, to: Address, amount: U256) -> bool { let fromBalance = read_balance_of(&from); let recipientBalance = read_balance_of(&to); let a_key = allowance_key(&from, &owasm_ethereum::sender()); let allowed = read_allowance(&a_key); if allowed < amount || amount == 0.into() || fromBalance < amount || to == from { false } else { let new_allowed = allowed - amount; let new_from_balance = fromBalance - amount; let new_recipient_balance = recipientBalance + amount; owasm_ethereum::write(&a_key, &new_allowed.into()); owasm_ethereum::write(&balance_key(&from), &new_from_balance.into()); owasm_ethereum::write(&balance_key(&to), &new_recipient_balance.into()); true } } } |
6. ++C
زبان سی پلاس پلاس (C plus plus) یک زبان همه منظوره است که بیش از 4.4 میلیون برنامه نویس در سراسر جهان دارد. بالاترین قدرت این زبان، قابلیت پیاده سازی بهینه برنامه های کاربردی پرمصرف و اجرای روان و بدون مشکل آنها است. همانطور که بلاک چین EOS از طریق ماشین مجازی WebAssembly (WASM) خودش، قرارداد هوشمند را پشتیبانی می کند، هر زبان دیگری نیز که قابلیت کامپایل به وب اسمبلی را داشته باشد، می تواند به عنوان زبان برنامه نویسی قرارداد هوشمند بلاک چین EOS استفاده شود. برای کسانی که می خواهند روی بلاک چین EOS قرارداد هوشمند بنویسند، پیشنهاد می شود که از زبان C++ برای برنامه نویسی قرارداد هوشمند استفاده کنند.
اکثر دانشجویان و برنامه نویسان با این زبان آشنا هستند و زبان C++ از منابع آموزشی، نمونه کدها و کتابخانه های غنی برخوردار است. ولی این زبان نیز معایب خودش را دارد. مشکل اصلی C++ این است که نوشتن کدهای memory-safe در C++ نسبت به Rust سخت تر است. همچنین اشاره گرها در این زبان بسیار پیچیده بوده و حافظه زیادی اشغال می کنند. ضمناً بلاک چین های محدود از این زبان پشتیبانی می کنند.
شبکه های بلاک چین که از زبان C++ پشتیبانی می کنند:
- سولانا (Solana)
- بلاک چین EOS
قرارداد هوشمند Log گیری به زبان C++ برای شبکه سولانا
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/** * @brief A program demonstrating logging */ #include <solana_sdk.h> extern uint64_t logging(SolParameters *params) { // Log a string sol_log("static string"); // Log 5 numbers as u64s in hexadecimal format sol_log_64(params->data[0], params->data[1], params->data[2], params->data[3], params->data[4]); // Log a slice sol_log_array(params->data, params->data_len); // Log a public key sol_log_pubkey(params->program_id); // Log all the program's input parameters sol_log_params(params); // Log the number of compute units remaining that the program can consume. sol_log_compute_units(); return SUCCESS; } extern uint64_t entrypoint(const uint8_t *input) { SolAccountInfo accounts[1]; SolParameters params = (SolParameters){.ka = accounts}; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(accounts))) { return ERROR_INVALID_ARGUMENT; } return logging(¶ms); } |
7. Golang
زبان Go یا Golang یک زبان برنامه نویسی متن باز است که در سال 2009 توسط گوگل توسعه داده شده است. زبان Go از نوع تایپ ایستا (statically typed) است و برنامه نویسی همروند را نیز پشتیبانی میکند. یعنی اجرای چند فرآیند به طور همزمان در این زبان امکانپذیر خواهد بود. سینتکس Golang بر پایه C طراحی شده است. استفاده از زبان Go برای دولوپر ها بسیار ساده است ولی تعداد برنامه نویسانی که می توانند با Golang کار کنند محدود بوده و در حدود 800 هزار نفر می باشد.
بخش بزرگی از قراردادهای هوشمند شبکه بلاک چین تجاری هایپرلجر فابریک (Hyperedger Fabric) به زبان گولنگ (Golang) نوشته شده است. زبان go مثل زبان c/c++ کارآیی بالا دارد و سینتکس آن نیز ساده است. کامپایل کدهای Go بسیار سریع و اجرای کدهای go نیز نسبت به کدهای بهینه شده پایتون، 40 برابر سریع تر است! زبان Go کتابخانه استاندارد غنی دارد و برنامه نویس مجبور نیست کتابخانه های ثالث پیچیده را یاد بگیرد. سینتکس زبان go نسبتا ساده است و یادگیری و خواندن کدهای go آسان است.
مشکل اصلی زبان Go این است که شی گرا نیست. همچنین این زبان فریم ورک خاصی ندارد (مثل rails برای زبان Ruby یا دیجنگو برای پایتون).
شبکه های بلاک چین که از زبان Go پشتیبانی می کنند:
- هایپرلجر فابریک (Hyperedger Fabric)
- بلاک چین NEO
قرارداد هوشمند توکن به زبان Go برای شبکه NEO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package tokencontract import ( "github.com/nspcc-dev/neo-go/examples/token/nep17" "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/util" ) const ( decimals = 8 multiplier = 100000000 ) var ( owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB") token nep17.Token ctx storage.Context ) // init initializes Token Interface and storage context for the Smart // Contract to operate with func init() { token = nep17.Token{ Name: "Awesome NEO Token", Symbol: "ANT", Decimals: decimals, Owner: owner, TotalSupply: 11000000 * multiplier, CirculationKey: "TokenCirculation", } ctx = storage.GetContext() } // Symbol returns the token symbol func Symbol() string { return token.Symbol } // Decimals returns the token decimals func Decimals() int { return token.Decimals } // TotalSupply returns the token total supply value func TotalSupply() int { return token.GetSupply(ctx) } // BalanceOf returns the amount of token on the specified address func BalanceOf(holder interop.Hash160) int { return token.BalanceOf(ctx, holder) } // Transfer token from one user to another func Transfer(from interop.Hash160, to interop.Hash160, amount int, data interface{}) bool { return token.Transfer(ctx, from, to, amount, data) } // Mint initial supply of tokens func Mint(to interop.Hash160) bool { return token.Mint(ctx, to) } |
8. JavaScript
جاوا اسکریپ یک زبان همه منظوره، شی گرا، داینامیک تایپ و سبک است که توسط Brendan Eich ساخته شده است. این زبان در کنار HTML و CSS اجزای سه گانه طراحی وب را تشکیل می دهند. از آنجا که جاوا اسکریپت برای نوآموزان برنامه نویسی، یک زبان آغازین (entry-level) محسوب می شود، اکثر بلاک چین ها از این زبان پشتیبانی می کنند و به همین خاطر، جاوا اسکریپت جایگاه بسیار خوبی در فضای بلاک چین پیدا کرده است.
پلتفرم NEO جزء شبکه های بلاک چینی است که تصمیم دارد به کاربران خود حق انتخاب دهد تا کاربر بتواند از بین زبان های برنامه نویسی مختلف، یک زبان را به دلخواه برای برنامه نویسی قرارداد هوشمند انتخاب کند. بلاک چین NEO از زبان جاوا اسکریپت برای برنامه نویسی قرارداد هوشمند پشتیبانی می کند. هایپرلجر فابریک نیز به برنامه نویسان جاوا اسکریپت اجازه برنامه نویسی قرارداد هوشمند به این زبان را داده است.
یکی از مشارکت های ویژه زبان جاوا اسکریپت در بلاک چین اتریوم، کتابخانه Web3.js است که امکان تعامل با قراردادهای هوشمند و خود شبکه اتریوم از طریق پروتکل های HTTP و WebSocket یا IPC را میسر می سازد.
نقطه ضعف جاوا اسکریپت این است که داینامیک تایپ است در حالیکه اغلب برنامه نویسان ترجیح می دهند از زبان های استاتیک تایپ برای برنامه نویسی و ساخت اپلیکیشن های بلاک چینی استفاده کنند.
شبکه های بلاک چین که از زبان جاوا اسکریپت پشتیبانی می کنند:
- هایپرلجر فابریک (Hyperedger Fabric)
- بلاک چین NEO
- شبکه سولانا (Solana)
قرارداد هوشمند توکن به زبان جاوا اسکریپت برای شبکه هایپرلجر فابریک
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
'use strict'; const { Contract } = require('fabric-contract-api'); const util = require('util'); /** * A program to support updating values in a smart contract */ class UpdateSmartContractValue extends Contract constructor(){ super('UpdateSmartContractValue'); } async transactionA(ctx, newValue) { let oldValue = await ctx.stub.getState(key); await ctx.stub.putState(key, Buffer.from(newValue)); return Buffer.from(newValue.toString()); } async transactionB(ctx) { // ..... } }; module.exports = UpdateSmartContractValue |
9. Java
زبان جاوا نیز یکی از زبان های برنامه نویسی محبوب است که در توسعه قرارداد هوشمند نیز استفاده می شود. زبان جاوا یک زبان شی گرا و مبتنی بر کلاس است که توسط شرکت Sun Microsystem در سال 1995 ساخته شد. اغلب ویژگی ها و سینتکس جاوا مبتنی بر زبان ++C است.
بلاک چین NEO که به حمایت از زبان های برنامه نویسی متعدد در توسعه قرارداد هوشند معروف است، از زبان جاوا نیز برای برنامه نویسی قرارداد هوشمند پشتیبانی می کند. هایپرلجر فابریک نیز به برنامه نویسان جاوا اجازه برنامه نویسی قرارداد هوشمند به این زبان را داده است.
شبکه های بلاک چین که از زبان جاوا پشتیبانی می کنند:
- بلاک چین NEO
- هایپرلجر فابریک (Hyperedger Fabric)
قرارداد هوشمند به زبان جاوا برای شبکه هایپرلجر فابریک
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/* * SPDX-License-Identifier: Apache-2.0 */ package org.example.contract; import static java.nio.charset.StandardCharsets.UTF_8; import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.ContractInterface; import org.hyperledger.fabric.contract.annotation.Contract; import org.hyperledger.fabric.contract.annotation.Default; import org.hyperledger.fabric.contract.annotation.Transaction; @Contract @Default public class MyAssetContract implements ContractInterface { public MyAssetContract() { } @Transaction() public boolean myAssetExists(Context ctx, String myAssetId) { byte[] buffer = ctx.getStub().getState(myAssetId); return (buffer != null && buffer.length > 0); } @Transaction() public void createMyAsset(Context ctx, String myAssetId, String value) { boolean exists = myAssetExists(ctx, myAssetId); if (exists) { throw new RuntimeException("The asset " + myAssetId + " already exists"); } MyAsset asset = new MyAsset(); asset.setValue(value); ctx.getStub().putState(myAssetId, asset.toJSONString().getBytes(UTF_8)); } @Transaction() public MyAsset readMyAsset(Context ctx, String myAssetId) { boolean exists = myAssetExists(ctx, myAssetId); if (!exists) { throw new RuntimeException("The asset " + myAssetId + " does not exist"); } MyAsset newAsset = MyAsset.fromJSONString(new String(ctx.getStub().getState(myAssetId), UTF_8)); return newAsset; } @Transaction() public void updateMyAsset(Context ctx, String myAssetId, String newValue) { boolean exists = myAssetExists(ctx, myAssetId); if (!exists) { throw new RuntimeException("The asset " + myAssetId + " does not exist"); } MyAsset asset = new MyAsset(); asset.setValue(newValue); ctx.getStub().putState(myAssetId, asset.toJSONString().getBytes(UTF_8)); } @Transaction() public void deleteMyAsset(Context ctx, String myAssetId) { boolean exists = myAssetExists(ctx, myAssetId); if (!exists) { throw new RuntimeException("The asset " + myAssetId + " does not exist"); } ctx.getStub().delState(myAssetId); } } |
10. C#
زبان برنامه نویسی سی شارپ (C#) یک زبان شی گرا و همه منظوره است که حدود سال 2000 توسط مایکروسافت به عنوان بخشی از پروژه دات نت ارائه شد. سینتکس زبان سی شارپ خیلی ساده و یادگیری و خواندن و نوشتن کدهای آن راحت است. نوشتن گیم با یونیتی و همچنین برنامه نویسی اندروید با سی شارپ جزء کاربردهای اصلی زبان سی شارپ می باشد.
مشکل سی شارپ در بلاک چین این است که تعداد محدودی از بلاک چین ها از سی شارپ برای نوشتن قرارداد هوشمند پشتیبانی می کنند. کارآیی (Performance) سی شارپ نیز به خوبی C/C++ نیست و در مقایسه با C مصرف حافظه بیشتری دارد. مشکل دیگر C# این است که برای اجرای یک برنامه باید ورژن دات نت مربوط به برنامه، به طور صحیح روی سیستم نصب شود که این قضیه زمان لازم برای نصب برنامه را بالا می برد.
شبکه های بلاک چین که از زبان C# پشتیبانی می کنند:
- بلاک چین NEO
- البته شبکه اتریوم کتابخانه Nethereum را برای تعامل برنامه های سی شارپ و دات نت با شبکه بلاک چین اتریوم ارائه کرده است
قرارداد هوشمند به زبان C# برای شبکه NEO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
using Neo; using Neo.SmartContract; using Neo.SmartContract.Framework; using Neo.SmartContract.Framework.Native; using Neo.SmartContract.Framework.Services; using System; namespace Helloworld { [ManifestExtra("Author", "Neo")] [ManifestExtra("Email", "dev@neo.org")] [ManifestExtra("Description", "This is a contract example")] public class Contract1 : SmartContract { //TODO: Replace it with your own address. [InitialValue("NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB", ContractParameterType.Hash160)] static readonly UInt160 Owner = default; private static bool IsOwner() => Runtime.CheckWitness(Owner); // When this contract address is included in the transaction signature, // this method will be triggered as a VerificationTrigger to verify that the signature is correct. // For example, this method needs to be called when withdrawing token from the contract. public static bool Verify() => IsOwner(); // TODO: Replace it with your methods. public static string MyMethod() { return Storage.Get(Storage.CurrentContext, "Hello"); } public static void _deploy(object data, bool update) { if (update) return; // It will be executed during deploy Storage.Put(Storage.CurrentContext, "Hello", "World"); } public static void Update(ByteString nefFile, string manifest) { if (!IsOwner()) throw new Exception("No authorization."); ContractManagement.Update(nefFile, manifest, null); } public static void Destroy() { if (!IsOwner()) throw new Exception("No authorization."); ContractManagement.Destroy(); } } } |
نتیجه گیری
قراردادهای هوشمند بخش جدایی ناپذیر از بلاک چین بوده و اعتماد و عدم نیاز به شخص ثالث را در معاملات بین شرکای تجاری برقرار می کند.
مزایای استفاده از قرارداد هوشمند در معاملات و قراردادها:
- صرفه جویی در وقت
- کاهش هزینه های غیر ضروری
- افزایش شفافیت
وقتی از قرارداد هوشمند استفاده می شود، همه قوانین و بندهای قرارداد به صورت سورس کد، پیاده سازی شده و پس از قرار گرفتن قرارداد هوشمند روی بلاک چین و اجرایی شدن قرارداد، سلایق و تصمیمات انسانی هیچ دخالتی در معامله و قوانین معامله نخواهد داشت. امنیت اطلاعات در قرارداد هوشمند، تضمین شده است. چرا که تغییر یا از بین رفتن دیتای ذخیره شده در بلاک چین، غیر ممکن بوده و همچنین احتمال بروز حمله در شبکه بلاک چین نیز نزدیک به صفر است. بدون شک، قراردادهای هوشمند در مقایسه با سیستم سنتی، شیوه ای مطمئن برای پرداخت و معاملات تجاری هستند.
در این مقاله با زبان های مختلف قابل استفاده برای برنامه نویسی قراردادهای هوشمند آشنا شدید. برای هر کدام از این زبان ها یک مثال از قرارداد هوشمند روی بلاک چین مربوطه درج شد. اگر با زبان دیگری برای برنامه نویسی قرارداد هوشمند آشنا هستید حتما در نظرات درج کنید تا مورد بررسی قرار گیرد.
معرفی دوره آموزشی
با آموزش های تخصصی پیاده سازی قرارداد هوشمند امن و بهینه با ما همراه باشید. برای یادگیری پیاده سازی اصولی قرارداد هوشمند و ساخت توکن در بستر اتریوم می توانید در دوره آموزش برنامه نویسی بلاکچین و قرارداد هوشمند که در مرکز آموزش علوم نوین امیرکبیر برگزار می شود شرکت نمایید.
درباره مجید شبیری
کارشناس ارشد فناوری اطلاعات از دانشگاه صنعتی امیرکبیر. مدیر و مؤسس "علوم نوین امیرکبیر"، متخصص برنامه نویسی، شبکه، لینوکس و امنیت. از سال 84 همزمان با شروع تحصیلات دانشگاهی، وارد حوزه تخصصی مهندسی نرم افزار شدم و اکنون مشغول تحقیق، توسعه و آموزش در حوزه بلاک چین هستم و معتقدم بلاکچین به زودی فضای کسب و کارها را منقلب خواهد کرد.
نوشته های بیشتر از مجید شبیری
دیدگاهتان را بنویسید