<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>DDWorld</title><generator>teletype.in</generator><description><![CDATA[I'll try to explain to you how it works, but I don't promise that I can.]]></description><image><url>https://img1.teletype.in/files/00/9c/009c37a4-de7f-4a2f-8962-e51b6af48b60.png</url><title>DDWorld</title><link>https://teletype.in/@ddworld</link></image><link>https://teletype.in/@ddworld?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/ddworld?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/ddworld?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Thu, 23 Apr 2026 00:10:26 GMT</pubDate><lastBuildDate>Thu, 23 Apr 2026 00:10:26 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@ddworld/8Umg8HnYzJX</guid><link>https://teletype.in/@ddworld/8Umg8HnYzJX?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/8Umg8HnYzJX?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>LayerZero V2 OFT</title><pubDate>Sun, 11 Feb 2024 12:37:24 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/98/ae/98ae9976-b601-4aad-a298-6b20fd515732.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img1.teletype.in/files/09/04/0904f34b-267d-4b51-bf41-a06472c97fff.png"></img>Всем привет! Относительно недавно выпустили LayerZero V2 и я решил разобрать новую версию OFT. В данной статье рассмотрим основные функции OFT V2, развернем в двух тестовых сетях свой токен и переведем токены из одной сети в другую.]]></description><content:encoded><![CDATA[
  <figure id="VS16" class="m_column">
    <img src="https://img1.teletype.in/files/09/04/0904f34b-267d-4b51-bf41-a06472c97fff.png" width="1063" />
  </figure>
  <p id="WlGc">Всем привет! Относительно недавно выпустили LayerZero V2 и я решил разобрать новую версию OFT. В данной статье рассмотрим основные функции OFT V2, развернем в двух тестовых сетях свой токен и переведем токены из одной сети в другую.</p>
  <p id="GFdj"></p>
  <h3 id="NbQe"><strong>Начало</strong></h3>
  <p id="s9ls">Для начала я создам проект hardhat </p>
  <pre id="LHrh">npx hardhat </pre>
  <p id="v3rY">Дальше установим пакеты layerzerolabs</p>
  <pre id="iCO3">npm install @layerzerolabs/lz-evm-oapp-v2</pre>
  <blockquote id="o4zB">Если данный способ вам не удобен, то просто копируйте контракт <code>MyOFTV2</code> ниже в remix, потом компилируйте и все зависимости установятся сами.  Но не забудьте включить optimizer при компиляции.</blockquote>
  <p id="nyfk">Теперь создадим свой контракт MyOFTV2.</p>
  <pre id="ajlM" data-lang="cpp">// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import &quot;@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol&quot;;
contract MyOFTV2 is OFT{    
    constructor(        
      string memory _name,        
      string memory _symbol,        
      address _lzEndpoint,        
      address _delegate    
    ) OFT(_name, _symbol, _lzEndpoint, _delegate){        
        _mint(msg.sender, 1000 * 10 ** decimals());    
    }
}</pre>
  <p id="PV6R">По факту мы просто наследуем контракт OFT и минтим на наш адрес токены для перевода в будущем.</p>
  <p id="uuSz">Уже можно деплоить данный контракт в двух сетях и выполнять все наши задачи, но давайте рассмотрим что происходит под капотом, когда мы хотим перевести токены из одной сети в другую.</p>
  <p id="b7AY"></p>
  <h3 id="IihM">Обзор функций L0.</h3>
  <p id="KSep">Начнем с самого интересного и это то, как происходит cross chain swap токенов.</p>
  <p id="3uQV">Если мы перейдем в контракт OFT, то увидим две функции <code>_debit</code> и <code>_credit</code>. Данные функции переопределены из ядра <code>OFTCore</code>.</p>
  <p id="ikkg">Это нужно для того, чтоб мы могли добавить свою логику при переводе токенов и не изменять ядро.</p>
  <pre id="1MLh" data-lang="cpp"> function _debit(
        uint256 _amountLD,
        uint256 _minAmountLD,
        uint32 _dstEid
    ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
        (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);

        _burn(msg.sender, amountSentLD);
    }
    function _credit(
        address _to,
        uint256 _amountLD,
        uint32
    ) internal virtual override returns (uint256 amountReceivedLD) {
        
        _mint(_to, _amountLD);
        return _amountLD;
    }</pre>
  <p id="IAPY">Данные две функции предназначены для того, чтоб мы могли использовать механизм перевода токенов из одной сети в другую. Команда L0 это решила при помощи функций _mint и _burn. Смысл в том, чтоб при переводе токенов из Сети A мы сжигали токены из кошелька сети A и дальше чеканили токены в сети B на наш кошелек в сети B.</p>
  <p id="O9wz">Продвигаемся глубже и заходим в контракт <code>OFTCore</code>. Там есть достаточно много интересного, но пройдемся по самому главному.</p>
  <p id="lVne"><code>sharedDecimals</code> это функция которая дает нам возможность установить общий decimals для <strong>не</strong> evm сетей, так как некоторые сети имеют max uint64, что меньше чем evm ethereum max uint256 например. </p>
  <pre id="g2Vq" data-lang="cpp"> function sharedDecimals() public pure virtual returns (uint8) {        
     return 6;    
 }</pre>
  <p id="RzPF">функции <code>send</code> и <code>_lzReceive</code> очень важны во время перевода, так как они взаимодействуют с endpoint L0. </p>
  <pre id="xrhD" data-lang="cpp"> function send(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {
        (uint256 amountSentLD, uint256 amountReceivedLD) = _debit(
            _sendParam.amountLD,
            _sendParam.minAmountLD,
            _sendParam.dstEid
        );

        (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD);

        msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);
		oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD);

        emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD);
    }

    function _lzReceive(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata _message,
        address /*_executor*/, // @dev unused in the default implementation.
        bytes calldata /*_extraData*/ // @dev unused in the default implementation.
    ) internal virtual override {
        address toAddress = _message.sendTo().bytes32ToAddress();
  		uint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);

        if (_message.isComposed()) {
            bytes memory composeMsg = OFTComposeMsgCodec.encode(
                _origin.nonce,
                _origin.srcEid,
                amountReceivedLD,
                _message.composeMsg()
            );

            endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);
        }

        emit OFTReceived(_guid, _origin.srcEid, toAddress, amountReceivedLD);
    }
</pre>
  <p id="2r7W">Endpoint -  это специальные смарт-контракты L0, которые принимают обрабатывают и отправляют наши транзакции между сетями. Это если коротко.</p>
  <p id="2EPP">И так функции  <code>send</code> и <code>_lzReceive</code> работают с endpoint, но что именно они отправляют и принимают? </p>
  <p id="HsFr">В протоколе L0 есть такое понятие <strong>options</strong> (ранее вы могли слышать про них как adapterParams). </p>
  <p id="wKZu">Options - это способ передачи информации между сетями, так как сеть B не имеет ни какого понятия о состоянии сети A. Есть разные виды options, которые предоставляет нам L0. Нас же интересует lzReceiveOption. Эти параметры используются для передачи газа, который мы будем тратить для доставки сообщения и передачи msg.value, если это необходимо. </p>
  <p id="zkrb">Для их генерации команда L0 предоставила нам уже готовые функции. Их можно посмотреть в <a href="https://docs.layerzero.network/contracts/options#options-builders" target="_blank">документации</a> и открыть в <a href="https://remix.ethereum.org/#url=https://docs.layerzero.network/LayerZero/contracts/OptionsGenerator.sol" target="_blank">remix</a></p>
  <p id="A3Yb">Из важного, раньше наши options могли начинаться с так называемых магических числе 0001 и 0002 (идентификаторы options), но они устарели и их больше не используют. Теперь только 0003.</p>
  <p id="nJWN">Я для примера сгенерировал options с gas 200000 и msg.value 0. Это такие стандартные параметры, которые даже в документации L0 предоставляются.</p>
  <pre id="fn3b"> 0x00030100110100000000000000000000000000030d40</pre>
  <p id="51s0">функция <code>send</code> вызывается при отправке сообщения или в нашем случае токенов. Все наши параметры, которые мы передаем собираются и отправляются в <code>_lzSend</code> функцию,  которая в свою очередь выполняя некоторые проверки и отправляет наше сообщение в endpoint. </p>
  <blockquote id="Gj4w">Endpoint принимает наш запрос, обрабатывает и отправляет в сеть B транзакцию, вызвав для этого функцию <code>_lzReceive</code>. </blockquote>
  <p id="sjCO">О входных значениях кроме options для функции send поговорим чуть позже.</p>
  <p id="hesK">Функция <code>_lzReceive</code> уже будет вызываться конечной точкой (endpoint) в нашем контракте в сети B , и обрабатывать (расшифровывать) запакованные options. И таким образом наш перевод токенов будет завершен. </p>
  <p id="Lg04">Также у нас появляются новые понятия как SD LD. Грубо говоря это выравнивание по decimals разных сетей при помощи sharedDecimals. Как раз поэтому мы увидим новый параметр функции send, чуть позже.</p>
  <pre id="rKuQ" data-lang="cpp">function _toLD(uint64 _amountSD) internal view virtual returns (uint256 amountLD) {
        return _amountSD * decimalConversionRate;
    }
    function _toSD(uint256 _amountLD) internal view virtual returns (uint64 amountSD) {
        return uint64(_amountLD / decimalConversionRate);
    }</pre>
  <p id="xKAu">Если мы продвинемся дальше и перейдем в контракт OAppOptionsType3</p>
  <p id="4DVL">То там будет новая интересная функция setEnforcedOptions</p>
  <pre id="PZRq" data-lang="cpp">function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner {
        for (uint256 i = 0; i &lt; _enforcedOptions.length; i++) {
             _assertOptionsType3(_enforcedOptions[i].options);
            enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;
        }

        emit EnforcedOptionSet(_enforcedOptions);
    }</pre>
  <p id="EayA">Данная функция позволяет жёстко установить параметры options, то есть при вызове функции send, данные условия будут вызываться каждый раз. </p>
  <p id="xwkx">Теперь функция setPeer</p>
  <pre id="DAnw" data-lang="cpp">function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
        peers[_eid] = _peer;
        emit PeerSet(_eid, _peer);
    }</pre>
  <p id="h653">Данная функция позволяет установить связь между нашими контрактами в разных сетях. В принципе все.</p>
  <p id="2clC">Это было очень кратко и поверхностно, но для общего понимания процесса думаю достаточно, возможно в будущем разберем весь протокол более подробно.</p>
  <p id="uv4U">Также разумеется я рассмотрел не все функции, но тем не менее давайте развернем наш OFT и выполним перевод.</p>
  <h3 id="S289">Деплой</h3>
  <p id="Hw8U">Так как я работаю в hardhat, то я и деплой буду выполнять через hardhat. Если вы работаете в remix, то вам будет нужно подключить свой метамаск в нужной сети и деплоить уже там. </p>
  <p id="4Uxe">НО я советую научиться пользоваться hardhat и делать все там, потому что это более профессионально и быстрее, если научиться.</p>
  <p id="RtVa"><strong>Скрипт для деплоя в hardhat</strong></p>
  <p id="nn7J">В файл deploy.js в папку scripts вставляем данный код.</p>
  <pre id="RHE7" data-lang="javascript">
const hre = require(&quot;hardhat&quot;);

async function main() {

  const [deployer] = await ethers.getSigners();

  console.log(&quot;Deploying contracts with the account:&quot;, deployer.address);

  console.log(&quot;Account balance:&quot;, (await deployer.getBalance()).toString());
  
  const token = await ethers.deployContract(&quot;MyOFTV2&quot;, [&quot;Token name&quot;, &quot;Token symbol&quot;, &quot;0x6edce65403992e310a62460808c4b910d972f10f&quot;, &quot;Your wallet address&quot;]);

  console.log(&quot;Token address:&quot;, await token.address);
  
}

main()
  .then(() =&gt; process.exit(0))
  .catch((error) =&gt; {
    console.error(error);
    process.exit(1);
  });ocess.exit(1);
  });</pre>
  <p id="8nZ1">Я тут ни чего особенного не делаю, просто деплой. </p>
  <p id="32mF">Параметры конструктора:</p>
  <p id="hHe0">Первый, это название токена </p>
  <p id="T84C">Второй, это символ токена</p>
  <p id="OLWZ">Третий, это адрес endpoint</p>
  <p id="u0g8">Четвертый, это адрес владельца контракта, то есть наш адрес кошелька.</p>
  <p id="RuRc"><a href="https://docs.layerzero.network/contracts/endpoint-addresses" target="_blank">Адреса endpoint</a> </p>
  <p id="Gkdv">Я тут покажу два адреса, работать буду в них.</p>
  <h3 id="sepolia-testnet">Sepolia (Testnet)<a href="https://docs.layerzero.network/contracts/endpoint-addresses#sepolia-testnet" target="_blank">​</a></h3>
  <pre id="4EJu">endpointId: 40161
endpoint: 0x6edce65403992e310a62460808c4b910d972f10f</pre>
  <h3 id="mumbai-polygon-testnet">Mumbai (Polygon Testnet)<a href="https://docs.layerzero.network/contracts/endpoint-addresses#mumbai-polygon-testnet" target="_blank">​</a></h3>
  <pre id="psyj">endpointId: 40109
endpoint: 0x6edce65403992e310a62460808c4b910d972f10f</pre>
  <p id="jC0B">Как можно заметить адреса одинаковые, L0 позаботились о нас.</p>
  <p id="QUYn">endpointId (eid) это идентификатор конечной точки (ранее chainID). По ним endpoint понимает в какую сеть нужно отправить транзакцию. </p>
  <p id="uUyd"><strong>hardhat config</strong></p>
  <pre id="geqV" data-lang="javascript">  require(&quot;@nomicfoundation/hardhat-toolbox&quot;);
require(&quot;@nomiclabs/hardhat-etherscan&quot;);
const PRIVATE_KEY = &quot;YOUR_KEY&quot;
module.exports = {
  solidity: {
    compilers: [
      {
        version: &quot;0.8.23&quot;,
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
        },
      },
    ],
  },
  networks: {
      hardhat: {
        forking: {
          url: &quot;https://eth-mainnet.g.alchemy.com/v2/API&quot;,
          blockNumber: 18928295
        },
      },
      main: {
        url: &quot;https://eth-mainnet.g.alchemy.com/v2/API&quot;,
        chainId: 1,
        accounts: [
          PRIVATE_KEY
        ],
      },
      mumbai: {
        url: &quot;https://polygon-mumbai.g.alchemy.com/v2/API&quot;,
        chainId: 80001,
        accounts: [
          PRIVATE_KEY
        ],
      },
      sepolia: {
        url: &quot;https://eth-sepolia.g.alchemy.com/v2/API&quot;,
        chainId: 11155111,
        accounts: [
          PRIVATE_KEY
        ],
      },
  
      
    },
    etherscan:{
      apiKey: &quot;API&quot;,
    }
  }</pre>
  <p id="SGRY">Берите его и вставляйте в hardhat.config.js</p>
  <p id="NeiC">Устанавливайте свои параметры в пустые места:</p>
  <p id="U4PD">PRIVATE_KEY - приватник вашего кошелька метамаск. Как его брать думаю все знают.</p>
  <p id="gAcP">API для сетей sepolia и mumbai - я использую alchemy, но вы можете брать откуда вам удобно. Как создать свое приложение в alchemy можно легко найти в интернете или у меня в прошлых статьях.</p>
  <p id="Y59f">Дальше есть такой интересный параметр etherscan в конце конфига. Он нужен для верификации наших контрактов. Его можно взять, если вы зайдете на ehterscan mainnet и poligonscan mainnet и авторизуетесь. Потом зайдете в свой профиль, и там найдете вкладку API Keys. Заходим и создаем.</p>
  <figure id="zlEc" class="m_original">
    <img src="https://img1.teletype.in/files/08/ae/08ae2396-1ea5-4837-b5f0-11b060ed03a4.png" width="507" />
    <figcaption>etherscan </figcaption>
  </figure>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="tdh9">ВАЖНО! Хоть мы и создали api keys в etherscan и polygonscan mainnet, данный api keys можно использовать и в тестовых сетях как sepolia или mumbai. И хоть параметр в конфиге называется etherscan, мы туда вставляем api keys от etherscan и от polygonscan</p>
  </section>
  <p id="hpMo">И так, вы должны были создать 2 api ключа для etherscan и polygonscan, которые мы будем потом вставлять по очереди и верифицировать контракт.</p>
  <p id="BtRJ">Возможно это кажется достаточно сложным, но уверяю вас, если вы это сделаете, то вам больше не придется по пол часа тыкать в remix, чтоб верифицировать контракты, а просто одной командой в секунду подтверждать.</p>
  <p id="KesG">Данные настройки конфига и деплой скрипт можно использовать дальше в своих проектах просто копипастом и не париться.</p>
  <p id="x6MY"><strong>Теперь команды деплоя.</strong></p>
  <p id="7B2J">Если вы работаете в remix, то просто нажимаем кнопку deploy, предварительно подключив метамаск и выбрав нужную сеть. И не забываем что при компиляции вы должны были включить optimizer.</p>
  <p id="YOmC">Для hardhat:</p>
  <p id="CoCx">Открываем терминал, и мы должны находится в директории своего проекта.</p>
  <p id="9Prf">Выполняем 2 команды:</p>
  <p id="wEaG">Sepolia:</p>
  <pre id="xGrZ">npx hardhat run scripts\deploy.js --network sepolia </pre>
  <p id="1Wu2">Mumbai </p>
  <pre id="tduh">npx hardhat run scripts\deploy.js --network mumbai </pre>
  <p id="JM9i"></p>
  <p id="3jsZ">Если вы сделали все правильно, указали в конфиге все параметры, то у вас должно все получиться. Сохраняем адрес развернутого контракта, и можно его открыть в etherscan и polygonscan</p>
  <p id="Hv2r"></p>
  <p id="ukWi">Теперь верификация. Самое приятное, это то что нам не нужно использовать параметр все в одном файле (flatten), как это обычно делают при верификации через remix, и отображаться в etherscan каждый контракт будет отдельно, не как один огромный файл с кучей контрактов в нем.</p>
  <blockquote id="tqlP">Если вы использовали remix, то вам нужно сделать контракт MyOFTV2 flatten и перейти в etherscan. Там уже делать верификацию через него. Как это делать я уже показывал в прошлых статьях или вы можете найти в интернете.</blockquote>
  <p id="mGyJ">Команды для верификации hardhat:</p>
  <p id="4LYS">Sepolia:</p>
  <p id="Xrys">Вставляем в hardhat.config.js api keys от etherscan в параметр etherscan и выполняем команду</p>
  <pre id="KVDN">npx hardhat verify --network sepolia Address deploy contract &quot;MyOFTV2&quot; &quot;MOV&quot; &quot;0x6edce65403992e310a62460808c4b910d972f10f&quot; &quot;0xA788815dDDEfb278F06C248b03e7bBc1e5Ed6009&quot; </pre>
  <p id="rMPV">Mumbai:</p>
  <p id="a0Me">Вставляем в hardhat.config.js api keys от polygonscan в параметр etherscan и выполняем команду </p>
  <pre id="rMPV">npx hardhat verify --network mumbai address deploy contract &quot;MyOFTV2&quot; &quot;MOV&quot; &quot;0x6edce65403992e310a62460808c4b910d972f10f&quot; &quot;0xA788815dDDEfb278E06F248b03e7bBc1e5Ed6009&quot; </pre>
  <p id="BQNE"></p>
  <p id="xzGv">Заменяем фразу <strong>Address deploy contract </strong>на адрес вашего контракта в сети sepolia и mumbai соответственно.</p>
  <p id="8UKw">Также передаем параметры конструктора, я тут для примера написал свои, но вы меняйте их на свои, которые вы указывали во время деплоя. Можете скопировать их из deploy.js скрипта своего</p>
  <p id="oRZe">Если вы все сделали правильно, то у вас должно было все получиться и теперь можно открывать etherscan и polygonscam и там уже работать.</p>
  <p id="KRYB"></p>
  <h3 id="1k4O">Перевод токенов</h3>
  <p id="FOvg">Для начала открываем наш контракт в etherscan sepolia</p>
  <p id="NrVx">Переходим во вкладку Write Contract и подключаем кошелек, который мы использовали во время деплоя контрактов.</p>
  <p id="RorR">Далее находим функцию setPeer</p>
  <figure id="ZWPX" class="m_column">
    <img src="https://img2.teletype.in/files/d4/c3/d4c33370-46ce-43a8-ae00-d6b879436265.png" width="1920" />
  </figure>
  <p id="kB9Y">Вставляем eid mumbai и адрес контракта в сети mumbai.</p>
  <section style="background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="FZTi">ВАЖНО!!! Вы должны перевести ваш адрес в bytes32, грубо говоря просто дополнить нулями bytes20 как у меня.</p>
  </section>
  <pre id="F3sN">eid: 40109
bytes32: 0x000000000000000000000000b68C9f0c26c0911A81A3e33338abb28245F935Ef</pre>
  <p id="W6HA">Теперь подключайтесь к polygonscan mumbai и делаем тоже самое, но указываем параметры сети sepolia </p>
  <figure id="0YMd" class="m_column">
    <img src="https://img2.teletype.in/files/93/4e/934e928a-9c8d-4d7f-b82e-37f7f9e4c0e1.png" width="1897" />
  </figure>
  <pre id="gHNX">eid: 40161
bytes32: 0x0000000000000000000000009fb877E638F9e6De44D3043a74077eA8C8395537</pre>
  <p id="R3yN"></p>
  <p id="iYSh">Теперь возвращаемся в наш контракт в сети sepolia</p>
  <p id="OTWt">И нам нужно установить enforcedOptions, вызываем функцию setEnforcedOptions</p>
  <figure id="6y4N" class="m_column">
    <img src="https://img1.teletype.in/files/80/50/80502d04-4b14-4a7d-b45b-1ecab0dd29ba.png" width="1920" />
  </figure>
  <pre id="PQU0">[{eid: 40109,msgType: 1,options: 0x00030100110100000000000000000000000000030d40},{eid: 40109,msgType: 2,options: 0x00030100110100000000000000000000000000030d40}]</pre>
  <p id="BlWG">Здесь мы передаем массив структур с двумя элементами, можно скопировать мой и просто вставить.</p>
  <p id="NXm2">структура состоит из 3 параметров:</p>
  <p id="jey3">1) eid уже известный нам параметр <br />40109 для mumbai </p>
  <p id="TXgz">2) msgType тут есть только 2 типа сообщения и мы оба укажем, хоть будем пользоваться только SEND</p>
  <p id="gV5l">SEND = 1 </p>
  <p id="jrOq">SEND_AND_CALL = 2 </p>
  <p id="93aH">3) Это наши сгенерированные options ранее. 0x00030100110100000000000000000000000000030d40</p>
  <p id="DbnH"></p>
  <p id="td9j">Теперь главное мы выполняем вызов функции send</p>
  <figure id="bx45" class="m_column">
    <img src="https://img2.teletype.in/files/94/07/9407e4c1-cbfb-4e88-9a89-e5becee9b878.png" width="1920" />
    <figcaption>не уместилась в один скрин вся функция </figcaption>
  </figure>
  <figure id="uMXW" class="m_column">
    <img src="https://img1.teletype.in/files/c1/c3/c1c34723-40dd-4905-8e01-245c363c3ae1.png" width="1920" />
  </figure>
  <p id="rfV6">Наконец разбираемся с параметрами функции send:</p>
  <pre id="nYhM">msg.value: 0.002 
