---
title: "আপনি যা কিছু ক্যাশ করতে পারেন"
description: "সস্তা রোলআপ ট্রানজ্যাকশনের জন্য কীভাবে একটি ক্যাশিং কন্ট্রাক্ট তৈরি এবং ব্যবহার করতে হয় তা শিখুন"
author: "ওরি পোমেরান্টজ"
tags: ["লেয়ার 2", "ক্যাশিং", "স্টোরেজ", "স্কেলিং"]
skill: intermediate
breadcrumb: "রোলআপের জন্য ক্যাশিং"
published: 2022-09-15
lang: bn
---

রোলআপ ব্যবহার করার সময় ট্রানজ্যাকশনে একটি বাইটের খরচ একটি স্টোরেজ স্লটের খরচের চেয়ে অনেক বেশি ব্যয়বহুল। তাই, অনচেইনে যতটা সম্ভব তথ্য ক্যাশ করা যৌক্তিক।

এই নিবন্ধে আপনি শিখবেন কীভাবে এমনভাবে একটি ক্যাশিং কন্ট্রাক্ট তৈরি এবং ব্যবহার করতে হয় যাতে একাধিকবার ব্যবহার হওয়ার সম্ভাবনা রয়েছে এমন যেকোনো প্যারামিটার মান ক্যাশ করা হবে এবং (প্রথমবারের পরে) অনেক কম সংখ্যক বাইট সহ ব্যবহারের জন্য উপলব্ধ থাকবে, এবং কীভাবে অফচেইন কোড লিখতে হয় যা এই ক্যাশ ব্যবহার করে।

