Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

如何实现可升级的智能合约?

用香烟做的云
140 0 0
现如今,整个密码货币生态系统都是由智能合约所驱动!不管我们有多小心,或者我们的代码测试工作做得有多好,如果我们创建的是一个复杂的系统,那我们就有必要更新合约逻辑,以修补其存在的漏洞,或者添加必要的缺失功能。有时候,由于EVM虚拟机的更改或者被新发现的漏洞,我们可能需要去升级我们的智能合约。& y$ ^1 Q. f& z9 |8 ^ 一般来说,开发人员可以很容易地升级他们的软件,但区块链的情况是不一样的,因为它们有着难以更改的属性。如果我们部署了一个合约,这就好比是泼出去的水。然而,如果我们使用适当的技术,我们可以在不同的地址部署一个新的合约,并使得旧合约无效。下面是一些最常见的,创建可升级智能合约的方法。8 V5 R' N6 L# K, a4 g0 B . N; N0 [1 K a. S # c/ \6 ~ J" y" ` 主从合约(Master-Slave contract)$ m" ~( i/ e1 ` W& i ' K& r8 e0 O2 N, z3 u* y- ^* |- c主从技术,是可实现升级智能合约最为基础也是最容易理解的技术之一。在这种技术当中,我们部署一个主合约,以及其他合约,其中主合约负责存储所有其他合约的地址,并在需要时返回所需的地址。当这些合约需要和其它合约进行沟通时,它们会充当从合约,从主合约那里获取其它合约的最新地址。为了升级智能合约,我们只需要在网络上部署它,并更改主合约中的地址。虽然这远不是发展可升级智能合约的最佳方式,但它确是最简单的。这种方法存在着很多的局限性,其中之一是,我们不能轻易地把合约的数据或资产迁移到新合约中。 ( ?7 D& v& A. s8 e, n8 B% w4 g ' X9 W. @- P( f% z: S永久存储合约(Eternal Storage contract , P# d6 i+ g, K! v. `5 g; R* p) j; z0 ^7 E8 G/ r& k3 s6 k 在这种技术当中,我们将逻辑合约和数据合约彼此分离。数据合约应该是永久并且不可升级的。而逻辑合约可以根据需要进行多次升级,并将变化通知给数据合约。这是一项相当基本的技术,并且存在着一个明显的缺陷。由于数据合约是不可升级的,数据结构中需要的任何更改,或数据合约中存在的漏洞,都会导致所有数据变得无用。这种技术的另一个问题是,如果逻辑合约想要访问/操作区块链上的数据,那么这个逻辑合约将需要进行外部调用,而外部调用会消耗额外的gas。通常情况下,这种技术会和主从技术相结合,以促进合约间的通信。 ( r4 U8 G9 ~4 e' Z% P/ P 4 s, z p; `+ G* V9 m6 I# Q可升级存储代理合约 % c- [* M4 g' W6 w8 H7 ~4 ]; _ 9 q" k, Z* g/ z1 m. g q x我们可通过使永久存储合约充当逻辑合约的代理,以此防止支付额外的gas。这个代理合约,以及这个逻辑合约,将继承同一存储合约,那么它们的存储会在EVM虚拟机中对齐。这个代理合约将有一个回退函数,它将委托调用这个逻辑合约,那么这个逻辑合约就可以在代理存储中进行更改。这个代理合约将是永恒的。这节省了对存储合约多次调用所需的gas,不管数据做了多少的更改,就只需要一次委托调用。4 `) e; D: G+ o5 y6 I' v. C3 T 这项技术当中有三个组成部分:$ ?" c2 Q. I- l: y/ p! [$ K5 U5 y 代理合约(Proxy contract):它将充当永久存储并负责委托调用逻辑合约; 6 _' h0 F2 J4 I逻辑合约(Logic contract):它负责完成处理所有的数据; 0 |3 M, v0 T, x$ ]存储结构(Storage structure):它包含了存储结构,并会由代理合约和逻辑合约所继承,以便它们的存储指针能够在区块链上保持同步;% Y5 ]) ]; e" B. I + t- I. j. e6 m 5 [9 [0 b3 p/ N % j8 S% B9 ?2 f1 ^! D; k" A委托调用 ' J |: s; T* T0 P, A) B2 d1 s7 q& P" [# |+ v 该技术的核心在于EVM所提供的DELEGATECALL操作码,DELEGATECALL就像是一个普通的CALL 调用操作码,不同之处在于目标地址上的代码是在调用合约上下文中执行的,而原始调用的msg.sender以及msg.value将被保留。简单说,DELEGATECALL基本上允许(委托)目标合约在调用合约的存储中做它任何想做的事情。3 Z5 Q& ~. A% y 我们将利用这一点,并创建一个代理合约,它将使用DELEGATECALL操作码委托调用逻辑合约,这样我们就可以在代理合约中保持数据的安全,同时我们可以自由地更改逻辑合约。 & K! }3 ?; l" I0 z9 O7 z- O* S- m" C; }; a. |" O1 q 2 q1 T) c$ [0 m0 M! h0 J如何使用可升级存储代理合约 * d' { R5 B! m4 W, {/ G: J% }" y% G$ W 0 Z: v5 [; l- b& a让我们深入研究一下细节。我们需要的第一个合约是存储结构。它将定义我们需要的所有存储变量,并将由代理合约和执行合约所继承。它看起来会是这样的: ) Z: i# P' ?8 P" @. g. ~5 U
  1. <b>contract StorageStructure {& x2 L, x. u3 V7 Z3 o% a5 A6 S
  2. address public implementation;% b/ f! _* J( ^
  3. address public owner;" x9 `( [$ i9 t# G+ |7 o) O T4 h
  4. mapping (address => uint) internal points; , N/ A9 A. O5 Z- ]5 c
  5. uint internal totalPlayers; 9 m# z# r3 t7 y9 [0 q
  6. }</b>
复制代码
$ Y4 _' T8 ?, C & i- J5 }) _) V% E* T5 ?& | 我们现在需要一个执行/逻辑合约。让我们创建一个简单版的合约,在添加新玩家时不会增加totalPlayers计数器的数字。 * W8 G3 M6 G% A0 l7 w6 H7 X1 H f+ n" T( Y9 E( X
  1. contract ImplementationV1 is StorageStructure { , B5 T) M0 o; t5 |9 v1 Q3 a( `! z
  2. modifier onlyOwner() {6 V' c& s' ?% n- ]+ L8 b: F- g
  3. require (msg.sender == owner);! p0 h4 `) g, z! y* Y' d3 Q. ^! Z& a& L
  4. _; 3 l8 O/ r& F. Y6 g; U
  5. } 4 i, m, o4 e, M# C* a+ `
  6. function addPlayer(address _player, uint _points)/ k' x% j. B6 V& A$ P3 w
  7. public onlyOwner8 x0 ~$ d1 ?6 o# o9 I
  8. {/ S1 B/ v! M$ D9 `' J
  9. require (points[_player] == 0);3 n! E; j6 ]. k* a' v/ j
  10. points[_player] = _points;$ G3 F. _6 `3 D p" A" L
  11. } . u% \% |" u8 }0 K- N# b7 |$ N
  12. function setPoints(address _player, uint _points)* a' r- D# p6 y
  13. public onlyOwner % d9 G' z B2 N8 t) J1 J
  14. {. d* A) d. C, E7 g2 i( }
  15. require (points[_player] != 0); ( c! i" T- I/ p1 F1 v) `8 ~) @
  16. points[_player] = _points; S% \1 q0 U8 T- M% n, M1 U
  17. } 7 b: O5 H; W4 Q* C# K* | |
  18. }