dstEid: 40109 
to: 0x000000000000000000000000a788815dddefb278e06c248b03e7bbc1e5ed6009
amountLD: 543212345432123
minamountLD: 543000000000000
extraOptions: 0x
composeMsg: 0x
oftCmd: 0x
nativeFee: 2000000000000000
lzTokenFee: 0
_refundAddress: 0xA788815dDDEfb278E06C248b03e7bBc1e5Ed6009</pre>
  <p id="PAPJ">msg.value - это сумма на оплату транзакции в другой сети, 0.02 будет нам достаточно</p>
  <p id="PUHG">dstEid - уже знаем </p>
  <p id="R6dH">to - адрес на который мы переводим токены в другой сети </p>
  <p id="SAIb">amountLD - количество токенов</p>
  <p id="3c2M">minamountLD - минимальное количество токенов, которое будет отправлено, так как мы помним что у нас есть <code>sharedDecimals</code> которые, выравнивают decimals для не evm сетей, и так как мы указали 6, а у нашего токена decimals 18, то разумеется будет нужно выравнивать цену и мы сами можем указать это число. Как это сделать? Просто указать до 6 знаков amount. Так как у меня amount только 15 знаков, то и соответственно максимальное возможное число это 543000000000000 в minamountLD</p>
  <p id="iQAO">extraOptions - дополнительные опции которые склеиваются с enforcedOptrions если это требуется. Главное не дублируйте их, а то 2 раза выполните транзакцию.</p>
  <p id="hqzk">composeMsg - дополнительное сообщение или логика, которое выполниться после перевода, если это требуется</p>
  <p id="DiZv">nativeFee - ВАЖНО указать тоже число что и msg.value</p>
  <p id="jYAm">lzTokenFee - если вы хотите оплатить в токенах L0, иначе 0</p>
  <p id="Io1Z">_refundAddress - адрес куда придут средства в случае неудачно транзакции.</p>
  <p id="Qg2O"></p>
  <p id="OZPW">Теперь вызываем функцию send и смотрим на нашу транзакцию в <a href="https://testnet.layerzeroscan.com/" target="_blank">LayerZero scan</a></p>
  <p id="csmp">Для этого вставляйте в поиск L0 scan хэш вашей транзакции сети sepolia</p>
  <figure id="rBT9" class="m_column">
    <img src="https://img2.teletype.in/files/14/8c/148ca662-dade-4c18-aa6a-b93677a37cc4.png" width="1920" />
  </figure>
  <p id="8oqt">Вот допустим моя транзакция которую я только что указал,</p>
  <p id="YmeR">Тут мы можем увидеть хэш транзакции в сети sepolia и потом хэш транзакции в сети mumbai, дожидаемся Status: Delivered и проверяем баланс токенов в сети mumbai</p>
  <p id="QicO">Если мы зайдем в наш контракт в mumbai и вызовем функцию balanceOf, то увидим, что баланс токенов у нас увеличился, и как мы видим сумма была выравнена. (P.S там 643,а не 543, потому что я уже переводил до этого токены) </p>
  <figure id="rZI2" class="m_column">
    <img src="https://img1.teletype.in/files/0a/57/0a578cb4-542b-4716-88b3-b02014340ca7.png" width="1920" />
  </figure>
  <p id="PHhP">Особенность OFT V2 как можно заметить это sharedDecimals, и если вы работаете в evm сетях подобных ethereum, то лучше ставить sharedDecimals  = 18.</p>
  <p id="FQOP"></p>
  <p id="FIpq">На этом думаю можно закончить. Сегодня мы не только выполнили перевод токенов между сетями, но и разобрались что именно происходит в этот момент. Всем удачи.</p>
  <p id="y2Wg"></p>
  <p id="vYPF">Полезные ссылки:</p>
  <p id="f6hq"><a href="https://layerzero.gitbook.io/docs/evm-guides/contract-standards/oft-overview/v1-oft-vs-v2-oft-which-should-i-use" target="_blank">Различие OFT V1 и OFT V2</a></p>
  <p id="qMh4"><a href="https://docs.layerzero.network/contracts/oft" target="_blank">Документация L0</a></p>
  <p id="0nN6"><a href="https://testnet.layerzeroscan.com/tx/0xa46491c1bf0df7782151a1dcbe9b40295f3db51a67626128fbe191e9b4a8eb91" target="_blank">моя транзакция на L0 scan</a></p>
  <p id="sw2C"><a href="https://t.me/solidiks" target="_blank">Мой телегам канал</a> </p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/Iw4cjlheSCe</guid><link>https://teletype.in/@ddworld/Iw4cjlheSCe?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/Iw4cjlheSCe?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>zkSync глубокое погружение </title><pubDate>Sat, 25 Nov 2023 12:16:22 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/7b/98/7b98b3ba-1177-42d9-991b-83a4dc020b41.png"></media:content><category>zkSync</category><description><![CDATA[<img src="https://img2.teletype.in/files/55/d6/55d66cf6-7cae-45de-b577-ae8187975d09.png"></img>Привет всем! В данной статье я попытаюсь рассказать, как работает zkSync под капотом, как формируются блоки и транзакции. Все это будет представлено простым языком для понимания широкой аудитории.]]></description><content:encoded><![CDATA[
  <figure id="OwS6" class="m_column">
    <img src="https://img2.teletype.in/files/55/d6/55d66cf6-7cae-45de-b577-ae8187975d09.png" width="1063" />
  </figure>
  <p id="QC4c">Привет всем! В данной статье я попытаюсь рассказать, как работает zkSync под капотом, как формируются блоки и транзакции. Все это будет представлено простым языком для понимания широкой аудитории.</p>
  <p id="IgMZ"><strong>На чем основан zkSync:</strong></p>
  <p id="YMEl"><em><strong>Доказательства нулевого разглашения (ZKPs):</strong></em></p>
  <ul id="5uZo">
    <li id="A1qE"><em>Определение:</em> Это криптографические протоколы, которые позволяют доказать, что утверждение верно, не раскрывая информацию о самом утверждении.</li>
    <li id="DvOR"><em>Применение:</em> ZKP используются для повышения приватности, например в блокчейне, где они подтверждают действительность утверждения, не раскрывая основных данных.</li>
  </ul>
  <p id="ceJY"><em><strong>ZK Rollups:</strong></em></p>
  <ul id="b9Ic">
    <li id="uboh"><em>Определение:</em> Это масштабируемое решение для блокчейнов, которое повышает пропускную способность и снижает затраты на транзакции, перемещая обработку большей части транзакций вне цепи блоков. Они используют доказательства нулевого разглашения для обеспечения криптографических гарантий о действительности транзакций, не раскрывая деталей транзакций.</li>
    <li id="9SjI"><em>Принцип работы:</em> В ZK Rollups большинство транзакций происходит вне цепи блоков, и лишь краткая информация периодически отправляется на основной блокчейн. Затем доказательства нулевого разглашения используются для подтверждения действительности вычислений вне цепи.</li>
  </ul>
  <p id="OvHF"><em><strong>ZK-SNARKs (Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge):</strong></em></p>
  <ul id="fNlc">
    <li id="mW4I"><em>Определение:</em> Это конкретный тип систем доказательств нулевого разглашения, обеспечивающий лаконичные и неинтерактивные доказательства знания.</li>
    <li id="QIU6"><em>Применение:</em> ZK-SNARKs известны в блокчейн-приложениях, таких как Zcash, где они позволяют проверять транзакции без раскрытия отправителя, получателя или суммы транзакции.</li>
    <li id="eZ8U"><em>Принцип работы:</em> ZK-SNARKs используют математические конструкции, такие как эллиптические кривые и алгебраическая геометрия, для обеспечения эффективных и безопасных доказательств нулевого разглашения. Их краткость особенно ценна в блокчейне, где эффективность критически важна.</li>
  </ul>
  <p id="qHrq"><em><strong>Подробнее про данные концепции можно узнать <a href="https://web.mit.edu/~ezyang/Public/graph/svg.html" target="_blank">здесь</a>.</strong></em></p>
  <p id="0nGQ"><strong>Итак, про технологии я кратко рассказал, давайте уже ближе к теме:</strong></p>
  <p id="Zit6"></p>
  <h2 id="Tswx"><strong>Основные компоненты zkSync:</strong></h2>
  <p id="vBCp"><strong>Смарт-контракты:</strong></p>
  <ul id="SHlf">
    <li id="BrJb"><em>Смарт-контракт L1 и L2:</em> Обеспечивают взаимодействие между L1 (основной блокчейн) и L2 (сайдчейн zkSync).</li>
    <li id="4eZ2"><em>Мостовые контракты:</em> Организуют перемещение активов между L1 и L2.</li>
    <li id="wrpv"><em>Контракт объединения zkSync на Ethereum:</em> Объединяет L2-активы в контракты на L1.</li>
    <li id="CHtf"><em>Контракт верификатора доказательства L1:</em> Проверяет доказательства от серверной части перед добавлением блоков в L1.</li>
  </ul>
  <p id="3b3q"><strong>Серверная часть (Узел сети zkSync):</strong></p>
  <ul id="3quE">
    <li id="pzMT"><em>Мониторинг смарт-контракта L1:</em> Отслеживает депозиты или приоритетные операции.</li>
    <li id="uu8h"><em>Поддержание мемпула:</em> Принимает транзакции (Postgres).</li>
    <li id="odGs"><em>Извлечение транзакций из мемпула:</em> Выполняет транзакции на виртуальной машине и изменяет состояние.</li>
    <li id="mvjN"><em>Генерация блоков цепочки zkSync:</em> Подготавливает схемы для блоков, подлежащих проверке.</li>
    <li id="7JQQ"><em>Отправка блоков и доказательств в смарт-контракт L1:</em> Публикация API web3, совместимого с Ethereum.</li>
  </ul>
  <p id="LCqo"><strong>ZkEvm:</strong></p>
  <ul id="UlIi">
    <li id="l05X"><em>Виртуальная машина:</em> Выполняет смарт-контракты способом, совместимым с вычислениями с нулевым разглашением данных.</li>
  </ul>
  <p id="Pvow"><strong>Приложение Prover:</strong></p>
  <ul id="RT5z">
    <li id="lvJh"><em>Приложение для проверки:</em> Принимает блоки и метаданные, создает для них доказательство достоверности zk.</li>
  </ul>
  <p id="O87A"><strong>Уровень хранения:</strong></p>
  <ul id="0Uku">
    <li id="oPGL"><em>Различные компоненты и подкомпоненты взаимодействуют через уровень хранения БД.</em></li>
  </ul>
  <p id="lnyb"><strong>State Keeper:</strong></p>
  <ul id="orVE">
    <li id="7p6S"><em>Доступ к хранилищу ВМ для State Keeper:</em> Компонент, отвечающий за обработку выполнения транзакций и создание миниблоков и пакетов L1.</li>
  </ul>
  <p id="QFFc">Данный список отображает главные механизмы работы, но я не буду останавливаться на каждом компоненте подробно, только на неочевидных моментах.</p>
  <p id="jJ4F"><strong>Генерация блоков цепочки zkSync:</strong></p>
  <p id="bOOz"><strong>Из документации zksync:</strong></p>
  <blockquote id="sNOC"><em>Блоки L2 в настоящее время не играют основной роли в системе.</em> Это функция совместимости для поддержки инструментов, таких как Metamask, ожидающих частого изменения блока. Это позволяет инструментам предоставлять пользователям обратную связь, подтверждая, что их транзакция была добавлена.</blockquote>
  <p id="JHJD">Грубо говоря отправив транзакцию, вы моментально увидите ее выполнение, но она не будет еще добавлена в основную цепочку и сохранена на L1.</p>
  <h3 id="FPbT"><strong>Основные шаги для завершения транзакции:</strong></h3>
  <ul id="7LBJ">
    <li id="PJzb"><strong>Выполнить транзакцию в блоке State Keeper &amp; Seal</strong></li>
    <li id="FekE"><strong>Создать свидетеля</strong></li>
    <li id="pCNb"><strong>Создайте доказательство</strong></li>
    <li id="JlMh"><strong>Проверка доказательства на L1</strong></li>
  </ul>
  <p id="rc2T"></p>
  <h3 id="qke9"><strong>Выполнить транзакцию в блоке State Keeper &amp; Seal</strong></h3>
  <p id="E09Y">Задача State Keeper — брать транзакции из мемпула и помещать их в пакет L1. Это делается с помощью process_l1_batch() метода (метод написан на языке Rust)</p>
  <p id="jhR3">Этот метод берет следующую транзакцию из мемпула (которая может быть либо L1Tx, либо L2Tx, но L1Tx всегда имеет приоритет и они выполняются первыми), выполняет ее и проверяет, готов ли пакет L1 к запечатыванию</p>
  <p id="2dyu">L1Tx - это в основном депозиты или выводы средств из системы.</p>
  <p id="H2GW"><strong>Теперь про пакеты L1</strong></p>
  <figure id="cofG" class="m_column">
    <img src="https://img3.teletype.in/files/a2/12/a2127622-8578-4ce4-908e-5b5e88869e13.png" width="1164" />
    <figcaption>картинка взята из документации zksync</figcaption>
  </figure>
  <p id="RHO7">На данной картине очевидно показано как работает запечатывание пакетов L1.</p>
  <p id="Dv27">У нас есть транзакции L1tx и L2tx, транзакции L1tx разумеется идут в приоритете, и все эти транзакции собираются в миниблоки L2, эти миниблоки в свою очередь собираются в пакет L1, который как мы знаем  State Keeper готовит к запечатыванию. </p>
  <p id="JWED">У пакетов L1 (State Keeper) есть ограничение в 750 транзакций и еще ряд ограничений связанных с газлимитом и публикационными данными в 120кб</p>
  <p id="9Eqn">После того как State Keeper собрал пакет L1, нужно создать свидетеля</p>
  <p id="3Y1P">Так как  State Keeper выполняет операции, но не заявляет об их выполнении открыто, нам нужно создать доказательство zk или перейти на новый этап выполнения транзакции </p>
  <p id="2gV2"></p>
  <h3 id="QDXN"><strong>Создать свидетеля.</strong></h3>
  <p id="njY2">Если говорить простым языком, то нам нужно доказать заранее, что операция, которую мы выполнили, возможна. Алгоритм берет одну транзакцию и проверяет ее.</p>
  <p id="NKi4"><strong>Например:</strong></p>
  <p id="X8sx">Я хочу присвоить a = 44</p>
  <p id="jSZw">В обычном случае мы бы просто присвоили переменную считав данные из хранилища.</p>
  <p id="0cTT">Но в нашем случае мы должны проверить пути меркла и создать набор хешей, чтоб показать что эта операция возможна, и что мы не обманываем.</p>
  <p id="OcWI">Еще более простым языком мы должны найти текущий корень дерева меркла и взять его хеш, после чего взять наше число 44 и доказать ее существование в хранилище, то  есть проверить, что хеш дерева меркла не поменялся. Дальше мы должны создать новый хеш, который показывает что мы присвоили a=44 и таким образом докажем, что 44 существует в хранилище и создаем новый хеш в дереве меркла</p>
  <p id="dD4e">Если вы не знакомы с деревьями и деревом меркла, то этот этап будет трудно понять, поэтому советую ознакомиться.</p>
  <p id="AYe5">И так: </p>
  <p id="DQRk">Благодаря такой системе мы создаем свидетеля, который говорит нам, что да, транзакции возможны, и мы ему верим. </p>
  <p id="h7V2">Следующий этап, это генерация доказательства </p>
  <h3 id="YBLw"></h3>
  <h3 id="aQgZ"><strong>Создайте доказательство</strong></h3>
  <p id="R7uT">Тут мы берем наши данные свидетеля из прошлого пункта и разбиваем его на более мелкие части, создаем из них полиномы (математическое выражение), после чего в силу вступают zkSNARK. Чтоб сгенерировать само доказательство мы вычисляем это выражение в разных точках, и для этого требуется очень большие вычислительные мощности. </p>
  <p id="rCQb"><a href="https://web.mit.edu/~ezyang/Public/graph/svg.html" target="_blank">Вот наглядный пример как происходит вычисление доказательства вида ZK</a></p>
  <h3 id="ITq0"></h3>
  <p id="XARX"><strong>Проверка доказательства на L1 </strong></p>
  <p id="O7yw">Попробую объяснит на палацах:</p>
  <p id="Ss6a">У нас есть 4 переменных:</p>
  <ul id="Y5QD">
    <li id="nYOX"><strong>C</strong> : это ключи проверки</li>
    <li id="HSHO"><strong>In</strong> : представляет корневой хеш перед блоком транзакции.</li>
    <li id="qctJ"><strong>Out</strong> : представляет корневой хеш после блока транзакции.</li>
    <li id="7lKW"><strong>P </strong>: Это доказательство, предоставленное доказывающим.</li>
  </ul>
  <p id="aCCx">Логика этого заключается в том, что совпадающее доказательство «P» может существовать только в том случае, если <code>C(In) == Out</code>.</p>
  <p id="DlJB">Мы просто говорим, что наше вычисленное доказательство P имеет смысл, если наши хеши In и Out согласованы при помощи ключа C.</p>
  <p id="Pr7x">Главная задача этого пункта - сохранять синхронизацию L1 и L2</p>
  <p id="KOxa">Все данные в технологии <strong>ZK Rollups </strong>хранятся как calldata и zksync не исключение </p>
  <p id="przG"></p>
  <h3 id="JoCm"><strong>Смарт-контракты:</strong></h3>
  <p id="OooP">Рассмотрим различие ВМ эфира и zksync</p>
  <p id="bI1X"><strong>Поток Эфириума:</strong></p>
  <ol id="DYFH">
    <li id="ncUn"><strong>Написание Кода в Solidity:</strong> Начинаем с написания смарт-контракта на языке Solidity.</li>
    <li id="iBHz"><strong>Компиляция с Solc:</strong> Используем компилятор solc для преобразования кода в байт-код EVM, байт-код развертывания и ABI.</li>
    <li id="WhPM"><strong>Развертывание на Ethereum:</strong> Отправляем байт-код развертывания на адрес 0x000 в сеть Ethereum, где происходит выполнение байт-кода развертывания и размещение контракта по уникальному адресу.</li>
    <li id="vzJX"><strong>Взаимодействие с Контрактом:</strong> Теперь можем отправлять транзакции на адрес контракта, используя ABI для правильной передачи аргументов функций.</li>
    <li id="S3fP"><strong>Выполнение на EVM:</strong> Весь байт-код выполняется на Ethereum Virtual Machine (EVM), имеющей стек, память и хранилище.</li>
  </ol>
  <p id="8cD4"><strong>Поток zkSync:</strong></p>
  <ol id="shI0">
    <li id="srF2"><strong>Система Проверки (zkEVM):</strong> Основная часть zkSync - это система проверки. Используется виртуальная машина zkEVM, имеющая другой набор кодов операций и регистров.</li>
    <li id="dHy2"><strong>Компиляция с zk-solc:</strong> Используем отдельный компилятор zk-solc для создания байт-кода, совместимого с zkEVM.</li>
    <li id="WRqa"><strong>Развертывание в zkSync:</strong> Отправляем скомпилированный код в zkSync, где его выполнение происходит на zkEVM. Новые контракты могут быть развернуты в системе контрактов zkSync.</li>
    <li id="wcOU"><strong>Преимущества отдельного компилятора:</strong> Использование отдельного компилятора позволяет более быстрые и дешевые модификации, а также увеличивает гибкость системы.</li>
  </ol>
  <p id="1vrt">Таким образом, в случае zkSync, используется отдельная виртуальная машина (zkEVM) и компилятор (zk-solc) для обеспечения возможности эффективного проведения доказательств в системе проверки.</p>
  <p id="oYRJ"></p>
  <h3 id="7yQ1">Сжатие байт кода смарт-контрактов</h3>
  <p id="vD7D">Поскольку zkSync являемся накопителем — все байт-коды, которые контракты используют в нашей цепочке, должны быть скопированы в L1 (чтобы при необходимости цепочку можно было реконструировать из L1).</p>
  <p id="3ZTL">Учитывая необходимость сократить используемое пространство, байт-код сжимается перед отправкой в ​​L1.</p>
  <p id="A1OU">На высоком уровне байт-код разбивается на коды операций (размером 8 байт), присваивается двухбайтовый индекс, а вновь сформированная последовательность байтов (индексы) проверяется и отправляется на L1</p>
  <p id="KAgQ"></p>
  <p id="rKyK"><code>000000000000000A<br />000000000000000D<br />000000000000000A<br />000000000000000C<br />000000000000000B<br />000000000000000B<br />000000000000000D<br />000000000000000A</code></p>
  <p id="kehE"><code>Словарь </code></p>
  <p id="eybK"><code>0 -&gt; 0xA (count: 3)<br />1 -&gt; 0xD (count: 2, first seen: 1)<br />2 -&gt; 0xB (count: 2, first seen: 4)<br />3 -&gt; 0xC (count: 1)</code></p>
  <p id="cpiI"><code>Сжатый байткод </code></p>
  <p id="wknR"><code>0008 0000 <br />000000000000000A<br />000000000000000D<br />000000000000000B<br />000000000000000C<br /><br />0000 0001 0000 0003 0002 0002 0001 0000</code><br /></p>
  <p id="11kR">Дальше данный байт-код проходит проверку и публикуется на L1. Для этого есть отдельный смарт-контракт.</p>
  <p id="A9hk">На самом деле данные или транзакции, которые мы просто отправляем, тоже сжимаются своими алгоритмами.</p>
  <p id="h35L"><a href="https://docs.google.com/spreadsheets/d/1ejK1MJfVehcwjgjVDFD3E2k1EZ7auqbG_y0DKidS9nA/edit#gid=0" target="_blank">Также есть таблица, где показаны основные коды операций</a></p>
  <p id="l4lb"></p>
  <p id="ulSI">Как вы могли заметить я часто упоминал серверную часть, это нам говорит, что zkSync на данный момент не полностью децентрализованный протокол, и поэтому может прослеживаться цензура, но как нам заверяют разработчики, это не произойдет и они стремятся перейти к полностью децентрализованной системе. Будем ждать.</p>
  <p id="5Iai"></p>
  <h3 id="JiTa">Как происходят обращение к смарт-контрактам L1:</h3>
  <p id="5C4X">ZkSync используем настройку DiamondProxy, которая позволяет иметь фиксированную неизменяемую точку входа (DiamondProxy), которая перенаправляет запросы к различным контрактам (кранам), которые могут быть независимо обновлены и/или заморожены. </p>
  <figure id="ytC8" class="m_column">
    <img src="https://img3.teletype.in/files/e2/e6/e2e650f5-3715-4c69-ab1c-7d332ce1b37f.png" width="834" />
    <figcaption>кратинка взята из документации zkSync</figcaption>
  </figure>
  <h4 id="ZJoX">Diamond</h4>
  <p id="uREA">Технически этот смарт-контракт L1 действует как соединитель между Ethereum (L1) и zkSync (L2). Этот контракт проверяет подтверждение действительности и доступность данных, обрабатывает связь L2 &lt;-&gt; L1, завершает переход состояния L2 и многое другое.</p>
  <p id="QQjw">На уровне L2 также развернуты важные контракты, которые также могут выполнять логику, называемую <em>системными контрактами</em> . Использование связи L2 &lt;-&gt; L1 может повлиять как на L1, так и на L2</p>
  <p id="K9y3"></p>
  <p id="lkg3">На самом деле в zkSync есть кучу разных контрактов и каждый выполняет свою роль, но если я начну о всех них рассказывать, то на это уйдет слишком много времени, поэтому оставлю данную тему на другой раз.</p>
  <p id="9OQ9"></p>
  <p id="QnWT">На этом все. Я постарался донести вам достаточно трудную информацию, которую сложно осознать с первого раза, так что если вы что то поняли, уже хорошо. Теперь вы знаете как на самом деле происходят транзакции и формирования блоков в zkSync это далеко не все решения, которые есть в zkSync, но для понимания я думаю достаточно. На самом деле очень сильный проект, который подает большие надежды.</p>
  <p id="HMTe"></p>
  <p id="Q6Bq"></p>
  <p id="1ex5">Ресурсы:</p>
  <p id="31ua"><a href="https://github.com/matter-labs/zksync-era/tree/main/docs" target="_blank">Документация zkSync</a></p>
  <p id="CikC"><a href="https://t.me/solidiks" target="_blank">Мой телеграмм канал</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/uetdHIfq1QY</guid><link>https://teletype.in/@ddworld/uetdHIfq1QY?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/uetdHIfq1QY?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>Solidity | Экземпляры контрактов</title><pubDate>Sun, 10 Sep 2023 11:35:53 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/f8/e5/f8e5f3f0-b14c-4cbb-92e2-afac5df0c28c.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img1.teletype.in/files/cb/ce/cbcefbc8-6cd6-4fe5-b580-7a69b2c0c147.png"></img>Всем примет. Сегодня говорим об экземплярах контрактов и их особенностях. Покажу как работать с ними и их подводные камни.]]></description><content:encoded><![CDATA[
  <figure id="6ZDE" class="m_column">
    <img src="https://img1.teletype.in/files/cb/ce/cbcefbc8-6cd6-4fe5-b580-7a69b2c0c147.png" width="1063" />
  </figure>
  <p id="rW5U">Всем примет. Сегодня говорим об экземплярах контрактов и их особенностях. Покажу как работать с ними и их подводные камни.</p>
  <h3 id="aCNt">Для чего вообще нам нужны экземпляры контрактов?</h3>
  <p id="r0tl"><strong>Взаимодействия с контрактом:</strong></p>
  <p id="2gX6">Самое очевидное, это для взаимодействия с контрактом.<br />Например: у нас есть контракт А, который реализует методы, и мы хотим их использовать у себя в контракте.</p>
  <p id="9Bfp"><strong>Раздельная функциональность:</strong></p>
  <p id="2KqQ">Иногда наш код удобно разделить на части, но как это было у меня в прошлом проекте, наследование использовать не получиться, потому что код слишком большой и в следствии смарт-контракт не может быть развернут. В таком случае нам приходит на помощь экземпляры контрактов.</p>
  <p id="Y754"><strong>Управление доступом:</strong></p>
  <p id="yYJz">Также удобно создавать ролевую систему, тоже было показано в прошлом проекте, где вызов функций смарт-контракта A мог только смарт-контракт B</p>
  <p id="O7bz">В общем это очень сильный инструмент для разработки, без которого нельзя. Важно понимать, что мы можем брать экземпляр только развернутого контракта. Поэтому мы передаем адрес контракта для его инициализации.</p>
  <h3 id="bOsg">Пример использования:</h3>
  <p id="mckc"><strong>Самый очевидный и популярный пример:</strong></p>
  <pre id="LLdB" data-lang="cpp">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
contract A{    
    IERC20 token;    
    constructor(address _token){        
        token = IERC20(_token);    
    }
}</pre>
  <p id="BeAQ">В данном примере я объявляю переменную типа интерфейс <code>IERC20</code></p>
  <p id="Qxe2"><code>token = IERC20(_token);</code> - эта строка кода создает переменную <code>token</code> и присваивает ей значение, являющееся экземпляром смарт-контракта, реализующего интерфейс <code>IERC20</code>  </p>
  <p id="n4GF"><code>address _token</code> - это адрес любого токена <code>ERC20</code>.</p>
  <p id="hHbY">Таким образом мы получаем доступ ко всем методам токена стандарта <code>ERC20</code>, адрес которого мы передали. </p>
  <p id="GCNz"><strong>Более сложный пример:</strong></p>
  <pre id="K4JE" data-lang="cpp"> // SPDX-License-Identifier: MITpragma solidity ^0.8.9;
