<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Solidity學習之代理合約

      什么是代理合約

      代理合約針對的是鏈上合約一經部署無法修改的問題,通過增加一層代理合約,就可以在不修改代理合約代碼和地址的前提下,對實際執行的邏輯進行調整,滿足了合約升級的需要。

      實現邏輯

      代理模式將狀態變量存儲在代理合約中,而邏輯執行在邏輯合約中。

      通過delegateCall調用,執行邏輯合約的同時,改變的是代理合約中的狀態變量,并且將執行結果返回給caller

      具體實現

      代理合約

      contract Proxy {
          address public implementation; // 邏輯合約地址
      
          /**
           * @dev 初始化邏輯合約地址
           */
          constructor(address implementation_){
              implementation = implementation_;
          }
          
      /**
      * @dev 回調函數,將本合約的調用委托給 `implementation` 合約
      * 通過assembly,讓回調函數也能有返回值
      */
      fallback() external payable {
          address _implementation = implementation;
          assembly {
              // 將msg.data拷貝到內存里
              // calldatacopy操作碼的參數: 內存起始位置,calldata起始位置,calldata長度
              calldatacopy(0, 0, calldatasize())
      
              // 利用delegatecall調用implementation合約
              // delegatecall操作碼的參數:gas, 目標合約地址,input mem起始位置,input mem長度,output area mem起始位置,output area mem長度
              // output area起始位置和長度位置,所以設為0
              // delegatecall成功返回1,失敗返回0
              let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
      
              // 將return data拷貝到內存
              // returndata操作碼的參數:內存起始位置,returndata起始位置,returndata長度
              returndatacopy(0, 0, returndatasize())
      
              switch result
              // 如果delegate call失敗,revert
              case 0 {
                  revert(0, returndatasize())
              }
              // 如果delegate call成功,返回mem起始位置為0,長度為returndatasize()的數據(格式為bytes)
              default {
                  return(0, returndatasize())
              }
          }
      }
      

      在代理合約中指定了一個implementation,即邏輯合約的地址,并且只實現了一個fallback()方法,所有發送給代理合約的調用都會通過fallback()發到邏輯合約上,代理合約只起到一個中轉的作用。

      代理合約的重點和難點就是fallback()的實現,代碼中用assembly標明使用的是內聯匯編的操作碼,可以直接執行一些內存操作,而不是經過solidity的高級語法。

      此處用到了5個內聯匯編的方法:

      • calldatacopy(destOffset,dataOffset, size)
      • calldatasize()
      • returndatacopy(destOffset,dataOffset,size)
      • returndatasize()
      • delegatecall(gas,address,destOffset,size, destOffset,size)

      其中兩個size方法比較好理解,也就是拿到數據的長度。

      而兩個call方法做了類似的事情,即從某個數據來源處將數據復制到合約的memory內存中,比如calldatacopy()就會從calldata獲取數據,而returndatacopy()則從returndata buffer里面獲取,拿到的是上一次 call / delegatecall / staticcall 的返回值。

      至于參數,第一個destOffset指向了內存的某個位置,是用來存放新數據的起始位置點,而dataOffset則是在獲取數據時候的偏移值,比如calldatacopydataOffset為1,那么就是從calldata的第二個字節開始取值,一共向后取size個字節的數據。

      最后就是delegatecall(),此處和高級語法中不同,參數用的都是內存值。首先指定了gas和調用的對象address,然后用四個參數標明calldatareturndata的存放位置,與copy的時候是一致的。

      此時可以總結一下fallback()做的事情:

      1. calldata復制到內存中起始為0的位置
      2. delegatecall調用address的方法,calldata來自于內存,而對于returndata,最后一個參數為0,表示返回值的處理范圍為0,也就是不做處理,這是因為把處理留到了后面。
      3. 將返回值從returndata buffer中復制到內存中
      4. 根據調用成功與否,選擇用revertreturn返回returndata

      重點

      • 不在delegatecall中讀取返回值是因為此時的返回值長度是不確定的,所以放到后面用returndatacopy去處理。
      • delegatecall處理返回值與否不會影響到returndata buffer中的數據,在下一次call調用之前,buffer中的返回值會一直存在。
      • 寫法中returndatacopy()的時候實際上覆蓋了原來的calldata,因為寫入的起點都是0,但因為calldatadelegatecall之后就沒用了,所以這是安全的

      邏輯合約

      此處實現一個簡單的邏輯合約即可,和正常的合約沒什么區別。

      contract Logic {
          address public implementation; // 與Proxy保持一致,防止插槽沖突
          uint public x = 99; 
          event CallSuccess(); // 調用成功事件
      
          // 這個函數會釋放CallSuccess事件并返回一個uint。
          // 函數selector: 0xd09de08a
          function increment() external returns(uint) {
              emit CallSuccess();
              return x + 1;
          }
      }
      

      邏輯合約唯一要注意的點就是必須定義代理合約中的成員變量,這是因為delegatecall的時候是根據邏輯合約的成員變量位置而去修改代理合約中相同位置的狀態量

      比如此處如果在函數中修改了implementation的值,因為implementtion位于slot[0]的位置,那么代理合約收到的修改指令同樣是修改slot[0]的值,如果邏輯合約中implementation不是第一個定義的,而是第二個,位于slot[1]的位置,一旦發生修改,即使代理合約中并不存在slot[1]的變量,也依然會在內存位置里覆蓋寫入,造成不可預知的問題。

      調用合約

      contract Caller{
          address public proxy; // 代理合約地址
      
          constructor(address proxy_){
              proxy = proxy_;
          }
      
          // 通過代理合約調用increment()函數
          function increment() external returns(uint) {
              ( , bytes memory data) = proxy.call(abi.encodeWithSignature("increment()"));
              return abi.decode(data,(uint));
          }
      }
      

      在調用合約中,方法簽名和參數都是根據邏輯合約來的,代理合約只起到中轉的作用。

      執行結果

      最后返回的結果是1,這是因為increment去讀取代理合約中的x值,位于slot[1],而代理合約在這個位置沒有定義變量,為0,所以此時返回0+1 =1

      可升級合約

      以代理合約為基礎,在其中增加一個upgrade()函數,就可以實現邏輯合約的升級。

          // 升級函數,改變邏輯合約地址,只能由admin調用
          function upgrade(address newImplementation) external {
              require(msg.sender == admin);
              implementation = newImplementation;
          }
      

      通過require控制升級函數只能由admin調用,調用upgrade()后邏輯合約就會變成傳入的地址。

      選擇器沖突

      在solidity中,函數選擇器是函數簽名的哈希的前4個字節,因為字節數少,所以會出現不同函數的函數選擇器相同的情況,被稱為選擇器沖突

      正常情況下,如果在合約中出現選擇器沖突,會在編譯的時候被編譯器發現并報錯,無法通過編譯。

      但是在可升級合約的場景中,因為代理合約實現了upgrade()方法,存在一種可能性,就是邏輯合約中有某個函數的函數選擇器與upgrage()方法相同。調用這個方法時,data先傳到了代理合約中,被認為是一次調用upgrade()的請求,從而出現了錯誤的調用。

      嚴重的情況下,因為傳入的參數不確定,可能會將邏輯合約指向黑洞地址。

      為了解決這一問題,有透明代理和UUPS兩種方法。

      透明代理

      透明代理的實現非常簡單,通過權限隔離的方式,在upgrade()fallback()方法中限制調用方的地址:upgrade()只能由管理員調用,而fallback()則必須由非管理員的地址調用。

      這樣一來就可以避免因為調用相同函數選擇器而錯誤調用升級函數的情況,保證了升級的安全。

         fallback() external payable {
              require(msg.sender != admin);
              (bool success, bytes memory data) = implementation.delegatecall(msg.data);
          }
      
          // 升級函數,改變邏輯合約地址,只能由admin調用
          function upgrade(address newImplementation) external {
              if (msg.sender != admin) revert();
              implementation = newImplementation;
          }
      

      但邏輯函數中依然有可能存在與upgrade()函數選擇器相同的方法,并且這個方法永遠無法被調用。但是這種情況下并不會影響合約的安全所以可以忽視,只是在寫合約時應當手動檢查避免無效函數出現。

      UUPS

      UUPS是universal upgradeable proxy standard的縮寫,指的是通用可升級代理標準。這一標準規定了升級合約要寫在邏輯合約中,利用編譯檢查來避免選擇器沖突的問題。

      因為delegatecall的特性,所以在邏輯合約中依然可以判斷msg的來源是否為管理員,并且修改代理合約中implementation的值,如下:

          function upgrade(address newImplementation) external {
              require(msg.sender == admin);
              implementation = newImplementation;
          }
      

      二者的優劣勢

      UUPS

      • ? 代理合約更小(只負責 delegatecall)
      • ? 升級邏輯靈活:每個實現合約可以定義自己的升級策略(如版本檢測、權限校驗)
      • ? 省 gas:因為 upgrade 邏輯不在 Proxy 中
      • ?? 如果你實現的 upgradeTo() 沒寫權限控制,會被任意升級(重大安全風險)
      • ?? 升級邏輯自己寫的不好,容易讓合約變磚(如升級自己指向了一個非實現合約)
      • ?? OpenZeppelin 要求必須繼承 UUPSUpgradeable 并帶 onlyProxy 修飾函數,防止錯誤調用

      透明代理

      • ? 最穩定、最傳統的升級方式
      • ? upgrade 權限由 Proxy 管理,不容易犯錯
      • ? 社區使用廣泛,工具鏈支持成熟
      • ? Proxy 更復雜、部署成本高
      • ? 所有調用都要通過 proxy,admin 地址不能調用邏輯函數(會被拒絕)
      • ? 無法動態調整 upgrade 權限策略(都是在 Proxy 里寫死的)
      posted @ 2025-08-07 13:37  Felix07  閱讀(24)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 成年黄页网站大全免费无码| 少妇高潮潮喷到猛进猛出小说| 国产成人精品久久一区二| 男女真人国产牲交a做片野外| 国产一区二区不卡91| 亚洲熟妇无码av另类vr影视| 高潮毛片无遮挡高清视频播放| 亚洲人妻精品中文字幕| 日韩av中文字幕有码| 乱码中文字幕| 亚洲精品无码久久一线| 隆尧县| 高潮精品熟妇一区二区三区| 久久精品国产国产精品四凭| 国产福利社区一区二区| 国产极品尤物粉嫩在线观看| 亚洲国产成人无码电影| 国产精品久久久久久妇女| 国产视频有码字幕一区二区| 在线亚洲高清揄拍自拍一品区| 成人一区二区三区在线午夜| 日本高清无卡码一区二区| 一区二区三区不卡国产| 欧美乱大交aaaa片if| 最好看的中文字幕国语| 成人国产精品一区二区不卡| 日本中文字幕乱码免费| 图片区小说区av区| 曲沃县| 一卡二卡三卡四卡视频区| 成人性生交片无码免费看| 99热精国产这里只有精品| 国产成人综合亚洲第一区| 无码一区二区三区av在线播放| 国产免费高清69式视频在线观看| 欧美乱大交xxxxx疯狂俱乐部| 可以在线观看的亚洲视频| 成人拍拍拍无遮挡免费视频| 久久综合开心激情五月天| 与子敌伦刺激对白播放| 无人区码一码二码三码区|