复制代码
4 m( W. x; ^$ z2 F" m7 H9 b3 C1 t+ g1 J3 Z! Z1 ^- H! K4 k 0 g7 f* h/ d# }, ~! o$ ^: G" E; f下面就是最关键的部分:代理合约;4 ?. D6 N/ h* q
  1. contract Proxy is StorageStructure {4 x2 ^4 s& e) D9 f; [+ z0 {
  2. # `) y5 _ a+ i* a0 A' t" I
  3. modifier onlyOwner() {4 n& n% N! h' R! w" Z1 M
  4. require (msg.sender == owner); i+ ]: Y3 p0 t5 |: ^8 D, Q3 }
  5. _; 7 y5 k( [8 g9 N7 a: D. h' w; u- X
  6. } 2 \8 Z7 f$ X( a( a5 p+ r
  7. /** o' A {- \8 _1 r6 ^; d7 B( [
  8. * @dev constructor that sets the owner address o% s* v- J7 z3 C/ i
  9. */- _: k# s% U4 d0 t4 l$ z8 F# E
  10. constructor() public {7 k9 ~ v% G$ i6 t0 i; [
  11. owner = msg.sender; 8 ?! _8 g( b. t- |0 _+ J0 h
  12. }) j3 E, I, r; D: O6 K/ m) f2 m$ J \8 m
  13. /**- S3 d; ]3 Y0 z$ K. i' |
  14. * @dev Upgrades the implementation address # b: u. I2 s9 e* [7 s. h
  15. * @param _newImplementation address of the new implementation : S- w1 t% R: T0 M5 g
  16. */5 B( M' @- `0 T: \6 B- ^
  17. function upgradeTo(address _newImplementation) 0 U" w3 d# I1 l8 s, P# H4 U& y
  18. external onlyOwner 8 _9 y# Y: A6 I) ~$ p
  19. {) x, q/ X, C( I) O" f2 U" u$ z2 f
  20. require(implementation != _newImplementation); 6 [; y, |) H: ]; T) K1 l: W
  21. _setImplementation(_newImplementation);& B, k+ A, o8 J' i/ T# q. g6 z5 R+ o
  22. }4 z; k% `4 h/ c
  23. /** 4 E2 \. H" K3 c1 h
  24. * @dev Fallback function allowing to perform a delegatecall4 ]5 U$ D% X; |3 Z; U2 n- Z( S% b
  25. * to the given implementation. This function will return 0 u; `8 ^: |& w; z& b5 H
  26. * whatever the implementation call returns$ M' i; ]. ^+ ?0 e! \, I- G
  27. */ k7 ^6 [6 K. t% a
  28. function () payable public { 0 P2 C9 q( F' B4 J, Y8 H
  29. address impl = implementation; 3 s6 E6 q. b) f1 ]! g/ L
  30. require(impl != address(0)); 1 [ x2 I( z7 B# }. W
  31. assembly { : O1 l9 `, F! Q% X9 i: O
  32. let ptr := mload(0x40) 7 I7 z- V9 P, ~9 R0 T' E0 H
  33. calldatacopy(ptr, 0, calldatasize): b1 _) h+ |- T2 [! d ^, y: ^6 C+ n
  34. let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0) 4 n! L" K9 z, W9 w* s
  35. let size := returndatasize 4 |! w1 |" e, ^/ x) }2 O
  36. returndatacopy(ptr, 0, size) : Y; T3 M' {1 }# N. f$ q
  37. switch result5 B% t* S: |1 J I' C/ s, U
  38. case 0 { revert(ptr, size) }" W: z* R; W. L7 k0 p3 S
  39. default { return(ptr, size) } ' W$ Q& ]0 O) Z+ G
  40. }! Z& @/ ]7 D3 I+ O3 l
  41. }, D9 C' r" h/ A' y
  42. 6 ?' G9 @, \9 d( E' ^, H( H
  43. /** , e9 A3 w; `- E* \$ K
  44. * @dev Sets the address of the current implementation5 M3 c5 o+ T% m7 U4 G G! m# @. v
  45. * @param _newImp address of the new implementation% q0 }4 M' B I/ g
  46. */6 H6 Y) O, t) T6 {8 R
  47. function _setImplementation(address _newImp) internal {( u& K( `! Z4 ?0 F3 m7 O% a
  48. implementation = _newImp;* |: G# \( D9 @3 ?( D7 n9 d$ U* e
  49. }; u$ W" Z3 L8 Z. M
  50. }
复制代码
# [1 c( S( M' N) `- l 7 o' B5 q0 l* U d& R9 m& r( e2 c, } : q" ^, }! G4 s# s* s$ N5 P 为了让合约生效,我们首先需要部署代理合约以及ImplementationV1合约,然后调用这个代理合约的upgradeTo(address)函数,同时pass掉我们的ImplementationV1合约地址。现在,我们可以忘记这个ImplementationV1合约的地址,并把代理合约的地址作为我们的主地址。 9 G! y0 D' P7 ]$ J1 Z( \; W3 K为了升级这个合约,我们需要创建一个新的逻辑合约实现,它可以是这样的: + W, |% T1 x' T: u" D' z) i
  1. contract ImplementationV2 is ImplementationV1 {8 l. q, y8 ~6 c1 j
  2. * v6 ~: ] w6 S4 k* S! H
  3. $ \( U0 T( R/ ~$ y
  4. function addPlayer(address _player, uint _points)& v8 O$ ^9 J- S, R4 A
  5. public onlyOwner! Z$ c+ e, O# N/ Z+ X. O% ^
  6. { $ p9 k6 A& u9 j
  7. require (points[_player] == 0); & _* {3 u4 Z4 T& _3 Y0 i+ n4 X
  8. points[_player] = _points;/ y) ~- n% U0 E2 m# W& A
  9. totalPlayers++; 5 [: x, p$ s6 N2 }: V2 D' M5 A) y, o$ u
  10. } ' m Y" D- ?: `. O, h, h, `: ?! Y' L
  11. }
复制代码
; I; \% W) |4 ^3 s- x X; t9 g. ` 7 N: k' T& X1 a& g! u你应该注意到,这个合约也继承了存储结构合约(StorageStructure contract),尽管它是间接地。 N" K" }$ o. H) Y, k! N所有的执行方案都必须继承这个存储结构合约,并且在部署代理合约后不得进行更改,以避免对代理的存储进行意外覆盖。 ( b4 _9 `& g# {7 ] ( y/ `6 _5 X$ H+ [) Y: O* f: B为了实现升级,我们在网络上部署这个合约,然后调用代理合约的upgradeTo(address) 函数,同时pass掉ImplementationV2合约的地址。+ d- j6 a: P) \( M0 U# ^ 这种技术,使得升级合约逻辑变得相当容易,但它仍然不允许我们升级合约的存储结构。我们可以通过使用非结构化的代理合约来解决这个问题。 # e& b' y( }, E( w7 @ & y6 V, E5 b+ N4 [( S& R" \& E ' t) r3 `1 S# b4 S) ` 1 j" T- r, D: d. Q# ^ 非结构化可升级存储代理合约 1 e: q( T6 e O2 }. c 4 ?1 l: w( F1 c; |6 f+ t1 N' V& y* C这是当前最先进的,可实现智能合约升级的方法之一。它通过保存合约地址以及在存储中固定位置所有者的方法,以实现它们不会被执行/逻辑合约提供的数据所覆盖。我们可以使用sload以及sstore操作码来直接读取和写入由固定指针所引用的特定存储槽。 $ c D4 F0 V% O: k- v; c! A9 w此方法利用了存储中状态变量的布局,以避免逻辑合约覆盖掉固定位置。如果我们将固定位置设置为0x7,那么在使用前7个存储槽后,它就会被覆盖掉。为了避免这种情况,我们将固定位置设置为类似keccak256(“org.govblocks.implemenation.address”).: b* v0 d1 N# `( h; c; p3 _ 这消除了在代理合约中继承存储结构合约的需要,这意味着我们现在也可以升级存储结构了。然而,升级存储结构是一项棘手的任务,因为我们需要确保,我们所提交的更改,不会导致新的存储布局与先前的存储布局不匹配。! m$ Z+ [& ^! F+ v0 k& x. Z ! B- E% o8 F% O0 W+ i2 Q; O4 m9 S这项技术有两个组成部分。 3 Y0 L8 U) @) l4 D8 E. U1、代理合约:它负责将执行合约的地址存储在一个固定的地址当中,并负责委托调用它; 2、执行合约:它是主要合约,负责把我逻辑以及存储结构;- ^7 |) \/ u6 o# n 你甚至可以将这项技术用于你现有的合约,因为它不需要对你的执行合约进行任何更改。 , z; w0 S: I3 s5 ? F' j: n) Z这个代理合约会是这样子的:2 s. L5 r# R9 z7 q% {
  1. contract UnstructuredProxy { / f8 s0 m; {& U+ a$ r2 M5 x
  2. " u! {8 T% w8 V( u+ q$ C5 o* i
  3. // Storage position of the address of the current implementation 3 J7 A& y8 p; }5 w' w$ J* M! D
  4. bytes32 private constant implementationPosition =5 A! a) f% b" e* ?0 }4 q& [
  5. keccak256("org.govblocks.implementation.address"); % B) x! f, }& a6 n, G
  6. // Storage position of the owner of the contract: u- o; P, @0 [- U" Y. H. _% z! t* g
  7. bytes32 private constant proxyOwnerPosition = 1 U: t+ b3 N( q2 ^' w
  8. keccak256("org.govblocks.proxy.owner"); 2 W5 x# g% k6 z+ O5 E
  9. /**4 g5 A! L5 f+ o, u
  10. * @dev Throws if called by any account other than the owner.* m' l7 V- i$ C2 Q
  11. */ 1 u- a! M) G6 N4 L. [
  12. modifier onlyProxyOwner() { 3 N4 g) P7 K: Z6 N+ T9 a% p6 D
  13. require (msg.sender == proxyOwner()); 1 Q2 ^- `- k% P' y/ z
  14. _; 2 ^8 U9 T$ D% M+ ~0 M
  15. }( _- A$ [* v6 Q; W5 z& T, F5 l
  16. /** 8 ?' A$ f9 ?9 J- e( H( j7 V
  17. * @dev the constructor sets owner 7 Z4 _; O. F3 V: B7 J) z# A
  18. */ H! b" |2 e$ N9 {' r& j! d6 m
  19. constructor() public {+ V' T" |+ ]" ?' v1 g
  20. _setUpgradeabilityOwner(msg.sender);- o6 ?& x" t' \% ~& E
  21. }9 [/ b/ _" T, L7 R' m
  22. /**6 @/ g( x% z* S% k* M; n
  23. * @dev Allows the current owner to transfer ownership1 R0 B0 v# I. F
  24. * @param _newOwner The address to transfer ownership to5 w5 R+ u! [6 p3 e' V: R! `8 O1 l
  25. */ / u- D% F, J+ `% b* Y- E0 u
  26. function transferProxyOwnership(address _newOwner) ; @, b, K% q0 S
  27. public onlyProxyOwner) e1 A# Q4 J4 t G0 U* ~
  28. {0 }, m9 V e& s0 v, z
  29. require(_newOwner != address(0)); : o1 p) g$ J- n) a- m w
  30. _setUpgradeabilityOwner(_newOwner); 6 Y' b) k; N' c: l$ n8 c4 |$ O
  31. } 4 R1 ?$ N u) n, o1 s
  32. /** * X1 L. H7 n9 a: L: _6 P, `
  33. * @dev Allows the proxy owner to upgrade the implementation , @: U- G& z. z
  34. * @param _implementation address of the new implementation# Z5 J/ z8 {7 i0 J5 `
  35. */7 \$ R6 u( `' ?+ ~
  36. function upgradeTo(address _implementation)3 ]5 m. X i/ q$ ~* |8 K. S+ {
  37. public onlyProxyOwner$ U! ~3 w$ Y y4 n' K: H" Q
  38. {6 G: W+ a- R/ }( w
  39. _upgradeTo(_implementation);5 M; c7 `; C3 \" T
  40. }7 k }! c7 p9 b3 l" q+ y
  41. /** + [ k8 a5 h% \/ p/ @8 W5 ~
  42. * @dev Tells the address of the current implementation# c, U% B Q+ g4 G; _; ~
  43. * @return address of the current implementation+ u( P; ?2 S5 C3 b
  44. */: \1 |/ f/ J0 z8 H7 |" Y
  45. function implementation() public view returns (address impl) { 6 @" P/ B$ W; E) ] }& t2 `7 ?
  46. bytes32 position = implementationPosition; ' G3 K$ V- L! h8 E3 l
  47. assembly { / E+ _9 _- [7 j# z4 ]
  48. impl := sload(position) 4 C0 _3 \) B, D o) a
  49. }4 N* f& G9 G( r; Z) P5 y/ i' z+ y
  50. }- L: J, S( x2 k. q
  51. /**( j6 z$ `9 j9 ~) E4 k& i
  52. * @dev Tells the address of the owner 1 v! D# r1 U$ d5 Y
  53. * @return the address of the owner1 X4 m; Q1 d3 a
  54. */& Q! N9 o8 \6 r% r
  55. function proxyOwner() public view returns (address owner) {# q- R. |3 M6 V" ~
  56. bytes32 position = proxyOwnerPosition;" l; Z- d- o& h, V: V8 ~
  57. assembly {+ Z. [) }3 U6 A2 f, _9 o# V
  58. owner := sload(position)) E3 a2 d: c, t$ a
  59. }) f4 T: k6 |* K/ X: I0 V. e% u
  60. } Q# ?& D" I6 { f, m u- K
  61. /** F8 ]% s* ]+ X5 Z( S9 l
  62. * @dev Sets the address of the current implementation7 Y1 I- [* Z6 z# c h; R. h7 b, C% l
  63. * @param _newImplementation address of the new implementation 3 e) j0 W h5 n8 e: I
  64. */ ?1 T5 \+ Z7 u1 e
  65. function _setImplementation(address _newImplementation) & u/ l% m- A3 y4 t8 \4 V" \" n
  66. internal( }9 t4 I1 U: q
  67. { 3 V0 h( A, h3 |! ]& y- ^8 L! Z
  68. bytes32 position = implementationPosition; 2 ~5 c8 e$ z) i. }8 Y
  69. assembly { 5 y) i) i! _; `7 Q \) q* O
  70. sstore(position, _newImplementation) 6 E% A1 ] S: V/ z- ?% r
  71. } 5 C& [$ P( _# m6 C3 b
  72. } ) k) V* [, s4 M0 \0 } x
  73. /** . i1 j. r! d) r3 s* ~0 |( `, j
  74. * @dev Upgrades the implementation address) [+ k8 V- N$ E7 [: L+ w
  75. * @param _newImplementation address of the new implementation . ]2 W+ {- R4 m: ^7 _. B7 e7 @& ?
  76. */ M6 ~7 F. j! u' T
  77. function _upgradeTo(address _newImplementation) internal {+ K7 L. R7 b A/ f% M$ T9 y1 I
  78. address currentImplementation = implementation();+ B; Y$ W+ }, ^2 [
  79. require(currentImplementation != _newImplementation); 2 M. u* _& g3 q
  80. _setImplementation(_newImplementation); , k( S6 H6 k# I* }' ~. |
  81. } 4 ~9 ?# f" ^; |& j" [
  82. D5 ?" a0 [6 J) v& u
  83. /** 2 j4 M& M [' R4 h3 M
  84. * @dev Sets the address of the owner' R7 c* j% x6 J& A) a0 E1 `4 x# J
  85. */ + m2 ^. z8 G1 V$ x. S; S
  86. function _setUpgradeabilityOwner(address _newProxyOwner) 7 p3 f" c1 S- q1 s/ @& q: i6 |4 b
  87. internal2 F3 a# @9 m7 V5 `4 _ D, g5 @. }
  88. {8 o2 j3 o- o6 ^, q) M
  89. bytes32 position = proxyOwnerPosition;4 l6 u$ F' y; q0 C: {
  90. assembly {3 f" T5 |1 \0 f' F1 S
  91. sstore(position, _newProxyOwner)" y' Z, x: C' m$ U
  92. } # S* W+ M( s" }! J- w
  93. }" T9 r) V1 @0 _+ ]1 Z
  94. }
复制代码
) A9 J8 q1 h: C: K7 Q$ w * F% B% M T" v$ q5 O6 V0 f 4 }4 y; ^9 k" R" n Q: V2 @ 如何使用非结构化可升级存储代理合约?3 _2 y! v; u2 F( i( ^# i% G5 D& @ + Q7 L, e2 P4 v; N0 O+ E% l6 ^ 使用非结构化可升级存储代理合约是非常简单的,因为这种技术几乎可以处理所有现有的合约。想要使用这种技术,你只需要遵循以下步骤: 8 K j, { X! r! ^9 L' i部署代理合约和执行合约;4 m* a. v' Q( f 调用代理合约的upgradeTo(address)函数,同时pass掉执行合约的地址。 ! [5 v( ~; S5 g9 b我们现在可以忘掉这个执行合约地址,然后把代理合约的地址作为主地址。( w: A9 @6 u6 M2 X+ u; P * G9 c4 b( o( s* G 而要升级这个新实施的合约,我们只需要部署新的执行合约,并调用代理合约的upgradeTo(address) 函数,同时pass掉这个新执行合约的地址。就是这么简单!% L$ z9 g: l X+ d; g8 I0 X% s 让我们简单举个例子。我们将再次使用上述可升级存储代理合约中使用的同一逻辑合约,但是我们不需要用到存储结构。因此,我们的ImplementationV1合约看起来会是这样的: : k, R ?8 g; ]( v5 @0 l$ T
  1. contract ImplementationV1 {& |6 Y7 G# F3 A+ E! F) ^0 a, l3 B$ _
  2. address public owner;; Q9 Z9 @' P- Y: } n3 W3 G; x
  3. mapping (address => uint) internal points; $ ]* p8 N; J5 Z0 L0 }
  4. % E" o3 U5 N" E# z3 u& b& V
  5. modifier onlyOwner() {' {2 a6 C* _9 n. T4 y
  6. require (msg.sender == owner); $ n7 k6 R: x+ R
  7. _; I9 G5 l' {1 M+ M2 B
  8. } 0 M/ t4 _5 T, i/ [" q5 S
  9. function initOwner() external { + R+ g3 N1 `$ i. \& \
  10. require (owner == address(0)); ' r* n# ^/ h' m. q: Z0 g+ J- r
  11. owner = msg.sender; G m' @7 h1 ]& x0 o- o
  12. } 9 j- `8 b1 j/ N9 z+ o3 M! s+ M
  13. function addPlayer(address _player, uint _points) , N1 v! {# z# F: K/ [- _
  14. public onlyOwner 7 s: S4 u: W2 R ~
  15. {6 L! a) W- I$ j" b; t& E
  16. require (points[_player] == 0); 7 v: {; r6 Z4 e! K/ t5 y" o4 ]
  17. points[_player] = _points; , R) b' O8 S6 ~' B9 E
  18. } - l9 X* N7 N9 J) p
  19. 1 X! g& k1 V% _
  20. function setPoints(address _player, uint _points)2 J9 }1 S. m- a! p" \
  21. public onlyOwner + W6 Z/ l6 }/ V! v5 |
  22. {: f1 q ^* H. h* E) I
  23. require (points[_player] != 0); $ W- m" G W0 G6 h% B
  24. points[_player] = _points;4 y0 Q, ?; B% B @
  25. } 4 T: [& \9 @$ ]3 C4 a2 O
  26. }