contract FirstContract{    
    struct human{        
        string name;        
        uint age;    
    }    
    address public owner;    
    mapping(address=&gt;human) public humans;
    function setName(string calldata _name) public{        
        humans[msg.sender].name = _name;    
    }    
    function setAge(uint _age) public{        
        humans[msg.sender].age = _age;    
    }
}</pre>
  <p id="Jt9E"></p>
  <pre id="y2yo" data-lang="cpp"> // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.9;
 import &quot;./test.sol&quot;;
contract B{    
    FirstContract public firstContract;    
        constructor(address _A){        
        firstContract = FirstContract(_A);    
    }
    function check() public view returns(string memory, uint){        
        (string memory _name, uint _age) = firstContract.humans(msg.sender);        
        return (_name, _age);    
    }    
    function checkOwner() public view returns(address){        
        return firstContract.owner();    
    }
}</pre>
  <p id="AWtP">Если у нас есть структура, маппинг или переменная, то обращаться мы можем к ней только под видом функции.</p>
  <p id="U5be"><code>firstContract.owner()</code>  - для того, чтоб узнать содержимое переменной нам нужно обратиться к ней, как к функции.</p>
  <p id="vA8w"><code>firstContract.owner</code>  - так это выглядело, если бы у нас <code>firstContract</code> вызывался через наследование.</p>
  <p id="IFf9">Для того, чтоб вытащить поля из структуры, которая находиться в маппинге, нужно создавать кортеж переменных и в данном случае мы обращаемся к маппингу как к функции, таким образом доставая нужные нам поля.</p>
  <p id="4jjo">Если мы хотим достать только определенное поле структуры, то можно сделать так: <code>(string memory _name, ) = firstContract.humans(msg.sender);</code></p>
  <p id="OKe6">Тут я достаю только имя из нашей структуры.</p>
  <p id="Hsft"><strong>Важно!</strong></p>
  <p id="5Ksa">Мы не можем изменять переменные у экземпляра контракта в своем контракте, если нет функций, разрешающих это сделать. </p>
  <p id="3sih">Как видно у меня в примере, в <code>FirstContract</code> я создал 2 функции устанавливающие значения полям структуры. И теперь вызвав эти функции у себя в смарт-контракте, мы можем изменить эти поля. </p>
  <p id="10Ge">Функции контракта B:</p>
  <pre id="uVrH" data-lang="cpp"> function setName_(string memory _name) public{        
     firstContract.humans(msg.sender).name = _name;    
 }</pre>
  <p id="FOfO">Такая запись нам не подойдет.</p>
  <pre id="Rgk8" data-lang="cpp"> function setName_(string memory _name) public{        
     firstContract.setName(_name);    
  }</pre>
  <p id="I6U5">Такая запись подойдет.</p>
  <p id="CGZZ">Нужно понимать, что сам контракт <code>B</code> не видит эти функции, как при наследовании, поэтому нельзя взять экземпляр и таким образом использовать все его функции. Нужно <strong>явно</strong> объявлять их у себя.</p>
  <p id="h2hg">Еще нельзя забывать, что мы должны импортировать интерфейс или сам смарт-контракт, чтоб наш контракт понимал с чем взаимодействовать.</p>
  <p id="rPP7">В общем это основные особенности, при использовании экземпляра контракта. Мы должны рассматривать каждую переменную как функцию, а все остальное такое же как и в наследовании.</p>
  <p id="NcYZ"></p>
  <p id="RUjT">Это была небольшая статься, потому что я для себя подметил ряд необычных механик, которые думаю можно было рассмотреть. Всем удачи.</p>
  <p id="LBSw"></p>
  <p id="eGGw"><a href="https://t.me/solidiks" target="_blank">Мой тг канал.</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/5MhvuJgcdqy</guid><link>https://teletype.in/@ddworld/5MhvuJgcdqy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/5MhvuJgcdqy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>АБС на блокчейне финал</title><pubDate>Tue, 05 Sep 2023 20:43:58 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/ad/25/ad2556d8-29b2-4cb9-a318-76e0711e109a.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img4.teletype.in/files/74/db/74dbdd44-430f-4da1-a560-0cad7581d11e.png"></img>Всем привет! В этой статье я заканчиваю свой проект с АБС на блокчейне. Получилось почти все что я хотел сделать и это меня радует. Расскажу про последние модули и доработки. Кто хочет потыкать весь код, то может найти его на гитхабе.]]></description><content:encoded><![CDATA[
  <figure id="ENX1" class="m_column">
    <img src="https://img4.teletype.in/files/74/db/74dbdd44-430f-4da1-a560-0cad7581d11e.png" width="1063" />
  </figure>
  <p id="jYYQ">Всем привет! В этой статье я заканчиваю свой проект с АБС на блокчейне. Получилось почти все что я хотел сделать и это меня радует. Расскажу про последние модули и доработки. Кто хочет потыкать весь код, то может найти его на гитхабе.</p>
  <p id="2JRn">Начну с того, что я не смог реализовать все модули, но те которые я хотел смог. Также я не дописал похожие модули, которые уже существуют, так как там меняется только логика применения, а код по сути тот же. </p>
  <p id="4DCj">В данной статье будут модули выдачи кредитов инвестиций и аналог Свифта для переводов между банками.</p>
  <p id="wdXe">Статья будет не большой, так сказать завершающей. </p>
  <p id="gJpC">Изменений почти нету с прошлой статьи поэтому сразу к новому:</p>
  <pre id="sGGE" data-lang="cpp"> // SPDX-License-Identifier: MITpragma solidity ^0.8.9;
