以太坊上的几种签名: 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...
Subscribe to rbtree
Subscribe to rbtree
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
本文从EVM操作码的角度,研究合约创建的详细过程。
solidity version = 0.8.15,optimizer = true,optimizer_runs = 200,evm_version = "london"
在线反汇编 https://ethervm.io/decompile
evm执行模拟使用foundry环境https://book.getfoundry.sh/reference/forge/forge-debug
合约代码如下
pragma solidity 0.8.15;
contract Test {
uint256 public val;
uint256 immutable c;
constructor(uint256 _val, uint256 _c) {
val = _val;
c = _c;
}
function setNumber(uint256 newNumber) public {
val = newNumber + c;
}
}
我们在在foundry环境中,在script文件里,使用new进行部署。
new的部署方式和外部账户通过交易部署合约其实是很类似的,都是message call的过程。
contract TestScript is Script {
function setUp() public view {}
function run() public {
vm.startBroadcast(); // record call information so deploy to chain
Test t = new Test(0x1234, 0x5678);
vm.stopBroadcast();
}
}
在TestScript执行new Test的时候,
我们可以观察一下memory的情况,这里存储的是calldata内容。
0x080~0x1fe: 这一段是deploy code(60a0开始 一直到 0033为之)
接在后面的还有两个uint256的数字,0x1234和0x5678,这两个是我们即将部署的合约的构造函数实参。

继续往下走,我们就会进入真正的deploy阶段。
把deploy code进行反汇编。
contract Contract {
function main() {
memory[0x40:0x60] = 0xa0;
var var0 = msg.value;
if (var0) { revert(memory[0x00:0x00]); }
var temp0 = memory[0x40:0x60];
var temp1 = code.length - 0x017e;
memory[temp0:temp0 + temp1] = code[0x017e:0x017e + temp1];
var var1 = temp0 + temp1;
memory[0x40:0x60] = var1;
var0 = 0x002f;
var var2 = temp0;
var0, var1 = func_003D(var1, var2);
storage[0x00] = var0;
memory[0x80:0xa0] = var1;
var temp2 = memory[0x80:0xa0];
memory[0x00:0x0103] = code[0x7b:0x017e];
memory[0x66:0x86] = temp2;
return memory[0x00:0x0103];
}
function func_003D(var arg0, var arg1) returns (var r0, var arg0) {
var var0 = 0x00;
var var1 = var0;
if (arg0 - arg1 i< 0x40) { revert(memory[0x00:0x00]); }
var temp0 = arg1;
r0 = memory[temp0:temp0 + 0x20];
arg0 = memory[temp0 + 0x20:temp0 + 0x20 + 0x20];
return r0, arg0;
}
}
代码的第1个关键点在storage[0x00] = var0;
这里实际上对应构造函数中的val = _val;
代码的第2个关键点是memory[0x00:0x0103] = code[0x7b:0x017e];
这里是把deploy code中的一部分代码拷贝到memory中。
deploy code其实有两部分,第一部分是用于deploy的代码部分,这些对应上面的反汇编代码。
60a060405234801561001057600080fd5b5060405161017e38038061017e8339 8101604081905261002f9161003d565b600091909155608052610061565b6000 806040838503121561005057600080fd5b505080516020909101519092909150 565b60805161010361007b6000396000606601526101036000f3fe
另一部分是将会被部署上链的代码,被叫做deployed code。这些代码就是在合约上链之后,后面与之交互时所使用的部分。
6080604052348015600f57600080fd5b506004361060325760003560e01c8063 3c6bb4361460375780633fb5c1cb146051575b600080fd5b603f60005481565b 60405190815260200160405180910390f35b6060605c3660046090565b606256 5b005b608a7f0000000000000000000000000000000000000000000000000000 0000000000008260a8565b60005550565b60006020828403121560a157600080 fd5b5035919050565b6000821982111560c857634e487b7160e01b6000526011 60045260246000fd5b50019056fea26469706673582212200b220ab5e3b5c534 b47030c3b89735e224c0d52e5454511a0274bc5bbbfd2abd64736f6c63430008 0f0033
在memory[0x00:0x0103] = code[0x7b:0x017e];执行之后,我们可以看到deployed code已经被拷贝到memory中。在操作码中,对应CODECOPY。

第3个关键点是memory[0x66:0x86] = temp2;
这一句对应构造函数中的c = _c;
因为c是immutable,所以c的值并不会占据storage空间,而是会被在字节码中写死。所以还需要在字节码中,把c的值赋上。

最后的形态如下:
memory中时最终的deployed code,而栈中的两个数字则指定了memory中deployed code的位置。
0x103 = 259,这就是deployed code的大小。

