前段時(shí)間 review 團(tuán)隊(duì)小伙伴合約代碼的時(shí)候像啼,提出有些變量是可以使用 immutable 來(lái)修飾的。但得到一個(gè)答復(fù):我們這是可升級(jí)合約,不能用 immutable领追,真的是這樣么?
0x01 OpenZeppelin 的警告
因?yàn)楝F(xiàn)在的可升級(jí)合約基本上都是使用的 OpenZeppelin 的合約模版响逢,估計(jì)可升級(jí)合約不能用 immutable 變量的說(shuō)法也是來(lái)源于 OpenZeppelin绒窑。
在 OpenZeppelin 的 Why can’t I use immutable
variables? 這個(gè)文檔里,確實(shí)解釋了"為什么不能用 immutable 變量"舔亭,主要有下面兩個(gè)原因:
- 可升級(jí)合約沒(méi)有構(gòu)造函數(shù)些膨,只有初始化函數(shù),因此它們無(wú)法處理 immutable 變量钦铺。
- 由于不可變變量的值存儲(chǔ)在字節(jié)碼中订雾,其值將在給定合約的所有代理之間共享。
只是這兩個(gè)原因不能用 immutable 變量矛洞,我感覺(jué)是比較牽強(qiáng)的洼哎。
0x02 什么時(shí)候我們需要用 immutable 變量
immutable 是對(duì)變量的一種硬性約束,一旦初始化就不再改變沼本。其中一個(gè)常見(jiàn)的場(chǎng)景是對(duì)固定合約地址的引用噩峦,比如對(duì) USDT 合約地址的引用,我們明確知道這個(gè)合約地址在固定鏈上是不會(huì)發(fā)生改變的擅威,但是因?yàn)槲覀冇锌赡茉诓煌溕喜渴鹞覀兊暮霞s壕探,起碼要在一個(gè)網(wǎng)絡(luò)的測(cè)試網(wǎng)和主網(wǎng)上部署我們的合約,直接用常量就很不方便郊丛,這個(gè)時(shí)候使用 immutable 變量李请,通過(guò)構(gòu)造函數(shù)初始化后就不再改變,是最符合預(yù)期的厉熟。
當(dāng)然导盅,這種情況下我們也可以使用正常的變量,但這會(huì)引入額外兩個(gè)問(wèn)題:
- Gas 消耗更高
- 存在未來(lái)被惡意改變的風(fēng)險(xiǎn)
所以對(duì)一個(gè)變量來(lái)說(shuō)揍瑟,能使用 immutable 約束的時(shí)候我們還是希望能夠盡量使用 immutable 約束白翻。
0x03 可升級(jí)合約就一定不能使用構(gòu)造函數(shù)么
非也。
這其實(shí)只是 OpenZeppelin 為了方便構(gòu)造函數(shù)誤用而額外加的規(guī)范性限制绢片,在可升級(jí)合約的實(shí)現(xiàn)合約中滤馍,使用構(gòu)造函數(shù)初始化正常變量可能會(huì)得到與預(yù)期不一致的結(jié)果,但初始化 immutable 變量得到的結(jié)果應(yīng)該是完全符合預(yù)期的底循。在實(shí)現(xiàn)合約構(gòu)造函數(shù)中初始化的 immutable 變量在可代理合約中都可以正常讀取巢株。
0x04 結(jié)論
immutable 變量在可升級(jí)合約中使用沒(méi)任何問(wèn)題:
- 盡管通常情況下,可升級(jí)合約的實(shí)現(xiàn)合約中我們遵循 OpenZeppelin 規(guī)范熙涤,使用初始化函數(shù) initialize 去初始化常規(guī)變量阁苞。但在需要的時(shí)候我們?nèi)匀豢梢允褂脴?gòu)造函數(shù)去初始化 immutable 變量困檩。
- 我們使用 immutable 變量的本意就是其初始化后就不再改變,所以同一個(gè)實(shí)現(xiàn)合約的不同代理看到同樣的值并沒(méi)啥問(wèn)題那槽。如果需要不同的值悼沿,實(shí)例化不同的實(shí)現(xiàn)合約就好了。
- 在使用升級(jí)插件的時(shí)候還是要注意把 unsafeAllow: constructor 和 unsafeAllow:state-variable-immutable 的開(kāi)關(guān)打開(kāi)骚灸,否則估計(jì)會(huì)報(bào)錯(cuò)糟趾。