import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
import &quot;./clientsInfo.sol&quot;;
contract CreditAndInvest{    
    ClientInfo clientInfo;    
    address clientInfoAddress;    
    struct creditClient{        
        address client;        
        mapping(address=&gt;uint) tokenBalanceCredit;    
    }    
    struct investClient{        
        address client;        
        mapping(address=&gt;uint) tokenBalanceInvest;    
    }    
    mapping(address=&gt;creditClient) creditClients;    
    mapping(address=&gt;investClient) investClients;    
    event newCredit(address indexed client, address token, uint amount);    
    event newInvest(address indexed client, uint amount);    
    constructor(address clientInfo_){        
        clientInfo = ClientInfo(clientInfo_);        
        clientInfoAddress = clientInfo_;    
    }        
    function Credit(address sender, address token, uint amount) public{        
        emit newCredit(sender, token, amount);    
    }    
    function getCredit(address sender, address _client, address token, uint amount) public{        
        require(clientInfo.ownersBank(sender) == true, &quot;not owner bank!&quot;);        
        clientInfo.getCredit(token, amount);        
        unchecked {            
            creditClients[_client].tokenBalanceCredit[token] += amount;        
        }    
    }    
    function invest(address sender, address token, uint amount) public{        
        //approve frontend        
        IERC20(token).transferFrom(sender, clientInfoAddress, amount);        
        emit newInvest(sender, amount);        
        //invest frontend    
    }
}</pre>
  <p id="ORjY">Модули кредитов и инвестиций я объединил, также расчет процента я решил оставить на фронт, потому что тупо удобнее.</p>
  <p id="KUhn">Создаю две структуры для отслеживания балансов клиентов.</p>
  <p id="nFSl">На самом деле тут все тоже самое, что и в аккредитиве: я хочу открыть кредит вызываю событие, которое сотрудник отслеживает и решает выдать мне кредит или нет. </p>
  <p id="kCV1">В инвестициях все проще. Я просто делаю депозит в банк и вызываю событие, что я сделал инвестицию и в зависимости от типа инвестиции мне уже считают мой процент прибыли за период. </p>
  <p id="1Rgn">Аналог Свифта:</p>
  <p id="Hcew">Я долго думал как сделать перевод между разными банками, чтоб он был быстрый и при этом мы могли делать перевод между любыми банками любой страны.</p>
  <p id="v3J3">В общем, если банк использует мой вендор, то данная технология работает, в другом случае, нужно, чтоб была аналогичная функция для перевода как у моего вендора. В обще реализация не полная.</p>
  <pre id="GHMo" data-lang="cpp"> function newTransfer(address fromBank, address toBank, address token, address toClient, uint amount) public{            
     updateBalanceInUSD(toClient);        
     (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
     require(price &gt; 0, &quot;Invalid price&quot;);        
     uint amountAllowance = IERC20(token).allowance(fromBank, toBank);        
     require(amountAllowance &gt;= amount, &quot;Not approve&quot;);        
     IERC20(token).transferFrom(fromBank, toBank, amount);         
     unchecked {            
         clients[toClient].balanceUsdt[token] += amount * uint(price);            
         clients[toClient].tokenBalance[token] += amount;        
     }    
 }    
 function transferBalanceFromBank(address sender, address to, address clientInfoBank, address token, uint amount, string memory inf) public{        
     updateBalanceInUSD(sender);        
     (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
     require(price &gt; 0, &quot;Invalid price&quot;);        
     unchecked {            
         clients[sender].balanceUsdt[token] -= amount * uint(price);            
         clients[sender].tokenBalance[token] -= amount;        
     }        
     IERC20(token).approve(clientInfoBank, amount);        
     bytes4 selector = bytes4(keccak256(&quot;newTransfer(address,address,address,address,uint256)&quot;));        
     (bool success, ) = clientInfoBank.call(abi.encodeWithSelector(selector, address(this), clientInfoBank, token, to, amount));        
     require(success, &quot;faild&quot;);        
     clients[sender].paymentInfo.push(payment(sender, to, inf, token, amount));    
 }</pre>
  <p id="Bt51">Добавлены функции в контракт clientInfo.</p>
  <p id="4gHj">Для того, чтоб перевести деньги на другой банк, я буду использовать низкоуровневый вызов, который будет вызывать функцию, где уже будет происходить перевод. И соответственно я правильно отображаю балансы каждого клиента разных банков. Тема рабочая, при условии что вендоры одинаковые. Можно кончено передавать селектор функции другого вендора, отвечающего за перевод банку или депозит, таким образом обойти данную проблему, но я думаю в данном примере этого достаточно.</p>
  <p id="AGxh"></p>
  <p id="KC85"></p>
  <p id="jfnd">На этом я хочу закончить данный проект. Сделали достаточно много всего. Код был объёмным и много раз приходилось все переделывать, но в итоге исход положительный. Данную тему можно очень хорошо развить кому интересно, так что дерзайте. Я же вернусь к изучению solidity и его различным стандартам и особенностям. Всем кто следил спасибо.</p>
  <p id="l8nd"></p>
  <p id="6F4q"><a href="https://github.com/DDWorld-dev/vendor-ABS" target="_blank">Полный проект на гитхабе</a></p>
  <p id="RVDG"><a href="https://t.me/solidiks" target="_blank">Мой тг канал</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/v9cJMNxRHL2</guid><link>https://teletype.in/@ddworld/v9cJMNxRHL2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/v9cJMNxRHL2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>АБС на блокчейне, модуль аккредитив</title><pubDate>Tue, 22 Aug 2023 19:19:40 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/63/2c/632c45d4-cbd8-45c7-9088-31cd58b0bb7d.png"></media:content><description><![CDATA[<img src="https://img4.teletype.in/files/3c/ee/3cee0b43-4564-45af-8dcc-db26723e3111.png"></img>Всем привет! Сегодня уже третья статья про АБС. Написал новый модуль аккредитив для банка. Появилось много необычного и интересного в проекте.]]></description><content:encoded><![CDATA[
  <figure id="tOoP" class="m_column">
    <img src="https://img4.teletype.in/files/3c/ee/3cee0b43-4564-45af-8dcc-db26723e3111.png" width="1063" />
  </figure>
  <p id="j63r">Всем привет! Сегодня уже третья статья про АБС. Написал новый модуль аккредитив для банка. Появилось много необычного и интересного в проекте.</p>
  <p id="D2lG">Начну с того, что я не бросал проект, просто времени было не много и появилась проблема в реализации, о которой я думал, но не знал что она придет так рано.</p>
  <h3 id="zrrr">Трудности и изменения:</h3>
  <p id="a6g6">Начну как всегда с трудностей. Изначально я хотел, чтоб все модули соединялись между собой через наследование, это было бы удобно, потому что в одном контракте лежала бы вся информация и реализация, но проект оказался больше чем я думал и уже на втором модуле при тестах я получил ошибку при диполе о вылете из за нехватке газа, что говорит о том что невозможно оценить газ, а значит контракта слишком большой.</p>
  <p id="fDn2">Решение простое: просто сделать каждый контракт отдельным.(каждый модуль это свой личный контракт). Пришлось перебирать весь код для решения проблемы. Так как контракт, где храниться информация о клиентах банка мне нужен в каждом модуле, следовательно нужно брать его экземпляр и в каждый модуль добавлять. Позже в коде все будет видно. Главная проблема заключается в том, что мы не можем менять состояние переменных структуры напрямую у экземпляра контракта, поэтому приходиться добавлять лишние функции и уже работать через них.</p>
  <p id="GHau">Также из за сложности кода решил пока что делать вендор для одного банка. Другими словами, такой вендор нужно отдельно деплоить для каждого нового банка. Но в будущем можно будет это исправить усложнив логику.</p>
  <p id="Xsv2">На самом деле в данной статье получилось так, что весь код прошлых статей абсолютно переписан, поэтому можно даже не смотреть на предыдущие статьи в этом плане.</p>
  <h3 id="MsnZ"><strong>Код:</strong></h3>
  <p id="Xv8T">Первый контракт clientInfo - содержит информацию о клиентах и основные функции по изменению состояний полей структуры клиента.</p>
  <pre data-lang="cpp" id="bGSR">// SPDX-License-Identifier: MITpragma solidity ^0.8.9;
import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
import &quot;@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol&quot;;
contract ClientInfo {        
    address public BANK;    
    address owner;    
    constructor(){        
        owner = msg.sender;    
    }    
    struct client{        
        string name;        
        address clientOwner;        
        address bank;        
        uint clientType;        
        bool accountDebet;        
        bool accountCredit;        
        payment[] paymentInfo;        
        document[] documents;        
        address[] allTokens;        
        mapping(address=&gt;uint) balanceUsdt;        
        mapping(address=&gt;uint) tokenBalance;    
    }    
    struct document{        
        uint typeDocument;        
        string documentInfo;        
        address document;    
    }    
    struct payment{        
        address from;        
        address to;        
        string message;        
        address token;        
        uint amount;    
    }    
    mapping(address=&gt;mapping(uint=&gt;bool)) public initSubscribe;    
    mapping(address=&gt;client) public clients;        
    mapping(address=&gt;address) oracles;    
    mapping(address=&gt;bool) public ownersBank;
    mapping(address=&gt;bool) module;    
    modifier onlyModules(address module_) {        
        require(module[module_] == true, &quot;only module can call&quot;);        
        _;    
    }    
    modifier onlyOwner(){        
        require(msg.sender == owner, &quot;not owner&quot;);        
        _;    
    }    
    function addModules(address _module) public onlyOwner{        
        module[_module] = true;    
    }    
    function createBank() public onlyModules(msg.sender){        
        BANK = msg.sender;        
        client storage newClientInfo = clients[msg.sender];        
        newClientInfo.name = &quot;BANK&quot;;        
        newClientInfo.clientOwner = msg.sender;        
        newClientInfo.bank = msg.sender;        
        newClientInfo.accountDebet = true;        
        newClientInfo.accountCredit = true;        
        newClientInfo.clientType = 3;        
        newClientInfo.documents.push(document(0, &quot;create bank&quot;, msg.sender));    
    }
    function addNeworacle(address token, address _oracle) public onlyModules(msg.sender){        
        oracles[token] = _oracle;    
    }
    function initializeClient(string calldata _name, address sender) public onlyModules(msg.sender){        
        require(BANK == msg.sender, &quot;not bank&quot;);        
        require(clients[sender].bank != msg.sender || clients[sender].clientOwner != sender, &quot;already exist&quot;);        
        client storage newClientInfo = clients[sender];        
        newClientInfo.name = _name;        
        newClientInfo.clientOwner = sender;        
        newClientInfo.bank = msg.sender;        
        newClientInfo.accountDebet = false;        
        newClientInfo.accountCredit = false;
        uint size;                
        assembly {            
            size := extcodesize(sender)        
        }        
        if (size &gt; 0) {            
            bytes4 expectedFunctionSignature = bytes4(keccak256(&quot;check(uint256,address)&quot;));            
            (bool success, ) = sender.call(abi.encodeWithSelector(expectedFunctionSignature, 1, sender));            
            require(success, &quot;faild&quot;);            
            if(success == true){                
                newClientInfo.clientType = 1;                
                newClientInfo.documents.push(document(0, &quot;entity person&quot;, sender));            
            }         
        }     
        else{            
            newClientInfo.clientType = 0;            
            newClientInfo.documents.push(document(0, &quot;natural person&quot;, sender));        
        }        
        newClientInfo.paymentInfo.push(payment(sender, address(this), &quot;new client&quot;, address(this), 0));        
    }       
    function setOwner(address _owner) public onlyModules(msg.sender){       
        require(msg.sender == BANK);        
        ownersBank[_owner] = true;    
    }
    function getClientInfo(address sender) public view onlyModules(msg.sender) returns(string memory,address, address, uint, uint, uint) {        
        return(clients[sender].name, clients[sender].clientOwner, clients[sender].bank, clients[sender].clientType, clients[sender].balanceUsdt[0xdAC17F958D2ee523a2206206994597C13D831ec7], clients[sender].tokenBalance[0xdAC17F958D2ee523a2206206994597C13D831ec7]);    
    }
    function setSubscribe(address sender, uint typeClient) public onlyModules(msg.sender){        
        initSubscribe[sender][typeClient] = true;    
    }
    function addAccount(address sender, uint typeAccount) public onlyModules(msg.sender){        
        if(typeAccount == 0){            
            clients[sender].accountDebet = true;        
        }        
        if(typeAccount == 1){            
            clients[sender].accountCredit = true;        
        }            
    }    
    function deletAccount(address sender, uint typeAccount) public onlyModules(msg.sender){         
        if(typeAccount == 0){            
            clients[sender].accountDebet = false;        
        }        
        if(typeAccount == 1){            
            clients[sender].accountCredit = false;        
        }    
    }    
    function updateBalanceInUSD(address sender) public{        
        for(uint i = 0; i &lt; clients[sender].allTokens.length; i++){            
            (, int256 price, , , ) = AggregatorV3Interface(oracles[clients[sender].allTokens[i]]).latestRoundData();            
            uint balance = clients[sender].balanceUsdt[clients[sender].allTokens[i]];            
            clients[sender].balanceUsdt[clients[sender].allTokens[i]] = balance * uint(price);        
         }    
     }    
     function transferBalanceInBank(address sender, address to, address token, uint amount, string memory inf) public{        
         (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
         require(price &gt; 0, &quot;Invalid price&quot;);        
         unchecked {            
             clients[sender].balanceUsdt[token] -= amount * uint(price);            
             clients[sender].tokenBalance[token] -= amount;            
             clients[to].balanceUsdt[token] += amount * uint(price);            
             clients[to].tokenBalance[token] += amount;                    
         }        
         clients[sender].paymentInfo.push(payment(sender, to, inf, token, amount));    
     }    
     function invest(address sender, address token, uint amount) public{        
         (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
         require(price &gt; 0, &quot;Invalid price&quot;);        
         clients[sender].balanceUsdt[token] -= amount * uint(price);        
         clients[sender].tokenBalance[token] -= amount;        
         clients[BANK].balanceUsdt[token] += amount * uint(price);        
         clients[BANK].tokenBalance[token] += amount;        
         clients[sender].paymentInfo.push(payment(sender, address(this), &quot;deposit balance&quot;, token, amount));    
     }     
     function depositBalance(address sender, address token, uint amount) public{        
         (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
         require(price &gt; 0, &quot;Invalid price&quot;);        
         clients[sender].balanceUsdt[token] += amount * uint(price);        
         clients[sender].tokenBalance[token] += amount;        
         clients[sender].paymentInfo.push(payment(sender, address(this), &quot;deposit balance&quot;, token, amount));    
     }    
     function withdrawlBalance(address sender, address token, uint amount) public{        
         IERC20(token).transfer(sender, amount);        
         (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
         require(price &gt; 0, &quot;Invalid price&quot;);        
         unchecked{            
             clients[sender].balanceUsdt[token] -= amount * uint(price);            
             clients[sender].tokenBalance[token] -= amount;        
         }        
         clients[sender].paymentInfo.push(payment(address(this), sender, &quot;withdrawal balance&quot;, token, amount));    
     }
}</pre>
  <p id="SOhZ">В данном контракте все функции, которые вы уже видели, они были в контакте Core, но из за особенностей solidity они теперь тут. Единственное, что я добавил, это модификатор onlyModules на ряд функций, что их вызывать могут только модули. Это дает нам гарантию, что извне ни кто не будет обращаться к этим функциям, так как мне это не нужно. Ну и для тех полей структуры, которые мне нужно будет менять значение я сделал функцию по их изменению, например setSubscribe функция которая устанавливает в маппинге initSubscribe значение true.</p>
  <p id="t6Mx">Контракт CreateClients - содержит функционал создания клиента банка</p>
  <pre data-lang="cpp" id="Qm1V">// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.9;
import &quot;./clientsInfo.sol&quot;;
contract CreateClients{    
    ClientInfo public clientInfo;    
    address letterOfCreditAddress;    
    constructor(address clientInfo_){        
        clientInfo = ClientInfo(clientInfo_);        
        letterOfCreditAddress = clientInfo_;    
    }    
    modifier onlyOwnerBank(address sender) {        
        require(clientInfo.ownersBank(sender) == true, &quot;Not an owner bank&quot;);        
        _;    
    }    
    function checkOwnerBank() public view returns(bool){        
        return clientInfo.ownersBank(msg.sender);    
    }        
    function addAccountDebet(address sender) public {        
        (, address isOwnerAccount, , , ,) = clientInfo.clients(sender);        
        require(isOwnerAccount == sender, &quot;Not client&quot;);        
        clientInfo.setSubscribe(sender, 0);    
    }    
    function addAccountCredit(address sender) public{          
        (, address isOwnerAccount, , , ,) = clientInfo.clients(sender);        
        require(isOwnerAccount == sender, &quot;Not client&quot;);        
        clientInfo.setSubscribe(sender, 1);            
    }    
    function complitesubscribersAccount(address _client, uint typeAccount, address sender) public onlyOwnerBank(sender){        
        require(clientInfo.initSubscribe(_client, typeAccount) == true, &quot;not complite&quot;);        
        require(typeAccount == 0 || typeAccount == 1, &quot;error&quot;);        
        clientInfo.addAccount(_client, typeAccount);    
    }    
    function deletAccountDebet(address sender) public{        
        (, address isOwnerAccount, , , bool debet,) = clientInfo.clients(sender);        
        require(isOwnerAccount == sender, &quot;Not client&quot;);        
        require(debet == true, &quot;not open&quot;);         
        clientInfo.setSubscribe(sender, 0);    
    }    
    function deletAccountCredit(address sender) public{        
        (, address isOwnerAccount, , , , bool credit) = clientInfo.clients(sender);        
        require(isOwnerAccount == sender, &quot;Not client&quot;);        
        require(credit == true, &quot;not open&quot;);        
        clientInfo.setSubscribe(sender, 1);    
    }    
    function comliteDeletAccount(address _client, uint typeAccount, address sender) public onlyOwnerBank(sender){        
        require(clientInfo.initSubscribe(_client, typeAccount) == true, &quot;not complite&quot;);        
        require(typeAccount == 0 || typeAccount == 1, &quot;error&quot;);        
        clientInfo.deletAccount(_client, typeAccount);    
    }
}</pre>
  <p id="d7zu">Тут тоже все функции знакомые. Показываю, чтоб было понятно как я обращаюсь к контракту clientInfo и изменяю состояния полей структуры</p>
  <p id="LdjB">Из интересного вот как вытаскивать поля из структуры, другие способы почему что не работают, компилятор ругается мол не видит их.</p>
  <pre data-lang="cpp" id="jLXn">(, address isOwnerAccount, , , ,) = clientInfo.clients(sender);</pre>
  <p id="AKgL">Возможно это из за того, что он обращается к переменным как к функциям, например если я хочу вытащить переменную BANK из контракта clientInfo, я должен обратиться к ней так:</p>
  <pre data-lang="cpp" id="kGYY"> clientInfo.BANK()</pre>
  <p id="1Mbz">Или к маппингу так:</p>
  <pre data-lang="cpp" id="lAjX"> clientInfo.ownersBank(sender) </pre>
  <p id="4JnU">то есть в случае наследования было бы так:</p>
  <pre data-lang="cpp" id="FOP0">clientInfo.ownersBank[sender]</pre>
  <p id="t16n">В общем если будете работать с экземплярами контрактов, то учитывайте данную особенность.</p>
  <p id="FA4M">Контракт Core - тоже старый контракт, все функции в нем знакомы, но покажу как его изменил с новым подходом:</p>
  <pre data-lang="cpp" id="dUhW"> // SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.9;
import &quot;./Clients.sol&quot;;
import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
contract Core is CreateClients{    
    address owner;    
    constructor(address clientInfo_) CreateClients(clientInfo_){        
        owner = msg.sender;    
    }    
    modifier onlyOwner() {        
        require(owner == msg.sender, &quot;faild&quot;);        
        _;    
    }    
    function transferBalance(uint amount, address token, address to, address sender) public{        
        (, address isOwnerAccount, address bank, , bool debet,) = clientInfo.clients(sender);        
        (, address isOwnerAccountTo, address bankTo, , bool debetTo,) = clientInfo.clients(to);        
        require(isOwnerAccount == sender, &quot;not client&quot;);        
        require(isOwnerAccountTo == to, &quot;not client&quot;);        
        require(msg.sender == clientInfo.BANK(), &quot;not bank&quot;);        
        require(debet == true, &quot;not open account&quot;);        
        require(debetTo == true, &quot;not open account&quot;);        
        require(bank == bankTo, &quot;different bank&quot;);        
        clientInfo.updateBalanceInUSD(sender);        
        clientInfo.transferBalanceInBank(sender, to, token, amount, &quot;new transfer balance&quot;);    
    }    
    function investing(address sender, address token, uint amount) public{        
        clientInfo.invest(sender, token, amount);    
    }    
    function deposit(uint amount, address token, address sender) public {        
        (, address isOwnerAccount, , , bool debet,) = clientInfo.clients(sender);        
        require(clientInfo.BANK() == msg.sender, &quot;not bank&quot;);        
        require(isOwnerAccount == sender, &quot;not client&quot;);        
        require(debet == true, &quot;not open account&quot;);        
        require(IERC20(token).allowance(sender, address(this)) &gt;= amount, &quot;chek allowance&quot;);        
        clientInfo.updateBalanceInUSD(sender);        
        IERC20(token).transferFrom(sender, letterOfCreditAddress, amount);        
        clientInfo.depositBalance(sender, token, amount);    
    }    
    function withdrawal(uint amount, address token, address sender) public{        
        (, address isOwnerAccount, , , bool debet,) = clientInfo.clients(sender);        
        require(clientInfo.BANK() == msg.sender, &quot;not bank&quot;);        
        require(isOwnerAccount == sender, &quot;not client&quot;);        
        require(debet == true, &quot;not open account&quot;);        
        require(IERC20(token).allowance(msg.sender, address(this)) &gt;= amount, &quot;chek allowance&quot;);        
        clientInfo.updateBalanceInUSD(sender);        clientInfo.withdrawlBalance(sender, token, amount);    
    }  
}</pre>
  <p id="Bzs8">По факту тут идет дублирование функций из clientInfo, но с добавлением ряда условий и ограничений, сделал так для безопасности, и в рамках модификатора onlyModules. Ну и конечно наследует контракт CreateClients.</p>
  <p id="bYtq">Новый контракт LettersOfCredit - это новый модуль который я реализовал в нашей АБС. Данный модуль это аккредитивы. Они работают по типу совершение сделки через гаранта, а банк в его роли. Например я (продавец) хочу продать 100 телефонов покупателю оптом, я заключаю аккредитив с банком и говорю, что после того как эти телефоны прибудут по месту назначения, банк покупателя обязуется выплатить деньги за эти телефоны в пользу продавцы, таким образом продавец уверен что его не обманут после отправки товара и ему заплатят. После покупатель уже с банком разбирается и оплачивает данный товар по частям как кредит или одним чеком. Я реализовал лишь концепцию и не соблюдал много условий, но в общем пойдет.</p>
  <pre data-lang="cpp" id="m5DO"> // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.9;
 import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
 import &quot;./clientsInfo.sol&quot;;
 contract LettersOfCredit{    
     ClientInfo clientInfo;     
     struct letterofcredit{        
     address from;        
     address to;        
     uint amount;        
     address token;        
     string conditions;        
     uint stage;    
     }    
     mapping(address=&gt;letterofcredit) public LettersOfCredits;    
         constructor(address clientInfo_){        
         clientInfo = ClientInfo(clientInfo_);    
     }    
     event newLetterOfCreditrequest(address to, string message);     
     function makeLettersOfCredit(address sender, address recipient, uint amount, address token, string calldata contitions) public {
         letterofcredit storage newLetterOfCredit = LettersOfCredits[sender];        
         newLetterOfCredit.from = sender;        
         newLetterOfCredit.to = recipient;        
         newLetterOfCredit.amount = amount;        
         newLetterOfCredit.token = token;        
         newLetterOfCredit.conditions = contitions;        
         newLetterOfCredit.stage = 1;    
     }    
     function confirmLetterOfCredit(address sender, address _client) public {        
         require(LettersOfCredits[_client].stage == 1, &quot;Error&quot;);        
         require(clientInfo.ownersBank(sender) == true, &quot;only owner bank can confirm&quot;);        
         LettersOfCredits[_client].stage = 2;    
     }    
     function sendLettersOfCredit(address sender, address _client) public{       
         require(LettersOfCredits[_client].stage == 2, &quot;Error&quot;);        
         require(clientInfo.ownersBank(sender) == true, &quot;only owner bank can send&quot;);        
         emit newLetterOfCreditrequest(LettersOfCredits[_client].to, LettersOfCredits[_client].conditions);        
         LettersOfCredits[_client].stage = 3;    
     }    
     function checkLetterOfCredit(address sender, address _client) public{        
         require(LettersOfCredits[_client].stage == 3, &quot;Error&quot;);        
         require(clientInfo.ownersBank(sender) == true, &quot;only bank can confirm check&quot;);        
         LettersOfCredits[_client].stage = 4;    
     }    
     function transferBalance_(uint amount, address token, address to, address sender) private{        
         clientInfo.updateBalanceInUSD(sender);        
         clientInfo.transferBalanceInBank(sender, to, token, amount, &quot;new transfer balance letter of credit&quot;);    
     }       
     function checkLetter(address sender) public view returns(address, address, uint, address, string memory, uint){        
         return (LettersOfCredits[sender].from, LettersOfCredits[sender].to, LettersOfCredits[sender].amount, LettersOfCredits[sender].token, LettersOfCredits[sender].conditions, LettersOfCredits[sender].stage);    
     }    
     function sendMoney(address sender, address _client) public{        
         require(LettersOfCredits[_client].stage == 4, &quot;error&quot;);        
         require(clientInfo.ownersBank(sender) == true, &quot;only bank can send money&quot;);        
         transferBalance_(LettersOfCredits[_client].amount, LettersOfCredits[_client].token, LettersOfCredits[_client].to, clientInfo.BANK());        
         LettersOfCredits[_client].stage = 5;    
     }    
     function compliteLetterOfCredit(address sender, address _client) public {        
         require(LettersOfCredits[_client].stage == 5, &quot;error&quot;);        
         require(clientInfo.ownersBank(sender) == true, &quot;only bank can send money&quot;);        
         delete LettersOfCredits[_client];    
     }
 }</pre>
  <p id="1xUs">Я создаю еще одну структуру, которая хранит в себе всю нужную информацию о аккредитиве. В целом все поля понятные кроме stage. Его я использую для определения этапа выполнения сделки.</p>
  <p id="hOAf"><strong>Функции:</strong></p>
  <p id="629Z">makeLettersOfCredit - функция для создание аккредитива, передаем просто все данные, после создания аккредитива мы от лица банка его подтверждаем</p>
  <p id="mQ9C">confirmLetterOfCredit - тут как раз вступает в игру stage. После подтверждения он меняется на 2. Как видно каждую функцию я ограничиваю stage, и ее можно вызвать только в определенный момент, а не когда замочиться.</p>
  <p id="18Ou">sendLettersOfCredit - функция оповещающая о том, что аккредитив вступает в силу, вызывая событие, так как их легко отслеживать на фронте.</p>
  <p id="eGpi">checkLetterOfCredit - функция вызывается после того как банк проверил что услуга выполнена и можно отправлять деньги</p>
  <p id="W2l0">transferBalance_ - приватная функция, которая делает сам перевод.</p>
  <p id="7lXJ">checkLetter - функция которая может выдать информацию об аккредитиве</p>
  <p id="JiRe">sendMoney - функция которая переводит деньги, обратите внимание что отправитель денег не человек, который создал аккредитив, а банк.</p>
  <p id="3Qg6">compliteLetterOfCredit - эта функция закрывает аккредитив после его выполнения</p>
  <p id="vq7f">В общем очень просто контракт с очевидными функциями. Также покрыл основную часть кода тестами. Это уже все на гитхабе, кому интересно.</p>
  <p id="PPPj">Контракт OtherContract - уже знакомый нам ипровезированный банк (надо бы название поменять).</p>
  <pre data-lang="cpp" id="tPcx">// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import &quot;./Core.sol&quot;;
import &quot;./letterOfCredit.sol&quot;;
contract OtherContract{    
    Core public coreInstance;    
    LettersOfCredit public letterInsctance;    
    ClientInfo public clientInfo;    
    constructor(address _coreAddress, address _LetterOfCredit, address clientInfo_) {        
        coreInstance = Core(_coreAddress);        
        letterInsctance = LettersOfCredit(_LetterOfCredit);        
        clientInfo = ClientInfo(clientInfo_);    
    }    
    function checkClient(address client) public view returns(string memory,address, address, uint, uint, uint){        
        return clientInfo.getClientInfo(client);    
    }        
    function setOwnerBank() public{        
        clientInfo.createBank();    
    }    
    function setOwnerClient(address owner_)  public{            
        clientInfo.setOwner(owner_);    
    }    
    function addNeworacle_(address token, address oracle) public{        
        clientInfo.addNeworacle(token, oracle);    
    }    
    function investing_(address token, uint amount) public{        
        coreInstance.investing(msg.sender, token, amount);    
    }    
    function _checkLetter(address sender) public view returns(address, address, uint, address, string memory, uint){        
        return letterInsctance.checkLetter(sender);    
    }    
    function _makeLetterOfCerdit(address to, uint amount, address token, string calldata contitions) public {        
        letterInsctance.makeLettersOfCredit(msg.sender, to, amount, token, contitions);    
    }    
    function _confirmLetterOfCredit(address _client) public{        
        letterInsctance.confirmLetterOfCredit(msg.sender, _client);    
    }    
    function _sendLettersOfCredit(address _client) public{        
        letterInsctance.sendLettersOfCredit(msg.sender, _client);    
    }    
    function _checkLetterOfCredit(address _client) public{        
        letterInsctance.checkLetterOfCredit(msg.sender, _client);    
    }    
    function _sendMoney(address _client) public{        
        letterInsctance.sendMoney(msg.sender, _client);    
    }    
    function _compliteLetterOfCredit(address _client) public{        
        letterInsctance.compliteLetterOfCredit(msg.sender, _client);    
    }    
    function openAccount(address _client, uint typeAccount) public{        
        coreInstance.complitesubscribersAccount(_client, typeAccount, msg.sender);    
    }    
    function deletAccount(address _client, uint typeAccount) public{        
        coreInstance.comliteDeletAccount(_client, typeAccount, msg.sender);            
    }    
    function deletAccountDebet_() public{               
        coreInstance.deletAccountDebet(msg.sender);    
    }    
    function deletAccountCredit_() public{               
        coreInstance.deletAccountCredit(msg.sender);    
    }    
    function addAccountDebet_() public{               
        coreInstance.addAccountDebet(msg.sender);    
    }    
    function addAccountCredit_() public{               
        coreInstance.addAccountCredit(msg.sender);    
    }    
    function newClient(string calldata _name) public{        
        clientInfo.initializeClient(_name, msg.sender);    
    }    
    function balance(address token) public view returns(uint){        
        return IERC20(token).balanceOf(msg.sender);    
    }
    function depositeToBank(uint amount, address token) public{        
        //approve frontend        
        coreInstance.deposit(amount, token, msg.sender);    
    }    
    function transferBalanceInBank(uint amount, address token, address to) public{        
        coreInstance.transferBalance(amount, token, to, msg.sender);   
    }    
    function checkAllowance(address token) public view returns(uint){        
        return IERC20(token).allowance(msg.sender, address(this));    
    }
}
</pre>
  <p id="WxTv">Тут все просто. В конструктор я передаю адреса моих модулей, получаю их экземпляр и просто вызываю все функции в одном контракте. Таким образом получаю доступ ко всем функциям.</p>
  <p id="46ju">На этом я закончу данную статью, надеюсь дальше буду чаще выкладывать их, но все зависит от сложности и времени, потому что пока я писал данный код столкнулся с очень многими мелкими проблемками, которые приходилось обходить и иногда уходило на это много времени.</p>
  <p id="z3TP">Оставляю кому интересно:</p>
  <p id="0LxV"><a href="https://t.me/solidiks" target="_blank">Мой тг канал</a> <br /><a href="https://github.com/DDWorld-dev/vendor-ABS" target="_blank">Этот проект на гитхабе</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/9JKa-0qqzjm</guid><link>https://teletype.in/@ddworld/9JKa-0qqzjm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/9JKa-0qqzjm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>Создаем свою АБС на блокчейне</title><pubDate>Tue, 01 Aug 2023 20:23:35 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/8f/ef/8fef1be8-9a0c-4d57-88bc-f3e77b6ea1eb.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img3.teletype.in/files/a7/c6/a7c69616-6c86-44d6-8447-cd1dd145e240.png"></img>Всем привет. Продолжаем создавать автоматизированную банковскую систему на блокчейне. В этой статье наш смарт-контракт Core был сильно изменен, так что будем разбираться по порядку. ]]></description><content:encoded><![CDATA[
  <figure id="Kljf" class="m_column">
    <img src="https://img3.teletype.in/files/a7/c6/a7c69616-6c86-44d6-8447-cd1dd145e240.png" width="1063" />
  </figure>
  <p id="t4Rt">Всем привет. Продолжаем создавать автоматизированную банковскую систему на блокчейне. В этой статье наш смарт-контракт Core был сильно изменен, так что будем разбираться по порядку. </p>
  <p id="PArY">Те кто не видел первую статью на эту тему настоятельно рекомендую просмотреть, чтоб понимать что происходит вообще тут. </p>
  <h2 id="0RXy">Интересные моменты</h2>
  <p id="n37Y">Из интересного я переосмыслил концепцию. Изначально я хотел, чтоб мой вендор  хранил всю информацию о всех банках, которые им бы пользовались. И соответственно вендор был бы нагружен всеми клиентами разных банков и самими банками. В процессе я понял, что это не удобно и слишком медленно. Я решил, что каждый банк будет интегрировать эту систему в свой смарт-контракт банка (получать экземпляр контракта через интерфейс и его адрес). Таким образом каждый смарт-контракт банка будет хранить всю информацию у себя внутри в безопасности. Из плюсов это анонимность каждого банка и эффективность. Из минусов, я пока не могу придумать как переводить средства из одно банка в другой одной транзакцией, потому что не могу менять балансы клиентов разных смарт-контрактов банков. Если у кого-то есть идеи, буду рад принять.</p>
  <p id="KObe">Вторя проблема, это отображение всего баланса в usd. Проблема в том, что наш клиент может иметь огромное количество токенов, у которых меняется цена токена за доллар и, чтоб отображать правильную цену аккаунта (в долларах) нам нужно обновлять баланс каждый определенный промежуток времени. Сделать это не трудно, но я пока не придумал как лучше.</p>
  <h2 id="JpIe">Что мы сегодня реализуем:</h2>
  <p id="oX8d">Я реализую все оставшиеся функции ядра АБС. (почти)</p>
  <p id="WogQ">Валютный учет <br />Безналичные расчеты <br />Учет и отчетность</p>
  <p id="QUJg">Так же еще осталось много недочетов, которые я буду в будущем исправлять.</p>
  <p id="8vCe">И как я уже сказал в функции &quot;Безналичные расчеты&quot; я не смог пока придумать перевод между разными банками (но это можно оставить на модуль SWIFT).</p>
  <h2 id="KbOe">Код:</h2>
  <p id="3kQC">Вот новый контракт Core. Сначала я расскажу про изменения, потом уже про нововведения.</p>
  <pre id="QlCu" data-lang="cpp">// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
import &quot;@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol&quot;;
contract Core {    
    address owner;    
    mapping(address=&gt;bool) public ownersBank;    
    address public BANK;    
    constructor(){        
        owner = msg.sender;    
    }    
    struct client{        
        string name;        
        address clientOwner;        
        address bank;        
        uint clientType;        
        bool accountDebet;        
        bool accountCredit;        
        bytes32[] paymentInfo;        
        document[] documents;        
        uint balanceUsdt;        
        mapping(address=&gt;uint) tokenBalance;        
        uint balanceCredit;    
    }    
    struct document{        
        uint typeDocument;        
        string documentInfo;        
        address document;    
    }    
    mapping(address=&gt;mapping(uint=&gt;bool)) initSubscribe;    
    mapping(address=&gt;client) private clients;   
    mapping(address=&gt;address) oracles;
    modifier onlyOwner() {        
        require(owner == msg.sender, &quot;faild&quot;);        
        _;    
    }    
    modifier onlyOwnerBank(address sender){        
        require(ownersBank[sender] == true);        
        _;    
    }    
    function checkOwnerBank() public view returns(bool){        
        return ownersBank[msg.sender];    
    }    
    modifier checkTransfer(address sender, address to) {        
        require(clients[sender].clientOwner == sender, &quot;not client&quot;);        
        require(clients[to].clientOwner == to, &quot;not client&quot;);        
        require(msg.sender == BANK, &quot;not bank&quot;);        
        require(clients[sender].accountDebet == true, &quot;not open account&quot;);        
        require(clients[to].accountDebet == true, &quot;not open account&quot;);        
        require(clients[to].bank == clients[sender].bank, &quot;different bank&quot;);        
        _;    
    }    
    function transferBalance(uint amount, address token, address to, address sender) public checkTransfer(sender, to){        
        (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
        require(price &gt; 0, &quot;Invalid price&quot;);        
        unchecked {            
            // need decimals             
            clients[sender].balanceUsdt -= (amount / 1000000) * (uint(price) / 100);             
            clients[sender].tokenBalance[token] -= amount;            
            clients[to].balanceUsdt += (amount / 1000000) * (uint(price) / 100);            
            clients[to].tokenBalance[token] += amount;                    
        }    
    }    
    function addNeworacle(address token, address _oracle) public onlyOwner(){        
        oracles[token] = _oracle;    
    }        
    //updateBalanceInUSD()???...    
    //function transferFromBank()???...        
    function deposit(uint amount, address token, address sender) public {        
        require(BANK == msg.sender, &quot;not bank&quot;);        
        require(clients[sender].clientOwner == sender, &quot;not client&quot;);        
        require(clients[sender].accountDebet == true, &quot;not open account&quot;);        
        require(IERC20(token).allowance(sender, address(this)) &gt;= amount, &quot;chek allowance&quot;);        
        IERC20(token).transferFrom(sender, BANK, amount);        
        (, int256 price, , , ) = AggregatorV3Interface(oracles[token]).latestRoundData();        
        require(price &gt; 0, &quot;Invalid price&quot;);        
        clients[sender].balanceUsdt += (amount / 1000000) * (uint(price) / 100);        
        clients[sender].tokenBalance[token] += amount;
    }    
    function withdrawal(uint amount, address token, address oracle, address sender) public{        
        require(BANK == msg.sender, &quot;not bank&quot;);        
        require(clients[sender].clientOwner == sender, &quot;not client&quot;);        
        require(clients[sender].accountDebet == true, &quot;not open account&quot;);        
        require(IERC20(token).allowance(msg.sender, address(this)) &gt;= amount, &quot;chek allowance&quot;);        
        IERC20(token).transferFrom(msg.sender, sender, amount);        
        (, int256 price, , , ) = AggregatorV3Interface(oracle).latestRoundData();        
        require(price &gt; 0, &quot;Invalid price&quot;);        
        clients[sender].balanceUsdt -= (amount / 1000000) * (uint(price) / 100);        
        clients[sender].tokenBalance[token] -= amount;    
    }      
    function balanceTokenAccount(address tokenAddress, address oracle, address sender) public view returns(uint, uint){        
        require(clients[sender].clientOwner == sender, &quot;not client&quot;);        
        uint balance = IERC20(tokenAddress).balanceOf(sender);        
        (, int256 price, , , ) = AggregatorV3Interface(oracle).latestRoundData();        
        require(price &gt; 0, &quot;Invalid price&quot;);           
        return (balance, balance*(uint(price)/100));    
    }    
    function createBank() public{        
        BANK = msg.sender;    
    }    
    function setOwner(address _owner) public{        
        require(msg.sender == BANK);        
        ownersBank[_owner] = true;    }
    function initializeClient(string calldata _name, address sender) public{        
        require(BANK == msg.sender, &quot;not bank&quot;);        
        require(clients[sender].bank != msg.sender || clients[sender].clientOwner != sender, &quot;already exist&quot;);        
        client storage newClientInfo = clients[sender];        
        newClientInfo.name = _name;        
        newClientInfo.clientOwner = sender;        
        newClientInfo.bank = msg.sender;        
        newClientInfo.accountDebet = false;        
        newClientInfo.accountCredit = false;
        uint size;                
        assembly {            
        size := extcodesize(sender)        
    }        
    if (size &gt; 0) {            
        bytes4 expectedFunctionSignature = bytes4(keccak256(&quot;check(uint256,address)&quot;));            
        (bool success, ) = sender.call(abi.encodeWithSelector(expectedFunctionSignature, 1, sender));            
        require(success, &quot;faild&quot;);            
        if(success == true){                
            newClientInfo.clientType = 1;                
            newClientInfo.documents.push(document(0, &quot;entity person&quot;, sender));            
        }         
    } else{            
        newClientInfo.clientType = 0;            
        newClientInfo.documents.push(document(0, &quot;natural person&quot;, sender));        
    }        
    newClientInfo.paymentInfo.push(bytes32(keccak256(bytes(&quot;Create client&quot;))));        
    }    
    function getClientInfo(address _client) public view returns(string memory, address, address, bytes32[] memory, uint, uint, uint) {        
        return (clients[_client].name, clients[_client].clientOwner, clients[_client].bank, clients[_client].paymentInfo, clients[_client].balanceUsdt, clients[_client].tokenBalance[0xdAC17F958D2ee523a2206206994597C13D831ec7], clients[_client].tokenBalance[0x514910771AF9Ca656af840dff83E8264EcF986CA]);    
    }    
    function addAccountDebet(address sender) public{          
        require(sender == clients[sender].clientOwner, &quot;Not client&quot;);        
        initSubscribe[sender][0] = true;    
    }    
    function addAccountCredit(address sender) public{          
        require(sender == clients[sender].clientOwner);        
        initSubscribe[sender][1] = true;    
    }    
    function complitesubscribersAccount(address _client, uint typeAccount, address sender) public onlyOwnerBank(sender){        
        require(initSubscribe[_client][typeAccount] == true, &quot;not complite&quot;);        
        require(typeAccount == 0 || typeAccount == 1, &quot;error&quot;);        
        if(typeAccount == 0){            
            clients[_client].accountDebet = true;            
            initSubscribe[_client][typeAccount] = false;        
        }        
        if(typeAccount == 1){            
            clients[_client].accountCredit = true;            
            initSubscribe[_client][typeAccount] = false;        
        }    
    }    
    function deletAccountDebet(address sender) public{        
        require(sender == clients[sender].clientOwner);        
        require(clients[sender].accountDebet == true, &quot;not open&quot;);        
        initSubscribe[sender][0] = true;    
    }    
    function deletAccountCredit(address sender) public{        
        require(sender == clients[sender].clientOwner);        
        require(clients[sender].accountCredit == true, &quot;not open&quot;);        
        initSubscribe[sender][1] = true;    
    }    
    function comliteDeletAccount(address _client, uint typeAccount, address sender) public onlyOwnerBank(sender){
        require(initSubscribe[_client][typeAccount] == true, &quot;not complite&quot;);        
        require(typeAccount == 0 || typeAccount == 1, &quot;error&quot;);        
        if(typeAccount == 0){            
            clients[_client].accountDebet = false;            
            initSubscribe[_client][typeAccount] = false;        
        }        
        if(typeAccount == 1){                      
            clients[_client].accountCredit = false;            
            initSubscribe[_client][typeAccount] = false;        
        }    
    }       
}</pre>
  <p id="hNUp">В структура client я добавил пару новых полей: <br />адрес банка и отображение балансов в двух видах usd и в токенах</p>
  <p id="UrDB">ownersBank теперь хранит в себе сотрудников банка. То есть у них будут открыты возможности на подобии подтверждения/открытия счета клиента.</p>
  <p id="3K6t">Также добавил ораклы для просмотра цены токена в долларах. Добавил функционал отображения цены в долларах токенов и функцию для сохранения новых адресов ораклов для токенов.</p>
  <p id="PaUF">Добавил функцию для открытия банка createBank. Теперь владелец банка должен вызвать данную функцию чтоб указать свой банк - банком в контракте core.</p>
  <p id="f04O">Добавил пару промежуточных функций для проверки, например checkOwnerBank</p>
  <p id="lqKc">Добавил функционал депозита и вывода средств в банк, а также перевод внутри банка.<br />deposit withdrawal transferBalance - функции безналичного расчета.</p>
  <p id="v0Ae">Добавил модификатор для перевода, который проверяет, чтоб все данные сходились и разрешал выполнить перевод средств. </p>
  <p id="OJja">Также добавил контракт, который эти функции реализует, что то типа банка на минималках. В нем я буду проверять функционал всего вендора.</p>
  <p id="2jVA">Есть еще тесты, которые я написал, для проверки работы всех функций. </p>
  <p id="V1Pf">Если вы загляните на гитхаб, то увидите там еще один смарт-контракт swap. Он нужен только для тестов, чтоб можно было за эфир купить токены и проверить функционал deposit transferBalance.</p>
  <p id="aV54">Из интересного: так как ораклы показывают свою цены в своих decimals, то пришлось выравнивать эти значения с токенами. Пока что реализация кривая и только для usdt, в будущем сделаю по умному.</p>
  <p id="9Txv">balanceTokenAccount по идее выводит цену токенов в usd и сами токены. </p>
  <p id="yjdb">Вопрос с approve. Так как мы должны дать разрешение на списание токенов, то это уже нужно реализовывать через фронтенд банку, как это делает например uniswap. Внутри контракта это сделать не возможно. Главное понимать, что approve мы даем не банку а вендору (core), так как он осуществляет перевод. Таким образом я хочу добиться безопасности. То есть я хочу чтоб вендор был как контракт доверия всех людей, чтоб они понимали что банк их не обманет.</p>
  <p id="dLwG">Еще надо четко понимать, что каждый вызов функции контракта core идет от банка, то есть msg.sender всегда смарт-контракт в core. </p>
  <p id="2okF">В общем делать еще много, но основной функционал реализован, осталось подчистить код и решить пару проблем и с ядром АБС закончили.</p>
  <p id="pL9r">доп код контракта недо-банка:</p>
  <pre id="2F7L" data-lang="cpp">contract OtherContract {    
    address public coreAddress;     
    Core public coreInstance;     
    constructor(address _coreAddress) {        
        coreAddress = _coreAddress;        
        coreInstance = Core(coreAddress);    
    }    
    function checkClient(address client) public view returns(string memory,address, address, bytes32[] memory, uint, uint, uint){        
        return coreInstance.getClientInfo(client);    
    }    
    function setOwnerBank() public{        
        coreInstance.createBank();    
    }    
    function setOwnerClient(address owner_)  public{            
        coreInstance.setOwner(owner_);    
    }    
    function openAccount(address client, uint typeAccount) public{        
        coreInstance.complitesubscribersAccount(client, typeAccount, msg.sender);    
    }    
    function addAccountDebet() public{               
        coreInstance.addAccountDebet(msg.sender);    
    }    
    function newClient(string calldata _name) public{        
        coreInstance.initializeClient(_name, msg.sender);    
    }    
    function balance(address token) public view returns(uint){        
        return IERC20(token).balanceOf(msg.sender);    
    }
    function depositeToBank(uint amount, address token) public{        
        //approve frontend        
        coreInstance.deposit(amount, token, msg.sender);    
    }    
    function getWETHPriceInUSD(address oracle) public view returns (uint256) {        
        (, int256 price, , , ) = AggregatorV3Interface(oracle).latestRoundData();        
        require(price &gt; 0, &quot;Invalid price&quot;);        
        return uint256(price) / 100; //делим на 100 так как decimals оракла 8 а у usdt decimals 6 =&gt; 8-6=2 лишние нули    
    }     
    function transferBalanceInBank(uint amount, address token, address to) public{         
        coreInstance.transferBalance(amount, token, to, msg.sender);     
    }
    function checkAllowance(address token) public view returns(uint){        
        return IERC20(token).allowance(msg.sender, address(this));     
    }
}</pre>
  <p id="GV0t"></p>
  <p id="U030">Надеюсь кому-то интересна данная тема, потому что проект необычный и достаточно много функциональный, то есть очень много тем затрагивает, следовательно большой опыт знаний накапливается. Всем спасибо.</p>
  <p id="mtCD"></p>
  <p id="z3TP">Оставляю кому интересно:</p>
  <p id="0LxV"><a href="https://t.me/solidiks" target="_blank">Мой тг канал</a> <br /><a href="https://github.com/DDWorld-dev/vendor-ABS" target="_blank">Этот проект на гитхабе</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/EDiLZnUZVa9</guid><link>https://teletype.in/@ddworld/EDiLZnUZVa9?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/EDiLZnUZVa9?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>Создаем свою АБС на блокчейне </title><pubDate>Sat, 29 Jul 2023 21:22:05 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/5b/e5/5be5ebd8-22a7-4fd2-b792-7dddd41e0ec2.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img4.teletype.in/files/77/6d/776d1ca9-9c65-4c42-96f9-e6fdaafcd0bb.png"></img>Всем привет. Сидел я тут как-то и думал, а почему бы не сделать свою автоматизированную банковскую систему на блокчейне и соблюсти почти все правила учета и отчетности от ЦБ РФ. Мне показалось это интересно и возможным сделать. Разумеется ЦБ не примет эту систему, но как для практики программирования идея думаю не плохая.]]></description><content:encoded><![CDATA[
  <figure id="Pvek" class="m_column">
    <img src="https://img4.teletype.in/files/77/6d/776d1ca9-9c65-4c42-96f9-e6fdaafcd0bb.png" width="1063" />
  </figure>
  <p id="BHHM">Всем привет. Сидел я тут как-то и думал, а почему бы не сделать свою автоматизированную банковскую систему на блокчейне и соблюсти почти все правила учета и отчетности от ЦБ РФ. Мне показалось это интересно и возможным сделать. Разумеется ЦБ не примет эту систему, но как для практики программирования идея думаю не плохая.</p>
  <p id="aA19">Буду создавать все в лайв режиме, поэтому не знаю получиться сделать все что задумал.</p>
  <h2 id="HVga">Теория</h2>
  <p id="nlCh">Начну с теории и что нам нужно будет сделать. </p>
  <p id="JxWZ">АБС (автоматизированная банковская система) это совокупность аппаратных средств и программ для создания информационной среды, которая выполняет управленческие и финансовые задачи в условии реального времени у банка.</p>
  <p id="XxFd">Добавлю также, что не надо путать слова автоматизированные и автоматические. Автоматизированные, где человек принимает окончательное решение, а в автоматических машина.</p>
  <p id="Jpi6">То, что я делаю не может быть принято ЦБ, потому что есть нарушения с моей стороны ряда статей ФЗ &quot;О ЦБ РФ&quot;. Поэтому рассматриваем этот проект как просто тренировка и закрепление знаний.</p>
  <p id="r5NR">Также нужно понимать, что я не создаю банк. Я создаю вендор, который можно интегрировать (в теории) в свой банк на блокчейне, если вы его создадите. </p>
  <p id="Vvv0"><strong>Модули АБС:</strong></p>
  <p id="jABd">Немного о модулях АБС.</p>
  <p id="prMF">Фрон-офис - это взаимодействие с клиентом<br />Бэк-офис - это функционал системы <br />Мидл-офис - это функционал проводки или бух учет. </p>
  <figure id="jZZE" class="m_original">
    <img src="https://img3.teletype.in/files/62/45/62458744-2608-4057-a77b-d19a4d2ef120.png" width="484" />
    <figcaption>Секретная картинка модулей АБС</figcaption>
  </figure>
  <p id="OjBf">На данной картинке выделены основные модули, которые нам нужно будет по идее реализовать. Но как я уже говорил, я делаю все в лайв режиме и не знаю на данный момент что получиться сделать, а что нет.</p>
  <p id="Gq6z">Бухгалтерский учет будет является транзакциями в блокчейне, поэтому как такового бух учета у нас не будет. Но называть я буду его все равно так.</p>
  <p id="L2EB">Еще один из недостатков: у нас не будет наличного расчета :(.</p>
  <p id="9uSk">Расписывать функционал каждого модуля не буду, потому что долго и не нужно. Когда будем подходить к реализации модуля, тогда и буду его объяснять.</p>
  <p id="5q7N">Начало:</p>
  <p id="gt3j">Начнем с реализации ядра.</p>
  <p id="otGA">Модуль является функциональным ядром интегрированной банковской системы</p>
  <p id="BqhP">Что я реализую в ядре:</p>
  <p id="78Nz">1. Тип клиента<br />2. Ведение счетов<br />3. Платежный документооборот<br />4. Безналичные расчеты<br />5. Картотеки (дебетовые и кредитные карты)<br />6. Валютный учет(основой сделаем доллар)<br />7. Хранение всей информации о платежах<br />8. Учет и отчетность</p>
  <p id="k4KZ">В данной статье я реализую все кроме: <br />Валютный учет <br />Безналичные расчеты <br />Учет и отчетность</p>
  <h2 id="gnsW">Код</h2>
  <p id="1eF1">И так для начала кину весь код, который есть на данный момент:</p>
  <pre id="GPNp" data-lang="cpp">// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
contract Core {    
    address owner;    
    constructor(){        
        owner = msg.sender;    
    }    
    struct client{        
        string name;        
        address clientOwner;         
        uint clientType;         
        bool accountDebet;        
        bool accountCredit;        
        bytes32[] paymentInfo;        
        document[] documents;    
    }    
    struct document{        
        uint typeDocument;        
        string documentInfo;        
        address document;    
    }    
    mapping(address=&gt;mapping(uint=&gt;bool)) initSubscribe;    
    mapping(address=&gt;client) clients;    
    modifier onlyOwner() {        
        require(owner == msg.sender, &quot;faild&quot;);        
        _;    
    }    
    function initializeClient(string calldata _name) public payable{        
        client storage newClientInfo = clients[msg.sender];        
        newClientInfo.name = _name;        
        newClientInfo.clientOwner = msg.sender;               
        newClientInfo.accountDebet = false;        
        newClientInfo.accountCredit = false;
        uint size;        
        address addr = msg.sender;        
        assembly {            
            size := extcodesize(addr)        
        }        
        if (size &gt; 0) {            
            bytes4 expectedFunctionSignature = bytes4(keccak256(&quot;myFunction(uint256,address)&quot;));            
            (bool success, ) = addr.call(abi.encodeWithSelector(expectedFunctionSignature, 1, addr)); 
            require(success, &quot;faild&quot;);           
            if(success == true){                
                newClientInfo.clientType = 1;                
                newClientInfo.documents.push(document(0, &quot;entity person&quot;, msg.sender));            
            }         
        }else{                
            newClientInfo.clientType = 0;                
            newClientInfo.documents.push(document(0, &quot;natural person&quot;, msg.sender));            
        }         
        newClientInfo.paymentInfo.push(bytes32(keccak256(bytes(&quot;Create client&quot;))));        
    }    
    function addAccountDebet() public{          
        require(msg.sender == clients[msg.sender].clientOwner);        
        initSubscribe[msg.sender][0] = true;    
    }    
    function addAccountCredit() public{          
        require(msg.sender == clients[msg.sender].clientOwner);        
        initSubscribe[msg.sender][1] = true;    
    }    
    function complitesubscribersAccount(address _client, uint typeAccount) public onlyOwner{        
        require(initSubscribe[_client][typeAccount] == true, &quot;not complite&quot;);        
        require(typeAccount == 0 || typeAccount == 1, &quot;error&quot;);        
        if(typeAccount == 0){            
            clients[_client].accountDebet = true;            
            initSubscribe[_client][typeAccount] = false;        
        }        
        if(typeAccount == 1){            
            clients[_client].accountCredit = true;            
            initSubscribe[_client][typeAccount] = false;        
        }    
    }    
    function deletAccountDebet() public{        
        require(msg.sender == clients[msg.sender].clientOwner);        
        require(clients[msg.sender].accountDebet == true, &quot;not open&quot;);        
        initSubscribe[msg.sender][0] = true;    
    }    
    function deletAccountCredit() public{        
        require(msg.sender == clients[msg.sender].clientOwner);        
        require(clients[msg.sender].accountCredit == true, &quot;not open&quot;);        
        initSubscribe[msg.sender][1] = true;    
    }    
    function comliteDeletAccount(address _client, uint typeAccount) public onlyOwner{        
        require(initSubscribe[_client][typeAccount] == true, &quot;not complite&quot;);        
        require(typeAccount == 0 || typeAccount == 1, &quot;error&quot;);        
        if(typeAccount == 0){            
            clients[_client].accountDebet = false;            
            initSubscribe[_client][typeAccount] = false;        
        }        
        if(typeAccount == 1){                      
            clients[_client].accountCredit = false;            
            initSubscribe[_client][typeAccount] = false;        
        }    
    }       
}</pre>
  <p id="AzvO">Сразу говорю, что не буду размусоливать его. Буду показывать основные моменты и общую концепцию. Это не конечная версия кода, будет еще все меняться и добавляться.</p>
  <p id="Yiy0">Итак:</p>
  <p id="ZmJt">Для начала я создаю структуру client которая будет содержать все данные клиента. И структуру document, которая должна содержать информацию о ценных бумагах или важных расписках например.</p>
  <p id="Impe">Дальше создаю маппинг, который будет нужен для подтверждения открытия счета initSubscribe. И маппинг для общего сбора всех клиентов clients.</p>
  <p id="BtK2"><strong>Функции</strong></p>
  <p id="VP8b">Функция initializeClient будет нужна для регистрации клиента в банке. Будут ставиться дефолтные значения, которые в будущем можно менять открывая новые привилегии. Из интересного, я решил разделять юрлицо и физлицо на два типа: если клиент, это обычный адрес кошелька, он будет физлицом, если же смарт-контракт то юрлицо. Проверку делаю через ассемблер функцией проверки на наличие кода extcodesize. Но так как это не гарантирует, что вызов идет от смарт-контракта, я добавил условие, что каждый контракт должен иметь функцию check, которая вызывается при проверке низкоуровневым вызовом, чтоб убедиться в том, что данный вызов идет из стороннего смарт-контракта.</p>
  <p id="obew">Функции addAccountDebet, addAccountCredit, deletAccountDebet, deletAccountCredit нужны для запроса открытия/закрытия счета в банке.</p>
  <p id="qjXh">complitesubscribersAccount и comliteDeletAccount нужны для владельца который будет решать можно ли открыть данному лицу счет или нет</p>
  <p id="36pQ">Я создал смарт-контракт, который хочет открыть счет в банке, чтоб удостовериться в работе моей концепции.</p>
  <pre id="BL5E" data-lang="cpp">contract OtherContract {    
    address public coreAddress;     
    Core public coreInstance; 
    constructor(address _coreAddress) {        
        coreAddress = _coreAddress;        
        coreInstance = Core(coreAddress);    
    }    
    function check(uint num, address addr) public pure{
    }    
    function callInitializeClient(string calldata _name) external {        
        coreInstance.initializeClient(_name);    
    }
}</pre>
  <p id="GqlB">Тут все просто. Передаю адрес Core и после создаю экземпляр смарт-контракта. </p>
  <p id="Wwez">Дальше обязательно добавляем функцию check и функцию callInitializeClient, которая вызывает initializeClient</p>
  <p id="yhu8">Чтоб все проверить, я создал hardhat проект, где будут все тесты.</p>
  <p id="2M7U">На гитхабе будет все, кто хочет посмотреть, то ссылка в конце статьи.</p>
  <p id="Xidr">Если вы хотите проверить, или что то добавить пишите мне в тг. Его можно найти в профиле моего канала.</p>
  <p id="N8DG"></p>
  <p id="XT4L">На этом я закончу эту статью. Пока что нет ни чего особенного и интересного, но в голове строится необычная картина. На данный момент я рассказал все, что хотел. Дальше больше. Вроде как таких похожих проектов не видел ни где, поэтому надеюсь зайдет кому-то.</p>
  <p id="3svZ"></p>
  <p id="EE7R">Мой тг для связи в шапке канала<br /><a href="https://t.me/solidiks" target="_blank">Мой тг канал</a> <br /><a href="https://github.com/DDWorld-dev/vendor-ABS" target="_blank">Этот проект на гитхабе</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/9F7V8NOfz__</guid><link>https://teletype.in/@ddworld/9F7V8NOfz__?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/9F7V8NOfz__?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>Solidity | Flash Loans Aave</title><pubDate>Fri, 28 Apr 2023 20:57:01 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/05/97/0597978c-58ad-4c0c-8000-8b0db866aab1.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img2.teletype.in/files/9e/44/9e4498fe-e44b-488b-880a-f4bdab57b775.png"></img>Привет всем! Сегодня пойдет речь о флэш кредитах. Эта тема для меня показалось очень крутой и перспективной, потому что идея очень революционна в своем базисе. ]]></description><content:encoded><![CDATA[
  <figure id="8o0e" class="m_column">
    <img src="https://img2.teletype.in/files/9e/44/9e4498fe-e44b-488b-880a-f4bdab57b775.png" width="1063" />
  </figure>
  <p id="FDgz">Привет всем! Сегодня пойдет речь о флэш кредитах. Эта тема для меня показалось очень крутой и перспективной, потому что идея очень революционна в своем базисе. </p>
  <h3 id="7Yqm">Как работают флэш кредиты?</h3>
  <p id="hSkz">Flashloans - это моментальный кредит без залога, почти на любую сумму, так еще и можно сказать беспроцентный, ты отдаешь сверху всего 0.05% от той суммы, которую взял.</p>
  <p id="jStQ">Это стало возможным благодаря атомарности блокчейна. То есть все транзакции неделимы. Или как написано в википедии. Либо все, либо ничего. </p>
  <p id="4MQI">Именно поэтому такие протоколы как Aave могут предложить вам кредит на любую сумму в рамках одного блока. </p>
  <p id="srWs">Конечно есть и минусы. В следствие того, что мы должны взять кредит и отдать в рамках одного блока получается так, что эти деньги не на долго у нас в кармане. Поэтому пока мало идей как использовать flashloan. Чаще всего их используют для арбитража dex. </p>
  <p id="1pYH">Ну ладно. Эту информацию можно найти в интернете очень просто, поэтому приступим к написанию смарт-контракта для flashloans!</p>
  <p id="ienB">Если у вас нету node.js то скачиваем <a href="https://nodejs.org/en" target="_blank">тут</a> . Нам понадобиться команда npm от туда.</p>
  <p id="laC6">Устанавливаем последнюю версию npm</p>
  <pre id="mZ3m">npm install -g npm
npm -v</pre>
  <p id="DvvK">Для начала создаем проект hardhat, я думаю все уже знают как это сделать, а кто нет, то это очень просто. </p>
  <p id="GXJH">В своей папки проекта через cmd пишем 4 команды:</p>
  <pre id="Ubiu" data-lang="cmake">npm init
npm install --save-dev hardhat
npx hardhat
npm install --save-dev &quot;hardhat@^2.14.0&quot; &quot;@nomicfoundation/hardhat-toolbox@^2.0.0&quot;</pre>
  <p id="o4iH">Устанавливаем ядро aave, чтоб подгрузить все СК, которые импортируем</p>
  <pre id="DFyK">npm install @aave/core-v3</pre>
  <p id="ZFVX">В папке contracts пишем новый смарт-контракт, а тот который предоставлен удаляем. Назовем его SimpleFlashLoan.</p>
  <pre id="5lvk" data-lang="cpp">// SPDX-License-Identifier: MITpragma solidity ^0.8.9;
import &quot;@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol&quot;;
import &quot;@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol&quot;;
import &quot;@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol&quot;;
contract SimpleFlashLoan is FlashLoanSimpleReceiverBase {    
    address payable owner;    
    string public str;    
    constructor(address _addressProvider) FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider)) {        
        owner = payable(msg.sender);    
    }
    function fn_RequestFlashLoan(address _token, uint256 _amount) public {       
        address receiverAddress = address(this);        
        address asset = _token;        
        uint256 amount = _amount;        
        bytes memory params = &quot;&quot;;        
        uint16 referralCode = 0;
        POOL.flashLoanSimple(            
          receiverAddress,            
          asset,            
          amount,            
          params,            
          referralCode        
        );    
    }    
    function  executeOperation(        
      address asset,        
      uint256 amount,       
      uint256 premium,        
      address initiator,        
      bytes calldata params    
    )  external override returns (bool) {                
        str = &quot;NEW Logic in FlashLoans!&quot;;                
        uint256 totalAmount = amount + premium;        
        IERC20(asset).approve(address(POOL), totalAmount);
        return true;    
    }
    receive() external payable {}
}</pre>
  <p id="zmRl">Очень простой смарт-контракт из четырех функций.</p>
  <p id="OtHZ"><strong>Переменные смарт-контракта:</strong></p>
  <p id="K0xy">owner - владелец СК</p>
  <p id="GTf5">str - просто строка</p>
  <p id="ZEnN"><strong>Конструктор:</strong></p>
  <p id="Vu9Q">Наш конструктор будет вызывать конструктор из смарт-контракта FlashLoanSimpleReceiverBase, который мы наследуем. Внутри того конструктора, мы должны передать аргумент, адрес провайдера пула Aave. Где его брать покажу позже. </p>
  <p id="yOeG">Переменная _addressProvider внутри FlashLoanSimpleReceiverBase будет обернута в интерфейс IPoolAddressesProvider, после чего там будет вызвана функция getPool(), которая вернет нам адрес Proxy Pool (контракт, который реализует все функции флэш кредитов). И все это сохранится в переменную POOL, которую нужно обернуть в интерфейс IPOOL, как и наш адрес, чтоб получить доступ к функция Proxy Pool контракта.</p>
  <p id="I7c0">На самом деле с точки зрения механики этого процесса, это самое сложное тут. Но если не париться, то просто передаем в конструктор адрес провайдера пула.</p>
  <p id="8Qet"><strong>Функция fn_RequestFlashLoan</strong></p>
  <p id="vIkC">Это функция флэш кредита, которую мы будем вызывать.</p>
  <p id="vhN1">Как мы помним, наша переменная POOL имеет возможность вызывать функции FlashLoan. Мы будем использовать только flashLoanSimple. </p>
  <p id="9h4P">Если посмотреть в интерфейс, то мы увидим какие параметры нам нужно передать:</p>
  <pre id="9vxm" data-lang="cpp"> function flashLoanSimple(    
  address receiverAddress,    
  address asset,    
  uint256 amount,    
  bytes calldata params,    
  uint16 referralCode  
 ) external;
 </pre>
  <p id="FtJC">receiverAddress - получатель кредита, в нашем случае наш СК <br />address receiverAddress = address(this)</p>
  <p id="Gd8p">asset - по сути адрес токена, который мы хотим взять в долг (у нас это аргумент функции _token)</p>
  <p id="6ZQ7">amount - ну тут понятно, что это количество токенов, которое берем в долг.</p>
  <p id="o1QJ">params - это какая-то доп информация к кредиту, например сообщение или фраза. В нашем случае пустая строка. (Если будете что то передавать, то не забудьте перевести информацию в байты) </p>
  <p id="atVd">referralCode - я честно так и не понял смысл этого параметра. В интерфейсе написано, что это регистрация интегратора, инициирующего операцию, для потенциального вознаграждения. Я так и не понял как это реализовать, поэтому пишем 0, так как мы будем выполнять операцию сами.</p>
  <p id="O4Zp">Дальше через нашу любимую переменную POOL из FlashLoanSimpleReceiverBase вызываем функцию flashLoanSimple и передам все параметры.</p>
  <p id="pVdh"><strong>Функция executeOperation</strong></p>
  <p id="yNiB">Данная функция будет сама вызываться после того, как на счет нашего смарт-контракта придут средства.</p>
  <p id="xsy3">Эту функцию нужно обязательно реализовать, так как она будет возвращать кредит. Эту функцию мы переопределяем, поэтому ее называем именно так. Без нее при вызове функции fn_RequestFlashLoan будет ошибка.</p>
  <blockquote id="wsnj">P.S внутри flashLoanSimple вызывается функция executeFlashLoanSimple в которой есть require, в котором идем проверка на то, что существует и правильно задана функция executeOperation со всеми параметрами. Там это делается очень хитро, поэтому забрать себе все токены не получиться, придется возвращать ((</blockquote>
  <p id="ry5K">По факту там такие же переменные как и в fn_RequestFlashLoan, помимо premium, переменная, которая отвечает за процент кредита. На сегодня это 0.05% от суммы.</p>
  <p id="WPgZ">Внутри функции мы можем производить логику с этими токенами, которые мы взяли в кредит. Для примера я задаю значение строке. Да, это не связано с токенами, но для простоты, я показал где нужно реализовывать логику. Дальше мы считаем токены, которые мы должны вернуть</p>
  <blockquote id="G9f4">Да с подсчетом токенов обмануть не получиться, я пробовал</blockquote>
  <p id="94Pv">После мы даем approve через интерфейс IERC20, на списание токенов и там они переводятся на адрес Pool proxy.</p>
  <blockquote id="hvv7">Хоть 2 последних аргумента мы не используем, они нужны для этой функции, иначе будет ошибка</blockquote>
  <p id="VMBy">Ура наш смарт-контракт FlashLoan готов. Давайте его развернем в сети Sepolia и проверим его работоспособность.</p>
  <p id="ouIe"><strong>Теперь, где же брать адреса пулов и токенов:</strong></p>
  <p id="NkHf">Для тестнета <a href="https://docs.aave.com/developers/deployed-contracts/v3-testnet-addresses" target="_blank">тут</a></p>
  <p id="K1vT">Нам нужна вкладка sepolia </p>
  <p id="jOZb">PoolAddressesProvider-Aave 0x0496275d34753A48320CA58103d5220d394FF77F</p>
  <p id="25S2">Для маиннета <a href="https://docs.aave.com/developers/deployed-contracts/v3-mainnet" target="_blank">тут</a></p>
  <p id="EP2D"><strong>Мой скрипт деплоя:</strong></p>
  <pre id="DPaH" data-lang="javascript">const hre = require(&#x27;hardhat&#x27;);const ethers = hre.ethers;
const path = require(&#x27;path&#x27;);
async function main() {  
  const [deployer] = await ethers.getSigners();
  console.log(&quot;account deploy:&quot;, deployer.address);
  console.log(&quot;Account balance:&quot;, (await deployer.getBalance()).toString());
  const FlashLoan = await ethers.getContractFactory(&quot;SimpleFlashLoan&quot;); //название контракта
  const FlashLoanDeploy = await FlashLoan.deploy(&quot;0x0496275d34753A48320CA58103d5220d394FF77F&quot;);  
  await FlashLoanDeploy.deployed()  
  console.log(&quot;address:&quot;, FlashLoanDeploy.address);
}
main()  
.then(() =&gt; process.exit(0))  
.catch((error) =&gt; {   
   console.error(error)    
 process.exit(1)  
})</pre>
  <p id="d4fl">Самый обычный скрипт на деплой. Думаю объяснять тут нечего. </p>
  <p id="1c7M">Дальше как развернуть в любой сети наш смарт-контракт. </p>
  <p id="Rubh">Способов много, я пользуюсь своим:</p>
  <p id="WKu2">В качестве провайдера я использую <a href="https://www.alchemy.com/" target="_blank">Alchemy</a>.</p>
  <p id="Qcus">Регистрируемся и заходим в раздел apps - create app. </p>
  <figure id="YTUP" class="m_column">
    <img src="https://img1.teletype.in/files/8d/b7/8db7beaa-e6e9-485b-957e-983897eb313c.png" width="1918" />
  </figure>
  <p id="lEig">Тут выбираем сеть sepolia</p>
  <p id="ufVR">Нажимаем create App и видим Api key и все остальное. Копируем HTTPS ссылку </p>
  <p id="i9LG">Дальше я покажу мой hardhat.config.js</p>
  <pre id="0jyx" data-lang="javascript"> require(&quot;@nomicfoundation/hardhat-toolbox&quot;);
const PRIVATE_KEY = &quot;PRIVATE KEY&quot;
module.exports = {  
  solidity: &quot;0.8.17&quot;,  
  networks: {    
    sepolia: {      
      url: &quot;https://eth-sepolia.g.alchemy.com/v2/API KEY&quot;,      
      chainId: 11155111,     
      accounts: [        
        PRIVATE_KEY      
      ],    
  },    
    goerli: {      
      url: &quot;https://eth-goerli.g.alchemy.com/v2/API KEY&quot;,      
      chainId: 5,      
      accounts: [        
        PRIVATE_KEY      
      ],    
    },    
    main: {      
      url: &quot;https://eth-mainnet.g.alchemy.com/v2/API KEY&quot;,      
      chainId: 1,      
      accounts: [        
        PRIVATE_KEY      
      ],    
    },    
    polygon: {      
      url: &quot;https://polygon-mainnet.g.alchemy.com/v2/API KEY&quot;,      
      chainId: 137,      
      accounts: [        
        PRIVATE_KEY      
      ],    
    },  
  },
};</pre>
  <p id="kRk4">Вместо url вставляете скопированную ссылку HTTPS из alchemy.</p>
  <p id="crT5">Там где accounts вы должны вставить свой приватный ключ кошелька. </p>
  <p id="v4uv">Где его взять:</p>
  <p id="Quvb">Заходите в Metamask и нажимаете на Export Private key.</p>
  <figure id="gbVM" class="m_original">
    <img src="https://img2.teletype.in/files/d4/5b/d45b37bf-3818-40ba-8128-f25dbb4a8ff5.png" width="436" />
  </figure>
  <p id="13Uk">Там его копируете и в конфиг вставляете. </p>
  <p id="Mq4t">Все конфиг настрен, деплой скрипт готов.</p>
  <p id="tooM"><strong>Команда для запуска скрипта deploy.js в сети sepolia:</strong></p>
  <p id="xjlM">Если вы как я меняли deploy.js скрип в проекте hardhat, то вот команда для его запуска:</p>
  <pre id="6G6m"> npx hardhat run scripts\deploy.js --network sepolia</pre>
  <blockquote id="pOnM">P.S сеть можно использовать любую </blockquote>
  <p id="Enhg">Главное чтоб у вас на аккаунте, у которого вы взяли Private Key были тестовые токены sepolia.  </p>
  <p id="zsyL">Взять токены можно <a href="https://sepoliafaucet.com/" target="_blank">тут</a></p>
  <p id="L3gf">Если у вас все прошло четко, то ваш смарт-контракт уже в сети.</p>
  <p id="fnlS">Заходим во вкладку mempool alchemy </p>
  <figure id="P5MV" class="m_column">
    <img src="https://img3.teletype.in/files/e0/3c/e03ca4d5-a43f-428d-81fc-95e610ca6dd7.png" width="1920" />
  </figure>
  <p id="tIH6">Ваша транзакция в последней строчки или первая сверху </p>
  <p id="0Fwu">Нажимаете на HASH и потом переходим в transaction</p>
  <figure id="nxCt" class="m_column">
    <img src="https://img1.teletype.in/files/cb/45/cb45b122-3371-434f-9ea4-737da8e5e1b8.png" width="1372" />
  </figure>
  <p id="Qzx0">Там будет address TO, переходим и видим наш смарт-контракт в etherscan</p>
  <figure id="zTPK" class="m_column">
    <img src="https://img2.teletype.in/files/9c/c0/9cc0badc-1460-47f6-97f0-8693195232c6.png" width="1920" />
  </figure>
  <p id="rgkD">Во вкладке contracts нужно верифицировать ваш СК. </p>
  <p id="D2mm">Для этого нажимаем на Verify and Publish.</p>
  <figure id="o0kG" class="m_column">
    <img src="https://img1.teletype.in/files/cf/76/cf76d74d-9543-42a7-bcb4-ee66e3e50890.png" width="1920" />
  </figure>
  <p id="VQTQ">Выбираем single File </p>
  <p id="V4WF">Версию компилятора (Та которая у вас стоит в конфиге. У меня 0.8.17)</p>
  <p id="nrlF">Выбираем вид лицензии. (Я использую MIT)</p>
  <figure id="AqIr" class="m_column">
    <img src="https://img2.teletype.in/files/dc/ea/dceada70-07db-4146-9284-6a22d9da3550.png" width="1917" />
  </figure>
  <p id="mKcW">Дальше нам нужно вставить код смарт-контракта, но со всеми файлами, которые мы импортировали. Чтоб это сделать быстро и просто заходим в remix. Создаем новый файл и вставляем туда наш смарт-контракт</p>
  <figure id="47hg" class="m_column">
    <img src="https://img3.teletype.in/files/2c/09/2c091b63-c1ee-46ca-a27f-5be54dc15b4f.png" width="1920" />
  </figure>
  <p id="j1P4">Тыкаем на наш смарт-контракт и нажимаем на flatten. Таким образом мы получим все смарт-контракты в одном файле Flashloans_flattened.sol в моем случае.</p>
  <p id="nOUR">Заходим в него и первой строчкой добавляем лицензию. Он ее не добавляет. Это важно. После берем и все копируем.</p>
  <p id="SoHC">Потом все вставляем в etherscane.</p>
  <figure id="sz8M" class="m_column">
    <img src="https://img2.teletype.in/files/56/37/56378a01-0cf9-4944-b6c5-e0dabc736fb0.png" width="1920" />
  </figure>
  <p id="5kPj">Нажимаем на verify and publish и ждем. Если все правильно, то идем обратно в наш смарт-контракт на etherscan</p>
  <p id="Z1Ku">Чтоб затестить наш кредит мы должны передать чуть чуть токенов в наш контракт для комиссии кредита. Я буду использовать dai токены. </p>
  <p id="4pnS">Важно. AAVE используют свои токены для тестовых сетей и чтоб их получить идем <a href="https://gho.aave.com/reserve-overview/?underlyingAsset=0xe5118e47e061ab15ca972d045b35193f673bcc36&marketName=proto_sepolia_gho_v3" target="_blank">сюда </a></p>
  <p id="MF7e">У названия токена нажимаем кнопку и добавляем их токен dai в метамаск</p>
  <figure id="3qQe" class="m_column">
    <img src="https://img4.teletype.in/files/b1/32/b13225e9-387d-4b84-8d9e-5ac40f083632.png" width="1920" />
  </figure>
  <p id="qLKc">Нажимаем на подчеркнутую строчку и получим их токены DAI</p>
  <figure id="Mr1X" class="m_original">
    <img src="https://img1.teletype.in/files/c9/0f/c90f3ad8-2004-43e5-b8f1-9503ccf6ab3c.png" width="436" />
    <figcaption>1000 токенов получите</figcaption>
  </figure>
  <p id="S8UR">ЭТО не настоящий DAI, а аля DAI от AAVE. Их создали для упрощения тестирования и, чтоб в их тестовых пулах было достаточно токенов для всех.</p>
  <p id="jqgO">Переходим на смарт-контракт токена dai и переводим чуть чуть токенов (я переведу 1 токен) на наш смарт-контракт flashloan по его адресу.</p>
  <figure id="9qmY" class="m_column">
    <img src="https://img2.teletype.in/files/dc/60/dc60c20c-8db4-4a0e-a665-fcbd95f52099.png" width="1920" />
  </figure>
  <blockquote id="ZXvy">amount в decimals, поэтому так много нулей. </blockquote>
  <p id="MBKm">Когда перевели токены на смарт-контракт flashloans, наконец, мы можем взять кредит!</p>
  <p id="EKc6">Возвращаемся в наш смарт-контракт и вызываем функцию fn_RequestFlashLoan</p>
  <p id="1KIU">Аргумент _token это адрес токена dai: </p>
  <p id="TbT2">dai - 0x68194a729C2450ad26072b3D33ADaCbcef39D574</p>
  <p id="EvtJ">amount я взял 100 dai</p>
  <p id="OFrQ"> _amount  - 1000000000000000000000</p>
  <figure id="tw5O" class="m_column">
    <img src="https://img3.teletype.in/files/64/8c/648cec1a-0c79-4469-9daf-981cfe3dcbb8.png" width="1912" />
  </figure>
  <p id="3Ub8">После выполнения транзакции идем смотреть ее в etherscan</p>
  <p id="bBwR">Если все прошло успешно то мы увидим:</p>
  <figure id="c067" class="m_column">
    <img src="https://img1.teletype.in/files/80/c1/80c1f02f-01cd-4de5-8369-aaa6d84e7a70.png" width="1920" />
  </figure>
  <p id="aR1m">ERC20 Tokens Transferred: На наш смарт-контракт перевели 100 токенов dai и потом их забрали с процентами. Все работает ура!</p>
  <p id="D5p4">Если вы зайдете в Read Contract и откроете строчку, то там будет наше сообщение, которое мы установили в функции executeOperation. </p>
  <p id="RtP5"></p>
  <p id="wHFe">На этом думаю можно закончить. У нас получилось реализовать Flashloan, это может не каждый рядовой пользователь как сказали AAVE в своей документации. Так что мы чуть чуть круче. Дальше больше. Возможно потом покажу как можно свапать токены которые мы взяли в кредит. Тоже интересно. Удачи!</p>
  <p id="TNCW"></p>
  <p id="H7Wy">tg: <a href="https://t.me/solidiks" target="_blank">мой телеграмчик)</a></p>
  <p id="D0TI">github: <a href="https://github.com/DDWorld-dev/FlashLoans" target="_blank">этот проект на гит хабе</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/1UlsL_uG4S-</guid><link>https://teletype.in/@ddworld/1UlsL_uG4S-?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/1UlsL_uG4S-?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>Solidity | ERC721Upgradeable</title><pubDate>Sun, 09 Apr 2023 17:51:28 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/10/a7/10a7d317-3ed1-4630-83cb-a282ccb2664e.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img4.teletype.in/files/f0/f2/f0f2c3a5-4e5f-4eaf-8bd0-401d553ec493.png"></img>Привет всем! Сегодня пойдет о самом популярном стандарте который есть на данный момент, это ERC721 или в простонародье NFT. Так как я понимаю, что это ну уж очень заезженная тема, я решил раскрыть ее немного с другой стороны, а именно, через upgrades.]]></description><content:encoded><![CDATA[
  <figure id="dquu" class="m_column">
    <img src="https://img4.teletype.in/files/f0/f2/f0f2c3a5-4e5f-4eaf-8bd0-401d553ec493.png" width="1063" />
  </figure>
  <p id="nvKZ">Привет всем! Сегодня пойдет о самом популярном стандарте который есть на данный момент, это ERC721 или в простонародье NFT. Так как я понимаю, что это ну уж очень заезженная тема, я решил раскрыть ее немного с другой стороны, а именно, через upgrades.</p>
  <p id="fcfT">Да я понимаю, что уже третья статья про proxy и upgradeable СК, но все таки тут будет достаточно полезно это улучшение. Ведь может быть случай, что вы создали свою коллекцию и она так завирусилась, что вы хотите например добавить функционал холдерам NFT и выдавать разные бонусы за их активность. Но если вы не сделали вашу NFT коллекцию обновляемой, то могут возникнуть сложности.</p>
  <p id="cvnj">Расскажу одну вещь для общего развития. Из за своей популярности NFT, многие стали думать, что NFT это картинка. НО, я хочу быть уверенным, что вы так не думаете. Потому что NFT, это <strong>non-fungible token </strong>или невзаимозаменяемые токены. То есть это токены, которые не могут быть поделены на части, и поэтому у них нет decemals, как у ЕРС20. Да, эта особенность позволила нам прикреплять за токеном какое то изображение, звук, видео или еще что-то, а можно и не прикреплять, в любом случае,  это не меняет его сущность этих токенов</p>
  <p id="B5NV">К чему я вел это долгое вступление?</p>
  <p id="W53s">Очень много людей стали хейтить разработчиков NFT за то, что они хранят данные прикреплённые к токену, например изображения, на внешних платформах, таких как filecoin и другие. Мол это ни фига не NFT. Они же их хранят в базе данных и каждый может получить к ним доступ. Формально это так, но нужно понимать, что хранение изображений в блокчейне будет стоить космических денег. Поэтому была и предложена эта реализация хранения. Причем если смотреть глубже, то чаще всего используется протокол ipfs, который генерирует ссылку через хэшировние криптографическими методами, для получения уникального и не повторимого cid, который уже используется в NFT. Сорян подгорело. У меня все. </p>
  <h3 id="BSPg">IERC721Upgradeable</h3>
  <pre id="MahN" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import &quot;./IERC165Upgradeable.sol&quot;;
interface IERC721Upgradeable is IERC165Upgradeable {       
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function safeTransferFrom(        
      address from,        
      address to,        
      uint256 tokenId,        
      bytes calldata data    
    ) external;
    function safeTransferFrom(        
      address from,        
      address to,        
      uint256 tokenId    
    ) external;
    function transferFrom(        
      address from,        
      address to,        
      uint256 tokenId    
    ) external;
    function approve(address to, uint256 tokenId) external;
    function setApprovalForAll(address operator, bool _approved) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function isApprovedForAll(address owner, address operator) external view returns (bool);}</pre>
  <p id="gUAv">Это обычный интерфейс ERC721 ни чего особенного. Даже чем то похож на ERC20. Я не буду объяснять что здесь за функции, и сделаю это по мере реализации стандарта дальше. Думаю так будет понятнее.</p>
  <h3 id="YVmV">ERC721Upgradeable</h3>
  <pre id="rWST" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import &quot;./IERC721Upgradeable.sol&quot;;
import &quot;./IERC721ReceiverUpgradeable.sol&quot;;
import &quot;./IERC721MetadataUpgradeable.sol&quot;;
import &quot;./utils/AddressUpgradeable.sol&quot;;
import &quot;./utils/ContextUpgradeable.sol&quot;;
import &quot;./utils/StringsUpgradeable.sol&quot;;
import &quot;./ERC165Upgradeable.sol&quot;;
import &quot;./utils/Initializable.sol&quot;;
contract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {    using AddressUpgradeable for address;    using StringsUpgradeable for uint256;
    string private _name;
    string private _symbol;
    mapping(uint256 =&gt; address) private _owners;
    mapping(address =&gt; uint256) private _balances;
    mapping(uint256 =&gt; address) private _tokenApprovals;
    mapping(address =&gt; mapping(address =&gt; bool)) private _operatorApprovals;
    function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {        
        __ERC721_init_unchained(name_, symbol_);    
    }
    function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {        
        _name = name_;        
        _symbol = symbol_;    
    }
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {        
        return            
          interfaceId == type(IERC721Upgradeable).interfaceId ||            
          interfaceId == type(IERC721MetadataUpgradeable).interfaceId ||            
          super.supportsInterface(interfaceId);    
    }
    function balanceOf(address owner) public view virtual override returns (uint256) {        
        require(owner != address(0), &quot;ERC721: address zero is not a valid owner&quot;);        
        return _balances[owner];    
    }
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {       
        address owner = _ownerOf(tokenId);        
        require(owner != address(0), &quot;ERC721: invalid token ID&quot;);        
        return owner;    
    }
    function name() public view virtual override returns (string memory) {        
        return _name;    
    }
    function symbol() public view virtual override returns (string memory) {        
        return _symbol;    
    }
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {        
        _requireMinted(tokenId);
        string memory baseURI = _baseURI();        
        return bytes(baseURI).length &gt; 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : &quot;&quot;;    
    }
    function _baseURI() internal view virtual returns (string memory) {        
        return &quot;&quot;;    
    }
    function approve(address to, uint256 tokenId) public virtual override {       
        address owner = ERC721Upgradeable.ownerOf(tokenId);        
        require(to != owner, &quot;ERC721: approval to current owner&quot;);
        require(            
        _msgSender() == owner || isApprovedForAll(owner, _msgSender()), 
         &quot;ERC721: approve caller is not token owner or approved for all&quot;        );
        _approve(to, tokenId);    
    }
    function getApproved(uint256 tokenId) public view virtual override returns (address) {        
        _requireMinted(tokenId);
        return _tokenApprovals[tokenId];    
    }
    function setApprovalForAll(address operator, bool approved) public virtual override {        
        _setApprovalForAll(_msgSender(), operator, approved);    
    }
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {        
        return _operatorApprovals[owner][operator];    
    }
    function transferFrom(        
      address from,       
      address to,        
      uint256 tokenId    
    ) public virtual override {        
        require(_isApprovedOrOwner(_msgSender(), tokenId), &quot;ERC721: caller is not token owner or approved&quot;);
        _transfer(from, to, tokenId);   
    }
    function safeTransferFrom(        
      address from,        
      address to,       
      uint256 tokenId    
    ) public virtual override {        
        safeTransferFrom(from, to, tokenId, &quot;&quot;);    
    }
    function safeTransferFrom(        
      address from,        
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) public virtual override {       
        require(_isApprovedOrOwner(_msgSender(), tokenId), &quot;ERC721: caller is not token owner or approved&quot;);        
        _safeTransfer(from, to, tokenId, data);    
    }
    function _safeTransfer(        
      address from,       
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) internal virtual {       
        _transfer(from, to, tokenId);       
        require(_checkOnERC721Received(from, to, tokenId, data), &quot;ERC721: transfer to non ERC721Receiver implementer&quot;);    
    }
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {        
        return _owners[tokenId];    
    }
    function _exists(uint256 tokenId) internal view virtual returns (bool) {        
        return _ownerOf(tokenId) != address(0);    
    }
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {       
        address owner = ERC721Upgradeable.ownerOf(tokenId);        
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);    
    }      
    function _safeMint(address to, uint256 tokenId) internal virtual {        
        _safeMint(to, tokenId, &quot;&quot;);    
    }
    function _safeMint(        
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) internal virtual {        
        _mint(to, tokenId);        
        require(            
        _checkOnERC721Received(address(0), to, tokenId, data),            
        &quot;ERC721: transfer to non ERC721Receiver implementer&quot;);    
    }
   function _mint(address to, uint256 tokenId) internal virtual {        
        require(to != address(0), &quot;ERC721: mint to the zero address&quot;);        
        require(!_exists(tokenId), &quot;ERC721: token already minted&quot;);
        _beforeTokenTransfer(address(0), to, tokenId, 1);
        require(!_exists(tokenId), &quot;ERC721: token already minted&quot;);
        unchecked {           
            _balances[to] += 1;        
        }
        _owners[tokenId] = to;
        emit Transfer(address(0), to, tokenId);
        _afterTokenTransfer(address(0), to, tokenId, 1);    
    }
    function _burn(uint256 tokenId) internal virtual {       
        address owner = ERC721Upgradeable.ownerOf(tokenId);
        _beforeTokenTransfer(owner, address(0), tokenId, 1);
        owner = ERC721Upgradeable.ownerOf(tokenId);
        delete _tokenApprovals[tokenId];
        unchecked {           
            _balances[owner] -= 1;        
        }        
        delete _owners[tokenId];
        emit Transfer(owner, address(0), tokenId);
        _afterTokenTransfer(owner, address(0), tokenId, 1);    
    }
    function _transfer(        
      address from,       
      address to,        
      uint256 tokenId    
    ) internal virtual {        
        require(ERC721Upgradeable.ownerOf(tokenId) == from, &quot;ERC721: transfer from incorrect owner&quot;);        
        require(to != address(0), &quot;ERC721: transfer to the zero address&quot;);
        _beforeTokenTransfer(from, to, tokenId, 1);
        require(ERC721Upgradeable.ownerOf(tokenId) == from, &quot;ERC721: transfer from incorrect owner&quot;);        delete _tokenApprovals[tokenId];
        unchecked {           
            _balances[from] -= 1;           
            _balances[to] += 1;        
        }        
        _owners[tokenId] = to;
        emit Transfer(from, to, tokenId);
        _afterTokenTransfer(from, to, tokenId, 1);    
    }
    function _approve(address to, uint256 tokenId) internal virtual {       
        _tokenApprovals[tokenId] = to;        
        emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);    
    }
    function _setApprovalForAll(        
      address owner,        
      address operator,       
      bool approved    
    ) internal virtual {        
      require(owner != operator, &quot;ERC721: approve to caller&quot;);        
      _operatorApprovals[owner][operator] = approved;        
      emit ApprovalForAll(owner, operator, approved);    
    }
    function _requireMinted(uint256 tokenId) internal view virtual {        
        require(_exists(tokenId), &quot;ERC721: invalid token ID&quot;);    
    }
    function _checkOnERC721Received(        
      address from,        
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) private returns (bool) {        
        if (to.isContract()) {            
            try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {               
                return retval == IERC721ReceiverUpgradeable.onERC721Received.selector;            
            } catch (bytes memory reason) {                
                if (reason.length == 0) {                    
                    revert(&quot;ERC721: transfer to non ERC721Receiver implementer&quot;);               
                } else {                    
                    assembly {                        
                        revert(add(32, reason), mload(reason))                    
                    }                
                }            
            }        
        } else {            
            return true;        
        }
    }
    function _beforeTokenTransfer(        
        address from,        
        address to,       
         uint256 firstTokenId,        
         uint256 batchSize   
    ) internal virtual {}
    function _afterTokenTransfer(        
      address from,        
      address to,       
      uint256 firstTokenId,        
      uint256 batchSize    
    ) internal virtual {}
    function __unsafe_increaseBalance(address account, uint256 amount) internal {       
        _balances[account] += amount;    
    }    
    uint256[44] private __gap;
 }</pre>
  <p id="KrxB"><strong>Маппинги:</strong></p>
  <pre id="oRKR">mapping(uint256 =&gt; address) private _owners;
mapping(address =&gt; uint256) private _balances;
mapping(uint256 =&gt; address) private _tokenApprovals;
mapping(address =&gt; mapping(address =&gt; bool)) private _operatorApprovals;</pre>
  <p id="DQkk">Первый маппинг будет хранить адрес владельца токена по его индексу.</p>
  <p id="8bvV">Второй маппинг будет хранить баланс токенов у владельца </p>
  <p id="W4dZ">Третий маппинг будет хранить индекс токена на перевод по адресу</p>
  <p id="9p6x">Четвертый маппинг будет хранить разрешение на перевод всех токенов сразу у его владельца (этих токенов), какому то человеку. </p>
  <p id="eXvR">Пройдемся по функциям основного контракта:</p>
  <p id="t647"><strong>__init__ </strong></p>
  <pre id="lxLI" data-lang="cpp"> function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {        
        __ERC721_init_unchained(name_, symbol_);    
    }
    function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {        
        _name = name_;        
        _symbol = symbol_;    
    }</pre>
  <p id="Qp58">Эти функции нам нужны для определения названия и символа токенов, потому что в обновляемых смарт-контрактах как мы знаем нельзя использовать конструктор.</p>
  <p id="L9Eh"><strong>supportsInterface</strong></p>
  <pre id="uHlh" data-lang="cpp"> function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {        
        return            
          interfaceId == type(IERC721Upgradeable).interfaceId ||            
          interfaceId == type(IERC721MetadataUpgradeable).interfaceId ||            
          super.supportsInterface(interfaceId);    
    }</pre>
  <p id="PFQb">Регистрирует контракт как средство реализации интерфейса, определенного селектора interfaceId. Просто вернет информацию о том, что СК поддерживает интерфейсы IERC721Upgradeable и IERC721MetadataUpgradeable. </p>
  <p id="vJrc"><strong>balanceOf и ownerOf</strong></p>
  <pre id="CEQZ" data-lang="cpp">function balanceOf(address owner) public view virtual override returns (uint256) {        
        require(owner != address(0), &quot;ERC721: address zero is not a valid owner&quot;);        
        return _balances[owner];    
    }
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {       
        address owner = _ownerOf(tokenId);        
        require(owner != address(0), &quot;ERC721: invalid token ID&quot;);        
        return owner;    
    }
    function _exists(uint256 tokenId) internal view virtual returns (bool) {        
        return _ownerOf(tokenId) != address(0);    
    }</pre>
  <p id="wrDh">Функция balanceOf вернет баланс токенов у адреса. Функция ownerOf вернет владельца токена, вызвав _ownerOf и проверит, что адрес не нулевой. Все происходит через маппинги. </p>
  <p id="Aln2"><strong>name и symbol</strong></p>
  <pre id="6ZTr">  function name() public view virtual override returns (string memory) {        
        return _name;    
    }
    function symbol() public view virtual override returns (string memory) {        
        return _symbol;    
    }</pre>
  <p id="V8la">Вернут имя и символ токена как в ERC20</p>
  <p id="HnpT"><strong>Функции tokenURI и _baseURI</strong></p>
  <pre id="BC8n"> function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {        
        _requireMinted(tokenId);
        string memory baseURI = _baseURI();        
        return bytes(baseURI).length &gt; 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : &quot;&quot;;    
    }
    function _baseURI() internal view virtual returns (string memory) {        
        return &quot;&quot;;    
    }</pre>
  <p id="Q62I">_baseURI это общее начало у ссылки на вашу картинку если простым языком. tokenURI возвращает картинку к которой прикреплен данный токен. Это так сказать наши метаданные. Ведь все любят NFT не за то, что они являются не взаимозаменяемыми токенами, а за то, что они дают нам возможность прикреплять за ними свое &quot;искусство&quot; и делать его уникальным. Функцию _requireMinted напишем позже, но она просто проверяет, что такой токен существует.</p>
  <p id="KdBI"><strong>Approve функции</strong></p>
  <pre id="OMZ0" data-lang="cpp">function approve(address to, uint256 tokenId) public virtual override {       
        address owner = ERC721Upgradeable.ownerOf(tokenId);        
        require(to != owner, &quot;ERC721: approval to current owner&quot;);
        require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()), 
         &quot;ERC721: approve caller is not token owner or approved for all&quot;);
        _approve(to, tokenId);    
    }
    function getApproved(uint256 tokenId) public view virtual override returns (address) {        
        _requireMinted(tokenId);
        return _tokenApprovals[tokenId];    
    }
    function setApprovalForAll(address operator, bool approved) public virtual override {        
        _setApprovalForAll(_msgSender(), operator, approved);    
    }
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {        
         return _operatorApprovals[owner][operator];    
    }
     function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {       
        address owner = ERC721Upgradeable.ownerOf(tokenId);        
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);    
    } 
    function _approve(address to, uint256 tokenId) internal virtual {       
        _tokenApprovals[tokenId] = to;        
        emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);    
    }
    function _setApprovalForAll(        
      address owner,        
      address operator,       
      bool approved    
    ) internal virtual {        
      require(owner != operator, &quot;ERC721: approve to caller&quot;);        
      _operatorApprovals[owner][operator] = approved;        
      emit ApprovalForAll(owner, operator, approved);    
    }</pre>
  <p id="BrYQ">approve функция как и в ERC20  дает нам разрешение на списывание токенов с нашего аккаунта. В начале передаем переменной owner владельца токена, который мы передали, после чего мы проверяем является ли человек, который вызвал функцию владельцем данного токена. Если да то вызывается функция _approve, где мы даем разрешение адресу to. </p>
  <p id="PDGR">getApproved просто вернет адрес, кому был дан апрув. С проверкой, что такой токен существует. </p>
  <p id="KQK9">setApprovalForAll устанавливает разрешение на перевод всех токенов, которые есть у человека. То есть если у вас 5 nft данного проекта, то вы даете разрешение на перевод всех пяти токенов. Вызывает внутри функцию _setApprovalForAll, где мы можем либо дать разрешение на все токены, либо убрать. (По маппингу это делаем)</p>
  <p id="wdRB">_isApprovedOrOwner будет проверять: дано разращение или нет. Обращается к getApproved и к isApprovedForAll, таким образом мы узнаем информацию сразу про все случаи разрешения. И так же узнает является ли владелец токена его владельцем как не странно.</p>
  <p id="Pqkw"><strong>Функции transfer</strong> </p>
  <pre id="Imn4" data-lang="cpp"> function transferFrom(        
      address from,       
      address to,        
      uint256 tokenId    
    ) public virtual override {        
        require(_isApprovedOrOwner(_msgSender(), tokenId), &quot;ERC721: caller is not token owner or approved&quot;);
        _transfer(from, to, tokenId);   
    }
    function safeTransferFrom(        
      address from,        
      address to,       
      uint256 tokenId    
    ) public virtual override {        
        safeTransferFrom(from, to, tokenId, &quot;&quot;);    
    }
    function safeTransferFrom(        
      address from,        
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) public virtual override {       
        require(_isApprovedOrOwner(_msgSender(), tokenId), &quot;ERC721: caller is not token owner or approved&quot;);        
        _safeTransfer(from, to, tokenId, data);    
    }
    function _safeTransfer(        
      address from,       
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) internal virtual {       
        _transfer(from, to, tokenId);       
        require(_checkOnERC721Received(from, to, tokenId, data), &quot;ERC721: transfer to non ERC721Receiver implementer&quot;);    
    }
    function _transfer(        
      address from,       
      address to,        
      uint256 tokenId    
    ) internal virtual {        
        require(ERC721Upgradeable.ownerOf(tokenId) == from, &quot;ERC721: transfer from incorrect owner&quot;);        
        require(to != address(0), &quot;ERC721: transfer to the zero address&quot;);
        _beforeTokenTransfer(from, to, tokenId, 1);
        require(ERC721Upgradeable.ownerOf(tokenId) == from, &quot;ERC721: transfer from incorrect owner&quot;);        delete _tokenApprovals[tokenId];
        unchecked {           
            _balances[from] -= 1;           
            _balances[to] += 1;        
        }        
        _owners[tokenId] = to;
        emit Transfer(from, to, tokenId);
        _afterTokenTransfer(from, to, tokenId, 1);    
    }
    </pre>
  <p id="Fd85">transferFrom Переводит токен, и проверяет дано было разрешение или нет через _isApprovedOrOwner. Ну и вызывает _transfer. Там снова идет проверка, что данный вызов от овенра токена и то, что получатель не нулевой адрес. _beforeTokenTransfer это функция каких то идей перед переводом, но их нет, и дальше смотрим, что эти идеи не привели к тому, что владелец токена поменялся (а вдруг идеи есть). После просто переводим токен. В маппенге владельцев мы меняем владельца токена. Сорри за многочисленную тавтологию. Ну и там мы можем вызвать функцию _afterTokenTransfer идей, которых нет после перевода как всегда.</p>
  <p id="ZKYT">safeTransferFrom делает все тоже самое, но эту функцию рекомендуют использовать по возможности вместо transferFrom из за одной особенности:</p>
  <p id="LCLO"><strong>_checkOnERC721Received</strong></p>
  <pre id="rGJO" data-lang="cpp">function _checkOnERC721Received(        
      address from,        
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) private returns (bool) {        
        if (to.isContract()) {            
            try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {               
                return retval == IERC721ReceiverUpgradeable.onERC721Received.selector;            
            } catch (bytes memory reason) {                
                if (reason.length == 0) {                    
                    revert(&quot;ERC721: transfer to non ERC721Receiver implementer&quot;);               
                } else {                    
                    assembly {                        
                        revert(add(32, reason), mload(reason))                    
                    }                
                }            
            }        
        } else {            
            return true;        
        }
    }</pre>
  <p id="CNnH">Данная особенность в safeTransferFrom заключается в том, что мы проверяем случай перевода на другой СК, так как мы помнем, что смарт-контракты также как и адресы кошельков могут владеть токенами. И если такой случай произошел. (isContract из прошлой статьи). Мы будем смотреть, что смарт-контракт, который хочет перевести токены поддерживает интерфейс IERC721ReceiverUpgradeable. Если да, то вызывается функция onERC721Received, и возвращается селектор, этот селектор должен быть равен селектору onERC721Received, если это не произошло, то мы откатываем транзакцию. И через ассемблер получаем причину отката. Это нужно для того, чтоб мы внутри СК, который делает этот вызов не делали разные пакости при переводе.</p>
  <p id="WsxR"><strong>Функции mint  </strong></p>
  <pre id="VAfg" data-lang="cpp"> function _safeMint(address to, uint256 tokenId) internal virtual {        
        _safeMint(to, tokenId, &quot;&quot;);    
    }
    function _safeMint(        
      address to,        
      uint256 tokenId,        
      bytes memory data    
    ) internal virtual {        
        _mint(to, tokenId);        
        require(            
        _checkOnERC721Received(address(0), to, tokenId, data),            
        &quot;ERC721: transfer to non ERC721Receiver implementer&quot;);    
    }
   function _mint(address to, uint256 tokenId) internal virtual {        
        require(to != address(0), &quot;ERC721: mint to the zero address&quot;);        
        require(!_exists(tokenId), &quot;ERC721: token already minted&quot;);
        _beforeTokenTransfer(address(0), to, tokenId, 1);
        require(!_exists(tokenId), &quot;ERC721: token already minted&quot;);
        unchecked {           
            _balances[to] += 1;        
        }
        _owners[tokenId] = to;
        emit Transfer(address(0), to, tokenId);
        _afterTokenTransfer(address(0), to, tokenId, 1);    
    }
    function _burn(uint256 tokenId) internal virtual {       
        address owner = ERC721Upgradeable.ownerOf(tokenId);
        _beforeTokenTransfer(owner, address(0), tokenId, 1);
        owner = ERC721Upgradeable.ownerOf(tokenId);
        delete _tokenApprovals[tokenId];
        unchecked {           
            _balances[owner] -= 1;        
        }        
        delete _owners[tokenId];
        emit Transfer(owner, address(0), tokenId);
        _afterTokenTransfer(owner, address(0), tokenId, 1);    
    }
     function _requireMinted(uint256 tokenId) internal view virtual {        
        require(_exists(tokenId), &quot;ERC721: invalid token ID&quot;);    
    }</pre>
  <p id="42tG">_safeMint это функция чеканки новых токенов. Мы можем делать ограничение или его не делать (supply). Но это уже надо в своем СК если хотите. А мы же вызываем _mint, где и происходит чеканка. Так же мы не забываем проверить, что функцию минт не хотят хакнуть, через _checkOnERC721Received</p>
  <blockquote id="xhVf">Второй _safeMint дает нам возможность еще передать данные data при минте</blockquote>
  <p id="5Gqy">Минт работает по принципу: кто вызвал, тот и получил токен. Перед пополнением баланса мы должны сделать ряд проверок, что вызываемый адрес не нулевой, что такой токен по id не был уже заминчен. Ну и установить владельца данного токена через маппинг.</p>
  <p id="uO3D">_burn логично, что удаляет токены у того, кто вызвал эту функцию.</p>
  <p id="omjt">_requireMinted, функция, которая помогает нам определить является ли этот токен уже заминченым.</p>
  <p id="GS2F"><strong>функции ownerOf и exists</strong></p>
  <pre id="afaT" data-lang="cpp">function _ownerOf(uint256 tokenId) internal view virtual returns (address) {        
        return _owners[tokenId];    
    }</pre>
  <p id="85iX">exists вернет true или false, если токен существует или нет.</p>
  <p id="LL8n">В целом весь стандарт реализован</p>
  <p id="cmIA"><strong>ERC721URIStorageUpgradeable</strong></p>
  <pre id="rG7E" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import &quot;./ERC721Upgradeable.sol&quot;;
import &quot;./utils/Initializable.sol&quot;;
abstract contract ERC721URIStorageUpgradeable is Initializable, ERC721Upgradeable {    
    function __ERC721URIStorage_init() internal onlyInitializing {    
    }
    function __ERC721URIStorage_init_unchained() internal onlyInitializing {    
    }    
    using StringsUpgradeable for uint256;
    mapping(uint256 =&gt; string) private _tokenURIs;
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {        
        _requireMinted(tokenId);
        string memory _tokenURI = _tokenURIs[tokenId];        
        string memory base = _baseURI();
        if (bytes(base).length == 0) {            
            return _tokenURI;        
        }        
        if (bytes(_tokenURI).length &gt; 0) {            
            return string(abi.encodePacked(base, _tokenURI));        
        }
        return super.tokenURI(tokenId);    
    }
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {        
        require(_exists(tokenId), &quot;ERC721URIStorage: URI set of nonexistent token&quot;);        
        _tokenURIs[tokenId] = _tokenURI;    
    }
    function _burn(uint256 tokenId) internal virtual override {        
        super._burn(tokenId);
        if (bytes(_tokenURIs[tokenId]).length != 0) {            
            delete _tokenURIs[tokenId];        
        }    
    }
    uint256[49] private __gap;
}</pre>
  <p id="3sDG">Этот контракт помогает нам хранить метаданные по id токена. </p>
  <pre id="gKfU" data-lang="cpp">mapping(uint256 =&gt; string) private _tokenURIs;</pre>
  <p id="jDhM">Хранит по маппингу ссылку на метаданные токена.</p>
  <p id="TJBz">tokenURI делает тоже самое, что и в самом стандарте.</p>
  <p id="2Fle">_setTokenURI устанавливает токену ссылку на метаданные.</p>
  <p id="kbCY">_burn стирает метаданные из токена. Таким образом можно перезаписать их в случае необходимости.</p>
  <p id="HsaZ"></p>
  <p id="nokS">В целом это весь стандарт. Конечно тут можно добавить кучу дополнений, но мы говорим про сам стандарт, поэтому без воды.</p>
  <p id="SHVJ"></p>
  <h3 id="AeHu">Пишем свой ERC721Upgradeabl проект.</h3>
  <p id="IgLd">Так как я уже говорил ранее, что не вижу смысла изобретать велосипед в proxy СК, то мы будем брать готовое решение из openzeppelin. </p>
  <p id="5U25">Для создания своего проекта будем использовать <a href="https://docs.openzeppelin.com/contracts/4.x/wizard" target="_blank">Wizard</a> </p>
  <pre id="Mdzm" data-lang="cpp"> // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; 
 import &quot;@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol&quot;; 
 import &quot;@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol&quot;; 
 import &quot;@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol&quot;; 
 import &quot;@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol&quot;; 
 import &quot;@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol&quot;; 
 import &quot;@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol&quot;; 
 contract MyToken is Initializable, ERC721Upgradeable, ERC721URIStorageUpgradeable, OwnableUpgradeable, UUPSUpgradeable { 
     using CountersUpgradeable for CountersUpgradeable.Counter;
     CountersUpgradeable.Counter private _tokenIdCounter;
      
     function initialize() initializer public { 
         __ERC721_init(&quot;MyToken&quot;, &quot;MTK&quot;); 
         __ERC721URIStorage_init(); 
         __Ownable_init(); 
         __UUPSUpgradeable_init(); 
     } 
     function _baseURI() internal pure override returns (string memory) { 
         return &quot;https://&quot;; 
     }
     function safeMint(address to, string memory uri) public onlyOwner { 
         uint256 tokenId = _tokenIdCounter.current(); 
         _tokenIdCounter.increment();
         _safeMint(to, tokenId); 
         _setTokenURI(tokenId, uri); 
     } 
     function _authorizeUpgrade(address newImplementation) internal onlyOwner override {}
     function _burn(uint256 tokenId) internal override(ERC721Upgradeable, ERC721URIStorageUpgradeable) { 
         super._burn(tokenId); 
     } 
     function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable, ERC721URIStorageUpgradeable) returns (string memory) { 
         return super.tokenURI(tokenId); 
     } 
}</pre>
  <p id="LiQj">Чтоб все библиотеки подтянулись мы должны импортировать их </p>
  <pre id="OSrj" data-lang="javascript"> npm install @openzeppelin/contracts-upgradeable</pre>
  <p id="OQoj">Так же, чтоб работали скрипты для деплоя </p>
  <pre id="2WbA" data-lang="javascript"> npm install --save-dev @openzeppelin/hardhat-upgrades</pre>
  <p id="fhEI">И импортируем в hardhat.config.js</p>
  <pre id="Bdq0" data-lang="javascript"> require(&#x27;@openzeppelin/hardhat-upgrades&#x27;);</pre>
  <p id="mh7E">Я надеюсь что такое hardhat объяснять не надо, но если что, то у меня есть уже статьи про это. Или посмотрите документация про деплой через hardhat <a href="https://docs.openzeppelin.com/learn/developing-smart-contracts" target="_blank">тут</a></p>
  <p id="yQ8c">Так вот сверху уже готовый NFT смарт-контракт для деплоя. </p>
  <p id="uv4f">Первая переменная будет счетчиком наших токенов. Это структура Counter с одной переменной uint из СК CounterUpgradeable. И так же мы для нее подключили библиотеку c математическими операциями.</p>
  <p id="jqtb">Вместо конструктора для деплоя мы используем функцию initialize, где передаем название и символ токена.</p>
  <p id="0m7C">_baseURI вернет начало ссылки на токен. Ну я поставил https:// но можно и свое.</p>
  <p id="Vbvs">safeMint функция покупки нфт, или чеканка. Можно ее использовать для своих минтинг сайтов. Внутри мы прибавляем на один _tokenIdCounter и разумеется в переменную tokenId передаем номер NFT. После мы просто вызываем функцию safeMint, где привязываем к адресу to текущий id NFT. Ну и устанавливаем uri для того, чтоб мы могли ей передать метаданные.</p>
  <p id="yN1U">_authorizeUpgrade обязательная функция для UUPS proxy как мы помним.</p>
  <p id="zPuT">tokenURI возвращает метаданные токена по id, например чтоб вывести их на экран на сайте. </p>
  <blockquote id="BQTQ">super означает вызвать функцию из дочернего контракта, где она есть.</blockquote>
  <p id="vayv">В целом как деплоить обновляемые контракты я показывал в прошлой статье, поэтому не буду повторяться, но могу сказать что тут все ровно тоже самое</p>
  <p id="dgBO"></p>
  <p id="PImu"></p>
  <p id="7mTr">Думаю на этом все. Сегодня я рассказал про ERC721, но не про обычную его реализацию а про upgradeable. Дальше больше. Я считаю это на много интереснее, чем читать про обычный заезженный стандарт, который уже каждый второй об шерстил вдоль и поперек. Удачи!</p>
  <p id="xDU1"></p>
  <p id="RByI">Полезные ссылки:</p>
  <p id="kC1c"><a href="https://docs.openzeppelin.com/contracts/4.x/wizard" target="_blank">Wizard</a></p>
  <p id="tXAp"><a href="https://docs.openzeppelin.com/learn/developing-smart-contracts" target="_blank">Как деплоить СК</a></p>
  <p id="DAtT"><a href="https://docs.openzeppelin.com/upgrades-plugins/1.x/hardhat-upgrades" target="_blank">Деплой через upgade </a></p>
  <p id="2AOm"><a href="https://docs.openzeppelin.com/contracts/3.x/upgradeable" target="_blank">Деплой ERC721Upgradeable</a></p>
  <p id="DFoe"><a href="https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#IERC721Receiver-onERC721Received-address-address-uint256-bytes-" target="_blank">Стандарт ERC721</a></p>
  <p id="4kKi"></p>
  <p id="EHgC">tg: <a href="https://t.me/solidiks" target="_blank">мой телеграмчик)</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@ddworld/CZ9N3e0fTRI</guid><link>https://teletype.in/@ddworld/CZ9N3e0fTRI?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld</link><comments>https://teletype.in/@ddworld/CZ9N3e0fTRI?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=ddworld#comments</comments><dc:creator>ddworld</dc:creator><title>Solidity | ERC1967 upgradeable Proxy </title><pubDate>Fri, 07 Apr 2023 22:28:24 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/30/42/304292bd-dd9d-4185-bcdc-5d486fb9ebe6.png"></media:content><category>Solidity</category><description><![CDATA[<img src="https://img1.teletype.in/files/45/31/45315040-c73d-4b80-a9bc-a81c2ce796a3.png"></img>Всем привет, сегодня пойдет речь о proxy, и стандарте ERC1967. В прошлой статье, а уже рассказывал про этот паттерн, но в этот раз мы подробно рассмотрим его и напишем сами с нуля.]]></description><content:encoded><![CDATA[
  <figure id="o20u" class="m_column">
    <img src="https://img1.teletype.in/files/45/31/45315040-c73d-4b80-a9bc-a81c2ce796a3.png" width="1063" />
  </figure>
  <p id="SHFT">Всем привет, сегодня пойдет речь о proxy, и стандарте ERC1967. В прошлой статье, а уже рассказывал про этот паттерн, но в этот раз мы подробно рассмотрим его и напишем сами с нуля.</p>
  <p id="VnH9">Начну с того, что есть два типа proxy upgradeable smart-contract. Первый тип это Transparent, а второй UUPS. </p>
  <p id="JVF6">Transparent более старый тип, он работает по принципу  все обновление и администрирования происходит в самом proxy. Это более затратно по газу, но более понятно с точки зрения логики.</p>
  <p id="RLqW">UUPS более новый тип и более актуальный на данный момент. Он работает по принципу обновление и администрирования происходит в самой реализации. То есть все функции для обновления и администрирования находятся в самом смарт контракте, а не в proxy. По факту мы увидим, что для нашего смарт-контракта, который мы хотим обновлять нам понадобиться просто унаследовать контракт UUPSUpgradeable, и реализовать все функции для обновления. </p>
  <p id="FZlD">Как вы поняли мы идем в ногу со временем, и поэтому рассмотрим второй тип proxy.</p>
  <p id="IYQF">Начнем по порядку. У нас будет много разных смарт-контрактов и давайте я вам объясню для чего каждый из них будет нужен, а потом мы их разберем.</p>
  <p id="G7nS">Proxy.sol - этот смарт-контракт вы может помните из прошлой статьи. Он реализует наш вызов delegatecall, на СК</p>
  <p id="1Euz">initializable.sol - этот смарт-контракт позволит нам создать псевдо конструктор, об этом по подробнее позже.</p>
  <p id="d1ts">ERC1967Upgrade.sol - смарт-контракт, который реализует все наши функции установки и обновления Implementation и слотов для них. </p>
  <p id="fqSa">ERC1967.sol - смарт-контракт, который реализует обновляемый proxy, то есть наследует все из ERC1967Upgrade.sol</p>
  <p id="AGG6">Address.sol - смарт-контракт, который будет нам помогать проверять адрес СК и делегировать вызовы. В общем небольшая утилита.</p>
  <p id="1Ubk">StorageSlot.sol - смарт-контракт, который поможет с записью и чтением информации в слоты, чтоб избежать конфликтов с обновляемыми СК. </p>
  <blockquote id="3mGH">P.S если не понятно о каких слотах идет речь и об их проблеме поймете по позже</blockquote>
  <p id="dxf0">Так же понадобиться интерфейс IERC1822.sol, для проверки адреса Implementation</p>
  <p id="pmIU">Разбор контрактов</p>
  <h3 id="HTmm">Proxy</h3>
  <pre id="dtjE" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract Proxy {
    function _delegate(address implementation) internal virtual {        
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result            
            case 0 {                
                revert(0, returndatasize())            
            }            
            default {                
                return(0, returndatasize())            
            }        
        }    
    }    
    function _implementation() internal view virtual returns (address);
    
    function _fallback() internal virtual {        
        _beforeFallback();        
        _delegate(_implementation());    
    }
    fallback() external payable virtual {        
        _fallback();    
    }
    receive() external payable virtual {        
        _fallback();    
    }
    function _beforeFallback() internal virtual {}
}</pre>
  <p id="DWqG">Об этом СК я уже рассказывал, и поэтому не буду подробно останавливаться. Здесь есть функция _delegate, которая на низкоуровневом языке делает вызов delegatecall и таким образом вызывает функции у Implementation, то есть у нашего СК. Чтоб мы могли вызывать все функции СК, то мы используем fallback функцию, которая вызывается всегда, когда в смарт-контракт попадает функция, которой не существует. И таким не хитрым способом Proxy получает доступ ко всем функциям нашего СК.</p>
  <h3 id="ZPLf">Address</h3>
  <pre id="UL3W" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library Address {        
    function isContract(address account) internal view returns (bool) {
        return account.code.length &gt; 0;    
    }
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {        
        return functionDelegateCall(target, data, &quot;Address: low-level delegate call failed&quot;);    
    }
    function functionDelegateCall(        
        address target,        
        bytes memory data,        
        string memory errorMessage    
    ) internal returns (bytes memory) {        
        (bool success, bytes memory returndata) = target.delegatecall(data);        
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);    
    }
    function verifyCallResultFromTarget(        
      address target,        
      bool success,       
      bytes memory returndata,        
      string memory errorMessage    
    ) internal view returns (bytes memory) {        
        if (success) {            
            if (returndata.length == 0) {                            
                require(isContract(target), &quot;Address: call to non-contract&quot;);            
            }            
            return returndata;        
        } else {            
            _revert(returndata, errorMessage);        
        }    
    }
    function _revert(bytes memory returndata, string memory errorMessage) private pure {        
        if (returndata.length &gt; 0) {            
            assembly {                
                let returndata_size := mload(returndata)                
                revert(add(32, returndata), returndata_size)            
            }        
        } else {            
            revert(errorMessage);        
        }    
    }
}</pre>
  <p id="il0k">На самом деле в openzeppelin там еще есть функции в этом СК, но они нам не нужны, поэтому я оставил только те, которые мы будем использовать.</p>
  <p id="LLiy">isContract - эта функция проверяет на то, что адрес является смарт-контрактом. account.code.length должен быть больше нуля если это СК, а если это просто адрес, то разумеется размер кода равен нулю.</p>
  <p id="W9hq">functionDelegateCall - вызывает в себе functionDelegateCall, это своего рода перегрузка функции, но это не важно, важно то, что это обычный delegatecall, на адрес который мы передали. Дальше мы можем увидеть, что идет <br />return verifyCallResultFromTarget</p>
  <p id="lVX0">verifyCallResultFromTarget - функция, которая будет проверять наш результат после delegatecall. Как она это делает: Если переменная success равна true, то идем дальше, если нет то откат через функцию  revert (о ней позже), если все таки true, то мы дальше начинаем проверять наши данные, которые вернули после вызова, если они равны нулю, то может возникнуть вопрос, а это вообще адрес смарт-контракта? Поэтому через require мы проверяем что наш адрес это СК, через функцию isContract из Address.sol. Если же данные есть, то мы точно знаем, что это адрес СК и возвращаем их. </p>
  <p id="82J0">А если у нас не прошел delegatecall, то мы попадаем в функцию revert.</p>
  <p id="GwDK">Тут мы смотрим, если у нас данные больше нуля, то мы делаем махинацию на assembly. Я сейчас постараюсь объяснить что происходит, но если вы на знакомы с тем как храниться динамические данные в солидити может показаться не понятно. </p>
  <p id="TDAd">let returndata_size := mload(returndata) тут мы в переменную загружаем первые 32 байта из returndata, потому что функция mload считывает первые 32 байта. Так как returndata имеет тип bytes memory, то первые 32 байта, являются длиной  returndata (длина 32 байта). Таким образом переменная returndata_size становиться указателем на длину returndata. Надеюсь понятно. </p>
  <p id="3KUe">revert(add(32, returndata), returndata_size) функция revert работает по принципу вернуть данные начиная с такого то места и с такой то длиной данных, так как мы знаем что первые 32 байта указывают на длину, то мы их пропускаем с помощью add(32, returndata) (add это сумма) и возвращаем данные, которые лежат после 32 байт длины. Таким образом мы вернем причину отката и сделаем откат. Ну а если данных нет, то просто причину вернем errorMessage и все.</p>
  <h3 id="4qf0">Теперь еще один вспомогательный контракт, это StorageSlot</h3>
  <pre id="3dke" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library StorageSlot {    
    struct AddressSlot {        
        address value;    
    }
    struct BooleanSlot {        
       bool value;    
    }        
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {                
        assembly {            
            r.slot := slot        
        }   
    }
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {               
        assembly {            
            r.slot := slot        
        }    
    }
}</pre>
  <p id="zT4Y">Тут тоже на много больше функций чем я покажу, но я не вижу смысла нагружать бесполезной инфой. У нас есть 2 простые структуры. И есть 2 функции, которые возвращают через ассемблер данные находящиеся в том или ином слоте в памяти, но вернее сказать слоты в storage. Первый вернет адрес на том месте, второй bool переменную. Все просто.</p>
  <h3 id="z1rf">Initializable</h3>
  <pre id="hyxF" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import &quot;../utils/Address.sol&quot;;
