برنامه نویسی

L1Opcode جدید را برای ذخیره‌سازی کلید امن و مقیاس‌پذیر SLOAD کنید

توابع انتزاع حساب متقابل زنجیره ای به لطف Keystores امکان پذیر خواهد بود. کاربران قادر خواهند بود چندین کیف پول قرارداد هوشمند را در چندین زنجیره و با یک کلید کنترل کنند. این می تواند تجربه کاربری خوب مورد انتظار را برای کاربران نهایی در مجموعه های اتریوم به ارمغان بیاورد.

برای اینکه این اتفاق بیفتد، باید بتوانیم داده‌های L1 را از مجموعه‌های L2 بخوانیم، که در حال حاضر فرآیند بسیار گرانی است. به همین دلیل اسکرول اخیراً پیش کامپایل را معرفی کرده است L1SLOAD که قادر است وضعیت L1 را سریع و ارزان بخواند. کیف پول ایمن یک نسخه ی نمایشی ارائه شده در Safecon Berlin 2024 ایجاد کرده است. فکر می کنم این فقط شروع است، این می تواند برنامه های زنجیره ای متقابل را در DeFi، بازی ها، شبکه های اجتماعی و بسیاری موارد دیگر بهبود بخشد.

بیایید اکنون با مثال‌های عملی، مفاهیم اساسی این بدوی جدید را بیاموزیم که راه جدیدی را برای تعامل با اتریوم باز می‌کند.

1. کیف پول Scroll Devnet خود را وصل کنید

درحال حاضر، L1SLOAD فقط در Scroll Devnet موجود است. توجه داشته باشید و آن را با Scroll Sepolia Testnet اشتباه نگیرید. اگرچه هر دو در Sepolia Testnet مستقر هستند، اما زنجیره های جداگانه ای هستند.

ما با اتصال کیف پول خود به Scroll Devnet شروع می کنیم:

  • نام: Scroll Devnet
  • RPC: https://l1sload-rpc.scroll.io
  • شناسه زنجیره: 222222
  • سمبل: Sepolia ETH
  • کاوشگر: https://l1sload-blockscout.scroll.io

به Scroll Devnet متصل شوید

2. در Scroll Devnet بودجه دریافت کنید

دو روش برای به دست آوردن وجوه در Scroll Devnet وجود دارد. یکی را که ترجیح می دهید انتخاب کنید.

ربات شیر ​​در تلگرام (توصیه می شود)

به این گروه تلگرامی بپیوندید و بنویسید /drop TUADDRESS (به عنوان مثال /drop 0xd8da6bf26964af9d7eed9e03e53415d37aa96045) برای دریافت وجوه به طور مستقیم به حساب خود.

پل د سپولیا

می توانید از طریق پل وجوه از Sepolia به Scroll Devnet ارسال کنید. دو راه برای رسیدن به این هدف وجود دارد اما در این مورد ما از Remix استفاده خواهیم کرد.

بیایید اکنون کیف پول شما را با Sepolia ETH به Sepolia Testnet متصل کنیم. به یاد داشته باشید که می توانید Sepolia ETH را به صورت رایگان در یک شیر آب دریافت کنید.

حال رابط زیر را کامپایل کنید.

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

interface ScrollMessanger {
    function sendMessage(address to, uint value, bytes memory message, uint gasLimit) external payable;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، در تب “Deploy & Run”، قرارداد زیر را وصل کنید: 0x561894a813b7632B9880BB127199750e93aD2cd5.

رابط اسکرول مسنجر را روی ریمیکس وصل کنید

اکنون می توانید ETH را با فراخوانی تابع ارسال کنید sendMessage به شرح زیر

