EVM虚拟机入门汇编入门(一)
介绍时间戳以及sha指令,并详细解释mapping的底层结构
操作码TIMESTAMP
是EVM中的一个操作码,用于获取当前区块的时间戳。以下是关于TIMESTAMP
操作码的详细信息:
功能:TIMESTAMP
操作码返回当前区块的Unix时间戳,即自1970年1月1日以来的秒数。
历史:自以太坊创世区块(Frontier)以来,TIMESTAMP
一直是EVM的一部分。
栈操作:
示例:
TIMESTAMP
操作码后,栈上将输出1636704767
。错误情况:
TIMESTAMP
操作码时gas不足,当前上下文所做的状态更改将被回滚。gas消耗:通常情况下,读取区块信息的操作码(如TIMESTAMP
)的gas消耗较低,但具体的gas成本取决于以太坊网络的当前gas定价策略。
TIMESTAMP
操作码在智能合约中非常有用,尤其是在需要根据当前时间执行特定逻辑时。例如,它可以用来实现时间锁定功能,确保某些操作只能在特定时间后执行。然而,开发者在使用TIMESTAMP
时应该考虑到时间戳可能受到矿工时间设置的影响,因此可能存在一定的偏差。
TIMESTAMP
操作码返回的Unix时间戳是相对于1970年1月1日(UTC)的秒数,这是计算机编程中常用的时间表示方式。在以太坊中,这个时间戳是通过以下方式确定的:
区块时间戳:每个以太坊区块都包含了一个时间戳,这个时间戳是由矿工在创建区块时设置的。它代表了矿工认为的区块创建时的UTC时间。
网络时间:以太坊网络没有中心化的时间源,因此区块时间戳依赖于矿工的本地时间。虽然矿工通常会设置一个接近实际时间的时间戳,但这个时间戳可能受到矿工本地时间设置的影响,或者在某些情况下,矿工可能会故意设置一个不同的时间戳。
不可篡改性:一旦区块被加入到区块链中,它的所有信息,包括时间戳,就变得不可篡改。这意味着即使时间戳与实际时间有偏差,它也会被永久记录在区块链上。
智能合约使用:智能合约可以使用TIMESTAMP
操作码来获取当前区块的时间戳,并根据这个时间戳来执行合约中的逻辑。例如,合约可以设置一个条件,只有在某个特定的时间戳之后才能执行某些操作。
偏差和限制:开发者在使用TIMESTAMP
操作码时应该意识到,时间戳可能会有偏差,并且不能保证与实际时间完全一致。此外,时间戳的精度通常受限于区块的生成时间,这可能在几秒到几分钟不等。
总的来说,TIMESTAMP
操作码提供了一种在智能合约中使用时间的方法,但它依赖于区块的时间戳,这个时间戳是由矿工设置的,并且一旦设置就无法更改。因此,虽然它可以用来实现基于时间的逻辑,但开发者应该考虑到可能存在的偏差和限制。
KECCAK256是EVM中的一个操作码,用于计算内存中给定数据的Keccak-256哈希值。以下是关于KECCAK256操作码的一些关键信息:
功能:KECCAK256操作码接收两个输入参数,分别是内存中的字节偏移量(offset)和要哈希的字节大小(size),然后输出这个数据的Keccak-256哈希值。
输入:
offset
:内存中的起始位置,从这个位置开始读取数据。size
:要读取并哈希的数据大小。输出:hash
,内存中指定数据的Keccak-256哈希值。
示例:
0xFFFFFFFF
处有一个值,输入1 0
作为offset
和size
,KECCAK256操作码将会计算从内存偏移量1
开始的0
个字节的哈希值,输出将是该数据的哈希值。错误情况:
gas消耗:
static_gas
:固定消耗30gas。dynamic_gas
:动态消耗,由6 * minimum_word_size + memory_expansion_cost
组成,其中minimum_word_size
是向上取整到最近的32字节倍数的size
。内存扩展成本:如果操作导致内存大小增加,还需要考虑内存扩展的成本。
估算gas成本:
offset
为0
,size
也为0
,当前内存大小为0
,那么静态gas加上动态gas的总和是30
。在智能合约中使用KECCAK256
操作码时,开发者需要确保提供足够的gas来覆盖操作的gas成本,并且要注意内存的使用情况,以避免栈溢出或其他错误。此外,由于KECCAK256
操作码会消耗较多的gas,因此它通常用于关键的哈希计算,而不是频繁的计算。
// Put the required value in memory
PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000
PUSH1 0
MSTORE
// Call the opcode
PUSH1 4
PUSH1 0
KECCAK256
这段代码是一系列以太坊虚拟机(EVM)的操作码,用于将一个32字节的值放入内存,然后计算这个值的Keccak-256哈希。下面是对这些操作的详细解释:
PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000:
这个操作码将32字节的十六进制值0xFFFFFFFF00000000000000000000000000000000000000000000000000000000000压入栈。PUSH32表示接下来的32字节将作为数据压入栈。
PUSH1 0:
这个操作码将值0压入栈。PUSH1表示接下来的1字节将作为数据压入栈。
MSTORE:
MSTORE操作码将栈顶的两个元素(在这个例子中是0和上面PUSH32操作压入的值)存储到内存中。第一个元素(0)作为内存的偏移量,第二个元素是要存储的值。因此,这将把0xFFFFFFFF...这个值存储到内存的起始位置(偏移量0)。
PUSH1 4:
将值4压入栈。这个值将作为KECCAK256操作码的size参数,表示要从内存中读取4个字节的数据来计算哈希。
PUSH1 0:
将值0压入栈。这个值将作为KECCAK256操作码的offset参数,表示要从内存的偏移量0开始读取数据。
KECCAK256:
KECCAK256操作码计算内存中从偏移量0开始的4个字节的数据的Keccak-256哈希,并把结果哈希值压入栈。
在这个例子中,由于我们存储的是一个32字节的值,但只计算了前4个字节的哈希,所以最终的哈希值将只反映这4个字节0xffffffff
的数据。
输出如下
29045a592007d0c246ef02c2223570da9522d0cf0f73282c79a1bc8f0bb2c238
那么这个生成可以用来,作为mapping储存的键。
在以太坊智能合约中,mapping
是一种用于存储键值对的数据结构,其中键可以是任何类型的数据(除了映射和动态数组)。然而,由于存储(storage)只能存储具有固定大小(256位)的数据,因此不能直接将映射的键作为存储的键使用。为了解决这个问题,以太坊使用SHA3
哈希函数来生成映射键的唯一标识符,这个过程通常称为“slot计算”。
以下是详细解释:
映射键的哈希:首先,需要对映射的键进行SHA3
哈希处理。例如,如果映射的键是一个地址或者一个字节串,那么直接对这个键进行SHA3
哈希。
位置信息的结合:在某些情况下,例如映射用作数组的一部分,可能还需要结合位置信息(例如数组索引)。这个位置信息也需要被编码并结合到映射键中,以确保每个元素的slot是唯一的。
生成slot:将映射键的哈希值与位置信息结合后,再次进行SHA3
哈希处理,得到的哈希值将用作存储中的slot。这个slot是一个256位的数值,它在存储中是唯一的,对应于映射中的一个特定元素。
存储值:使用计算得到的slot,就可以在存储中设置或检索映射元素的值了。例如,如果你想设置映射中的一个值,你可以使用SSTORE
操作码:
push2 0x1234
push1 0x27
push0
mstore
PUSH1 0x2
PUSH0
KECCAK256
sstore
这里是将key也就是0x27储存在memory的0偏移,然后将其取出0x27的部分sha,作为solt的key,储存0x1234作为value
检索值:当需要检索映射中的值时,可以使用相同的过程计算slot,然后使用SLOAD
操作码来读取存储中的值。
碰撞避免:由于SHA3
哈希函数的属性,即使两个不同的键和位置信息产生了相同的哈希值,再次哈希也会得到不同的结果,这确保了映射的每个元素在存储中都有一个唯一的slot,从而避免了碰撞。
内存和存储:在实际的Solidity编程中,通常会使用内置的mapping
语法,编译器会自动处理这些哈希和存储的操作。开发者通常不需要手动进行这些计算。
通过这种方式,以太坊智能合约能够高效地使用存储空间,同时保证了映射类型数据的灵活性和安全性。