abstract contract Initializable {       
    uint8 private _initialized;
    bool private _initializing;
    
    event Initialized(uint8 version);
    
    modifier initializer() {        
        bool isTopLevelCall = !_initializing;        
        require(            
          (isTopLevelCall &amp;&amp; _initialized &lt; 1) || (!Address.isContract(address(this)) &amp;&amp; _initialized == 1),
          &quot;Initializable: contract is already initialized&quot;        
        );        
        _initialized = 1;        
        if (isTopLevelCall) {            
            _initializing = true;        
        }        
        _;        
        if (isTopLevelCall) {           
             _initializing = false;            
             emit Initialized(1);        
        }    
    }
    modifier reinitializer(uint8 version) {        
        require(!_initializing &amp;&amp; _initialized &lt; version,
         &quot;Initializable: contract is already initialized&quot;);
        _initialized = version;       
        _initializing = true;        
        _;        
        _initializing = false;        
        emit Initialized(version);    
    }
    modifier onlyInitializing() {        
        require(_initializing, &quot;Initializable: contract is not initializing&quot;);        
        _;    
    }
    function _disableInitializers() internal virtual {        
        require(!_initializing, &quot;Initializable: contract is initializing&quot;);        
        if (_initialized != type(uint8).max) {            
            _initialized = type(uint8).max;            
            emit Initialized(type(uint8).max);        
        }    
    }
    function _getInitializedVersion() internal view returns (uint8) { 
        return _initialized;    
    }
    function _isInitializing() internal view returns (bool) {        
        return _initializing;    
    }
}</pre>
  <p id="mPqZ">Тут мы просто создаём модификаторы для того, чтоб функцию можно было вызвать только 1 раз. То есть псевдо конструктор. initializer это и делает. Как видно внутри модификатора, сначала идет проверка require, что наша функция еще не была вызвана ни разу. Для этого переменная _initialized должна быть меньше нуля и isTopLevelCall true. isTopLevelCall просто как флаг для того, чтоб не возможно было вызвать функцию саму в себе и перевызвать функцию(псевдо конструктор). Надеюсь понятно. Таким образом мы создали псевдо конструктор.</p>
  <p id="pnK8">Модификатор reinitializer работает так же, но для новых версий вашего СК. Главное отличие в том, что мы смотрим чтоб новая версия _initialized была больше предыдущей.</p>
  <p id="N5Ar">onlyInitializing просто смотрит был вызван конструктор или нет. Если нет то откат.</p>
  <p id="3jAU">_disableInitializers работает по принципу блокировки СК, если нам хочется предотвратить повторную инициализацию,  поставив максимальное число uint8 = 255, то мы больше не сможем инициализировать функции. Что логично, ведь в reinitializer есть условие что версия должна быть больше предыдущей. И в initializer, что переменная  _initialized меньше одного.</p>
  <p id="TY8H">Ну и две функции. Одна возвращает версию, другая возвращает _initializing переменную(True или False), то есть инициализирован контракт или нет.</p>
  <h3 id="wNzz">Интерфейс IERC1822</h3>
  <pre id="zTGb" data-lang="cpp">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC1822Proxiable {    
    function proxiableUUID() external view returns (bytes32);
}</pre>
  <p id="3Gzv">Просто возвращает слот хранения адреса implementation для UUPS proxy</p>
  <h3 id="wIvq">ERC1967Upgrade</h3>
  <pre id="DasR" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import &quot;../utils/Address.sol&quot;;