deploy函数的返回值就是最终的deployed code,紧接着deployed code就会被写入区块链,从而完成合约部署过程。
本文从EVM操作码的角度,研究合约创建的详细过程。
solidity version = 0.8.15,optimizer = true,optimizer_runs = 200,evm_version = "london"
在线反汇编 https://ethervm.io/decompile
evm执行模拟使用foundry环境https://book.getfoundry.sh/reference/forge/forge-debug
合约代码如下
pragma solidity 0.8.15;
contract Test {
uint256 public val;
uint256 immutable c;
constructor(uint256 _val, uint256 _c) {
val = _val;
c = _c;
}
function setNumber(uint256 newNumber) public {
val = newNumber + c;
}
}
我们在在foundry环境中,在script文件里,使用new进行部署。
new的部署方式和外部账户通过交易部署合约其实是很类似的,都是message call的过程。
contract TestScript is Script {
function setUp() public view {}
function run() public {
vm.startBroadcast(); // record call information so deploy to chain
Test t = new Test(0x1234, 0x5678);
vm.stopBroadcast();
}
}
在TestScript执行new Test的时候,
我们可以观察一下memory的情况,这里存储的是calldata内容。
0x080~0x1fe: 这一段是deploy code(60a0开始 一直到 0033为之)
接在后面的还有两个uint256的数字,0x1234和0x5678,这两个是我们即将部署的合约的构造函数实参。

继续往下走,我们就会进入真正的deploy阶段。
把deploy code进行反汇编。
contract Contract {
function main() {
memory[0x40:0x60] = 0xa0;
var var0 = msg.value;
if (var0) { revert(memory[0x00:0x00]); }
var temp0 = memory[0x40:0x60];
var temp1 = code.length - 0x017e;
memory[temp0:temp0 + temp1] = code[0x017e:0x017e + temp1];
var var1 = temp0 + temp1;
memory[0x40:0x60] = var1;
var0 = 0x002f;
var var2 = temp0;
var0, var1 = func_003D(var1, var2);
storage[0x00] = var0;
memory[0x80:0xa0] = var1;
var temp2 = memory[0x80:0xa0];
memory[0x00:0x0103] = code[0x7b:0x017e];
memory[0x66:0x86] = temp2;
return memory[0x00:0x0103];
}
function func_003D(var arg0, var arg1) returns (var r0, var arg0) {
var var0 = 0x00;
var var1 = var0;
if (arg0 - arg1 i< 0x40) { revert(memory[0x00:0x00]); }
var temp0 = arg1;
r0 = memory[temp0:temp0 + 0x20];
arg0 = memory[temp0 + 0x20:temp0 + 0x20 + 0x20];
return r0, arg0;
}
}
代码的第1个关键点在storage[0x00] = var0;
这里实际上对应构造函数中的val = _val;
代码的第2个关键点是memory[0x00:0x0103] = code[0x7b:0x017e];
这里是把deploy code中的一部分代码拷贝到memory中。
deploy code其实有两部分,第一部分是用于deploy的代码部分,这些对应上面的反汇编代码。
60a060405234801561001057600080fd5b5060405161017e38038061017e8339 8101604081905261002f9161003d565b600091909155608052610061565b6000 806040838503121561005057600080fd5b505080516020909101519092909150 565b60805161010361007b6000396000606601526101036000f3fe
另一部分是将会被部署上链的代码,被叫做deployed code。这些代码就是在合约上链之后,后面与之交互时所使用的部分。
6080604052348015600f57600080fd5b506004361060325760003560e01c8063 3c6bb4361460375780633fb5c1cb146051575b600080fd5b603f60005481565b 60405190815260200160405180910390f35b6060605c3660046090565b606256 5b005b608a7f0000000000000000000000000000000000000000000000000000 0000000000008260a8565b60005550565b60006020828403121560a157600080 fd5b5035919050565b6000821982111560c857634e487b7160e01b6000526011 60045260246000fd5b50019056fea26469706673582212200b220ab5e3b5c534 b47030c3b89735e224c0d52e5454511a0274bc5bbbfd2abd64736f6c63430008 0f0033
在memory[0x00:0x0103] = code[0x7b:0x017e];执行之后,我们可以看到deployed code已经被拷贝到memory中。在操作码中,对应CODECOPY。

第3个关键点是memory[0x66:0x86] = temp2;
这一句对应构造函数中的c = _c;
因为c是immutable,所以c的值并不会占据storage空间,而是会被在字节码中写死。所以还需要在字节码中,把c的值赋上。

最后的形态如下:
memory中时最终的deployed code,而栈中的两个数字则指定了memory中deployed code的位置。
0x103 = 259,这就是deployed code的大小。

deploy函数的返回值就是最终的deployed code,紧接着deployed code就会被写入区块链,从而完成合约部署过程。
No activity yet