Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

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

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2