  • به: آدرس حساب EOA شما. آدرسی که در L2 وجوه دریافت می کند.
  • مقدار: مقدار اتری که می خواهید در L2 در قالب wei دریافت کنید. مثلا اگر بفرستید 0.01 ETH شما باید به عنوان یک پارامتر ارسال کنید 10000000000000000
  • پیام: خالی بگذارید، فقط ارسال کنید 0x00
  • محدودیت گاز: 1000000 باید کافی باشه

همچنین به یاد داشته باشید که مقداری ارزش را به تراکنش خود منتقل کنید. و مقداری ETH اضافی برای پرداخت هزینه های L2 اضافه کنید، 0.001 باید بیش از حد کافی باشد بنابراین اگر برای مثال ارسال کردید 0.01 ETH روی پل، یک تراکنش با 0.011 ETH برای پوشش هزینه ها.

همچنین به یاد داشته باشید که یک هزینه اضافی خرج کنید value در معامله شما یعنی برای پرداخت هزینه های L2 کمی اتریوم اضافی اضافه کنید. 0.001 باید بیش از حد کافی باشد بنابراین، برای مثال، اگر 0.01 ETH روی پل ارسال کردید، یک تراکنش با آن ارسال کنید 0.011 ETH برای پوشش کمیسیون

ETH را از Sepolia به Scroll Devnet ارسال کنید

روی دکمه کلیک کنید transact و وجوه شما باید در حدود 15 دقیقه برسد.

2. قرارداد خود را در L2 راه اندازی کنید

همانطور که قبلا ذکر کردیم، L1SLOAD وضعیت قراردادها را در L1 از L2 می خواند. بیایید اکنون یک قرارداد ساده در L1 راه اندازی کنیم که سپس مقدار متغیر را می خوانیم number از L2.

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.20;

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract L1Storage {

    uint256 public number;

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public {
        number = num;
    }

    /**
     * @dev Return value
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حالا با store(uint256 num) تابع و ارسال یک مقدار جدید. مثلا بگذریم 42.

یک مقدار را در L1 ذخیره کنید

3. یک اسلات از L2 بدست آورید

قرارداد زیر را در L2 با عبور آدرس قراردادی که به تازگی در L1 راه اندازی کرده ایم به عنوان پارامتر در سازنده راه اندازی می کنیم.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;

interface IL1Blocks {
    function latestBlockNumber() external view returns (uint256);
}

contract L2Storage {
    address constant L1_BLOCKS_ADDRESS = 0x5300000000000000000000000000000000000001;
    address constant L1_SLOAD_ADDRESS = 0x0000000000000000000000000000000000000101;
    uint256 constant NUMBER_SLOT = 0;
    address immutable l1StorageAddr;

    uint public l1Number;

    constructor(address _l1Storage) {
        l1StorageAddr = _l1Storage;
    }

    function latestL1BlockNumber() public view returns (uint256) {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        return l1BlockNum;
    }

    function retrieveFromL1() public {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        bytes memory input = abi.encodePacked(l1BlockNum, l1StorageAddr, NUMBER_SLOT);
        bool success;
        bytes memory ret;
        (success, ret) = L1_SLOAD_ADDRESS.call(input);
        if (success) {
            (l1Number) = abi.decode(ret, (uint256));
        } else {
            revert("L1SLOAD failed");
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توجه داشته باشید که این قرارداد ابتدا تماس می گیرد latestL1BlockNumber() برای دریافت آخرین بلوک در L1 که برای خواندن در L2 در دسترس است. بعد زنگ میزنیم L1SLOAD (opcode 0x101) ارسال آدرس قرارداد در L1 به عنوان پارامتر و شکاف 9، جایی که متغیر number در داخل آن قرارداد قرار دارد.

حالا میتونیم زنگ بزنیم retrieveFromL1() برای بدست آوردن مقدار ذخیره شده قبلی

L2SLOAD L1 حالت قرمز از L2

مثال شماره 2: خواندن انواع دیگر متغیرها

Solidity اسلات های متغیر را به همان ترتیبی که اعلام شده اند ذخیره می کند. این برای ما کاملاً راحت است. به عنوان مثال، در قرارداد زیر، account در اسلات شماره 0 ذخیره می شود، number در شماره 1 و text در 2.

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

contract AdvancedL1Storage {
    address public account;
    uint public number;
    string public text;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما می توانیم ببینیم که چگونه می توانیم مقادیر انواع مختلف را بدست آوریم: uint256، آدرس و غیره… رشته ها به دلیل ماهیت متغیر اندازه آنها کمی متفاوت هستند.

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.20;

interface IL1Blocks {
    function latestBlockNumber() external view returns (uint256);
}

contract L2Storage {
    address constant L1_BLOCKS_ADDRESS = 0x5300000000000000000000000000000000000001;
    address constant L1_SLOAD_ADDRESS = 0x0000000000000000000000000000000000000101;
    address immutable l1ContractAddress;

    address public account;
    uint public number;
    string public test;

    constructor(address _l1ContractAddress) { //0x5555158Ea3aB5537Aa0012AdB93B055584355aF3
        l1ContractAddress = _l1ContractAddress;
    }

    // Internal functions

    function latestL1BlockNumber() internal view returns (uint256) {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        return l1BlockNum;
    }

    function retrieveSlotFromL1(uint blockNumber, address l1StorageAddress, uint slot) internal returns (bytes memory) {
        bool success;
        bytes memory returnValue;
        (success, returnValue) = L1_SLOAD_ADDRESS.call(abi.encodePacked(blockNumber, l1StorageAddress, slot));
        if(!success)
        {
            revert("L1SLOAD failed");
        }
        return returnValue;
    }

    function decodeStringSlot(bytes memory encodedString) internal pure returns (string memory) {
        uint length = 0;
        while (length < encodedString.length && encodedString[length] != 0x00) {
            length++;
        }
        bytes memory data = new bytes(length);
        for (uint i = 0; i < length; i++) {
            data[i] = encodedString[i];
        }
        return string(data);
    }

    // Public functions

    function retrieveAddress() public {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        account = abi.decode(retrieveSlotFromL1(l1BlockNum, l1ContractAddress, 0), (address));
    }

    function retrieveNumber() public {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        number = abi.decode(retrieveSlotFromL1(l1BlockNum, l1ContractAddress, 1), (uint));
    }

    function retrieveString() public {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        test = decodeStringSlot(retrieveSlotFromL1(l1BlockNum, l1ContractAddress, 2));
    }

    function retrieveAll() public {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        account = abi.decode(retrieveSlotFromL1(l1BlockNum, l1ContractAddress, 0), (address));
        number = abi.decode(retrieveSlotFromL1(l1BlockNum, l1ContractAddress, 1), (uint));
        test = decodeStringSlot(retrieveSlotFromL1(l1BlockNum, l1ContractAddress, 2));
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مثال شماره 3: موجودی توکن ERC20 را در L1 بخوانید

ما با راه اندازی یک توکن نسبتا ساده ERC20 شروع کردیم.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract SimpleToken is ERC20 {
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply
    ) ERC20(name, symbol) {
        _mint(msg.sender, initialSupply * 1 ether);
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، قرارداد زیر را در L2 راه‌اندازی می‌کنیم و به عنوان پارامتر آدرس توکنی را که به تازگی در L1 راه‌اندازی کرده‌ایم، ارسال می‌کنیم.

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.20;

interface IL1Blocks {
    function latestBlockNumber() external view returns (uint256);
}

contract L2Storage {
    address constant L1_BLOCKS_ADDRESS = 0x5300000000000000000000000000000000000001;
    address constant L1_SLOAD_ADDRESS = 0x0000000000000000000000000000000000000101;
    address immutable l1ContractAddress;

    uint public l1Balance;

    constructor(address _l1ContractAddress) {
        l1ContractAddress = _l1ContractAddress;
    }

    // Internal functions
    function latestL1BlockNumber() public view returns (uint256) {
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        return l1BlockNum;
    }

    function retrieveSlotFromL1(uint blockNumber, address l1StorageAddress, uint slot) internal returns (bytes memory) {
        bool success;
        bytes memory returnValue;
        (success, returnValue) = L1_SLOAD_ADDRESS.call(abi.encodePacked(blockNumber, l1StorageAddress, slot));
        if(!success)
        {
            revert("L1SLOAD failed");
        }
        return returnValue;
    }

    // Public functions
    function retrieveL1Balance(address account) public {
        uint slotNumber = 0;
        uint256 l1BlockNum = IL1Blocks(L1_BLOCKS_ADDRESS).latestBlockNumber();
        l1Balance = abi.decode(retrieveSlotFromL1(
            l1BlockNum,
            l1ContractAddress,
            uint(keccak256(
                abi.encodePacked(uint160(account),slotNumber)
                )
            )
            ), (uint));
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قراردادهای OpenZeppelin به راحتی نقشه تعادل توکن را در شکاف 0 قرار می دهند. بنابراین می توانید تماس بگیرید retrieveL1Balance() ارسال آدرس دارنده به عنوان پارامتر و موجودی توکن در متغیر ذخیره می شود l1Balance. همانطور که در کد می بینید، فرآیند به این صورت است که ابتدا آدرس را به uint160 تبدیل می کنیم و سپس آن را با شکاف نگاشت که 0 است، هش می کنیم. این به این دلیل است که Solidity نگاشت ها را به این صورت پیاده سازی می کند.

با تشکر از خواندن این راهنما!

من را در dev.to و YouTube برای همه چیزهایی که به توسعه بلاک چین به زبان اسپانیایی مرتبط است دنبال کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا