以太坊上的几种签名: 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
<100 subscribers
<100 subscribers
同质化代币,我们可以看一下它转移代币的接口,可以看到只需要给定三个变量:from(支付方地址)、to(接收方地址)、value(转移数额)。
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
在具体的实现里,我们需要一个map来存储每个地址拥有多少token
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
......
}
关于ERC20,我认为还有一个属性值得一提——decimals
function decimals() public view returns (uint8)
这是一个可选接口,不过大部分ERC20代币都选择实现它。这个数字实际上规定了token的最小分割单位。例如decimals=18,那么该代币的最小单位就是0.000000000000000001.
这是一个大家很少会注意到的属性,不过我觉得它很重要,这让ERC20代币近乎可以做到无限细分,赋予了ERC20代币非常大的流动性。ERC721和ERC1155都不具备这个属性,因此它们的最小分割单位就是1.
非同质化代币,同样看一下转移代币的接口,可以看到和ERC20代币相比,value被替换成了tokenId。value不再需要,因为ERC721代币只能一个一个转。需要一个tokenId,因为不同tokenId的代币是不一样的。
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
在具体的实现里,我们需要两个map,一个是和ERC20类似的balance,记录每个地址拥有多少数量的代币;此外还需要反过来记录每个tokenID被哪个地址拥有。
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
......
}
ERC721和ERC20有2个显著的不同点。第一,非同质化,不同ID的token是不一样的;第二,token不可分割,最小分割单位就是1,不能转0.5个token给别人。这让ERC721代币流动性远不如ERC20.
同时支持同质化和非同质化的代币。
它是对ERC721的升级,在ERC721中,每个tokenId只可以有一个token,而ERC1155的每个tokenId却可以有多个token。
对于同一个ERC1155下不同tokenID的token,它们是非同质化不可相互替代的;对于相同tokenID的token,它们是同质化可替代的。
可以看一个opeasea上ERC1155的例子
https://opensea.io/collection/noox-badge
游戏装备、徽章是比较适合采用这种模式的。需要区分不同类型的装备(不同类型对应不同的tokenID),但是每个人拥有的相同类型的装备是不需要区分的(对应相同tokenID下多个同质化token)。
其实,对ERC1155而言,如果每个tokenID下只有一个token,那么它就相当于变成了ERC721。
不过,如果只有一个tokenID,这个tokenID下有很多token,那么它可以相当于ERC20吗?并不能,因为它没有decimals属性,不支持转移非整数个token,它依然不会具备ERC20那样的流动性。
我们还是可以看一下它的代币转移接口。有一个data参数,这个参数是用来作为合约账户接受代币时回调用的,我们不必关注。可以看到除了from和to地址参数外,它同时具备ERC20的value参数和ERC721的id参数。
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
在具体的实现里,我们需要一个“二维的”map。_balances[id][addr]表示账户addr拥有的tokenID为id的token数目。
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;
......
}
刚才说ERC1155是对ERC721的改进,它允许每个tokenID拥有多个实例。ERC3525是对ERC721的另一种改进,如果要实现ERC3525,必须实现所有ERC721接口,因此上面所说的ERC721的代币转移方法在ERC3525上依然是适用的。这个方法应该会把from账户拥有的所有tokenId为特定值的token转移给to账户。
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
和ERC1155一样,ERC3525也允许同一个tokenId之下有多个实例。但不同的是,ERC3525还具有一个叫valueDecimals()的属性,类似于ERC20的decimals()
“Token also contains a valueproperty, representing the quantitative nature of the token. The meaning of the ‘value’ property is quite like that of the ‘balance’ property of an EIP-20 token. ”
/**
* @notice Get the number of decimals the token uses for value - e.g. 6, means the user
* representation of the value of a token can be calculated by dividing it by 1,000,000.
*/
function valueDecimals() external view returns (uint8);
这意味着每个tokenID之下的token不但是同质化的,并且是可以细分为非整数单位的。举个例子,我拥有1个单位的tokenID为10的token,那么我可以给你转0.5个单位。这是ERC1155做不到的,ERC1155只能转整数个单位。
所以ERC3525里相同tokenID的代币,就相当于ERC20代币。
下面是ERC3525的其中一个代币转移方法,咋一看和ERC1155接近(有tokenId和value),不过因为valueDecimals的存在,使得这里value的含义截然不同。
function transferFrom(
uint256 _fromTokenId,
address _to,
uint256 _value
) external payable returns (uint256);
上面说到,token具有value属性,此外,token还具有一个slot属性!
“Each token has a ‘slot’ attribute, ensuring that the value of two tokens with the same slot be treated as fungible, adding fungibility to the value property of the tokens.”
如果两个token的slot相同,即使它们的tokenId不一样,它们依然是可以被看作是同质化的。举个例子,游戏里面有2个装备,tokenID分别是0和1,如果是ERC1155,它们是不一样的东西;而如果是ERC3525,还需要继续看tokenId的slot属性。
function slotOf(uint256 _tokenId) external view returns (uint256);
如果0和1具有相同的slot值,那么它们是同质化的。ERC3525有下面这样一样方法,用于相同slot但不同tokenId的token互转。使用这个方法,我可以把我的tokenID为1的装备转成tokenID为2的装备。
function transferFrom(
uint256 _fromTokenId,
uint256 _toTokenId,
uint256 _value
) external payable;
如果tokenId的slot属性被设计为可以被管理员账户修改,那么就赋予了token非常大的灵活性,我们可以让原本同质化的token变得非同质化,也可以反过来。
在游戏里,装备升级似乎可以完美对应这一场景,slot对应装备的等级。我拿到一个低级装备,一开始slot为0,那么它和其他所有slot为0的装备等效。现在我升级,slot变成1,那么它不再和slot为0的低级装备等效,而开始和slot为1的装备等效。
不过在游戏装备的场景中,最小单位都是整数,这里发挥不出valueDecimals的作用。举个更一般的例子,如果是积分会员卡,有不同等级的会员(对应slot不同),卡积分可以为小数。此时,同等级会员的积分是等效的,而不同等级的会员积分是非等效的,用户可以花钱升级会员等级。在这样的例子里,ERC3525的作用就可以非常明显地体现出来。
同质化代币,我们可以看一下它转移代币的接口,可以看到只需要给定三个变量:from(支付方地址)、to(接收方地址)、value(转移数额)。
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
在具体的实现里,我们需要一个map来存储每个地址拥有多少token
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
......
}
关于ERC20,我认为还有一个属性值得一提——decimals
function decimals() public view returns (uint8)
这是一个可选接口,不过大部分ERC20代币都选择实现它。这个数字实际上规定了token的最小分割单位。例如decimals=18,那么该代币的最小单位就是0.000000000000000001.
这是一个大家很少会注意到的属性,不过我觉得它很重要,这让ERC20代币近乎可以做到无限细分,赋予了ERC20代币非常大的流动性。ERC721和ERC1155都不具备这个属性,因此它们的最小分割单位就是1.
非同质化代币,同样看一下转移代币的接口,可以看到和ERC20代币相比,value被替换成了tokenId。value不再需要,因为ERC721代币只能一个一个转。需要一个tokenId,因为不同tokenId的代币是不一样的。
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
在具体的实现里,我们需要两个map,一个是和ERC20类似的balance,记录每个地址拥有多少数量的代币;此外还需要反过来记录每个tokenID被哪个地址拥有。
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
......
}
ERC721和ERC20有2个显著的不同点。第一,非同质化,不同ID的token是不一样的;第二,token不可分割,最小分割单位就是1,不能转0.5个token给别人。这让ERC721代币流动性远不如ERC20.
同时支持同质化和非同质化的代币。
它是对ERC721的升级,在ERC721中,每个tokenId只可以有一个token,而ERC1155的每个tokenId却可以有多个token。
对于同一个ERC1155下不同tokenID的token,它们是非同质化不可相互替代的;对于相同tokenID的token,它们是同质化可替代的。
可以看一个opeasea上ERC1155的例子
https://opensea.io/collection/noox-badge
游戏装备、徽章是比较适合采用这种模式的。需要区分不同类型的装备(不同类型对应不同的tokenID),但是每个人拥有的相同类型的装备是不需要区分的(对应相同tokenID下多个同质化token)。
其实,对ERC1155而言,如果每个tokenID下只有一个token,那么它就相当于变成了ERC721。
不过,如果只有一个tokenID,这个tokenID下有很多token,那么它可以相当于ERC20吗?并不能,因为它没有decimals属性,不支持转移非整数个token,它依然不会具备ERC20那样的流动性。
我们还是可以看一下它的代币转移接口。有一个data参数,这个参数是用来作为合约账户接受代币时回调用的,我们不必关注。可以看到除了from和to地址参数外,它同时具备ERC20的value参数和ERC721的id参数。
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
在具体的实现里,我们需要一个“二维的”map。_balances[id][addr]表示账户addr拥有的tokenID为id的token数目。
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;
......
}
刚才说ERC1155是对ERC721的改进,它允许每个tokenID拥有多个实例。ERC3525是对ERC721的另一种改进,如果要实现ERC3525,必须实现所有ERC721接口,因此上面所说的ERC721的代币转移方法在ERC3525上依然是适用的。这个方法应该会把from账户拥有的所有tokenId为特定值的token转移给to账户。
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
和ERC1155一样,ERC3525也允许同一个tokenId之下有多个实例。但不同的是,ERC3525还具有一个叫valueDecimals()的属性,类似于ERC20的decimals()
“Token also contains a valueproperty, representing the quantitative nature of the token. The meaning of the ‘value’ property is quite like that of the ‘balance’ property of an EIP-20 token. ”
/**
* @notice Get the number of decimals the token uses for value - e.g. 6, means the user
* representation of the value of a token can be calculated by dividing it by 1,000,000.
*/
function valueDecimals() external view returns (uint8);
这意味着每个tokenID之下的token不但是同质化的,并且是可以细分为非整数单位的。举个例子,我拥有1个单位的tokenID为10的token,那么我可以给你转0.5个单位。这是ERC1155做不到的,ERC1155只能转整数个单位。
所以ERC3525里相同tokenID的代币,就相当于ERC20代币。
下面是ERC3525的其中一个代币转移方法,咋一看和ERC1155接近(有tokenId和value),不过因为valueDecimals的存在,使得这里value的含义截然不同。
function transferFrom(
uint256 _fromTokenId,
address _to,
uint256 _value
) external payable returns (uint256);
上面说到,token具有value属性,此外,token还具有一个slot属性!
“Each token has a ‘slot’ attribute, ensuring that the value of two tokens with the same slot be treated as fungible, adding fungibility to the value property of the tokens.”
如果两个token的slot相同,即使它们的tokenId不一样,它们依然是可以被看作是同质化的。举个例子,游戏里面有2个装备,tokenID分别是0和1,如果是ERC1155,它们是不一样的东西;而如果是ERC3525,还需要继续看tokenId的slot属性。
function slotOf(uint256 _tokenId) external view returns (uint256);
如果0和1具有相同的slot值,那么它们是同质化的。ERC3525有下面这样一样方法,用于相同slot但不同tokenId的token互转。使用这个方法,我可以把我的tokenID为1的装备转成tokenID为2的装备。
function transferFrom(
uint256 _fromTokenId,
uint256 _toTokenId,
uint256 _value
) external payable;
如果tokenId的slot属性被设计为可以被管理员账户修改,那么就赋予了token非常大的灵活性,我们可以让原本同质化的token变得非同质化,也可以反过来。
在游戏里,装备升级似乎可以完美对应这一场景,slot对应装备的等级。我拿到一个低级装备,一开始slot为0,那么它和其他所有slot为0的装备等效。现在我升级,slot变成1,那么它不再和slot为0的低级装备等效,而开始和slot为1的装备等效。
不过在游戏装备的场景中,最小单位都是整数,这里发挥不出valueDecimals的作用。举个更一般的例子,如果是积分会员卡,有不同等级的会员(对应slot不同),卡积分可以为小数。此时,同等级会员的积分是等效的,而不同等级的会员积分是非等效的,用户可以花钱升级会员等级。在这样的例子里,ERC3525的作用就可以非常明显地体现出来。
No activity yet