আপনি যদি নিবন্ধটি এড়িয়ে যেতে চান এবং শুধুমাত্র সোর্স কোড দেখতে চান, তবে [এটি এখানে রয়েছে](https://github.com/qbzzt/20220915-all-you-can-cache)। ডেভেলপমেন্ট স্ট্যাক হলো [Foundry](https://getfoundry.sh/introduction/installation/)।

## সামগ্রিক ডিজাইন {#overall-design}

সরলতার স্বার্থে আমরা ধরে নেব সমস্ত ট্রানজ্যাকশন প্যারামিটার হলো `uint256`, যা 32 বাইট দীর্ঘ। যখন আমরা একটি ট্রানজ্যাকশন পাই, তখন আমরা প্রতিটি প্যারামিটারকে এভাবে পার্স করব:

1. যদি প্রথম বাইট `0xFF` হয়, তবে পরবর্তী 32 বাইটকে একটি প্যারামিটার মান হিসেবে নিন এবং এটি ক্যাশে লিখুন।

2. যদি প্রথম বাইট `0xFE` হয়, তবে পরবর্তী 32 বাইটকে একটি প্যারামিটার মান হিসেবে নিন কিন্তু এটি ক্যাশে লিখবেন _না_।

3. অন্য যেকোনো মানের জন্য, শীর্ষ চারটি বিটকে অতিরিক্ত বাইটের সংখ্যা হিসেবে নিন এবং নিচের চারটি বিটকে ক্যাশ কীর সবচেয়ে গুরুত্বপূর্ণ বিট (most significant bits) হিসেবে নিন। এখানে কিছু উদাহরণ দেওয়া হলো:

   | কল ডেটায় বাইট | ক্যাশ কী |
   | :---------------- | --------: |
   | 0x0F              |      0x0F |
   | 0x10,0x10         |      0x10 |
   | 0x12,0xAC         |    0x02AC |
   | 0x2D,0xEA, 0xD6   |  0x0DEAD6 |

## ক্যাশ ম্যানিপুলেশন {#cache-manipulation}

ক্যাশটি [`ক্যাশ.sol`](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/src/Cache.sol)-এ প্রয়োগ করা হয়েছে। চলুন এটি লাইন ধরে ধরে দেখি।

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;


contract Cache {

    bytes1 public constant INTO_CACHE = 0xFF;
    bytes1 public constant DONT_CACHE = 0xFE;
```

এই ধ্রুবকগুলো (constants) সেই বিশেষ ক্ষেত্রগুলো ব্যাখ্যা করতে ব্যবহৃত হয় যেখানে আমরা সমস্ত তথ্য প্রদান করি এবং এটি ক্যাশে লিখতে চাই বা চাই না। ক্যাশে লেখার জন্য পূর্বে অব্যবহৃত স্টোরেজ স্লটগুলোতে দুটি [`SSTORE`](https://www.evm.codes/#55) অপারেশনের প্রয়োজন হয়, যার প্রতিটির জন্য 22100 গ্যাস খরচ হয়, তাই আমরা এটিকে ঐচ্ছিক করে তুলি।

```solidity

    mapping(uint => uint) public val2key;
```

মান এবং তাদের কীগুলোর মধ্যে একটি [ম্যাপিং](https://www.geeksforgeeks.org/solidity/solidity-mappings/)। ট্রানজ্যাকশন পাঠানোর আগে মানগুলোকে এনকোড করার জন্য এই তথ্যটি প্রয়োজনীয়।

```solidity
    // লোকেশন n-এ কী n+1 এর মান রয়েছে, কারণ আমাদের সংরক্ষণ করতে হবে
    // শূন্যকে "ক্যাশে নেই" হিসেবে।
    uint[] public key2val;
```

আমরা কী থেকে মানগুলোতে ম্যাপিংয়ের জন্য একটি অ্যারে ব্যবহার করতে পারি কারণ আমরা কীগুলো নির্ধারণ করি এবং সরলতার জন্য আমরা এটি ক্রমানুসারে করি।

```solidity
    function cacheRead(uint _key) public view returns (uint) {
        require(_key <= key2val.length, "Reading uninitialize cache entry");
        return key2val[_key-1];
    }  // cacheRead
```

ক্যাশ থেকে একটি মান পড়ুন।

```solidity
    // ক্যাশে কোনো মান না থাকলে তা লিখুন
    // টেস্টটি কাজ করার জন্য শুধুমাত্র পাবলিক করা হয়েছে
    function cacheWrite(uint _value) public returns (uint) {
        // মানটি যদি আগে থেকেই ক্যাশে থাকে, তবে বর্তমান কী রিটার্ন করুন
        if (val2key[_value] != 0) {
            return val2key[_value];
        }
```

একই মান একাধিকবার ক্যাশে রাখার কোনো মানে নেই। যদি মানটি আগে থেকেই সেখানে থাকে, তবে কেবল বিদ্যমান কীটি রিটার্ন করুন।

```solidity
        // যেহেতু 0xFE একটি বিশেষ ক্ষেত্র, তাই ক্যাশ ধারণ করতে পারে এমন সবচেয়ে বড় কী
        // হলো 0x0D এবং এরপরে ১৫টি 0xFF। ক্যাশের দৈর্ঘ্য যদি আগে থেকেই এত
        // বড় হয়, তবে ফেইল করবে।
        //                              1 2 3 4 5 6 7 8 9 A B C D E F
        require(key2val.length+1 < 0x0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
            "cache overflow");
```

আমার মনে হয় না আমরা কখনো এত বড় ক্যাশ পাব (প্রায় 1.8\*10<sup>37</sup> এন্ট্রি, যা সংরক্ষণ করতে প্রায় 10<sup>27</sup> TB প্রয়োজন হবে)। তবে, আমার বয়স যথেষ্ট হয়েছে এটা মনে রাখার জন্য যে ["640kB সর্বদা যথেষ্ট হবে"](https://quoteinvestigator.com/2011/09/08/640k-enough/)। এই পরীক্ষাটি খুব সস্তা।

```solidity
        // পরবর্তী কী ব্যবহার করে মানটি লিখুন
        val2key[_value] = key2val.length+1;
```

রিভার্স লুকআপ যোগ করুন (মান থেকে কী পর্যন্ত)।

```solidity
        key2val.push(_value);
```

ফরোয়ার্ড লুকআপ যোগ করুন (কী থেকে মান পর্যন্ত)। যেহেতু আমরা ক্রমানুসারে মান নির্ধারণ করি, তাই আমরা এটিকে শেষ অ্যারে মানের পরে যোগ করতে পারি।

```solidity
        return key2val.length;
    }  // cacheWrite
```

`key2val`-এর নতুন দৈর্ঘ্য রিটার্ন করুন, যা হলো সেই সেল যেখানে নতুন মানটি সংরক্ষিত আছে।

```solidity
    function _calldataVal(uint startByte, uint length)
        private pure returns (uint)
```

এই ফাংশনটি কল ডেটা থেকে ইচ্ছামতো দৈর্ঘ্যের (32 বাইট পর্যন্ত, ওয়ার্ড সাইজ) একটি মান পড়ে।

```solidity
    {
        uint _retVal;

        require(length < 0x21,
            "_calldataVal length limit is 32 bytes");
        require(length + startByte <= msg.data.length,
            "_calldataVal trying to read beyond calldatasize");
```

এই ফাংশনটি ইন্টারনাল, তাই যদি বাকি কোডটি সঠিকভাবে লেখা হয় তবে এই পরীক্ষাগুলোর প্রয়োজন নেই। তবে, এগুলোর জন্য খুব বেশি খরচ হয় না তাই আমরা এগুলো রাখতে পারি।

```solidity
        assembly {
            _retVal := calldataload(startByte)
        }
```

এই কোডটি [Yul](https://docs.soliditylang.org/en/v0.8.16/yul.html)-এ রয়েছে। এটি কল ডেটা থেকে একটি 32 বাইট মান পড়ে। এটি কাজ করে এমনকি যদি কল ডেটা `startByte+32`-এর আগে থেমে যায় কারণ EVM-এ ইনিশিয়ালাইজ না করা স্পেসকে শূন্য হিসেবে বিবেচনা করা হয়।

```solidity
        _retVal = _retVal >> (256-length*8);
```

আমরা অগত্যা একটি 32 বাইট মান চাই না। এটি অতিরিক্ত বাইটগুলো থেকে মুক্তি দেয়।

```solidity
        return _retVal;
    } // _calldataVal


    // _fromByte থেকে শুরু করে কল ডেটা থেকে একটি একক প্যারামিটার পড়ুন
    function _readParam(uint _fromByte) internal
        returns (uint _nextByte, uint _parameterValue)
    {
```

কল ডেটা থেকে একটি একক প্যারামিটার পড়ুন। মনে রাখবেন যে আমাদের কেবল পড়া মানটিই রিটার্ন করতে হবে না, বরং পরবর্তী বাইটের অবস্থানও রিটার্ন করতে হবে কারণ প্যারামিটারগুলো 1 বাইট থেকে 33 বাইট পর্যন্ত দীর্ঘ হতে পারে।

```solidity
        // প্রথম বাইট আমাদের বলে দেয় কীভাবে বাকি অংশ ব্যাখ্যা করতে হবে
        uint8 _firstByte;

        _firstByte = uint8(_calldataVal(_fromByte, 1));
```

Solidity সম্ভাব্য বিপজ্জনক [ইমপ্লিসিট টাইপ কনভার্সন](https://docs.soliditylang.org/en/v0.8.16/types.html#implicit-conversions) নিষিদ্ধ করে বাগের সংখ্যা কমানোর চেষ্টা করে। একটি ডাউনগ্রেড, উদাহরণস্বরূপ 256 বিট থেকে 8 বিটে, এক্সপ্লিসিট হতে হবে।

```solidity

        // মানটি পড়ুন, কিন্তু এটি ক্যাশে লিখবেন না
        if (_firstByte == uint8(DONT_CACHE))
            return(_fromByte+33, _calldataVal(_fromByte+1, 32));

        // মানটি পড়ুন এবং এটি ক্যাশে লিখুন
        if (_firstByte == uint8(INTO_CACHE)) {
            uint _param = _calldataVal(_fromByte+1, 32);
            cacheWrite(_param);
            return(_fromByte+33, _param);
        }

        // আমরা যদি এখানে পৌঁছাই তার মানে হলো আমাদের ক্যাশ থেকে পড়তে হবে

        // পড়ার জন্য অতিরিক্ত বাইটের সংখ্যা
        uint8 _extraBytes = _firstByte / 16;
```

নিচের [নিবল (nibble)](https://en.wikipedia.org/wiki/Nibble) নিন এবং ক্যাশ থেকে মানটি পড়ার জন্য এটিকে অন্যান্য বাইটের সাথে একত্রিত করুন।

```solidity
        uint _key = (uint256(_firstByte & 0x0F) << (8*_extraBytes)) +
            _calldataVal(_fromByte+1, _extraBytes);

        return (_fromByte+_extraBytes+1, cacheRead(_key));

    }  // _readParam


    // n সংখ্যক প্যারামিটার পড়ুন (ফাংশনগুলো জানে তারা কতগুলো প্যারামিটার আশা করে)
    function _readParams(uint _paramNum) internal returns (uint[] memory) {
```

আমরা কল ডেটা থেকেই আমাদের কাছে থাকা প্যারামিটারের সংখ্যা পেতে পারি, কিন্তু যে ফাংশনগুলো আমাদের কল করে তারা জানে যে তারা কতগুলো প্যারামিটার আশা করে। তাদের আমাদের বলতে দেওয়াটা সহজ।

```solidity
        // আমরা যে প্যারামিটারগুলো পড়ি
        uint[] memory params = new uint[](_paramNum);

        // প্যারামিটারগুলো বাইট 4 থেকে শুরু হয়, এর আগে এটি ফাংশন সিগনেচার
        uint _atByte = 4;

        for(uint i=0; i<_paramNum; i++) {
            (_atByte, params[i]) = _readParam(_atByte);
        }
```

আপনার প্রয়োজনীয় সংখ্যা না পাওয়া পর্যন্ত প্যারামিটারগুলো পড়ুন। যদি আমরা কল ডেটার শেষ ছাড়িয়ে যাই, তবে `_readParams` কলটি রিভার্ট করবে।

```solidity

        return(params);
    }   // readParams

    // _readParams টেস্ট করার জন্য, চারটি প্যারামিটার পড়া টেস্ট করুন
    function fourParam() public
        returns (uint256,uint256,uint256,uint256)
    {
        uint[] memory params;
        params = _readParams(4);
        return (params[0], params[1], params[2], params[3]);
    }    // fourParam
```

Foundry-এর একটি বড় সুবিধা হলো এটি Solidity-তে পরীক্ষা লেখার অনুমতি দেয় ([নিচে ক্যাশ পরীক্ষা করা দেখুন](#testing-the-cache))। এটি ইউনিট পরীক্ষাগুলোকে অনেক সহজ করে তোলে। এটি এমন একটি ফাংশন যা চারটি প্যারামিটার পড়ে এবং সেগুলো রিটার্ন করে যাতে পরীক্ষাটি যাচাই করতে পারে যে সেগুলো সঠিক ছিল।

```solidity
    // একটি মান নিন, বাইটগুলো রিটার্ন করুন যা এটিকে এনকোড করবে (সম্ভব হলে ক্যাশ ব্যবহার করে)
    function encodeVal(uint _val) public view returns(bytes memory) {
```

`encodeVal` হলো এমন একটি ফাংশন যা অফচেইন কোড কল করে কল ডেটা তৈরি করতে সাহায্য করে যা ক্যাশ ব্যবহার করে। এটি একটি একক মান গ্রহণ করে এবং এটিকে এনকোড করা বাইটগুলো রিটার্ন করে। এই ফাংশনটি একটি `view`, তাই এর জন্য কোনো ট্রানজ্যাকশনের প্রয়োজন হয় না এবং বাহ্যিকভাবে কল করা হলে কোনো গ্যাস খরচ হয় না।

```solidity
        uint _key = val2key[_val];

        // মানটি এখনও ক্যাশে নেই, এটি যোগ করুন
        if (_key == 0)
            return bytes.concat(INTO_CACHE, bytes32(_val));
```

[EVM](/developers/docs/evm/)-এ সমস্ত ইনিশিয়ালাইজ না করা স্টোরেজকে শূন্য বলে ধরে নেওয়া হয়। তাই যদি আমরা এমন কোনো মানের জন্য কী খুঁজি যা সেখানে নেই, তবে আমরা একটি শূন্য পাই। সেক্ষেত্রে এটিকে এনকোড করা বাইটগুলো হলো `INTO_CACHE` (যাতে এটি পরের বার ক্যাশ করা হয়), যার পরে প্রকৃত মানটি থাকে।

```solidity
        // কী যদি <0x10 হয়, তবে এটিকে একটি একক বাইট হিসেবে রিটার্ন করুন
        if (_key < 0x10)
            return bytes.concat(bytes1(uint8(_key)));
```

একক বাইটগুলো সবচেয়ে সহজ। আমরা কেবল [`bytes.concat`](https://docs.soliditylang.org/en/v0.8.16/types.html#the-functions-bytes-concat-and-string-concat) ব্যবহার করে একটি `bytes<n>` টাইপকে একটি বাইট অ্যারেতে পরিণত করি যা যেকোনো দৈর্ঘ্যের হতে পারে। নাম সত্ত্বেও, এটি শুধুমাত্র একটি আর্গুমেন্ট প্রদান করা হলে ভালোভাবে কাজ করে।

```solidity
        // দুই বাইটের মান, 0x1vvv হিসেবে এনকোড করা হয়েছে
        if (_key < 0x1000)
            return bytes.concat(bytes2(uint16(_key) | 0x1000));
```

যখন আমাদের কাছে এমন একটি কী থাকে যা 16<sup>3</sup>-এর চেয়ে কম, তখন আমরা এটিকে দুটি বাইটে প্রকাশ করতে পারি। আমরা প্রথমে `_key`-কে, যা একটি 256 বিট মান, একটি 16 বিট মানে রূপান্তর করি এবং প্রথম বাইটে অতিরিক্ত বাইটের সংখ্যা যোগ করতে লজিক্যাল অর (logical or) ব্যবহার করি। তারপর আমরা এটিকে একটি `bytes2` মানে পরিণত করি, যা `bytes`-এ রূপান্তরিত হতে পারে।

```solidity
        // নিচের লাইনগুলোকে লুপ হিসেবে করার সম্ভবত কোনো চতুর উপায় আছে,
        // কিন্তু এটি একটি ভিউ ফাংশন তাই আমি প্রোগ্রামারের সময় এবং
        // সরলতার জন্য অপ্টিমাইজ করছি।

        if (_key < 16*256**2)
            return bytes.concat(bytes3(uint24(_key) | (0x2 * 16 * 256**2)));
        if (_key < 16*256**3)
            return bytes.concat(bytes4(uint32(_key) | (0x3 * 16 * 256**3)));
             .
             .
             .
        if (_key < 16*256**14)
            return bytes.concat(bytes15(uint120(_key) | (0xE * 16 * 256**14)));
        if (_key < 16*256**15)
            return bytes.concat(bytes16(uint128(_key) | (0xF * 16 * 256**15)));
```

অন্যান্য মানগুলো (3 বাইট, 4 বাইট ইত্যাদি) একইভাবে পরিচালনা করা হয়, শুধুমাত্র ভিন্ন ফিল্ড সাইজ সহ।

```solidity
        // আমরা যদি এখানে পৌঁছাই, তবে কিছু একটা ভুল হয়েছে।
        revert("Error in encodeVal, should not happen");
```

যদি আমরা এখানে পৌঁছাই তবে এর অর্থ হলো আমরা এমন একটি কী পেয়েছি যা 16\*256<sup>15</sup>-এর চেয়ে কম নয়। কিন্তু `cacheWrite` কীগুলোকে সীমাবদ্ধ করে তাই আমরা 14\*256<sup>16</sup> পর্যন্তও পৌঁছাতে পারি না (যার প্রথম বাইট 0xFE হবে, তাই এটি `DONT_CACHE`-এর মতো দেখাবে)। তবে ভবিষ্যতের কোনো প্রোগ্রামার যদি কোনো বাগ নিয়ে আসে, সেই ক্ষেত্রে একটি পরীক্ষা যোগ করতে আমাদের খুব বেশি খরচ হয় না।

```solidity
    } // encodeVal

}  // Cache
```

### ক্যাশ পরীক্ষা করা {#testing-the-cache}

Foundry-এর অন্যতম সুবিধা হলো [এটি আপনাকে Solidity-তে পরীক্ষা লেখার অনুমতি দেয়](https://getfoundry.sh/forge/tests/overview/), যা ইউনিট পরীক্ষা লেখা সহজ করে তোলে। `Cache` ক্লাসের পরীক্ষাগুলো [এখানে](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/test/Cache.t.sol) রয়েছে। যেহেতু টেস্টিং কোড পুনরাবৃত্তিমূলক হয়, যেমনটা পরীক্ষাগুলোর ক্ষেত্রে হয়ে থাকে, তাই এই নিবন্ধটি কেবল আকর্ষণীয় অংশগুলো ব্যাখ্যা করে।

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";


// কনসোলের জন্য `forge test -vv` রান করতে হবে।
import "forge-std/console.sol";
```

এটি কেবল বয়লারপ্লেট যা টেস্ট প্যাকেজ এবং `console.log` ব্যবহার করার জন্য প্রয়োজনীয়।

```solidity
import "src/Cache.sol";
```

আমরা যে কন্ট্রাক্টটি পরীক্ষা করছি তা আমাদের জানতে হবে।

```solidity
contract CacheTest is Test {
    Cache cache;

    function setUp() public {
        cache = new Cache();
    }
```

প্রতিটি পরীক্ষার আগে `setUp` ফাংশনটি কল করা হয়। এই ক্ষেত্রে আমরা কেবল একটি নতুন ক্যাশ তৈরি করি, যাতে আমাদের পরীক্ষাগুলো একে অপরকে প্রভাবিত না করে।

```solidity
    function testCaching() public {
```

পরীক্ষাগুলো হলো এমন ফাংশন যাদের নাম `test` দিয়ে শুরু হয়। এই ফাংশনটি মৌলিক ক্যাশ কার্যকারিতা পরীক্ষা করে, মানগুলো লেখে এবং সেগুলো আবার পড়ে।

```solidity
        for(uint i=1; i<5000; i++) {
            cache.cacheWrite(i*i);
        }

        for(uint i=1; i<5000; i++) {
            assertEq(cache.cacheRead(i), i*i);
```

এভাবেই আপনি [`assert...` ফাংশনগুলো](https://getfoundry.sh/reference/forge-std/std-assertions/) ব্যবহার করে প্রকৃত পরীক্ষাটি করেন। এই ক্ষেত্রে, আমরা পরীক্ষা করি যে আমরা যে মানটি লিখেছি সেটিই আমরা পড়েছি কিনা। আমরা `cache.cacheWrite`-এর ফলাফল বাতিল করতে পারি কারণ আমরা জানি যে ক্যাশ কীগুলো লিনিয়ারভাবে নির্ধারিত হয়।

```solidity
        }
    }    // testCaching


    // একই মান একাধিকবার ক্যাশ করুন, নিশ্চিত করুন যে কী একই
    // থাকে
    function testRepeatCaching() public {
        for(uint i=1; i<100; i++) {
            uint _key1 = cache.cacheWrite(i);
            uint _key2 = cache.cacheWrite(i);
            assertEq(_key1, _key2);
        }
```

প্রথমে আমরা প্রতিটি মান দুবার ক্যাশে লিখি এবং নিশ্চিত করি যে কীগুলো একই (যার অর্থ দ্বিতীয় লেখাটি আসলে ঘটেনি)।

```solidity
        for(uint i=1; i<100; i+=3) {
            uint _key = cache.cacheWrite(i);
            assertEq(_key, i);
        }
    }    // testRepeatCaching
```

তাত্ত্বিকভাবে এমন একটি বাগ থাকতে পারে যা পরপর ক্যাশ লেখাকে প্রভাবিত করে না। তাই এখানে আমরা কিছু লেখা করি যা পরপর নয় এবং দেখি যে মানগুলো এখনো পুনরায় লেখা হয়নি।

```solidity
    // একটি মেমরি বাফার থেকে একটি uint পড়ুন (আমরা যে প্যারামিটারগুলো
    // পাঠিয়েছি তা ফেরত পাচ্ছি কিনা তা নিশ্চিত করতে)
    function toUint256(bytes memory _bytes, uint256 _start) internal pure
        returns (uint256)
```

একটি `bytes memory` বাফার থেকে একটি 256 বিট ওয়ার্ড পড়ুন। এই ইউটিলিটি ফাংশনটি আমাদের যাচাই করতে দেয় যে আমরা যখন ক্যাশ ব্যবহার করে এমন একটি ফাংশন কল চালাই তখন আমরা সঠিক ফলাফল পাই কিনা।

```solidity
    {
        require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
        uint256 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x20), _start))
        }
```

Yul `uint256`-এর বাইরের ডেটা স্ট্রাকচার সমর্থন করে না, তাই যখন আপনি মেমরি বাফার `_bytes`-এর মতো আরও পরিশীলিত ডেটা স্ট্রাকচার উল্লেখ করেন, তখন আপনি সেই স্ট্রাকচারের ঠিকানা পান। Solidity `bytes memory` মানগুলোকে একটি 32 বাইট ওয়ার্ড হিসেবে সংরক্ষণ করে যাতে দৈর্ঘ্য থাকে, যার পরে প্রকৃত বাইটগুলো থাকে, তাই বাইট নম্বর `_start` পেতে আমাদের `_bytes+32+_start` গণনা করতে হবে।

```solidity

        return tempUint;
    }     // toUint256

    // fourParams()-এর জন্য ফাংশন সিগনেচার, সৌজন্যে
    // https://www.4byte.directory/signatures/?bytes4_signature=0x3edc1e6d
    bytes4 constant FOUR_PARAMS = 0x3edc1e6d;

    // আমরা সঠিক মানগুলো ফেরত পাচ্ছি কিনা তা দেখার জন্য কিছু কনস্ট্যান্ট মান
    uint256 constant VAL_A = 0xDEAD60A7;
    uint256 constant VAL_B =     0xBEEF;
    uint256 constant VAL_C =     0x600D;
    uint256 constant VAL_D = 0x600D60A7;
```

পরীক্ষার জন্য আমাদের কিছু ধ্রুবক প্রয়োজন।

```solidity
    function testReadParam() public {
```

আমরা প্যারামিটারগুলো সঠিকভাবে পড়তে পারি কিনা তা পরীক্ষা করতে `fourParams()` কল করুন, যা এমন একটি ফাংশন যা `readParams` ব্যবহার করে।

```solidity
        address _cacheAddr = address(cache);
        bool _success;
        bytes memory _callInput;
        bytes memory _callOutput;
```

ক্যাশ ব্যবহার করে কোনো ফাংশন কল করার জন্য আমরা সাধারণ ABI মেকানিজম ব্যবহার করতে পারি না, তাই আমাদের লো লেভেল [`<address>.call()`](https://docs.soliditylang.org/en/v0.8.16/types.html#members-of-addresses) মেকানিজম ব্যবহার করতে হবে। সেই মেকানিজমটি ইনপুট হিসেবে একটি `bytes memory` নেয় এবং আউটপুট হিসেবে সেটি (পাশাপাশি একটি বুলিয়ান মান) রিটার্ন করে।

```solidity
        // প্রথম কল, ক্যাশ খালি
        _callInput = bytes.concat(
            FOUR_PARAMS,
```

একই কন্ট্রাক্টের জন্য ক্যাশ করা ফাংশন (সরাসরি ট্রানজ্যাকশন থেকে কলের জন্য) এবং ক্যাশ না করা ফাংশন (অন্যান্য স্মার্ট কন্ট্রাক্ট থেকে কলের জন্য) উভয়ই সমর্থন করা দরকারী। এটি করার জন্য আমাদের সবকিছু [একটি `fallback` ফাংশনে](https://docs.soliditylang.org/en/v0.8.16/contracts.html#fallback-function) রাখার পরিবর্তে সঠিক ফাংশনটি কল করতে Solidity মেকানিজমের ওপর নির্ভর করা চালিয়ে যেতে হবে। এটি করা সংযোজনযোগ্যতা অনেক সহজ করে তোলে। বেশিরভাগ ক্ষেত্রে ফাংশনটি শনাক্ত করার জন্য একটি একক বাইটই যথেষ্ট হবে, তাই আমরা তিনটি বাইট (16\*3=48 গ্যাস) নষ্ট করছি। তবে, আমি যখন এটি লিখছি তখন সেই 48 গ্যাসের দাম 0.07 সেন্ট, যা সহজ, কম বাগ প্রবণ কোডের একটি যুক্তিসঙ্গত খরচ।

```solidity
            // প্রথম মান, এটি ক্যাশে যোগ করুন
            cache.INTO_CACHE(),
            bytes32(VAL_A),
```

প্রথম মান: একটি ফ্ল্যাগ যা বলে যে এটি একটি সম্পূর্ণ মান যা ক্যাশে লেখা দরকার, যার পরে মানটির 32 বাইট থাকে। অন্য তিনটি মান একই রকম, তবে `VAL_B` ক্যাশে লেখা হয় না এবং `VAL_C` হলো তৃতীয় এবং চতুর্থ উভয় প্যারামিটার।

```solidity
             .
             .
             .
        );
        (_success, _callOutput) = _cacheAddr.call(_callInput);
```

এখানেই আমরা আসলে `Cache` কন্ট্রাক্টটি কল করি।

```solidity
        assertEq(_success, true);
```

আমরা আশা করি কলটি সফল হবে।

```solidity
        assertEq(cache.cacheRead(1), VAL_A);
        assertEq(cache.cacheRead(2), VAL_C);
```

আমরা একটি খালি ক্যাশ দিয়ে শুরু করি এবং তারপর `VAL_A` যোগ করি যার পরে `VAL_C` থাকে। আমরা আশা করব প্রথমটির কী 1 হবে এবং দ্বিতীয়টির 2 হবে।

```
assertEq(toUint256(_callOutput,0), VAL_A);
        assertEq(toUint256(_callOutput,32), VAL_B);
        assertEq(toUint256(_callOutput,64), VAL_C);
        assertEq(toUint256(_callOutput,96), VAL_C);
```

আউটপুট হলো চারটি প্যারামিটার। এখানে আমরা যাচাই করি যে এটি সঠিক।

```solidity
        // দ্বিতীয় কল, আমরা ক্যাশ ব্যবহার করতে পারি
        _callInput = bytes.concat(
            FOUR_PARAMS,

            // ক্যাশে প্রথম মান
            bytes1(0x01),
```

16-এর নিচের ক্যাশ কীগুলো মাত্র এক বাইটের হয়।

```solidity
            // দ্বিতীয় মান, এটি ক্যাশে যোগ করবেন না
            cache.DONT_CACHE(),
            bytes32(VAL_B),

            // তৃতীয় এবং চতুর্থ মান, একই মান
            bytes1(0x02),
            bytes1(0x02)
        );
        .
        .
        .
    }   // testReadParam
```

কলের পরের পরীক্ষাগুলো প্রথম কলের পরের পরীক্ষাগুলোর মতোই।

```solidity
    function testEncodeVal() public {
```

এই ফাংশনটি `testReadParam`-এর মতো, তবে প্যারামিটারগুলো এক্সপ্লিসিটভাবে লেখার পরিবর্তে আমরা `encodeVal()` ব্যবহার করি।

```solidity
        .
        .
        .
        _callInput = bytes.concat(
            FOUR_PARAMS,
            cache.encodeVal(VAL_A),
            cache.encodeVal(VAL_B),
            cache.encodeVal(VAL_C),
            cache.encodeVal(VAL_D)
        );
        .
        .
        .
        assertEq(_callInput.length, 4+1*4);
    }   // testEncodeVal
```

`testEncodeVal()`-এ একমাত্র অতিরিক্ত পরীক্ষা হলো `_callInput`-এর দৈর্ঘ্য সঠিক কিনা তা যাচাই করা। প্রথম কলের জন্য এটি 4+33\*4। দ্বিতীয়টির জন্য, যেখানে প্রতিটি মান আগে থেকেই ক্যাশে রয়েছে, এটি 4+1\*4।

```solidity
    // কী যখন একটি একক বাইটের চেয়ে বেশি হয় তখন encodeVal টেস্ট করুন
    // সর্বোচ্চ তিন বাইট কারণ ক্যাশ চার বাইট পর্যন্ত পূরণ করতে অনেক সময়
    // লাগে।
    function testEncodeValBig() public {
        // ক্যাশে বেশ কয়েকটি মান রাখুন।
        // বিষয়গুলো সহজ রাখতে, মান n-এর জন্য কী n ব্যবহার করুন।
        for(uint i=1; i<0x1FFF; i++) {
            cache.cacheWrite(i);
        }
```

উপরের `testEncodeVal` ফাংশনটি ক্যাশে কেবল চারটি মান লেখে, তাই [ফাংশনের যে অংশটি মাল্টি-বাইট মানগুলো নিয়ে কাজ করে](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/src/Cache.sol#L144-L171) তা পরীক্ষা করা হয় না। কিন্তু সেই কোডটি জটিল এবং ত্রুটি-প্রবণ।

এই ফাংশনের প্রথম অংশটি হলো একটি লুপ যা 1 থেকে 0x1FFF পর্যন্ত সমস্ত মান ক্রমানুসারে ক্যাশে লেখে, যাতে আমরা সেই মানগুলোকে এনকোড করতে পারি এবং জানতে পারি সেগুলো কোথায় যাচ্ছে।

```solidity
        .
        .
        .

        _callInput = bytes.concat(
            FOUR_PARAMS,
            cache.encodeVal(0x000F),   // এক বাইট        0x0F
            cache.encodeVal(0x0010),   // দুই বাইট     0x1010
            cache.encodeVal(0x0100),   // দুই বাইট     0x1100
            cache.encodeVal(0x1000)    // তিন বাইট 0x201000
        );
```

এক বাইট, দুই বাইট এবং তিন বাইট মান পরীক্ষা করুন। আমরা এর বাইরে পরীক্ষা করি না কারণ পর্যাপ্ত স্ট্যাক এন্ট্রি লিখতে খুব বেশি সময় লাগবে (অন্তত 0x10000000, প্রায় এক বিলিয়নের এক-চতুর্থাংশ)।

```solidity
        .
        .
        .
        .
    }    // testEncodeValBig


    // অত্যধিক ছোট বাফারের সাথে আমরা একটি রিভার্ট পাই কিনা তা টেস্ট করুন
    function testShortCalldata() public {
```

অস্বাভাবিক ক্ষেত্রে কী ঘটে তা পরীক্ষা করুন যেখানে পর্যাপ্ত প্যারামিটার নেই।

```solidity
        .
        .
        .
        (_success, _callOutput) = _cacheAddr.call(_callInput);
        assertEq(_success, false);
    }   // testShortCalldata
```

যেহেতু এটি রিভার্ট করে, তাই আমাদের যে ফলাফল পাওয়া উচিত তা হলো `false`।

```
// এমন ক্যাশ কীগুলো দিয়ে কল করুন যা সেখানে নেই
    function testNoCacheKey() public {
        .
        .
        .
        _callInput = bytes.concat(
            FOUR_PARAMS,

            // প্রথম মান, এটি ক্যাশে যোগ করুন
            cache.INTO_CACHE(),
            bytes32(VAL_A),

            // দ্বিতীয় মান
            bytes1(0x0F),
            bytes2(0x1234),
            bytes11(0xA10102030405060708090A)
        );
```

এই ফাংশনটি চারটি সম্পূর্ণ বৈধ প্যারামিটার পায়, তবে ক্যাশটি খালি থাকায় সেখানে পড়ার মতো কোনো মান নেই।

```solidity
        .
        .
        .
    // অত্যধিক দীর্ঘ বাফারের সাথে সবকিছু ঠিকঠাক কাজ করে কিনা তা টেস্ট করুন
    function testLongCalldata() public {
        address _cacheAddr = address(cache);
        bool _success;
        bytes memory _callInput;
        bytes memory _callOutput;

        // প্রথম কল, ক্যাশ খালি
        _callInput = bytes.concat(
            FOUR_PARAMS,

            // প্রথম মান, এটি ক্যাশে যোগ করুন
            cache.INTO_CACHE(), bytes32(VAL_A),

            // দ্বিতীয় মান, এটি ক্যাশে যোগ করুন
            cache.INTO_CACHE(), bytes32(VAL_B),

            // তৃতীয় মান, এটি ক্যাশে যোগ করুন
            cache.INTO_CACHE(), bytes32(VAL_C),

            // চতুর্থ মান, এটি ক্যাশে যোগ করুন
            cache.INTO_CACHE(), bytes32(VAL_D),

            // "গুড লাক"-এর জন্য আরও একটি মান
            bytes4(0x31112233)
        );
```

এই ফাংশনটি পাঁচটি মান পাঠায়। আমরা জানি যে পঞ্চম মানটি উপেক্ষা করা হয় কারণ এটি একটি বৈধ ক্যাশ এন্ট্রি নয়, যা অন্তর্ভুক্ত না হলে একটি রিভার্ট ঘটাত।

```solidity
        (_success, _callOutput) = _cacheAddr.call(_callInput);
        assertEq(_success, true);
        .
        .
        .
    }   // testLongCalldata

}        // CacheTest

```

## একটি নমুনা অ্যাপ্লিকেশন {#a-sample-app}

Solidity-তে পরীক্ষা লেখা খুবই ভালো, কিন্তু দিনের শেষে একটি বিকেন্দ্রীকৃত অ্যাপ্লিকেশন (dapp)-কে কার্যকর হতে হলে চেইনের বাইরে থেকে আসা রিকোয়েস্টগুলো প্রসেস করতে সক্ষম হতে হবে। এই নিবন্ধটি দেখায় কীভাবে `WORM` সহ একটি dapp-এ ক্যাশিং ব্যবহার করতে হয়, যার অর্থ হলো "Write Once, Read Many" (একবার লিখুন, অনেকবার পড়ুন)। যদি কোনো কী এখনো লেখা না হয়ে থাকে, তবে আপনি এতে একটি মান লিখতে পারেন। যদি কীটি আগে থেকেই লেখা থাকে, তবে আপনি একটি রিভার্ট পাবেন।

### কন্ট্রাক্ট {#the-contract}

[এটি হলো কন্ট্রাক্ট](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/src/WORM.sol)। এটি মূলত আমরা ইতিমধ্যে `Cache` এবং `CacheTest`-এর সাথে যা করেছি তারই পুনরাবৃত্তি করে, তাই আমরা কেবল আকর্ষণীয় অংশগুলো কভার করি।

```solidity
import "./Cache.sol";

contract WORM is Cache {
```

`Cache` ব্যবহার করার সবচেয়ে সহজ উপায় হলো এটিকে আমাদের নিজস্ব কন্ট্রাক্টে ইনহেরিট করা।

```solidity
    function writeEntryCached() external {
        uint[] memory params = _readParams(2);
        writeEntry(params[0], params[1]);
    }    // writeEntryCached
```

এই ফাংশনটি উপরের `CacheTest`-এর `fourParam`-এর মতো। যেহেতু আমরা ABI স্পেসিফিকেশনগুলো অনুসরণ করি না, তাই ফাংশনে কোনো প্যারামিটার ডিক্লেয়ার না করাই ভালো।

```solidity
    // আমাদের কল করা সহজ করুন
    // writeEntryCached()-এর জন্য ফাংশন সিগনেচার, সৌজন্যে
    // https://www.4byte.directory/signatures/?bytes4_signature=0xe4e4f2d3
    bytes4 constant public WRITE_ENTRY_CACHED = 0xe4e4f2d3;
```

যে এক্সটার্নাল কোডটি `writeEntryCached` কল করে তাকে `worm.writeEntryCached` ব্যবহার করার পরিবর্তে ম্যানুয়ালি কল ডেটা তৈরি করতে হবে, কারণ আমরা ABI স্পেসিফিকেশনগুলো অনুসরণ করি না। এই ধ্রুবক মানটি থাকা কেবল এটি লেখাকে সহজ করে তোলে।

মনে রাখবেন যে যদিও আমরা `WRITE_ENTRY_CACHED`-কে একটি স্টেট ভেরিয়েবল হিসেবে সংজ্ঞায়িত করি, এটিকে বাহ্যিকভাবে পড়ার জন্য এর গেটার ফাংশন, `worm.WRITE_ENTRY_CACHED()` ব্যবহার করা প্রয়োজন।

```solidity
    function readEntry(uint key) public view
        returns (uint _value, address _writtenBy, uint _writtenAtBlock)
```

রিড ফাংশনটি একটি `view`, তাই এর জন্য কোনো ট্রানজ্যাকশনের প্রয়োজন হয় না এবং কোনো গ্যাস খরচ হয় না। ফলস্বরূপ, প্যারামিটারের জন্য ক্যাশ ব্যবহার করার কোনো সুবিধা নেই। ভিউ ফাংশনগুলোর ক্ষেত্রে স্ট্যান্ডার্ড মেকানিজম ব্যবহার করা সবচেয়ে ভালো যা সহজতর।

### টেস্টিং কোড {#the-testing-code}

[এটি হলো কন্ট্রাক্টের জন্য টেস্টিং কোড](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/test/WORM.t.sol)। আবার, আসুন কেবল আকর্ষণীয় বিষয়গুলোর দিকে নজর দিই।

```solidity
    function testWReadWrite() public {
        worm.writeEntry(0xDEAD, 0x60A7);

        vm.expectRevert(bytes("entry already written"));
        worm.writeEntry(0xDEAD, 0xBEEF);
```

[এভাবেই (`vm.expectRevert`)](https://book.getfoundry.sh/cheatcodes/expect-revert#expectrevert) আমরা একটি Foundry পরীক্ষায় নির্দিষ্ট করি যে পরবর্তী কলটি ব্যর্থ হওয়া উচিত এবং ব্যর্থতার রিপোর্ট করা কারণটি কী। এটি তখন প্রযোজ্য হয় যখন আমরা কল ডেটা তৈরি করার এবং লো লেভেল ইন্টারফেস (`<contract>.call()` ইত্যাদি) ব্যবহার করে কন্ট্রাক্ট কল করার পরিবর্তে `<contract>.<function name>()` সিনট্যাক্স ব্যবহার করি।

```solidity
    function testReadWriteCached() public {
        uint cacheGoat = worm.cacheWrite(0x60A7);
```

এখানে আমরা এই বিষয়টি ব্যবহার করি যে `cacheWrite` ক্যাশ কী রিটার্ন করে। এটি এমন কিছু নয় যা আমরা প্রোডাকশনে ব্যবহার করার আশা করব, কারণ `cacheWrite` স্টেট পরিবর্তন করে এবং তাই কেবল একটি ট্রানজ্যাকশনের সময় কল করা যেতে পারে। ট্রানজ্যাকশনগুলোর কোনো রিটার্ন মান থাকে না, যদি তাদের ফলাফল থাকে তবে সেই ফলাফলগুলো ইভেন্ট হিসেবে নির্গত হওয়ার কথা। তাই `cacheWrite` রিটার্ন মানটি কেবল অনচেইন কোড থেকে অ্যাক্সেসযোগ্য এবং অনচেইন কোডের প্যারামিটার ক্যাশিংয়ের প্রয়োজন নেই।

```solidity
        (_success,) = address(worm).call(_callInput);
```

এভাবেই আমরা Solidity-কে বলি যে যদিও `<contract address>.call()`-এর দুটি রিটার্ন মান রয়েছে, আমরা কেবল প্রথমটি নিয়েই ভাবি।

```solidity
        (_success,) = address(worm).call(_callInput);
        assertEq(_success, false);
```

যেহেতু আমরা লো লেভেল `<address>.call()` ফাংশন ব্যবহার করি, তাই আমরা `vm.expectRevert()` ব্যবহার করতে পারি না এবং কল থেকে পাওয়া বুলিয়ান সাফল্যের মানটি দেখতে হবে।

```solidity
    event EntryWritten(uint indexed key, uint indexed value);

        .
        .
        .

        _callInput = bytes.concat(
            worm.WRITE_ENTRY_CACHED(), worm.encodeVal(a), worm.encodeVal(b));
        vm.expectEmit(true, true, false, false);
        emit EntryWritten(a, b);
        (_success,) = address(worm).call(_callInput);
```

এভাবেই আমরা যাচাই করি যে কোডটি Foundry-তে [সঠিকভাবে একটি ইভেন্ট নির্গত করে](https://getfoundry.sh/reference/cheatcodes/expect-emit/)।

### ক্লায়েন্ট {#the-client}

Solidity পরীক্ষার ক্ষেত্রে আপনি এমন কোনো JavaScript কোড পান না যা আপনি কাট করে আপনার নিজের অ্যাপ্লিকেশনে পেস্ট করতে পারেন। এই টিউটোরিয়ালের মূল সংস্করণটি WORM-কে অপটিমিজম Goerli-তে ডিপ্লয় করেছিল, যা ইতিমধ্যে বাতিল করা হয়েছে। বর্তমানে ক্লায়েন্টটি চালানোর জন্য, WORM-কে একটি সমর্থিত OP Stack নেটওয়ার্কে পুনরায় ডিপ্লয় করুন, যেমন [OP Sepolia](https://docs.optimism.io/op-stack/introduction/op-stack), তারপর JavaScript ক্লায়েন্টে প্রাপ্ত কন্ট্রাক্ট ঠিকানাটি ব্যবহার করুন।

[আপনি এখানে ক্লায়েন্টের জন্য JavaScript কোড দেখতে পারেন](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/javascript/index.js)। নমুনা রিপোজিটরিটি অপটিমিজম Goerli-এর জন্য লেখা হয়েছিল, তাই এটি চালানোর আগে, আপনার টার্গেট নেটওয়ার্কের জন্য `javascript/.env.example` এবং `javascript/index.js`-এ RPC এন্ডপয়েন্ট এবং এক্সপ্লোরার URL-গুলো আপডেট করুন। এটি ব্যবহার করতে:

1. গিট রিপোজিটরিটি ক্লোন করুন:

   ```sh
   git clone https://github.com/qbzzt/20220915-all-you-can-cache.git
   ```

2. প্রয়োজনীয় প্যাকেজগুলো ইনস্টল করুন:

   ```sh
   cd javascript
   yarn
   ```

3. কনফিগারেশন ফাইলটি কপি করুন:

   ```sh
   cp .env.example .env
   ```

4. আপনার কনফিগারেশনের জন্য `.env` সম্পাদনা করুন:

   | প্যারামিটার | মান |
   | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   | MNEMONIC | একটি অ্যাকাউন্টের জন্য নেমোনিক (mnemonic) যার কাছে একটি ট্রানজ্যাকশনের জন্য অর্থ প্রদান করার মতো পর্যাপ্ত ETH রয়েছে। [অপটিমিজমের ফসেট ডক্স](https://docs.optimism.io/app-developers/tools/faucets) বর্তমান টেস্টনেট ফসেটগুলোর তালিকা দেয়। |
   | OPTIMISM_GOERLI_URL | আপনি যেখানে WORM পুনরায় ডিপ্লয় করবেন সেই নেটওয়ার্কের জন্য RPC URL। OP Sepolia-এর জন্য, একটি OP Sepolia RPC এন্ডপয়েন্ট ব্যবহার করুন যেমন `https://sepolia.optimism.io`, অথবা আপনার প্রোভাইডারের অন্য কোনো এন্ডপয়েন্ট। |

5. `index.js` রান করুন।

   ```sh
   node index.js
   ```

   এই নমুনা অ্যাপ্লিকেশনটি প্রথমে WORM-এ একটি এন্ট্রি লেখে, কল ডেটা এবং একটি ব্লক এক্সপ্লোরারে ট্রানজ্যাকশনের একটি লিঙ্ক প্রদর্শন করে। তারপর এটি সেই এন্ট্রিটি আবার পড়ে, এবং এটি যে কী ব্যবহার করে এবং এন্ট্রির মানগুলো (মান, ব্লক নম্বর এবং লেখক) প্রদর্শন করে।

ক্লায়েন্টের বেশিরভাগ অংশই হলো সাধারণ বিকেন্দ্রীকৃত অ্যাপ্লিকেশন (dapp) JavaScript। তাই আমরা আবার কেবল আকর্ষণীয় অংশগুলো নিয়ে আলোচনা করব।

```javascript
.
.
.
const main = async () => {
    const func = await worm.WRITE_ENTRY_CACHED()

    // প্রতিবার একটি নতুন কী প্রয়োজন
    const key = await worm.encodeVal(Number(new Date()))
```

একটি নির্দিষ্ট স্লটে কেবল একবারই লেখা যেতে পারে, তাই আমরা স্লটগুলো পুনরায় ব্যবহার না করার বিষয়টি নিশ্চিত করতে টাইমস্ট্যাম্প ব্যবহার করি।

```javascript
const val = await worm.encodeVal("0x600D")

// একটি এন্ট্রি লিখুন
const calldata = func + key.slice(2) + val.slice(2)
```

Ethers আশা করে যে কল ডেটা একটি হেক্স স্ট্রিং হবে, `0x`-এর পরে জোড় সংখ্যক হেক্সাডেসিমাল সংখ্যা থাকবে। যেহেতু `key` এবং `val` উভয়ই `0x` দিয়ে শুরু হয়, তাই আমাদের সেই হেডারগুলো সরিয়ে ফেলতে হবে।

```javascript
const tx = await worm.populateTransaction.writeEntryCached()
tx.data = calldata

sentTx = await wallet.sendTransaction(tx)
```

Solidity টেস্টিং কোডের মতো, আমরা একটি ক্যাশ করা ফাংশনকে স্বাভাবিকভাবে কল করতে পারি না। এর পরিবর্তে, আমাদের একটি লো লেভেল মেকানিজম ব্যবহার করতে হবে।

```javascript
    .
    .
    .
    // এইমাত্র লেখা এন্ট্রিটি পড়ুন
    const realKey = '0x' + key.slice(4)  // FF ফ্ল্যাগটি সরান
    const entryRead = await worm.readEntry(realKey)
    .
    .
    .
```

এন্ট্রিগুলো পড়ার জন্য আমরা সাধারণ মেকানিজম ব্যবহার করতে পারি। `view` ফাংশনগুলোর সাথে প্যারামিটার ক্যাশিং ব্যবহার করার কোনো প্রয়োজন নেই।
## উপসংহার {#conclusion}

এই নিবন্ধের কোডটি হলো একটি প্রুফ অফ কনসেপ্ট, এর উদ্দেশ্য হলো ধারণাটি সহজে বোঝানো। একটি প্রোডাকশন-রেডি সিস্টেমের জন্য আপনি কিছু অতিরিক্ত কার্যকারিতা প্রয়োগ করতে চাইতে পারেন:

- এমন মানগুলো পরিচালনা করুন যা `uint256` নয়। উদাহরণস্বরূপ, স্ট্রিং।
- একটি গ্লোবাল ক্যাশের পরিবর্তে, ব্যবহারকারী এবং ক্যাশগুলোর মধ্যে একটি ম্যাপিং থাকতে পারে। বিভিন্ন ব্যবহারকারী বিভিন্ন মান ব্যবহার করে।
- ঠিকানাগুলোর জন্য ব্যবহৃত মানগুলো অন্যান্য উদ্দেশ্যে ব্যবহৃত মানগুলো থেকে আলাদা। শুধুমাত্র ঠিকানাগুলোর জন্য একটি পৃথক ক্যাশ রাখা যৌক্তিক হতে পারে।
- বর্তমানে, ক্যাশ কীগুলো "আগে আসলে, সবচেয়ে ছোট কী" অ্যালগরিদমে রয়েছে। প্রথম ষোলোটি মান একটি একক বাইট হিসেবে পাঠানো যেতে পারে। পরবর্তী 4080টি মান দুটি বাইট হিসেবে পাঠানো যেতে পারে। পরবর্তী প্রায় এক মিলিয়ন মান তিন বাইট ইত্যাদি। একটি প্রোডাকশন সিস্টেমের উচিত ক্যাশ এন্ট্রিগুলোতে ইউসেজ কাউন্টার রাখা এবং সেগুলোকে পুনর্গঠিত করা যাতে ষোলোটি _সবচেয়ে সাধারণ_ মান এক বাইট হয়, পরবর্তী 4080টি সবচেয়ে সাধারণ মান দুই বাইট হয় ইত্যাদি।

  তবে, এটি একটি সম্ভাব্য বিপজ্জনক অপারেশন। ঘটনাগুলোর নিম্নলিখিত ক্রমটি কল্পনা করুন:

  1. নোয়াম নাইভ (Noam Naive) যে ঠিকানায় টোকেন পাঠাতে চায় তা এনকোড করতে `encodeVal` কল করে। সেই ঠিকানাটি অ্যাপ্লিকেশনে ব্যবহৃত প্রথম ঠিকানাগুলোর মধ্যে একটি, তাই এনকোড করা মান হলো 0x06। এটি একটি `view` ফাংশন, কোনো ট্রানজ্যাকশন নয়, তাই এটি নোয়াম এবং সে যে নোডটি ব্যবহার করে তার মধ্যে সীমাবদ্ধ এবং অন্য কেউ এটি সম্পর্কে জানে না

  2. ওয়েন ওনার (Owen Owner) ক্যাশ রিঅর্ডারিং অপারেশন চালায়। খুব কম লোকই আসলে সেই ঠিকানাটি ব্যবহার করে, তাই এটি এখন 0x201122 হিসেবে এনকোড করা হয়েছে। একটি ভিন্ন মান, 10<sup>18</sup>-কে 0x06 নির্ধারণ করা হয়েছে।

  3. নোয়াম নাইভ তার টোকেনগুলো 0x06-এ পাঠায়। সেগুলো `0x0000000000000000000000000de0b6b3a7640000` ঠিকানায় যায় এবং যেহেতু কেউ সেই ঠিকানার প্রাইভেট কী জানে না, তাই সেগুলো সেখানেই আটকে থাকে। নোয়াম _খুশি নয়_।

  এই সমস্যাটি এবং ক্যাশ রিঅর্ডারের সময় মেমপুলে থাকা ট্রানজ্যাকশনগুলোর সম্পর্কিত সমস্যা সমাধানের উপায় রয়েছে, তবে আপনাকে অবশ্যই এটি সম্পর্কে সচেতন হতে হবে।

আমি এখানে অপটিমিজমের সাথে ক্যাশিং প্রদর্শন করেছি, কারণ আমি একজন অপটিমিজম কর্মী এবং এই রোলআপটি আমি সবচেয়ে ভালো জানি। তবে এটি এমন যেকোনো রোলআপের সাথে কাজ করা উচিত যা অভ্যন্তরীণ প্রক্রিয়াকরণের জন্য ন্যূনতম খরচ নেয়, যাতে এর তুলনায় লেয়ার 1 (l1)-এ ট্রানজ্যাকশন ডেটা লেখা প্রধান ব্যয় হয়।

[আমার আরও কাজের জন্য এখানে দেখুন](https://cryptodocguy.pro/)।
