在 Solidity 中初始化一个固定长度的大数组
Initialize a big fixed length array in Solidity
我的第一个项目是在以太坊上开发一款游戏,我面临着存储和气体限制。我想在区块链上存储一个存储智能合约,以便在部署后查询。我真的需要用我手动插入的常量值初始化一个固定长度的数组。我的情况如下:
contract A {
...some states variables/modifiers and events......
uint[] public vector = new uint[](162);
vector = [.......1, 2, 3,......];
function A () {
....some code....
ContractB contract = new ContractB(vector);
}
....functions....
}
此代码未部署。显然我在混音时超出了气体限制。我尝试了以下方法:
- 我将向量拆分为 10 个不同的向量,然后仅将其中一个传递给构造函数。有了这个部署工作。
我真的只需要一个向量,因为它表示图的边集,其中 ContractB 是构建图的数据结构。向量元素的顺序如下:
vector = [edge1From, edge1To, edge2From, edge2To,.......]
我得到了 81 条边(向量中有 162 个条目)。
我想我可以创建一个 setData 函数,在部署后调用此函数将值一个一个地推送到向量中,但这不是我的情况,因为我需要在调用之前填充向量
ContractB contract = new ContractB(vector);
现在看来我有两个疑惑:
1) 在 A 构造函数中的函数调用中尝试将向量作为参数传递是我错了吗?
2) 我可以看到我可以为边缘创建双重映射。像
mapping (bool => mapping(uint => uint))
但是我将需要多键值映射(从同一点开始的更多边)并且我将无法像对向量一样一次初始化所有映射?
如果数组的值范围足够小,您可以通过为 uints
使用更合适的大小来节省 gas 消耗。以太坊将值存储到 32 字节的插槽中,您为每个使用的插槽支付 20,000 gas。如果您能够使用较小尺寸的 uint
(请记住,uint
与 uint256
相同),您将能够节省 gas 使用量。
例如,考虑以下合约:
pragma solidity ^0.4.19;
contract Test {
uint256[100] big;
uint128[100] small;
function addBig(uint8 index, uint256 num) public {
big[index] = num;
}
function addSmall(uint8 index, uint128 num1, uint128 num2) public {
small[index] = num1;
small[index + 1] = num2;
}
}
每次使用以前未使用的索引调用 addBig()
的执行成本将略高于 20,000 gas,并导致将一个值添加到数组中。每次调用 addSmall()
将花费大约 26,000,但是您向数组添加了 2 个元素。两者都只使用 1 个存储槽。如果你可以小于 uint128
.
,你可以获得更好的结果
另一种选择(取决于您是否需要操作数组数据)是将您的 vector
存储在链外。您可以使用 oracle 检索数据或将数据存储在 IPFS 中。
如果这些选项都不适合您的用例,那么您将不得不更改您的数据结构and/or使用多个事务来初始化您的数组。
为什么合约需要在构建时初始化?
这应该有效
pragma solidity ^0.4.2;
contract Graph {
address owner;
struct GraphEdge {
uint128 from;
uint128 to;
}
GraphEdge[] public graph;
bool public initialized = false;
constructor() public {
owner = msg.sender;
}
function addEdge(uint128 edgeFrom, uint128 edgeTo) public {
require(!initialized);
graph.push(GraphEdge({
from: edgeFrom,
to: edgeTo
}));
}
function finalize() public {
require(msg.sender == owner);
initialized = true;
}
}
contract ContractB {
Graph graph;
constructor(address graphAddress) public {
Graph _graph = Graph(graphAddress);
require(_graph.initialized());
graph = _graph;
}
}
我的第一个项目是在以太坊上开发一款游戏,我面临着存储和气体限制。我想在区块链上存储一个存储智能合约,以便在部署后查询。我真的需要用我手动插入的常量值初始化一个固定长度的数组。我的情况如下:
contract A {
...some states variables/modifiers and events......
uint[] public vector = new uint[](162);
vector = [.......1, 2, 3,......];
function A () {
....some code....
ContractB contract = new ContractB(vector);
}
....functions....
}
此代码未部署。显然我在混音时超出了气体限制。我尝试了以下方法:
- 我将向量拆分为 10 个不同的向量,然后仅将其中一个传递给构造函数。有了这个部署工作。
我真的只需要一个向量,因为它表示图的边集,其中 ContractB 是构建图的数据结构。向量元素的顺序如下:
vector = [edge1From, edge1To, edge2From, edge2To,.......]
我得到了 81 条边(向量中有 162 个条目)。
我想我可以创建一个 setData 函数,在部署后调用此函数将值一个一个地推送到向量中,但这不是我的情况,因为我需要在调用之前填充向量
ContractB contract = new ContractB(vector);
现在看来我有两个疑惑:
1) 在 A 构造函数中的函数调用中尝试将向量作为参数传递是我错了吗?
2) 我可以看到我可以为边缘创建双重映射。像
mapping (bool => mapping(uint => uint))
但是我将需要多键值映射(从同一点开始的更多边)并且我将无法像对向量一样一次初始化所有映射?
如果数组的值范围足够小,您可以通过为 uints
使用更合适的大小来节省 gas 消耗。以太坊将值存储到 32 字节的插槽中,您为每个使用的插槽支付 20,000 gas。如果您能够使用较小尺寸的 uint
(请记住,uint
与 uint256
相同),您将能够节省 gas 使用量。
例如,考虑以下合约:
pragma solidity ^0.4.19;
contract Test {
uint256[100] big;
uint128[100] small;
function addBig(uint8 index, uint256 num) public {
big[index] = num;
}
function addSmall(uint8 index, uint128 num1, uint128 num2) public {
small[index] = num1;
small[index + 1] = num2;
}
}
每次使用以前未使用的索引调用 addBig()
的执行成本将略高于 20,000 gas,并导致将一个值添加到数组中。每次调用 addSmall()
将花费大约 26,000,但是您向数组添加了 2 个元素。两者都只使用 1 个存储槽。如果你可以小于 uint128
.
另一种选择(取决于您是否需要操作数组数据)是将您的 vector
存储在链外。您可以使用 oracle 检索数据或将数据存储在 IPFS 中。
如果这些选项都不适合您的用例,那么您将不得不更改您的数据结构and/or使用多个事务来初始化您的数组。
为什么合约需要在构建时初始化?
这应该有效
pragma solidity ^0.4.2;
contract Graph {
address owner;
struct GraphEdge {
uint128 from;
uint128 to;
}
GraphEdge[] public graph;
bool public initialized = false;
constructor() public {
owner = msg.sender;
}
function addEdge(uint128 edgeFrom, uint128 edgeTo) public {
require(!initialized);
graph.push(GraphEdge({
from: edgeFrom,
to: edgeTo
}));
}
function finalize() public {
require(msg.sender == owner);
initialized = true;
}
}
contract ContractB {
Graph graph;
constructor(address graphAddress) public {
Graph _graph = Graph(graphAddress);
require(_graph.initialized());
graph = _graph;
}
}