复制代码
0 |& C( d* A8 F1 T0 c: }. b8 k# B" Y6 r/ }0 { 下一步是部署这个执行合约以及我们的代理合约。然后,再调用代理合约的upgradeTo(address) 函数,同时pass掉执行合约的地址。 6 N0 `, z) A0 [# r V) m" j$ D- O- S( Z& y你可能注意到,在这个执行合约中,甚至没有声明totalPlayers变量,我们可以升级这个执行合约,其中具有 totalPlayers变量,这个新的执行合约看起来会是这样的: ( w1 {7 `: t7 S/ ^# V
  1. contract ImplementationV2 is ImplementationV1 { ( \$ c" [+ U8 H: q, l* T d, c
  2. uint public totalPlayers; / t6 o, E, O( c9 c) C, g3 g5 x
  3. ) K6 t+ H4 f) s! v7 h0 A3 ], x
  4. + \0 v }& D ]. A& ~
  5. function addPlayer(address _player, uint _points) 2 I7 U2 f3 d/ g9 B" M& `
  6. public onlyOwner 0 g+ B# C$ j' y% A0 N6 K9 p
  7. {- ^: o1 V D8 B8 l: `
  8. require (points[_player] == 0); [ g+ h' M0 d* n& w
  9. points[_player] = _points; 1 V" Z1 p& h8 O
  10. totalPlayers++;- f, x5 M9 V |+ |3 v8 \0 b: C: p
  11. } + F+ e* L( v: ~5 X0 h& C7 F
  12. }
复制代码
0 h& [7 I% f0 Y: h6 ?9 X7 h2 f& D6 g& G 而要升级这个新的执行合约,我们需要做的,就是在网络上部署这个合约,然后,嗯你猜对了,就是调用代理合约的upgradeTo(address)函数,并同时pass掉我们新执行合约的地址。现在,我们的合约已演变为能够保持跟踪 totalPlayers,同时仍然为用户提供相同的地址。; o$ U* P# ~8 G; @ `3 k4 B# s : @0 \* k- R) E7 N* V# I这种方法是强大的,但也存在着一些局限性。主要关注的一点是,代理合约拥有者(proxyOwner)有太多的权力。而且,这种方法对复杂的系统而言是不够的。对于构建具有可升级合约的 dApp而言,组合主从合约以及非结构化可升级存储代理合约,会是更为灵活的一种方法,这也是作者所在的GovBlocks所使用的方法。 ) l0 u; m* e7 X/ d" _ s0 p 1 ^- r v2 R) z " f O, g1 Q' g% ?/ r% X 结论 :/ W0 K# \8 y: A* Z 非结构化存储代理合约,是创建可升级智能合约最先进的技术之一,但它仍然是不完美的。毕竟,我们并不希望dApp所有者对dApp具有不当的控制权。如果开发者拥有了这种权力,那这个dapp还能称之为去中心化应用吗?在这里,我建议读者可以阅读下Nitika提出的反对使用onlyOwner的论点。你也可以在GitHub上窥探到我们的代理合约。% x |/ c5 f8 v2 D" L 希望这篇文章可以帮助你创建可升级的智能合约。 4 O, U7 q- M; J1 }同时向Zepplin在代理技术方面进行的工作致敬。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

用香烟做的云 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    2