import &quot;../utils/StorageSlot.sol&quot;;
import &quot;../interfaces/draft-IERC1822.sol&quot;;
abstract contract ERC1967Upgrade {     
    bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    event Upgraded(address indexed implementation);
    function _getImplementation() internal view returns (address) {        
        return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;    
    }
    function _setImplementation(address newImplementation) private {        
        require(Address.isContract(newImplementation), 
        &quot;ERC1967: new implementation is not a contract&quot;);       
        StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;    
    }
    function _upgradeTo(address newImplementation) internal {        
        _setImplementation(newImplementation);        
        emit Upgraded(newImplementation);    
    }
    function _upgradeToAndCall(        
      address newImplementation,        
      bytes memory data,        
      bool forceCall    
    ) internal {        
        _upgradeTo(newImplementation);        
        if (data.length &gt; 0 || forceCall) {              
            Address.functionDelegateCall(newImplementation, data);       
        }    
    }
    
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {        
        if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {            
            _setImplementation(newImplementation);        
        } else {            
            try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {                
                require(slot == _IMPLEMENTATION_SLOT, &quot;ERC1967Upgrade: unsupported proxiableUUID&quot;);            
            } catch {                
                revert(&quot;ERC1967Upgrade: new implementation is not UUPS&quot;);            
            }           
            _upgradeToAndCall(newImplementation, data, forceCall);        
         }    
         
    }       
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    event AdminChanged(address previousAdmin, address newAdmin);
    function _getAdmin() internal view returns (address) {        
        return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;    
    }
    function _setAdmin(address newAdmin) private {       
        require(newAdmin != address(0), &quot;ERC1967: new admin is the zero address&quot;);        
        StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;   
    }
    function _changeAdmin(address newAdmin) internal {        
        emit AdminChanged(_getAdmin(), newAdmin);        
        _setAdmin(newAdmin);    
    }
   uint256[50] private __gap;
}</pre>
  <p id="ajoH">Тут у меня тоже не все написано, потому что в openzeppelin есть такая реализация, как beacon, которую я не использую. </p>
  <p id="kHcj">Первая и вторая переменная constant, это номер слота переменных, которые специально спрятаны, чтоб избежать взломов и атак на наш proxy. Дальше мы увидим как они используются. То есть переменные под этими слотами мы не знаем где находятся и поэтому к ним сложно добраться.</p>
  <blockquote id="LWZC">Чтоб их получить мы делали что то на подобии этого : <br />bytes32 private constant implementationPosition = bytes32(uint256( keccak256(&#x27;eip1967.proxy.implementation&#x27;)) - 1 )); </blockquote>
  <p id="qFto">Функции:</p>
  <p id="qm5x"><strong>_getImplementation</strong></p>
  <pre id="Pulr" data-lang="cpp">function _getImplementation() internal view returns (address) {        
        return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;    
    }</pre>
  <p id="P53s">При помощи нашего StorageSlot достает из нашего слота адрес implementation.</p>
  <p id="5Uc1"><strong>_setImplementation</strong></p>
  <pre id="LHPl" data-lang="cpp">function _setImplementation(address newImplementation) private {        
        require(Address.isContract(newImplementation), 
        &quot;ERC1967: new implementation is not a contract&quot;);       
        StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;    
    }</pre>
  <p id="ECZF">При помощи Address проверяем что наш новый адрес, это СК. И просто в наш слот  _IMPLEMENTATION_SLOT устанавливаем новый адрес. </p>
  <p id="Gam3"><strong>_upgradeTo</strong></p>
  <pre id="jtip" data-lang="cpp">function _upgradeTo(address newImplementation) internal {        
        _setImplementation(newImplementation);        
        emit Upgraded(newImplementation);    
    }</pre>
  <p id="ETgV">Функция установки нового implementation. Просто вызываем _setImplementation так как она private.</p>
  <p id="fvHN"><strong>_upgradeToAndCall</strong></p>
  <pre id="nV3z" data-lang="cpp">function _upgradeToAndCall(        
      address newImplementation,        
      bytes memory data,        
      bool forceCall    
    ) internal {        
        _upgradeTo(newImplementation);        
        if (data.length &gt; 0 || forceCall) {              
            Address.functionDelegateCall(newImplementation, data);       
        }    
    }</pre>
  <p id="SZAw">В целом тоже самое что и _upgradeTo, но мы можем еще передать data, и благодаря этому сделать низкоуровневый вызов, обычно используется во время установки implemenation и вызывает функцию initialize, как конструктор. Если <br />data = 0, то мы пропускаем этот вызов. ForceCall работает так, что даже если data=0, все равно делать delegatecall. Зачем это я так и не понял, потому что почти всегда используют false у данной переменной.</p>
  <p id="r8Lv"><strong>_upgradeToAndCallUUPS</strong></p>
  <pre id="2CB2" data-lang="cpp">function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {        
        if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {            
            _setImplementation(newImplementation);        
        } else {            
            try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {                
                require(slot == _IMPLEMENTATION_SLOT, &quot;ERC1967Upgrade: unsupported proxiableUUID&quot;);            
            } catch {                
                revert(&quot;ERC1967Upgrade: new implementation is not UUPS&quot;);            
            }           
            _upgradeToAndCall(newImplementation, data, forceCall);        
         }    
         
    }     </pre>
  <p id="mJOD">Функция, которая обновляет implementation для UUPS. Тут мы смотрим на _ROLLBACK_SLOT, если в нем находится true переменная, то устанавливаем implementation первый раз, если нет, то это означает, что это новая версия СК и нужно добавить пару проверок. При помощи try catch мы пытаемся, обращаясь через интерфейс от лица нового implementation к функции proxiableUUID (ее напишем позже), узнать слот хранения implementation. Это нужно делать чтоб у нас получилось вернуть значение. Если у нас не получиться это сделать, значит может быть такое, что кто то делегирует эту функцию (_upgradeToAndCallUUPS) саму в себя и тем самым блокирует контракт. И еще slot, он должен быть равен слоту _IMPLEMENTATION_SLOT. Если же не получилось, то просто откат. И только потом, когда все проверки прошли, мы уже обновляем наш implementation адрес.</p>
  <p id="I35q">Дальше слот хранения админа СК.</p>
  <p id="7fzh">Админ функции </p>
  <pre id="1Cof" data-lang="cpp">  function _getAdmin() internal view returns (address) {        
        return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;    
    }
    function _setAdmin(address newAdmin) private {       
        require(newAdmin != address(0), &quot;ERC1967: new admin is the zero address&quot;);        
        StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;   
    }
    function _changeAdmin(address newAdmin) internal {        
        emit AdminChanged(_getAdmin(), newAdmin);        
        _setAdmin(newAdmin);    
    }</pre>
  <p id="csQB">Ну тут все просто. _getAdmin по слоту возвращает админа. _setAdmin проверяет, что новый адрес не нулевой и устанавливает в _ADMIN_SLOT нового овнера. _changeAdmin меняет админа, при помощи _setAdmin.</p>
  <p id="CZQ9">uint256[50] private __gap строчка, которая гарантирует, что не будет проблем со слотами, не будет наложений и смещений. Грубо говоря это резервация пространства для новых переменных в бедующих обновлениях. То есть в данном случает у нас есть целых 50 свободных слотов.</p>
  <p id="8Tys"><strong>ERC1967</strong></p>
  <pre id="lEo4" data-lang="cpp">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import &quot;../Proxy.sol&quot;;
import &quot;./ERC1967Upgrade.sol&quot;;

contract ERC1967Proxy is Proxy, ERC1967Upgrade {    
    uint8 private _initializd;    uint256 public x1;    
    constructor(address _logic, bytes memory _data) payable {        
        _upgradeToAndCall(_logic, _data, false);    
    }    
    function _implementation() internal view virtual override returns (address impl) {        
        return ERC1967Upgrade._getImplementation();    
    }    
}</pre>
  <p id="rOvd">Это наш Proxy СК. Тут мы реализуем конструктор, в котором вызываем _upgradeToAndCall и устанавливаем implementation. </p>
  <p id="BKSG">Ну и _implementation функция, которая возвращает наш адрес СК.</p>
  <p id="6YYi">По идее всю логику нужно реализовать тут, но это не советуют делать и для этого  создают СК, который наследует этот и там уже все реализуют.</p>
  <h3 id="rz74"><strong>UUPSUpgradeable</strong> </h3>
  <pre id="TGZk" data-lang="cpp"> // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import &quot;./interfaces/draft-IERC1822.sol&quot;;
import &quot;./ERC1967/ERC1967Upgrade.sol&quot;;
import &quot;./utils/Initializable.sol&quot;;
abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade, Initializable {    
    function __UUPSUpgradeable_init() internal onlyInitializing {    
    }
    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {    
    }    
    address private immutable __self = address(this);
    modifier onlyProxy() {        
        require(address(this) != __self, 
        &quot;Function must be called through delegatecall&quot;);        
        require(_getImplementation() == __self, 
        &quot;Function must be called through active proxy&quot;);        
        _;   
    }
    modifier notDelegated() {         
         require(address(this) == __self, 
         &quot;UUPSUpgradeable: must not be called through delegatecall&quot;);        
         _;    
    }
    function proxiableUUID() external view virtual override notDelegated returns (bytes32) {        
        return _IMPLEMENTATION_SLOT;    
    }
    function upgradeTo(address newImplementation) public virtual onlyProxy {        
        _authorizeUpgrade(newImplementation);        
        _upgradeToAndCallUUPS(newImplementation, new bytes(0), false);    
    }
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {        
         _authorizeUpgrade(newImplementation);        
         _upgradeToAndCallUUPS(newImplementation, data, true);    
    }
    function _authorizeUpgrade(address newImplementation) internal virtual;
    uint256[50] private __gap; 
}</pre>
  <p id="fNYB">__UUPSUpgradeable_init и __UUPSUpgradeable_init_unchained нужны по факту для инициализации каких то идей, но их нет. А так, если серьезно, то используется для проверки, что инициализация псевдо конструктора прошла.</p>
  <p id="amj0">onlyProxy модификатор, который говорит что только Proxy может вызывать функции. </p>
  <p id="0mhy">notDelegated не дает делегировать функции. </p>
  <p id="Ksry">Это все нужно если у нас есть функции с одинаковыми селекторами, и чтоб не произошел казус. И некоторые функции нельзя было делегировать для безопасности, как мы уже и заметили в _upgradeToAndCallUUPS.</p>
  <p id="M7mY">proxiableUUID </p>
  <p id="PuYb">Та самая функция которая возвращает слот implementstion, и мы ее вызывали в _upgradeToAndCallUUPS.</p>
  <p id="YZDL">upgradeTo очевидно, что просто обновляет наш адрес СК (implementation), и upgradeToAndCall делает тоже самое. В целом понятно, что идет вызов функций из ERC1967Upgrade. </p>
  <p id="a0ma">_authorizeUpgrade функция которая переопределяется в нашем СК, который мы хотим потом обновлять(implementation)</p>
  <p id="qsRF"><strong>Пример СК, который мы хотим в будущем обновить:</strong></p>
  <pre id="67zE" data-lang="cpp">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import &quot;./utils/Initializable.sol&quot;;
import &quot;./UUPSUpgradeable.sol&quot;;
contract V1 is Initializable, UUPSUpgradeable{    
    uint256 public number;    
    address owner;    
    modifier onlyOwner(){        
        require(owner == msg.sender, &quot;error&quot;);        
        _;    
    }    
    function initialize(uint _number) public payable initializer {        
        __UUPSUpgradeable_init();        
        number = _number;        
        owner = msg.sender;    
    }    
    function _authorizeUpgrade(address newImplementation) internal onlyOwner override{}
}
</pre>
  <p id="Hfn4"><strong>И обновлённая версия</strong> </p>
  <pre id="ZcSm" data-lang="cpp">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import &quot;./utils/Initializable.sol&quot;;
import &quot;./UUPSUpgradeable.sol&quot;;
contract V1 is Initializable, UUPSUpgradeable{    
    uint256 public number;    
    address owner;    
    uint256 public newNumber;
    modifier onlyOwner(){        
        require(owner == msg.sender, &quot;error&quot;);        
        _;    
    }    
    function initialize(uint _number) public payable initializer {        
        __UUPSUpgradeable_init();        
        number = _number;
        newNumber = _newNumber;        
        owner = msg.sender;    
    }    
    function _authorizeUpgrade(address newImplementation) internal onlyOwner override{}
}
</pre>
  <p id="dbva">Нужно заметить что мы наследуем именно UUPSUpgradeable и Initializable.</p>
  <p id="d4Pz">Вот мы и реализовали свой ERC1967 proxy смарт-контракт. Сразу хочется сказать, что для использования этот код не рекомендуется. Потому что я убрал некоторые вещи, которые показались не очень нужными и поэтому не стоит рисковать и писать самим этот стандарт. Тот же самый beacon, который по хорошему тоже должен быть и случай Transparent как бы ни кто не исключал. Поэтому используйте версию из openzeppelin и не парьтесь. Главное что мы знаем как эта штука работает изнутри и понимаем что происходит и для чего это нужно. Это как минимум упростит написание своих upgradeable СК. А как максимум, вы будете знать то, что обычно другие считают не нужным знать.</p>
  <p id="HeNa"><strong>Бонус</strong></p>
  <p id="Ye3h">Если ты дожил до этого момента, то покажу скрип деплоя. На самом деле он есть в openzeppelin, но когда я писал его, то в некоторых моментах не мог сразу понять что от меня хотят.</p>
  <p id="IUNY">Устанавливаем эту библиотеку для взаимодействия.</p>
  <pre id="olbC" data-lang="javascript"> npm install @openzeppelin/contracts-upgradeable</pre>
  <p id="QF9V">Деплой первой версии </p>
  <pre id="HfDh" data-lang="javascript"> const { ethers, upgrades } = require(&quot;hardhat&quot;);
async function main() {  
  if (network.name === &quot;hardhat&quot;) {    
  console.warn(      
  &quot;You are trying to deploy a contract to the Hardhat Network, which&quot; +   
   &quot;gets automatically created and destroyed every time. Use the Hardhat&quot; +        
   &quot; option &#x27;--network localhost&#x27;&quot;    
   );  
  }
  const [deployer] = await ethers.getSigners()
  console.log(&quot;Deploying with&quot;, await deployer.getAddress())
  const V1 = await ethers.getContractFactory(&quot;V1&quot;);  
  const v1 = await upgrades.deployProxy(V1.address, [53], {
    kind: &#x27;uups&#x27;
  });  
  await v1.deployed();  
  console.log(&quot;deployed to:&quot;, v1.address);
}
main().catch((error) =&gt; { 
 console.error(error);  
 process.exitCode = 1;
});</pre>
  <p id="V3Kj">Тут все как всегда, но есть изменение:</p>
  <pre id="oP8G" data-lang="javascript">const v1 = await upgrades.deployProxy(V1.address, [0]);  </pre>
  <p id="HaxA">Мы деплоим не как обычно через deploy(), а через deployProxy. Передаем адрес СК и в массив, наши аргументы, которые будут переданы и вызваны в функции initialize, если такая имеется. И главное: если мы хотим делать деплой через UUPS, то это нужно явно указать через kind переменную. Так как по умолчанию будет использоваться transpenrent.</p>
  <blockquote id="AGor">v1 переменная будет нашим Proxy смарт-контрактом, а не нашим смарт-контрактом V1.</blockquote>
  <p id="YzRh">Деплой новой версии </p>
  <pre id="kaQD" data-lang="javascript"> const { ethers, upgrades } = require(&quot;hardhat&quot;);
async function main() {  
  if (network.name === &quot;hardhat&quot;) {    
  console.warn(      
  &quot;You are trying to deploy a contract to the Hardhat Network, which&quot; +   
   &quot;gets automatically created and destroyed every time. Use the Hardhat&quot; +        
   &quot; option &#x27;--network localhost&#x27;&quot;    
   );  
  }
  addressProxy = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  const V2 = await ethers.getContractFactory(&quot;V2&quot;); 
  const v2 = await upgrades.upgradeProxy(addressProxy, V2, {
    call:{
     fn:&quot;initialize(uint256), 
     args:[35])
    }
    });
  
  console.log(&quot;deployed to:&quot;, v2.address);
}
main().catch((error) =&gt; { 
 console.error(error);  
 process.exitCode = 1;
});</pre>
  <p id="iTC6">Тут мы просто будем деплоить новую версию СК через upgradeProxy. </p>
  <p id="56gj">И необычная особенность upgradeProxy()</p>
  <pre id="9keL" data-lang="javascript">const v2 = await upgrades.upgradeProxy(addressProxy, V2, {
    call:{
     fn:&quot;initialize(uint256), 
     args:[35]
    }
    });</pre>
  <p id="bfuH">Мы передаем адрес Proxy и новый адрес обновлённого СК. Последнюю переменную call нужно использовать в том случае, если нам снова нужно вызвать конструктор, и вот таким необычным способом нужно передавать туда функцию, которую мы хотим вызвать и аргументы в массиве. Тут не будет как в deployProxy, где достаточно передать аргументы и он сам найдет нашу функцию initialize и сделает все что нужно. Но и так нормально.</p>
  <p id="cUbu"></p>
  <p id="EgfO"></p>
  <p id="80qN">Ну на этом я пожалуй закончу свой долгий монолог про ERC1967. Сегодня мы изнутри посмотрели как работает Proxy и как мы можем обновлять свои СК. А главное узнали специфику и хитрости которые можно использовать для хранения информации, например через спрятанные слоты. Дальше больше. И теперь когда мы будем писать свои обновляемые СК мы будем знать что у этой машины под капотом.</p>
  <p id="lH4g"></p>
  <p id="Jaaa">Полезные ссылки: </p>
  <p id="O06b"><a href="https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies" target="_blank">Что такое proxy</a></p>
  <p id="PKX5"><a href="https://docs.openzeppelin.com/contracts/4.x/api/proxy#transparent-vs-uups" target="_blank">Реализация Transparent и UUPS</a></p>
  <p id="NNbM"><a href="https://docs.openzeppelin.com/upgrades-plugins/1.x/" target="_blank">Как написать скрипт деплой</a></p>
  <p id="mcaz"><a href="https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#upgrade-proxy" target="_blank">API плагина upgrade</a></p>
  <p id="xVqH"><a href="https://eips.ethereum.org/EIPS/eip-1967" target="_blank">Реализация EIP-1967</a></p>
  <p id="d9I4"></p>
  <p id="KVcT">tg: <a href="https://t.me/solidiks" target="_blank">мой телеграмчик)</a></p>
  <p id="aKca">GitHub: <a href="https://github.com/DDWorld-dev/ERC1967-Proxy" target="_blank">Этот проект на гит хабе</a></p>

]]></content:encoded></item></channel></rss>