前言 在以太坊上,我们可以通过部署智能合约来实现我们需要的功能,合约代码中我们往往需要定义一些变量,这就涉及到了智能合约变量的存储机制。 对此,知道创宇区块链安全实验室根据 Solidity?所有的变量命名类型对智能合约的存储机制进行了分析研究。 存储机制 每个在以太坊虚拟机(EVM)中运行的智能合约的状态都在链上永久地存储着。 这些值存储在一个巨大的数组中,数组的长度为 2^256,下标从零开始且每一个数组能够储存 32 字节( 256 个比特)长度的值。并且存储是稀疏的,并没有那么密集。 Solidity 的数据变量类型分为两类 值类型- value type 引用类型- reference type 值类型 布尔型 (bool) 2bit (0/1) 整型 (int/uint )?根据关键字的不同表示不同长度,int8 表示 8bits 有符号数 定长浮点型 (fixed/ufixed) Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量 地址类型 (adress) 160bits 地址类型成员变量 (balance,transfer....) balance ?uint 256 (256bits) transfer () uint256 (256bits) 定长字节数组 (byte[1]/bytes[1]) 定义数组时定义长度 引用类型 不定长字节数组类型 (bytes[]/byte[],string,uint[]....) 结构体 (struct) 映射 (mapping) 简单分析 写一个简单值类型的合约 pragma solidity ^0.4.25; contract TEST{ bool a=false; bool b=true; int16 c=32767; uint16 d=0x32; byte e=10; bytes1 f=11; bytes2 g=22; uint h=0x1; //uint是uint256的简称 address i=0xbc6581e11c216B17aDf5192E209a7F95a49e6837; } 优化存储原则: 如果下一个变量长度和上一个变量长度加起来不超过 256bits,它们就会存储在同一个插槽里。 根据交易查询到的存储在以太坊虚拟机上面的值,下面进行分析: 0x0000000000000000000000000000000000000000000000160b0a00327fff0100 slot0 //0x00 a false //0x01 b true //0x7fff c 32767 //0x0032 d 0x32 //0x0a e 10 //0x0b f 11 //0x0016 g 22 0x0000000000000000000000000000000000000000000000000000000000000001 slot1 // h 0x1 0x000000000000000000000000bc6581e11c216b17adf5192e209a7f95a49e6837 slot2 // i 0x2 从上面可以看出: 各个类型的存储长度 存储顺序从后往前 存储优化原则 byte.length==bytes1.length==8bits 数组类型 定长数组 pragma solidity ^0.4.25; contract TEST{ bytes8[5] a = [byte(0x6a),0x68,0x79,0x75]; bool b=true; } 可以看的我虽然规定了了长度为 5,但是实际上只用了 4 个,所以就只是用了四个 bytes8 的空间是不是可以加一个,编译器会报错。 变长数组 pragma solidity ^0.4.25; contract TEST{ uint[] a=[0x77,0x88,0x99]; function add(){ a.push(0x66); } } We1c0me_t0_th3_w0_ rd1d_o7_bl0ck6hain 根据交易查询到的存储在以太坊虚拟机上面的值,下面进行分析: 0x0000000000000000000000000000000000000000000000000000000000000003 slot0 //存储的是数组a的长度3 0x0000000000000000000000000000000000000000000000000000000000000077 slotx //a[0] 0x0000000000000000000000000000000000000000000000000000000000000088 slot(x+1) //a[1] 0x0000000000000000000000000000000000000000000000000000000000000099 slot(x+2) //a[2] Storage Address 的由来 x=keccak_256(slot) slot 是指数组长度存储的位置,此处对应的就是 0,对应的值就是: 0x0000000000000000000000000000000000000000000000000000000000000000 import sha3 import binascii def byte32(i): return binascii.unhexlify('%064x'%i) #计算时需要进行填充 a=sha3.keccak_256(byte32(0)).hexdigest() print(a) #0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 x 此后 a[1],a[2] 对应偏移 1,2 个插槽然后我们在调用 add() 函数看,发生了什么: 第一步改变了数组 a 的长度 第二步在 a[2] 后面的一个插槽写入 0x66 字符串类型 pragma solidity ^0.4.25; contract TEST{ string a='whoami'; } from Crypto.Util.number import * b=0x77686f616d69 print(long_to_bytes(b)) #b'whoami' #0xc 代表字符串长度 每个字母占 2 个十六进制位 pragma solidity ^0.4.25; contract TEST{ string a='知道创宇'; } from Crypto.Util.number import * b=0xe79fa5e98193e5889be5ae87 print(long_to_bytes(b).decode('utf-8')) #知道创宇 #0x18 每个汉字占 6 个十六进制位 pragma solidity ^0.4.25; contract TEST{ string a='Genius only means hard-working all one\'s life.'; } 此时的存储方式和数组类 from Crypto.Util.number import * b=0x47656e697573206f6e6c79206d65616e7320686172642d776f726b696e6720616c6c206f6e652773206c6966652e print(long_to_bytes(b)) #b"Genius only means hard-working all one's life." 思考了一下,比如像下面这样写,调用add函数后会发生什么 pragma solidity ^0.4.25; contract TEST{ string a='abcdf'; function add(){ a='Genius only means hard-working all one\'s life.'; } } 结构体类型 pragma solidity ^0.4.25; contract TEST{ struct test{ bool a; uint8 b; uint c; string d; } test student=test(true,0x01,0xff,'abcd'); } 依旧按照存储优化原则 a,b slot0 c ? ? ? ?slot1 d ? ? ? ?slot2 如果 d 超出了 32 字节,那么此时x=x=keccak_256(2) pragma solidity ^0.4.25; contract TEST{ struct test{ bool a; uint8 b; uint c; string d; } test[] student; function add(){ student.push(test(true,0x01,0xff,'abcd')); } } 和变长数组存储类似,只不过以结构体长度为一个存储周期改变。 映射类型 pragma solidity ^0.4.25; contract TEST{ mapping(address=>uint) blance; function add(){ blance[0xbc6581e11c216B17aDf5192E209a7F95a49e6837]=0x01; } } 计算的规则是这样的,x=keccak_256(key+slot) key 表映射类型的关键字 slot 代表定义映射类型变量对应的插槽 import sha3 import binascii def byte32(i): return binascii.unhexlify('%064x'%i) key=0xbc6581e11c216B17aDf5192E209a7F95a49e6837 b=byte32(key)+byte32(0) a=sha3.keccak_256(b).hexdigest() print(a) #21d25f73dd60df1532a052f5f1044cb0f7986a3f609d8674628447c29af248fb pragma solidity ^0.4.25; contract TEST{ mapping(uint8=>string) blance; function add(){ blance[0xb]="Genius only means hard-working all one's life."; } } import sha3 import binascii def byte32(i): return binascii.unhexlify('%064x'%i) key=0xb b=byte32(key)+byte32(0) a=sha3.keccak_256(b).hexdigest() print(a) #9115655cbcdb654012cf1b2f7e5dbf11c9ef14e152a19d5f8ea75a329092d5a6 slot a=sha3.keccak_256(byte32(slot)).hexdigest() #3f6f2497fb590e494002b67c712e1fba86767d2906fb8e1ddae48d2b7d91908b 综合练习 pragma solidity >0.5.0; contract StorageExample6 { uint256 a = 11; uint8 b = 12; uint128 c = 13; bool d = true; uint128 e = ?14; uint256[] public array = ?[401,402,403,405,406]; address owner; mapping(address => UserInfo) public users; string ?str="name value"; struct UserInfo { string name; uint8 age; uint8 weight; uint256[] orders; uint64[3] lastLogins; } constructor()public { owner=msg.sender; addUser(owner,"admin",17,120); } function addUser(address user,string memory name,uint8 age,uint8 weight) public { require(age>0 && age <100 ,"bad age"); uint256[] memory orders; uint64[3] memory logins; users[user] = UserInfo({ name: name, age: ? ?age, ?weight:weight, orders:orders, ?lastLogins:logins }); } function addLog(address user,uint64 id1,uint64 id2,uint64 id3) public{ UserInfo storage u = users[user]; assert(u.age>0); u.lastLogins[0]=id1; u.lastLogins[1]=id2; u.lastLogins[2]=id3; } function addOrder(address user,uint256 orderID) public{ UserInfo storage u = users[user]; assert(u.age>0); u.orders.push(orderID); } function getLogins(address user) public view returns (uint64,uint64,uint64){ UserInfo storage u = users[user]; return ?(u.lastLogins[0],u.lastLogins[1],u.lastLogins[2]); } function getOrders(address user) public view returns (uint256[] memory){ UserInfo storage u = users[user]; return ?u.orders; } } 避免太过冗长,放个图 解题练习 web3.eth.getStorageAt(address, position [, defaultBlock] [, callback]) address:String - 要读取的地址 position:Number - 存储中的索引编号 defaultBlock:Number|String - 可选,使用该参数覆盖 web3.eth.defaultBlock 属性值 callback:Function - 可选的回调函数, 其第一个参数为错误对象,第二个参数为结果。 题目一 --Vault // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Vault { bool public locked; bytes32 private password; constructor(bytes32 _password) public { locked = true; password = _password; } function unlock(bytes32 _password) public { if (password == _password) { locked = false; } } } 定义为私有变量只能组织其他合约访问,但是无法阻止公开访问按照其代码,可以知道 password 的存储位置是 1 web3.eth.getStorageAt(contract.address, 1) 直接使用 contract.unlock("A very strong secret password :\)")//密码错误 contract.unlock(web3.utils.hexToBytes('0x412076657279207374726f6e67207365637265742070617373776f7264203a29')) 题目二 --Lock Box pragma solidity 0.4.24; import "../CtfFramework.sol"; contract Lockbox1 is CtfFramework{ uint256 private pin; constructor(address _ctfLauncher, address _player) public payable CtfFramework(_ctfLauncher, _player) { pin = now%10000; } function unlock(uint256 _pin) external ctf{ require(pin == _pin, "Incorrect PIN"); msg.sender.transfer(address(this).balance); } } 读取私有变量 constructor 只在构造的时候执行一次 总结 本篇文章详细讲解了智能合约的优化存储原则,数组类型,字符串类型,结构体类型和映射类型的存储机制。同时提供了基于 python 的计算代码,用以验证机制分析的正确性。 当然,本文涉及的智能合约设计并不复杂,在实际开发过程中远比此复杂,需要经历一些分析,在能找到正确的存储位置。 最后,希望通过本文章可以帮助大家进一步的了解智能合约。 查看更多 —- 编译者/作者:知道创宇区块链安 玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。 |
智能合约变量储存机制详解
2021-09-28 知道创宇区块链安 来源:区块链网络
- 上一篇:北美矿商或将再次迎来出海高峰
- 下一篇:受中国监管压力,第二大矿池9月30日结束
LOADING...
相关阅读:
- 本周重点关注的、BTC、AVAX、ALGO、XTZ、EGLD、机构投资者涌入ETH以太坊价2021-09-28
- NFT新手入门教程:NFT去哪里买?有哪些平台和项目?2021-09-28
- 女孩瞥视表情包 NFT 售出近 6 万美元,真人表情包为何有价值?2021-09-28
- Facebook 宣布设立 5000 万美元的投资基金,负责开发其虚拟元节2021-09-28
- 关于在卡尔达诺 (ADA) 峰会上首次亮相的人工智能医疗机器人的意见分歧2021-09-28