Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

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

本版积分规则

成为第一个吐槽的人

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

    0

  • 关注

    0

  • 主题

    2