استاندارد پراکسی مینیمال این امکان را می دهد که هنگام ساخت کلون، مقادیر دلخواه به آن بدهیم. البته برای این کار نیاز به یک تراکنش اضافه برای مقداردهی اولیه داریم. استاندارد MetaProxy این مرحله را حذف می کند. با این روش، می توان مقادیر مورد نظر را مستقیماً در بایت کد پراکسی قرار داد. در نتیجه دیگر نیازی به ذخیره سازی در حالت قرارداد نیست.
MetaProxy هم مانند پراکسی مینیمال از بایت کدی کوچک و بهینه استفاده می کند، اما یک تفاوت اساسی دارد. در این ساختار، توسعه دهنده می تواند برای هر کلون، یک متادیتای خاص و تغییرناپذیر اختصاص دهد. این متادیتا ممکن است عدد، رشته یا هر نوع داده دیگری باشد و هیچ محدودیتی از نظر طول ندارد.
توسعه دهنده از این متادیتا برای ارسال آرگومان به توابع قرارداد پیاده سازی شده استفاده می کند تا رفتار هر کلون را به صورت مستقل تنظیم کند.
از آنجا که بایت کد MetaProxy ساختار ثابتی دارد، ابزارهایی مثل Etherscan می توانند آن را به راحتی شناسایی کنند. این ابزارها همچنین بررسی می کنند که پراکسی به کدام قرارداد پیاده سازی متصل شده و چه متادیتایی به آن اضافه شده است.
بایت کد MetaProxy بدون متادیتا:
1 |
600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73bebebebebebebebebebebebebebebebebebebebe5af43d3d93803e603457fd5bf3 |
این بایت کد دقیقاً ۶۵ بایت است. بخش اول آن ۱۱ بایت دارد که مخصوص راه اندازی اولیه است. بخش دوم نیز ۵۴ بایت دارد که کد اجرایی را شامل می شود.
اگرچه ساختار این بایت کد شبیه به پراکسی مینیمال است، اما تفاوتهایی هم دارد. مثلاً بخشی از آن شامل دستورهای اضافه است. در تحلیل های فنی، این بخش ها را معمولاً با رنگ سبز مشخص می کنند. بعداً درباره این تفاوت ها بیشتر توضیح می دهیم.
آدرس زیر به صورت پیشفرض در بایت کد وجود دارد:
1 |
0xbebebebebebebebebebebebebebebebebebebebe |
ایجاد یک قرارداد ERC20 با استفاده از استاندارد MetaProxy
در این بخش، قصد داریم یک کلون از قرارداد ERC20 با استفاده از استاندارد MetaProxy ایجاد کنیم. بیایید ببینیم این کار چگونه انجام می شود و متوجه شویم که متادیتا چطور به این کلون اضافه می شود.
برای پیاده سازی قرارداد ERC20، از نسخه قابل ارتقای OpenZeppelin یعنی ERC20Upgradeable
استفاده می کنیم. این نسخه به جای استفاده از سازنده (constructor)، تابعی به نام ERC20_init
دارد که متغیرهای حالت (مثل نام و نماد توکن) را مقداردهی اولیه می کند. دلیل این کار آن است که در الگوهای پراکسی، از جمله MetaProxy، امکان استفاده از سازنده وجود ندارد.
علت این محدودیت آن است که سازنده تنها در زمان استقرار قرارداد اصلی اجرا می شود. اگر از سازنده استفاده کنیم، متغیرهای حالت مانند name
و symbol
فقط در قرارداد پیاده سازی مقداردهی می شوند و نه در کلون. در نتیجه، کلون ERC20 که از طریق MetaProxy ساخته می شود، این مقادیر را دریافت نمی کند؛ زیرا سازنده در واقع اطلاعات را در فضای ذخیره سازی قرارداد اصلی قرار می دهد، نه در کلون.
با این حال، در این پروژه حتی از تابع مقداردهی اولیه نیز استفاده نمی کنیم. چون می توانیم name
، symbol
و totalSupply
را مستقیماً به صورت متادیتا به بایت کد کلون اضافه کنیم و سپس هنگام نیاز، آن ها را از همان بایت کد استخراج کنیم.
قرارداد پیاده سازی 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 |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; contract ERC20Implementation is ERC20Upgradeable { // get ERC20 name from the metadata function name() public view virtual override returns (string memory name__) { (name__, , ) = getMetadata(); } // get ERC20 symbol from the metadata function symbol() public view virtual override returns (string memory symbol__) { (, symbol__, ) = getMetadata(); } // get ERC20 total supply from the metadata function totalSupply() public view virtual override returns (uint256 totalSupply_) { (, , totalSupply_) = getMetadata(); } // mint function function mint(uint amount) public { _mint(msg.sender, amount * 10 ** 18); } /// returns the decoded metadata of this (ERC20 MetaProxy) contract. function getMetadata() public pure returns ( string memory name__, string memory symbol__, uint256 totalSupply__ ) { bytes memory data; assembly { let posOfMetadataSize := sub(calldatasize(), 32) let size := calldataload(posOfMetadataSize) let dataPtr := sub(posOfMetadataSize, size) data := mload(64) mstore(64, add(data, add(size, 32))) mstore(data, size) let memPtr := add(data, 32) calldatacopy(memPtr, dataPtr, size) } //return the decoded the metadata return abi.decode(data, (string, string, uint256)); } } |
دریافت متادیتا
در قرارداد پیاده سازی، از تابع getMetadata
برای بازگرداندن متادیتای کلون استفاده می کنیم. از آنجا که در استاندارد MetaProxy، هر بار که تابعی از کلون فراخوانی می شود، متادیتا به صورت خودکار بارگذاری می شود (این رفتار بخشی از طراحی این استاندارد است که در ادامه مقاله به آن خواهیم پرداخت)، تابع getMetadata
به ما کمک می کند تا متادیتا را از ورودی فراخوانی استخراج کنیم و به صورت یک تاپل (Tuple) برگردانیم.
این تابع در توابع name
، symbol
و totalSupply
قرارداد ERC20 نیز کاربرد دارد. به این صورت که هر کدام از این توابع، بخشی از متادیتا را از طریق getMetadata
دریافت می کنند؛ برای مثال، نام و نماد به صورت رشته متنی (string
) استخراج می شوند و مقدار عرضه کل (totalSupply
) به صورت عدد صحیح بدون علامت (uint256
).
ما این تابع را بر اساس پیاده سازی نمونه ای که در اینجا ارائه شده، توسعه داده و متناسب با نیازهای خود در قرارداد ERC20 تغییر داده ایم.
قرارداد Factory
در مستندات اصلی EIP، لینکی برای پیاده سازی قرارداد MetaProxyFactory نیز وجود دارد. ما این قرارداد را در پروژه خود import می کنیم و از آن ارث بری (inherit) می کنیم.
قرارداد MetaProxyFactory شامل منطق مربوط به ایجاد کلون های جدید از نوع MetaProxy است. این کد مشخص می کند که چگونه می توان به صورت برنامه نویسی، کلون های جدیدی ساخت که دارای متادیتای مخصوص به خود هستند.
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 |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./ERC20Implementation.sol"; import "./MetaProxyFactory.sol"; contract ERC20MetaProxyFactory is MetaProxyFactory { address[] public proxyAddresses; function createClone( string memory _name, string memory _symbol, uint256 _initialSupply ) public returns (address) { // Encode the ERC20 constructor arguments bytes memory metadata = abi.encode(_name, _symbol, _initialSupply); // Create the proxy address proxyAddress = _metaProxyFromBytes( address(new ERC20Implementation()), metadata ); proxyAddresses.push(proxyAddress); return proxyAddress; } } |
ایجاد کلون – توضیح قرارداد Factory
قرارداد ERC20MetaProxyFactory
همان کارخانه ساخت کلون در پروژه ما است. از طریق این قرارداد میتوان نمونههای جدیدی از کلون ها را ایجاد کرد. برای انجام این کار، از تابع _metaProxyFromBytes
استفاده می کنیم که از قرارداد پایه MetaProxyFactory
به ارث رسیده است.
تابع _metaProxyFromBytes
دو ورودی دریافت می کند:
-
آدرس قرارداد پیاده سازی (Implementation Contract): به همین دلیل، ابتدا یک قرارداد
ERC20Implementation
جدید ایجاد می کنیم (با استفاده از کلمه کلیدیnew
) و آدرس آن را به عنوان ورودی می فرستیم. -
متادیتا: اطلاعاتی که قصد داریم در بایت کد کلون قرار دهیم.
از آنجا که بایت کد قراردادهای هوشمند در قالب هگزادسیمال (hex) نوشته می شود، قبل از الحاق متادیتا به بایت کد، باید آن را با استفاده از ABI رمزگذاری (abi encode) کنیم. به همین دلیل، ابتدا آرگومان های تابع createClone
را رمزگذاری می کنیم و سپس نتیجه را به عنوان متادیتا به تابع _metaProxyFromBytes
می دهیم.
این تابع کلون جدید را ایجاد می کند و آدرس آن را به عنوان خروجی بازمی گرداند.
در ادامه، امضای (signature) تابع _metaProxyFromBytes
را مشاهده می کنید:
1 2 3 |
function _metaProxyFromBytes (address targetContract, bytes memory metadata) internal returns (address) { // code that deploys new clones here } |
استقرار کلون (Deploying the Clone)
در این بخش، یک اسکریپت Hardhat ارائه شده که وظیفه دارد قراردادها را مستقر کند و با یک کلون مستقرشده در شبکه Sepolia تعامل برقرار کند:
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 |
const hre = require("hardhat"); async function main() { const ERC20ProxyFactory = await hre.ethers.getContractFactory( "ERC20MetaProxyFactory" ); const erc20ProxyFactory = await ERC20ProxyFactory.deploy(); // deploy the erc20 proxy factory contract await erc20ProxyFactory.deployed(); console.log( `ERC20 proxy factory contract deployed to ${erc20ProxyFactory.address}` ); // create clone const tx1 = await erc20ProxyFactory.createClone( "Meta Token V1", "MTV1", "150000000000000000000000" //150,000 initial supply * 10^18 decimals ); await tx1.wait(); const proxyCloneAddress = await erc20ProxyFactory.proxyAddresses(0); console.log("Proxy clone deployed to", proxyCloneAddress); // load the clone const proxyClone = await hre.ethers.getContractAt( "ERC20Implementation", proxyCloneAddress ); // retrieve the metadata const metadata = await proxyClone.getMetadata(); console.log("metadata for clone: ", metadata); //retrieve the "name" string from the metadata const name = await proxyClone.name(); console.log("ERC20 name of clone from metadata: ", name); const tx2 = await proxyClone.mint(150_000); tx2.wait(); } main().catch((error) => { console.error(error); process.exitCode = 1; }); |
1 2 3 4 5 6 7 8 9 10 11 |
ERC20 proxy factory contract deployed to 0xd45f2c555ba30aCb89EB0a3fff6a4416f8cC06e2 Proxy clone deployed to 0x5170672424194899F52B29E60e85C1632F0C732e metadata for clone: [ 'MetaProxy Token', 'MPRXT', BigNumber { value: "150000000000000000000000" }, name__: 'MetaProxy Token', symbol__: 'MPRXT', totalSupply__: BigNumber { value: "150000000000000000000000" } ] ERC20 name of clone from metadata: MetaProxy Token |
همچنین ما قراردادهای خود را در شبکه Sepolia مستقر کردهایم و در ادامه، مشخصات سه قرارداد کلیدی را مشاهده میکنید:
نکته مهم اینجاست که Etherscan در صفحه مربوط به قرارداد ERC20 مبتنی بر MetaProxy، دکمه های “Read” و “Write as Proxy” را نمایش می دهد. با این کار، Etherscan نشان می دهد که این قرارداد را نه به عنوان یک قرارداد معمولی، بلکه به عنوان یک پراکسی واقعی شناسایی کرده است.
برای راحتی بیشتر در توسعه، کدی که نوشتهایم یک لیست از کلونهای مستقرشده را نگه میدارد تا در محیط Hardhat بتوانیم بهراحتی به آنها دسترسی داشته باشیم. البته این بخش کاملاً اختیاری است و ضرورتی برای پیاده سازی آن وجود ندارد.
مدیریت خطاهای Revert
همان طور که در مقدمه اشاره شد، اگر در زمان فراخوانی یک تابع توسط کلون پراکسی، خطایی در قرارداد پیاده سازی رخ دهد، پیام خطا (revert payload) به کلون بازمیگردد و به کاربر نمایش داده میشود.
بیایید این رفتار را در عمل بررسی کنیم و ببینیم آیا همانطور که انتظار داریم کار میکند یا نه.
در همان مثال قبلی قرارداد ERC20، تابع transferFrom
را بدون تنظیم allowance (مقدار مجاز انتقال) فراخوانی میکنیم تا ببینیم آیا تراکنش موفق میشود یا پیام خطا به ما بازگردانده میشود.
برای این آزمایش از اسکریپت Hardhat زیر استفاده میکنیم:
1 2 3 4 5 6 7 |
try { await proxyClone.transferFrom( proxyCloneAddress, erc20ProxyFactory.address,2000000000); } catch (error) { console.error(error); } |
1 2 |
Error: VM Exception while processing transaction: reverted with reason string 'ERC20: insufficient allowance' |
توضیح بایت کد کلون ERC20 مستقر شده
قبلاً اشاره کردیم که متادیتای کلون در انتهای بایت کد آن قرار میگیرد. در این بخش، قصد داریم ساختار دقیق بایت کد نهایی کلون MetaProxy را بررسی کنیم تا بفهمیم هر بخش از آن چه نقشی دارد.
در تمام کلونها، بایت کد اولیه با ساختار استاندارد MetaProxy آغاز میشود. تنها تفاوت این است که در انتهای هر کلون، متادیتای مخصوص آن اضافه شده است.
اکنون بیایید نگاهی به بایت کد کلون ERC20 بیندازیم:
1 |
0x363d3d373d3d3d3d60368038038091363936013d731bf70065f6b4e424b7b642b3a76a5e01f208e3fc5af43d3d93803e603457fd5bf3000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000a50726f7879546f6b656e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000650546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0 |
همانطور که مشاهده میکنید، بایتکد حاوی صفرهای زیادی است که معمولاً به دلیل ساختار رمزگذاری ABI برای دادههای با طول متغیر ایجاد میشوند.
بایت کد نهایی این کلون، در مجموع ۳۱۰ بایت طول دارد.
در ادامه، هر بخش از این بایت کد را جداگانه بررسی میکنیم و دقیقاً توضیح میدهیم که کدام قسمت به کدام متغیر مربوط میشود.
1 2 3 4 5 6 |
<=== the runtime bytecode of the MetaProxy standard ===> 0x363d3d373d3d3d3d60368038038091363936013d731bf70065f6b4e424b7b642b3a76a5e01f208e3fc5af43d3d93803e603457fd5bf3 <===> <=== the abi encoded metadata ===> 000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000a50726f7879546f6b656e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000650546f6b656e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0 <===> |
رمزگذاری متادیتا شامل آدرسهای حافظه (offsets) برای محل ذخیره مقادیر، طول رشتههای رمزگذاریشده، خود مقادیر، و اشارهگر به حافظه آزاد (free memory pointer) است. در این بخش، به صورت دقیق ساختار این متادیتا را بررسی می کنیم.
بر اساس استاندارد ABI، قرارداد ابتدا سه بخش ۳۲ بایتی را در متادیتا قرار می دهد. اگر مقدار مورد نظر نوع ثابتی داشته باشد، مستقیماً مقدار را در همان اسلات می نویسد. اما اگر نوع آن دینامیک باشد، فقط آدرس حافظه را در اسلات قرار می دهد و مقدار واقعی را در بخش های بعدی متادیتا ذخیره می کند.
در مثال ما، سه مقدار داریم: name
، symbol
و totalSupply
. name
و symbol
از نوع رشته هستند و نوع دینامیک دارند. بنابراین، قرارداد برای آن ها آدرس محل داده ها را در متادیتا قرار می دهد. اما برای totalSupply
که نوع عدد صحیح بدون علامت دارد، مقدار را مستقیماً در همان بخش متادیتا می نویسد. به این ترتیب، قرارداد می تواند هنگام اجرا، مقادیر دینامیک را از حافظه بخواند و مقادیر ثابت را مستقیماً استفاده کند.
1 2 3 4 5 6 7 8 |
// memory[0x00 - 0x20] 0000000000000000000000000000000000000000000000000000000000000060 // memory offset for name string // memory[0x20 - 0x40] 00000000000000000000000000000000000000000000000000000000000000a0 // memory offset for symbol string // memory[0x40 - 0x60] 000000000000000000000000000000000000000000000000000000174876e800 // the encoded total supply (uint256) // memory[0x60 - 0x80] 000000000000000000000000000000000000000000000000000000000000000a // the length of the name string (0x0a == 10) // memory[0x80 - 0xa0] 50726f7879546f6b656e00000000000000000000000000000000000000000000 // the encoded name string // memory[0xa0 - 0xc0] 0000000000000000000000000000000000000000000000000000000000000006 // the length of the symbol string (6) // memory[0xc0 - 0xe0] 50546f6b656e0000000000000000000000000000000000000000000000000000 // the encoded symbol string // memory[0xe0 ] 00000000000000000000000000000000000000000000000000000000000000e0 // the length of the metadata (0xe0 == 224) |
همانطور که پیشتر اشاره کردیم، کد اجرایی (runtime code) در استاندارد MetaProxy برابر با ۵۴ بایت است. اگر بایت کد کلون ERC20 را به دو بخش تقسیم کنیم و ۵۴ بایت اول را که مربوط به کد اجرایی است جدا کنیم، بخش باقیمانده شامل دو قسمت میشود:
-
متادیتای رمزگذاری شده با ABI به طول ۲۲۴ بایت
-
مقدار طول متادیتا که در انتهای بایت کد و در قالب یک کلمه ۳۲ بایتی (word) ذخیره شده است
مطابق با استاندارد:
«…تمام داده هایی که بعد از بایت کد MetaProxy میآیند، میتوانند شامل هر نوع متادیتایی باشند. اما آخرین ۳۲ بایت از بایت کد باید طول دقیق متادیتا را به بایت نشان دهند.»
در مثال ما، متادیتا دقیقاً ۲۲۴ بایت طول دارد و مقدار آن در ۳۲ بایت نهایی ذخیره شده است:
1 |
0x00000000000000000000000000000000000000000000000000000000000000e0 |
در واقع، این روش به توسعه دهنده اجازه میدهد با نوشتن چند خط کد ساده، متادیتا را از قرارداد استخراج کرده و آن را تحلیل کند؛ بدون نیاز به پیاده سازی ساختارهای پیچیده برای ردیابی داده ها.
1 |
let posOfMetadataSize := sub(calldatasize(), 32) |
بیایید گام به گام از طریق mnemonic های بایت کد پیش برویم
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 |
<=== start of the runtime bytecode ===> // Note that RETURNDATASIZE is used in some parts of the bytecode to push zero to the stack. // This is because RETURNDATASIZE (2 gas) costs less gas than a PUSH1 0 (3 gas). // copy transaction calldata [00] CALLDATASIZE [01] RETURNDATASIZE [02] RETURNDATASIZE [03] CALLDATACOPY // prepare the stack for a delegate call [04] RETURNDATASIZE [05] RETURNDATASIZE [06] RETURNDATASIZE [07] RETURNDATASIZE [08] PUSH1 36 // 0x36 == 54, this is the length of the runtime code [0a] DUP1 [0b] CODESIZE // get the length of the clone's bytecode + the metadata, which is 310 bytes [0c] SUB // subtract the runtime code from the bytecode, to get the metadata (the remaining 256 bytes). this is used in the delegatecall [0d] DUP1 [0e] SWAP2 [0f] CALLDATASIZE [10] CODECOPY // copy the metadata to memory and forward it to the implementation contract during the delegatecall. [11] CALLDATASIZE [12] ADD [13] RETURNDATASIZE // push the address of the implementation contract to the stack and perform the delegatecall [14] PUSH20 1bf70065f6b4e424b7b642b3a76a5e01f208e3fc [29] GAS [2a] DELEGATECALL // copy the return data (the result of the call) to memory and set up the stack for a conditional jump [2b] RETURNDATASIZE [2c] RETURNDATASIZE [2d] SWAP4 [2e] DUP1 [2f] RETURNDATACOPY [30] PUSH1 34 //jump to line 34 and return the result of the call if it was successful, else revert on line 33 [32] JUMPI [33] REVERT [34] JUMPDEST [35] RETURN <<=== the metadata starts from here ===>> |
این توضیح، یک مرور کلی از نحوه عملکرد بایت کد کلون است. به طور خلاصه، این بایتکد ابتدا دادههای ورودی تراکنش (calldata) را کپی میکند و سپس با استفاده از دستور delegatecall
، آنها را همراه با متادیتا به قرارداد پیادهسازی ارسال میکند.
نکته مهم این است که چون متادیتا در تمام فراخوانی ها به صورت خودکار ارسال میشود، حتی در توابعی که به آن نیاز ندارند نیز همراه درخواست خواهد بود. برای مثال، تابع balanceOf()
در استاندارد ERC20 هیچ ارتباطی با متادیتا ندارد، اما همچنان متادیتا در هنگام فراخوانی این تابع ارسال میشود. این موضوع بخشی از ساختار کلی استاندارد MetaProxy است که همیشه متادیتا را به delegatecall ضمیمه میکند.
جمع بندی
توسعه دهندگان، استاندارد MetaProxy در EIP-3448 را بهعنوان نسخه ای پیشرفته تر از استاندارد EIP-1167 (Minimal Proxy) معرفی کردهاند. این استاندارد امکان میدهد تا متادیتای تغییرناپذیر به بایت کد اجرایی هر کلون اضافه شود.
با استفاده از MetaProxy، توسعه دهنده می تواند مقدارهای دلخواه را مستقیماً در بایت کد کلون تعریف کند، به جای آن که آنها را در حافظه ذخیره کند. این کار باعث میشود نیاز به استفاده از storage کاهش یابد و در نتیجه هزینه گس نیز کمتر شود.
علاوه بر این، ابزارهای شخص ثالث مانند Etherscan با استفاده از بایتکد شناختهشده این استاندارد، بهطور دقیق تشخیص میدهند هر کلون به کدام آدرس از قرارداد پیادهسازی متصل میشود و متادیتای ضمیمهشده به آن را استخراج میکنند. استاندارد MetaProxy با حفظ سادگی پراکسی مینیمال، انعطاف پذیری بیشتری را فراهم می کند و در عین حال، سازگاری مناسبی با ابزارهای موجود دارد.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۱۹ تیر ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس