以太坊上的几种签名: eth_sign, personal_sign, eth_signTypedData
以太坊的签名算法是ECDSA-secp256k1,以下介绍的每一种签名都是基于该算法,只是用来签名的数据不同。1 交易签名 eth_sign以太坊上,签名之前的交易结构如下。let transaction = { to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', value: ethers.utils.parseEther('1'), data: '0xE0A293E08F72454CEd99E1769c3ebd21fD2C20a1', gasLimit: '22000', maxFeePerGas: ethers.utils.parseUnits('20', 'gwei'), maxPriorityFeePerGas: ethers.utils.parseUnits('5', 'gwei'), nonce: 1, type: 2, chainId: chainId, // 31337 } 各项目的含义不再介绍,有兴趣可以查阅https://ethereum.org/en/developers/docs/transactions/...
Solidity学习-可升级合约(Transparent/UUPS/Beacon)
以太坊合约原生并不支持升级,目前的升级一般是采用代理模式实现的。代理模式在代理模式中,有2个合约:Proxy和Implementation,用户总是和Proxy进行交互。Proxy在收到用户的调用请求后,并不执行自身的代码,而是通过delegatecall去执行Implementation合约的代码。delegatecall的特殊之处就是,它并不切换上下文,因此Implementation的代码所处理的存储空间是Proxy合约的存储空间,而非自己的。 Proxy合约里存储了Implementation合约的地址,这个地址是可修改的,当我们需要进行合约升级的时候,只需要重新部署一个新的Implementation合约,同时把Proxy合约中存储的地址改成新合约的地址就行了。升级前后,合约的存储空间没有任何变化(依然是Proxy的存储空间),地址也没有变化(依然是Proxy的地址),因此升级过程对用户完全透明。 合约升级之后,要保证storage变量的兼容性。新版本可以在旧版本的变量之后增加新的变量,但是不可以删除或者修改旧版本的变量。因为自始自终,变量都只存储在Proxy合约里,升...
用golang开发ethereum
之前看到一个用golang开发以太坊的教程 https://goethereumbook.org/zh/ 这个教程非常详细,然而它太陈旧了,目前很多go-ethereum函数接口已经有所修改。最明显的是,EIP1559之后,交易的格式的已经大不一样。因此,我基于上述教程,依据最新的go-ethereum(v1.10.26)对代码demo进行了修改。 用golang开发以太坊的一个好处是,可以很方便的查看和调试geth的源码,可以帮助我们更深入地理解以太坊的底层实现。 代码库为 https://github.com/CryptoRbtree/goeth-client 下面对主要功能做简单介绍。1 账户首先需要调用ethclient.DialContext连接一个rpc,这个rpc可以是外部服务商提供的公链rpc,也可以是本地区块链的。var ( ctx = context.Background() url = "https://eth-mainnet.g.alchemy.com/v2/" + os.Getenv("ALCHEMY_ID") client, err = ethclie...
以太坊上的几种签名: eth_sign, personal_sign, eth_signTypedData
以太坊的签名算法是ECDSA-secp256k1,以下介绍的每一种签名都是基于该算法,只是用来签名的数据不同。1 交易签名 eth_sign以太坊上,签名之前的交易结构如下。let transaction = { to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', value: ethers.utils.parseEther('1'), data: '0xE0A293E08F72454CEd99E1769c3ebd21fD2C20a1', gasLimit: '22000', maxFeePerGas: ethers.utils.parseUnits('20', 'gwei'), maxPriorityFeePerGas: ethers.utils.parseUnits('5', 'gwei'), nonce: 1, type: 2, chainId: chainId, // 31337 } 各项目的含义不再介绍,有兴趣可以查阅https://ethereum.org/en/developers/docs/transactions/...
Solidity学习-可升级合约(Transparent/UUPS/Beacon)
以太坊合约原生并不支持升级,目前的升级一般是采用代理模式实现的。代理模式在代理模式中,有2个合约:Proxy和Implementation,用户总是和Proxy进行交互。Proxy在收到用户的调用请求后,并不执行自身的代码,而是通过delegatecall去执行Implementation合约的代码。delegatecall的特殊之处就是,它并不切换上下文,因此Implementation的代码所处理的存储空间是Proxy合约的存储空间,而非自己的。 Proxy合约里存储了Implementation合约的地址,这个地址是可修改的,当我们需要进行合约升级的时候,只需要重新部署一个新的Implementation合约,同时把Proxy合约中存储的地址改成新合约的地址就行了。升级前后,合约的存储空间没有任何变化(依然是Proxy的存储空间),地址也没有变化(依然是Proxy的地址),因此升级过程对用户完全透明。 合约升级之后,要保证storage变量的兼容性。新版本可以在旧版本的变量之后增加新的变量,但是不可以删除或者修改旧版本的变量。因为自始自终,变量都只存储在Proxy合约里,升...
用golang开发ethereum
之前看到一个用golang开发以太坊的教程 https://goethereumbook.org/zh/ 这个教程非常详细,然而它太陈旧了,目前很多go-ethereum函数接口已经有所修改。最明显的是,EIP1559之后,交易的格式的已经大不一样。因此,我基于上述教程,依据最新的go-ethereum(v1.10.26)对代码demo进行了修改。 用golang开发以太坊的一个好处是,可以很方便的查看和调试geth的源码,可以帮助我们更深入地理解以太坊的底层实现。 代码库为 https://github.com/CryptoRbtree/goeth-client 下面对主要功能做简单介绍。1 账户首先需要调用ethclient.DialContext连接一个rpc,这个rpc可以是外部服务商提供的公链rpc,也可以是本地区块链的。var ( ctx = context.Background() url = "https://eth-mainnet.g.alchemy.com/v2/" + os.Getenv("ALCHEMY_ID") client, err = ethclie...
Share Dialog
Share Dialog
Subscribe to rbtree
Subscribe to rbtree
交易是一个以太坊账户发给另一个以太坊账户的指令,例如我给你转账1ETH,我mint某个NFT,这些都是交易。交易是以太坊上非常重要的概念,实际上以太坊就是一个交易驱动的状态机,我们发出并被成功执行的每一个交易都是在改变以太坊的状态。

那么一个交易包含哪些内容呢?
recipient:交易的接收地址。可以是外部账户地址,例如我给你转账,这里就会写上你的地址;也可以是合约账户地址,例如我mint某个NFT,这里就需要填写NFT的合约地址。
signature:发送者的签名,确切地说是发送者的私钥对交易内容的签名。该内容保证以太坊的安全性,如果有人想要模仿你发起一笔交易,ta可以伪造交易中的其他任何内容,但唯独无法生成签名。从这里大家也可以看出,为什么在不可信的网站随意用钱包签名是一件危险的事情,虽然签名这一操作并不会上链,但攻击者可以利用你的签名内容来发起交易。
value:发送给接收者的ETH,单位是WEI,1 ETH = 10e18 WEI
data(payload):二进制数据,本项是可选的,主要在跟合约账户交互的时候使用,后面会详述。
gasLimit:交易的最大gas限制,若交易消耗的gas超过该限制,整个交易失败回滚。
maxPriorityFeePerGas:给矿工的小费。
maxFeePerGas:baseFeePerGas + maxPriorityFeePerGas的最大限制。其中baseFeePerGas由以太坊网络的拥堵状况决定,越拥堵就会越贵。
nonce:交易序号,当你创建了一个以太坊账户,你发起的第一笔交易nonce是0,第二笔交易nonce是1,以此类推。这个字段是用来保障交易顺序的,如果nonce为0的交易没有被处理,nonce为1的交易是不会被处理的。这个序号也可以用来防范攻击者进行重放攻击。此外,矿工挖矿的时候,也有一个叫nonce的东西,矿工需要找到一个能让区块hash符合挖矿难度要求的数字,挖矿的nonce和这里的nonce并不是一个东西。
在小狐狸钱包里,我们可以对以上内容进行修改:
recipient,value,data(Hex Data)

maxFeePerGas、maxPriorityFeePerGas、gasLimit
(我不明白为什么小狐狸把那一项标注成Max base fee,实测可以发现那一项并不是最大的base,而是最大的base+priority)

nonce
一般来说,我们不必主动调节nonce,但有一种情况是我们想取消某个交易。如果你的交易请求已经广播出去了,这个时候即使它还没有上链,也是没有办法取消的,只能用一个相同nonce的交易去覆盖,新交易的maxPriorityFeePerGas要比旧交易高10%以上,一般我们会用一笔给自己转账的交易来覆盖。当然如果你恰好有新的一笔交易要执行,你也可以直接发起新的交易,然后手动把nonce减1,确保priority比之前高10%。

常规交易:一个外部账户到另一个外部账户的交易,此时recipient是一个外部账户地址,例如我给你转0.1ETH。
合约执行:一个外部账户到一个合约账户的交易,此时recipient是一个合约账户地址,例如我mint一个NFT。
合约部署:当交易中不包含recipient字段或者该字段为null时,说明该交易的目的是在区块链上部署一个新的合约。
关于外部账户和合约账户,其实单看地址数字本身是无法区分这两种账户的,二者也都可以拥有资产(eth、token等)。二者之间最大的区别是,合约账户包含可执行代码,它并不像合约账户那样由私钥控制,而是由代码控制。合约账户地址也不是由公私钥对生成的,而是根据它部署者的地址和部署时那笔交易的nonce生成的。
只有外部账户可以主动发起交易,合约账户不可以主动发起交易。合约账户的代码是可以随意编写的,它当然也可以与其他合约账户或外部账户交互,这叫内部交易(internal transaction),内部交易并不是真正的交易,因为它不可以独立存在,必须由某个外部账户先执行合约,才有可能触发内部交易。
单纯的ETH转账是以太坊上最简单的交易,当一笔交易不携带data的时候,它就是一个简单的ETH转账,转账的数额就是交易携带的value。
需要注意的是,既可以给外部账户转账,也可以给合约账户转账。虽然二者的data字段都为空,但给合约账户转账的时候,会自动执行合约账户的receive函数,如果合约账户没有receive函数,会执行fallback函数。此时要求fallback必须是payable的,如果fallback不是payable的或者合约没有定义fallback,转账会失败。可见,给合约账户转账是可能失败的,但是给外部账户转账一定会成功,即使这个外部账户不存在(不存在指某个地址在区块链历史记录里从未出现过,其实可以认为不存在的地址被默认为外部账户)。
与其把转账看成一种单独的类型,倒不如把它看成一种交易的特例,即不携带data的交易。而携带data的交易本身也是具备转账能力的,因为任何一个交易都有value字段。当我们不想给接收方账户转账ETH的时候,只不过是把value字段的值设置成了0而已。
在小狐狸钱包里,我们在转账的时候可以选择ETH或者任何一种代币,看起来似乎是一样的,但实际上ETH的转账和ERC20代币的转账完完全全是两码事。

ETH转账时,recipient填写的是接收方地址,data是空,value是转账的数额;ERC20代币转账时,recipient则需要填写代币的合约地址,value是0(因为此时你并不需要向合约转ETH),接收方地址和转账数额则是通过代币合约的transfer函数参数传入的。
transfer函数是ERC20代币标准要求必须实现的函数。你可以创建自己的代币,不实现transfer功能,不过这样的话它就不能称作ERC20代币了。
https://ethereum.org/en/developers/docs/standards/tokens/erc-20/
function transfer(address _to, uint256 _value) public returns (bool success)
ETH转账是以太坊内置的功能,每个账户的ETH余额也不需要我们费心维护。而ERC20代币转账则是需要合约开发者自行实现的,一般来说,合约内部会维护一个mapping,记录所有代币持有者的持有量,在transfer的时候调整账户余额。这一点给了代币设计者很大的自由度,例如我想创建一个转账就会导致通缩的代币,我就可以在调整账户余额的时候,只给接收者余额增加发送者支付额度的90%。
ETH转账的data

ERC20代币转账的data

外部账户去调用合约账户的函数。此时,data字段就派上用场了。(刚才说的ERC20代币转账就是合约交互的一个例子)
我们可以在etherscan上找一个合约交互的例子,然后点开它的data数据
https://etherscan.io/address/0xf13b8d8b5a86f770b662cf5bf8690db9492bbe6b

etherscan具有一定的解析能力,可以看出这段input data其实是在调用合约的mint函数。

实际上,我们可以在小狐狸钱包里面点转账,receive填写合约地址,然后在hex data里面填写上面那串二进制代码。和从网页触发的合约交互是一样的效果(当然要保证value足够)

data字段的前4个字节是函数名和函数参数的hash,合约执行的时候,正是据此判断调用者想要调用合约里的哪个函数。
钱包可能会存储一些比较常用的函数名对应的hash,所以有些交易中,钱包可以帮我们识别出正在调用合约的哪个函数。如果没有存,就只会像上面的图中那样,只提示一个contract interaction。
我们在小狐狸钱包里面,试着把上面例子里面data的前四个字节改成0xab834bab,可以发现此函数被识别出来了-ATOMIC MATCH_。当然,识别出来和能正确运行是两码事,因为这个是我瞎改的,recipient合约里并没有这个函数,肯定会执行失败。

data字段的后续内容为函数参数,是按照特定标准编码的,这里不再详述,有兴趣自行查看https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding
etherscan里也可以解析,例如上面的例子,前两个参数分别为2和0,后面两个参数为空

有些复杂的交易etherscan解析不出来,也可以去这个网站 https://ethtx.info/
从上面的描述可以发现,data数据里面并没有写明要调用合约的哪个函数,只是有一个hash,那么代码要如何执行呢?如果有熟悉C语言的朋友,可以考虑一下C语言的函数调用和这里有什么不一样。C语言的函数调用,需要已知函数的内存地址,而在区块链的世界里,肯定不能这样做,因为每个矿工的本地内存地址都不会一样的。在区块链的世界里,实际上使用哈希指针取代了内存指针,在执行的时候,需要遍历合约的所有函数hash,如果找到和data的前4个字节相同的,则开始执行相应的函数代码。
我们依然使用上面的合约作为例子
https://etherscan.io/address/0xf13b8d8b5a86f770b662cf5bf8690db9492bbe6b#code
我们可以找到合约的部署code,一般以60806040开头

我们找到第二个60806040,然后复制到末尾,这才是最后部署到链上的代码(具体原因下一节再说明)。

把内容粘贴到这个在线反汇编工具上 https://ethervm.io/decompile 进行decompose
可以看到下面的内容:

居然出现了一个main函数!如果你去看上面合约的源代码,会发现源代码里并不存在main函数,这个函数是自动生成的。这个函数的一个重要功能就是根据input前4字节的hash去寻找函数。上面的代码中,var0就是前4个字节的内容。下面就是用if条件语句,去和合约里的所有函数的hash进行比较,命中之后就会执行相应的函数。

所以实际上,部署在区块链上的合约是会有一个main入口函数的,所有和合约的交互都是从这个main函数开始的。
上面复制合约code的时候,并没有从开头开始,而是从第二个60806040开始的,这是为什么呢?
之前说过,合约部署也是一个交易,它是recipient为空的特殊交易,它有自己的data。我们刚才在etherscan上看到的creation code实际上就是部署的时候的data。
可以在这里查证,https://etherscan.io/tx/0x1bcabc8bd59af966a55591b396e8f620deb9f2dcfdd366e873811b47dbe7edbc
那么这段data会只包含我们想要部署的合约内容吗?当然不是,这段data是我们想要部署的code的生成器,它的返回值才是我们想要部署的内容。这段data会执行我们想要部署的合约的构造函数,然后返回我们真正想要部署的code,这些code会真正被写入区块链。
合约中并非所有内容都会被部署到链上,只在部署时需要被调用的构造函数并不会上链,只会被构造函数调用的内部函数也不会上链。
参考资料
[1]https://ethereum.org/en/developers/docs/transactions/
[2]https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html
[3]https://docs.soliditylang.org/en/latest/contracts.html#creating-contracts
交易是一个以太坊账户发给另一个以太坊账户的指令,例如我给你转账1ETH,我mint某个NFT,这些都是交易。交易是以太坊上非常重要的概念,实际上以太坊就是一个交易驱动的状态机,我们发出并被成功执行的每一个交易都是在改变以太坊的状态。

那么一个交易包含哪些内容呢?
recipient:交易的接收地址。可以是外部账户地址,例如我给你转账,这里就会写上你的地址;也可以是合约账户地址,例如我mint某个NFT,这里就需要填写NFT的合约地址。
signature:发送者的签名,确切地说是发送者的私钥对交易内容的签名。该内容保证以太坊的安全性,如果有人想要模仿你发起一笔交易,ta可以伪造交易中的其他任何内容,但唯独无法生成签名。从这里大家也可以看出,为什么在不可信的网站随意用钱包签名是一件危险的事情,虽然签名这一操作并不会上链,但攻击者可以利用你的签名内容来发起交易。
value:发送给接收者的ETH,单位是WEI,1 ETH = 10e18 WEI
data(payload):二进制数据,本项是可选的,主要在跟合约账户交互的时候使用,后面会详述。
gasLimit:交易的最大gas限制,若交易消耗的gas超过该限制,整个交易失败回滚。
maxPriorityFeePerGas:给矿工的小费。
maxFeePerGas:baseFeePerGas + maxPriorityFeePerGas的最大限制。其中baseFeePerGas由以太坊网络的拥堵状况决定,越拥堵就会越贵。
nonce:交易序号,当你创建了一个以太坊账户,你发起的第一笔交易nonce是0,第二笔交易nonce是1,以此类推。这个字段是用来保障交易顺序的,如果nonce为0的交易没有被处理,nonce为1的交易是不会被处理的。这个序号也可以用来防范攻击者进行重放攻击。此外,矿工挖矿的时候,也有一个叫nonce的东西,矿工需要找到一个能让区块hash符合挖矿难度要求的数字,挖矿的nonce和这里的nonce并不是一个东西。
在小狐狸钱包里,我们可以对以上内容进行修改:
recipient,value,data(Hex Data)

maxFeePerGas、maxPriorityFeePerGas、gasLimit
(我不明白为什么小狐狸把那一项标注成Max base fee,实测可以发现那一项并不是最大的base,而是最大的base+priority)

nonce
一般来说,我们不必主动调节nonce,但有一种情况是我们想取消某个交易。如果你的交易请求已经广播出去了,这个时候即使它还没有上链,也是没有办法取消的,只能用一个相同nonce的交易去覆盖,新交易的maxPriorityFeePerGas要比旧交易高10%以上,一般我们会用一笔给自己转账的交易来覆盖。当然如果你恰好有新的一笔交易要执行,你也可以直接发起新的交易,然后手动把nonce减1,确保priority比之前高10%。

常规交易:一个外部账户到另一个外部账户的交易,此时recipient是一个外部账户地址,例如我给你转0.1ETH。
合约执行:一个外部账户到一个合约账户的交易,此时recipient是一个合约账户地址,例如我mint一个NFT。
合约部署:当交易中不包含recipient字段或者该字段为null时,说明该交易的目的是在区块链上部署一个新的合约。
关于外部账户和合约账户,其实单看地址数字本身是无法区分这两种账户的,二者也都可以拥有资产(eth、token等)。二者之间最大的区别是,合约账户包含可执行代码,它并不像合约账户那样由私钥控制,而是由代码控制。合约账户地址也不是由公私钥对生成的,而是根据它部署者的地址和部署时那笔交易的nonce生成的。
只有外部账户可以主动发起交易,合约账户不可以主动发起交易。合约账户的代码是可以随意编写的,它当然也可以与其他合约账户或外部账户交互,这叫内部交易(internal transaction),内部交易并不是真正的交易,因为它不可以独立存在,必须由某个外部账户先执行合约,才有可能触发内部交易。
单纯的ETH转账是以太坊上最简单的交易,当一笔交易不携带data的时候,它就是一个简单的ETH转账,转账的数额就是交易携带的value。
需要注意的是,既可以给外部账户转账,也可以给合约账户转账。虽然二者的data字段都为空,但给合约账户转账的时候,会自动执行合约账户的receive函数,如果合约账户没有receive函数,会执行fallback函数。此时要求fallback必须是payable的,如果fallback不是payable的或者合约没有定义fallback,转账会失败。可见,给合约账户转账是可能失败的,但是给外部账户转账一定会成功,即使这个外部账户不存在(不存在指某个地址在区块链历史记录里从未出现过,其实可以认为不存在的地址被默认为外部账户)。
与其把转账看成一种单独的类型,倒不如把它看成一种交易的特例,即不携带data的交易。而携带data的交易本身也是具备转账能力的,因为任何一个交易都有value字段。当我们不想给接收方账户转账ETH的时候,只不过是把value字段的值设置成了0而已。
在小狐狸钱包里,我们在转账的时候可以选择ETH或者任何一种代币,看起来似乎是一样的,但实际上ETH的转账和ERC20代币的转账完完全全是两码事。

ETH转账时,recipient填写的是接收方地址,data是空,value是转账的数额;ERC20代币转账时,recipient则需要填写代币的合约地址,value是0(因为此时你并不需要向合约转ETH),接收方地址和转账数额则是通过代币合约的transfer函数参数传入的。
transfer函数是ERC20代币标准要求必须实现的函数。你可以创建自己的代币,不实现transfer功能,不过这样的话它就不能称作ERC20代币了。
https://ethereum.org/en/developers/docs/standards/tokens/erc-20/
function transfer(address _to, uint256 _value) public returns (bool success)
ETH转账是以太坊内置的功能,每个账户的ETH余额也不需要我们费心维护。而ERC20代币转账则是需要合约开发者自行实现的,一般来说,合约内部会维护一个mapping,记录所有代币持有者的持有量,在transfer的时候调整账户余额。这一点给了代币设计者很大的自由度,例如我想创建一个转账就会导致通缩的代币,我就可以在调整账户余额的时候,只给接收者余额增加发送者支付额度的90%。
ETH转账的data

ERC20代币转账的data

外部账户去调用合约账户的函数。此时,data字段就派上用场了。(刚才说的ERC20代币转账就是合约交互的一个例子)
我们可以在etherscan上找一个合约交互的例子,然后点开它的data数据
https://etherscan.io/address/0xf13b8d8b5a86f770b662cf5bf8690db9492bbe6b

etherscan具有一定的解析能力,可以看出这段input data其实是在调用合约的mint函数。

实际上,我们可以在小狐狸钱包里面点转账,receive填写合约地址,然后在hex data里面填写上面那串二进制代码。和从网页触发的合约交互是一样的效果(当然要保证value足够)

data字段的前4个字节是函数名和函数参数的hash,合约执行的时候,正是据此判断调用者想要调用合约里的哪个函数。
钱包可能会存储一些比较常用的函数名对应的hash,所以有些交易中,钱包可以帮我们识别出正在调用合约的哪个函数。如果没有存,就只会像上面的图中那样,只提示一个contract interaction。
我们在小狐狸钱包里面,试着把上面例子里面data的前四个字节改成0xab834bab,可以发现此函数被识别出来了-ATOMIC MATCH_。当然,识别出来和能正确运行是两码事,因为这个是我瞎改的,recipient合约里并没有这个函数,肯定会执行失败。

data字段的后续内容为函数参数,是按照特定标准编码的,这里不再详述,有兴趣自行查看https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding
etherscan里也可以解析,例如上面的例子,前两个参数分别为2和0,后面两个参数为空

有些复杂的交易etherscan解析不出来,也可以去这个网站 https://ethtx.info/
从上面的描述可以发现,data数据里面并没有写明要调用合约的哪个函数,只是有一个hash,那么代码要如何执行呢?如果有熟悉C语言的朋友,可以考虑一下C语言的函数调用和这里有什么不一样。C语言的函数调用,需要已知函数的内存地址,而在区块链的世界里,肯定不能这样做,因为每个矿工的本地内存地址都不会一样的。在区块链的世界里,实际上使用哈希指针取代了内存指针,在执行的时候,需要遍历合约的所有函数hash,如果找到和data的前4个字节相同的,则开始执行相应的函数代码。
我们依然使用上面的合约作为例子
https://etherscan.io/address/0xf13b8d8b5a86f770b662cf5bf8690db9492bbe6b#code
我们可以找到合约的部署code,一般以60806040开头

我们找到第二个60806040,然后复制到末尾,这才是最后部署到链上的代码(具体原因下一节再说明)。

把内容粘贴到这个在线反汇编工具上 https://ethervm.io/decompile 进行decompose
可以看到下面的内容:

居然出现了一个main函数!如果你去看上面合约的源代码,会发现源代码里并不存在main函数,这个函数是自动生成的。这个函数的一个重要功能就是根据input前4字节的hash去寻找函数。上面的代码中,var0就是前4个字节的内容。下面就是用if条件语句,去和合约里的所有函数的hash进行比较,命中之后就会执行相应的函数。

所以实际上,部署在区块链上的合约是会有一个main入口函数的,所有和合约的交互都是从这个main函数开始的。
上面复制合约code的时候,并没有从开头开始,而是从第二个60806040开始的,这是为什么呢?
之前说过,合约部署也是一个交易,它是recipient为空的特殊交易,它有自己的data。我们刚才在etherscan上看到的creation code实际上就是部署的时候的data。
可以在这里查证,https://etherscan.io/tx/0x1bcabc8bd59af966a55591b396e8f620deb9f2dcfdd366e873811b47dbe7edbc
那么这段data会只包含我们想要部署的合约内容吗?当然不是,这段data是我们想要部署的code的生成器,它的返回值才是我们想要部署的内容。这段data会执行我们想要部署的合约的构造函数,然后返回我们真正想要部署的code,这些code会真正被写入区块链。
合约中并非所有内容都会被部署到链上,只在部署时需要被调用的构造函数并不会上链,只会被构造函数调用的内部函数也不会上链。
参考资料
[1]https://ethereum.org/en/developers/docs/transactions/
[2]https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html
[3]https://docs.soliditylang.org/en/latest/contracts.html#creating-contracts
<100 subscribers
<100 subscribers
No activity yet