赞
踩
此处主要是针对链接中,增加中文理解说明,有需要可查看原文
(文中参考代码主要摘自该链接)https://solidity-by-example.org/
常用是require/revert, 可以将错误信息返出去,便于知道错误在哪
assert存在意义在哪不确定,可以使用require替代
另外针对assert的区别。 0.8.x测试没发现和require有什么区别,但是在低版本区别很大
例:0.4.x eth-usdt,可以去查看他的源码
如果一个交易gasLimit设置的100w,实际消耗大概是5w,代码最后面增加(require,revert)/ assert 导致出错
如果使用require/revert, 实际gasUsed = gasPrice * 5w,然后回退了
如果使用assert, 实际gasUsed = gasPrice * 100w, 会把gas消耗完…!
key=>value 格式
mapping(address => uint) public myMap;
mapping(address => mapping(uint => bool)) public nested;
myMap[_addr] = _i;
myMap[_addr];
delete myMap[_addr];
pragma solidity ^0.8.7; contract Array { // 初始化数组的几种方式 uint[] public arr;//可变长度数组,初始长度0 uint[] public arr2 = [1, 2, 3];//可变长度数组,初始长度3,有对应值 // 固定长度数组,所有元素是默认值,当前例子为 0, 这个数组不可以push/pop改变长度 uint[10] public myFixedSizeArr; //通过下标获取数组元素 function get(uint i) public view returns (uint) { return arr[i]; } //可返回整个数组,这种方法需要避免长度很长可增长的数组 //查询方法也受gasLimit限制,查询过多内容时会超限制 function getArr() public view returns (uint[] memory) { return arr; } //数量很长的分页/区间查询 function getArr1(uint256 pageNo, uint256 pageSize)public view returns(uint256[]memory list) { uint len = arr.length; uint start = pageNo * pageSize; if(len == 0 || start >= len){ return new uint[](0); } uint end = start + pageSize; if(end > len){ end = len; } uint arrLen = end - start; list = new uint[](arrLen); uint index; for(;start < end ; start ++){ list[index++] = start; } } function push(uint i) public { //追加到数组,数组长度加1 arr.push(i); } function pop() public { //移除数组的最后一个元素 arr.pop(); } //返回数组长度 function getLength() public view returns (uint) { return arr.length; } function remove(uint index) public { //delete 操作不对修改数组长度,只是把索引位置的值重置为默认值,当前例子为0 delete arr[index]; } //如果想移除一个值,且改变数组长度, //可以先替换值, 在pop //注: 该方式会导致数组值不是原来的插入顺序 function remove2(uint index)public{ arr[index] = arr[arr.length-1]; arr.pop(); } function examples() external { // 在内存中创建数组,只能创建固定大小 uint[] memory a = new uint[](5); } }
通过struct将相关数据放一起
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Todos { struct Todo { string text; bool completed; } // An array of 'Todo' structs Todo[] public todos;//public声明, 外部可以通过 todos[index],输入index会返回整个结构体 function create(string memory _text) public { // 3种初始化结构的方式 // 像方法一样顺序传参 todos.push(Todo(_text, false)); // key value mapping todos.push(Todo({text: _text, completed: false})); // 初始化一个空的结构体,并针对每个字段赋值, 如果结构体中存在数组/其他结构体时,这种方式合适 Todo memory todo; todo.text = _text; //未显视赋值的变量为类型初始值 // todo.completed initialized to false todos.push(todo); } // Solidity automatically created a getter for 'todos' so // you don't actually need this function. function get(uint _index) public view returns (string memory text, bool completed) { Todo storage todo = todos[_index]; return (todo.text, todo.completed); } //如果是低版本的,会要求在文件头部声明 pragma experimental ABIEncoderV2; function getObject(uint _index)public view returns(Todo memory){ return todos[_index]; } // update text function update(uint _index, string memory _text) public { Todo storage todo = todos[_index]; todo.text = _text; } // update completed function toggleCompleted(uint _index) public { Todo storage todo = todos[_index]; todo.completed = !todo.completed; } }
可在函数调用前做一定处理,以及执行之后,再做一定处理
主要功能
modifier onlyOwner() { require(msg.sender == owner, "Not owner"); //下划线是一个特殊字符,仅在modifier中, 标志执行方法的其他代码 _; } //判断输入的地址不是0地址 modifier validAddress(address _addr) { require(_addr != address(0), "Not valid address"); _; } //防止重入,调用函数前先把状态改了, 函数执行完后, 再把状态改回来 modifier noReentrancy() { require(!locked, "No reentrancy"); locked = true; _; locked = false; }
简单理解就是日志,便于知道区块内部执行了什么
event Transfer(address indexed from, address indexed to, uint value);
日志定义里面有个indexed修饰符, 最多允许3个参数使用该修饰符
event的数据,在transaction中logs中存在两个不同位置,
indexed 修饰的 在topic中 而其他的在data中
"logs": [
{
"address": "0x55d398326f99059ff775485246999027b3197955",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000eb2d2f1b8c558a40207669291fda468e50c12345",
"0x000000000000000000000000bcdc55ce32a3d875d440c6fec0354919ab812345"
],
"data": "0x00000000000000000000000000000000000000000000001b1ae4d6e2ef500000"
}
]
logs中说明
address 表示该事件是哪个合约地址的(注意是实际发出的地址, 比如A合约调用B代币, address是B代币的地址,而不是A合约的地址)
topics 是个数组,topics[0] 表示事件名,后面的就是顺序取事件中的indexed修饰的参数
以下针对Transfer事件的说明
topics[0]对应的内容是 keccak256(bytes('Transfer(address,address,uint256)'))
topics[1]对应的内容是事件中的 from
topics[2]对应的内容是事件中的 to
data 是非indexed修饰的参数,顺序取
去掉前面的0x后, 每64位表示一个参数,解析的时候对应参数类型解析即可
继承多父类是从左到右的顺序
参考下面代码中注释, 链接中描述顺序是Y-X-child 是错的
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; // Base contract X contract X { string public name; constructor(string memory _name) { name = _name; } } // Base contract Y contract Y { string public text; constructor(string memory _text) { text = _text; } } // 有两种方式用参数初始化父合约。 //方法一、 在合约继承的时候填参数 contract B is X("Input to X"), Y("Input to Y") { } //方法二、在构造方法中传参 contract C is X, Y { constructor(string memory _name, string memory _text) X(_name) Y(_text) {} } // 父构造函数总是按照继承的顺序调用,而不管子合约的构造函数中列出的父合约的顺序。 // 继承是从左到右的 //下面两个的顺序都是 X,Y,child contract D is X, Y { constructor() X("X was called") Y("Y was called") {} } contract E is X, Y { constructor() Y("Y was called") X("X was called") {} }
以下是例子
继承的顺序是从左到右;
例:E is C,B (和链接中有override(B, C)
有改动)
先继承C,再继承B; C/B有同名方法,后继承B,所以B会覆盖掉C的方法实现
虽然方法中有 override(B, C)
但是无效,以合约中is的继承顺序, B在后面返回的结果是B
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; /* Graph of inheritance A / \ B C / \ / F D,E */ contract A { function foo() public pure virtual returns (string memory) { return "A"; } } // Contracts inherit other contracts by using the keyword 'is'. contract B is A { // Override A.foo() function foo() public pure virtual override returns (string memory) { return "B"; } } contract C is A { // Override A.foo() function foo() public pure virtual override returns (string memory) { return "C"; } } // Contracts can inherit from multiple parent contracts. // When a function is called that is defined multiple times in // different contracts, parent contracts are searched from // right to left, and in depth-first manner. contract D is B, C { // D.foo() returns "C" // since C is the right most parent contract with function foo() function foo() public pure override(B, C) returns (string memory) { return super.foo(); } } contract E is C, B { // E.foo() returns "B" // since B is the right most parent contract with function foo() function foo() public pure override(B, C) returns (string memory) { return super.foo(); } } // Inheritance must be ordered from “most base-like” to “most derived”. // Swapping the order of A and B will throw a compilation error. contract F is A, B { function foo() public pure override(A, B) returns (string memory) { return super.foo(); } }
根据下面的描述以及测试结果
B,C中foo/bar的区别是 bar都是使用super.bar(),而foo都是使用A.bar()
调用D.foo, 调用了C,再调用了A
调用D.bar,调用了C,然后调用B,最后调用了A,(A只调用了一次)
两个的区别可以看出,同名方法是后继承覆盖先继承。
C中bar调用的super,最终到了B, B再调用superA
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; /* Inheritance tree A / \ B C \ / D */ contract A { // This is called an event. You can emit events from your function // and they are logged into the transaction log. // In our case, this will be useful for tracing function calls. event Log(string message); function foo() public virtual { emit Log("A.foo called"); } function bar() public virtual { emit Log("A.bar called"); } } contract B is A { function foo() public virtual override { emit Log("B.foo called"); A.foo(); } function bar() public virtual override { emit Log("B.bar called"); super.bar(); } } contract C is A { function foo() public virtual override { emit Log("C.foo called"); A.foo(); } function bar() public virtual override { emit Log("C.bar called"); super.bar(); } } contract D is B, C { // Try: // - Call D.foo and check the transaction logs. // Although D inherits A, B and C, it only called C and then A. // - Call D.bar and check the transaction logs // D called C, then B, and finally A. // Although super was called twice (by B and C) it only called A once. function foo() public override(B, C) { super.foo(); } function bar() public override(B, C) { super.bar(); } }
声明的函数和地址加了payable才可以接收ether
如果对一个普通地址转ether,需要先用payable(address)转换成 address payable,再进行转ether
pragma solidity ^0.8.7; contract Payable { // Payable address can receive Ether address payable public owner; // Payable constructor can receive Ether constructor() payable { owner = payable(msg.sender); } // Function to deposit Ether into this contract. // Call this function along with some Ether. // The balance of this contract will be automatically updated. function deposit() public payable {} }
接收 Ether 的合约必须至少具有以下功能之一
如果msg.data为空,会调用 receive()
否则
会调用fallback()
如果接收地址是普通的地址,可以使用transfer最简单
如果接收地址是合约,且合约里面有功能的话,需要使用call,否则可能gas不足导致失败
文中介绍2019.12月后建议使用call和re-entrancy结合使用
OpenZeppelin-ReentrancyGuard
call 除了防止重入,也可以手动设置gas使用量,限制接收地址消耗太多gas
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; contract ReceiveEther { /* Which function is called, fallback() or receive()? send Ether | msg.data is empty? / \ yes no / \ receive() exists? fallback() / \ yes no / \ receive() fallback() */ event RecInfo(address indexed _from, uint256 _type, uint256 _value, bytes _data); // Function to receive Ether. msg.data must be empty receive() external payable { emit RecInfo(msg.sender, 1, msg.value, bytes("")); } // Fallback function is called when msg.data is not empty fallback() external payable { emit RecInfo(msg.sender, 2, msg.value,msg.data); } function getBalance() public view returns (uint) { return address(this).balance; } } contract SendEther { function sendViaTransfer(address payable _to) public payable { // This function is no longer recommended for sending Ether. _to.transfer(msg.value); } function sendViaSend(address payable _to) public payable { // Send returns a boolean value indicating success or failure. // This function is not recommended for sending Ether. bool sent = _to.send(msg.value); require(sent, "Failed to send Ether"); } function sendViaCall(address payable _to) public payable { // Call returns a boolean value indicating success or failure. // This is the current recommended method to use. // (bool sent, bytes memory data) = _to.call{value: msg.value,gas:66666}(""); (bool sent, bytes memory data) = _to.call{value: msg.value, gas: 2300}(""); //0.5.x //(bool sent, bytes memory data) = _to.call.value(msg.value).gas(2300)(""); require(sent, "Failed to send Ether"); } }
这是一个不带任何参数且不返回任何内容的函数
主要功能:
需要注意的是,如果使用transfer/send给该合约发送ether,受gas(2300)限制
实现该方法即可接收
function() external payable {
}
分了两个动作;
如果只实现fallback()方法,就和之前的function()功能一样了
/*
Which function is called, fallback() or receive()?
send Ether
|
msg.data is empty?
/ \
yes no
/ \
receive() exists? fallback()
/ \
yes no
/ \
receive() fallback()
*/
call是与其他合约交互的低级功能
call会返回bool
call合约的时候,如果data没有匹配到合约方法,会调用fillback函数
以下举例几种使用call调用其他合约的方式
(比如有些合约没有开源,但是区块浏览器可以看到交互记录/data,这个时候可以直接使用call data的方式调用合约)
contract Caller { event Response(bool success, bytes data); // Let's imagine that contract B does not have the source code for // contract A, but we do know the address of A and the function to call. function testCallFoo(address payable _addr) public payable { // You can send ether and specify a custom gas amount (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}( abi.encodeWithSignature("foo(string,uint256)", "call foo", 123) ); emit Response(success, data); } // Calling a function that does not exist triggers the fallback function. function testCallDoesNotExist(address _addr) public { (bool success, bytes memory data) = _addr.call( abi.encodeWithSignature("doesNotExist()") ); emit Response(success, data); } function getCalldata(uint256 params1)public pure returns (bytes memory){ bytes4 method = 0xbe221111; return abi.encodeWithSelector(method, params1); } }
delegatecall是一个类似于call的低级函数。
当合约A执行delegatecall到合约时B,B代码被执行
执行后修改的信息是合约A里面的,且调用其他合约时,msg.sender是A,msg.value也是从A扣
调用合约时的data,前4个字节(8位)是指定调用哪个方法名
transfer(address,uint256) 方法hash的结果是
0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
取前面8位就是 a9059cbb, 后面跟随的就是参数
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract FunctionSelector {
/*
"transfer(address,uint256)"
0xa9059cbb
"transferFrom(address,address,uint256)"
0x23b872dd
*/
function getSelector(string calldata _func) external pure returns (bytes4) {
return bytes4(keccak256(bytes(_func)));
}
}
调用合约有两种方式
另外下面附上参数是结构体的形式调用方式, key/value的方式对应
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; contract Callee { uint public x; uint public value; function setX(uint _x) public returns (uint) { x = _x; return x; } function setXandSendEther(uint _x) public payable returns (uint, uint) { x = _x; value = msg.value; return (x, value); } } contract Caller { function setX(Callee _callee, uint _x) public { uint x = _callee.setX(_x); } function setXFromAddress(address _addr, uint _x) public { Callee callee = Callee(_addr); callee.setX(_x); } function setXandSendEther(Callee _callee, uint _x) public payable { (uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x); } } //结构体调用例子 ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ tokenIn: DAI, tokenOut: WBNB, fee: poolFee, recipient: msg.sender, deadline: block.timestamp, amountIn: amountIn, amountOutMinimum: 0, sqrtPriceLimitX96: 0 }); // The call to `exactInputSingle` executes the swap. amountOut = swapRouter.exactInputSingle(params);
其他合约可以使用new,或者create2的方式创建
0.8.0开始,create2 也支持使用new关键字加上指定salt来创建
后面附上new和create2提前计算合约地址的方式
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; contract Car { address public owner; string public model; address public carAddr; constructor(address _owner, string memory _model) payable { owner = _owner; model = _model; carAddr = address(this); } } contract CarFactory { Car[] public cars; function create(address _owner, string memory _model) public { Car car = new Car(_owner, _model); cars.push(car); } function createAndSendEther(address _owner, string memory _model) public payable { Car car = (new Car){value: msg.value}(_owner, _model); cars.push(car); } function create2( address _owner, string memory _model, bytes32 _salt ) public { Car car = (new Car){salt: _salt}(_owner, _model); cars.push(car); } function create2AndSendEther( address _owner, string memory _model, bytes32 _salt ) public payable { Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model); cars.push(car); } function getCar(uint _index) public view returns ( address owner, string memory model, address carAddr, uint balance ) { Car car = cars[_index]; return (car.owner(), car.model(), car.carAddr(), address(car).balance); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.5.17; contract Car { address public owner; string public model; address public carAddr; constructor() public payable { } } contract CarFactory { bytes32 public initCodeHash; //使用create2的方式提前计算地址 function pairFor(uint256 _n) public view returns (address pair) { pair = address(uint(keccak256(abi.encodePacked( hex'ff', address(this), keccak256(abi.encodePacked(_n)), initCodeHash )))); } uint256 public curNum = 0; //该方式不知道如何传参数 function createPair() public returns (address pair) { bytes memory bytecode = type(Car).creationCode; bytes32 salt = keccak256(abi.encodePacked(curNum++)); assembly { pair := create2(0, add(bytecode, 32), mload(bytecode), salt) } } //使用create的方式提前计算地址 function addressFrom(address _origin, uint _nonce) public pure returns (address _address) { bytes memory data; if (_nonce == 0x00) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(0x80)); else if (_nonce <= 0x7f) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, uint8(_nonce)); else if (_nonce <= 0xff) data = abi.encodePacked(byte(0xd7), byte(0x94), _origin, byte(0x81), uint8(_nonce)); else if (_nonce <= 0xffff) data = abi.encodePacked(byte(0xd8), byte(0x94), _origin, byte(0x82), uint16(_nonce)); else if (_nonce <= 0xffffff) data = abi.encodePacked(byte(0xd9), byte(0x94), _origin, byte(0x83), uint24(_nonce)); else data = abi.encodePacked(byte(0xda), byte(0x94), _origin, byte(0x84), uint32(_nonce)); bytes32 hash = keccak256(data); assembly { mstore(0, hash) _address := mload(0) } } }
try/catch 只允许外部调用以及合约创建时才可以捕获
下面例子还可以catch revert/require, 和assert的区别
或者统一处理
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; // External contract used for try / catch examples contract Foo { address public owner; constructor(address _owner) { require(_owner != address(0), "invalid address"); assert(_owner != 0x0000000000000000000000000000000000000001); owner = _owner; } function myFunc(uint x) public pure returns (string memory) { require(x != 0, "require failed"); return "my func was called"; } } contract Bar { event Log(string message); event LogBytes(bytes data); Foo public foo; constructor() { // This Foo contract is used for example of try catch with external call foo = new Foo(msg.sender); } // Example of try / catch with external call // tryCatchExternalCall(0) => Log("external call failed") // tryCatchExternalCall(1) => Log("my func was called") function tryCatchExternalCall(uint _i) public { try foo.myFunc(_i) returns (string memory result) { emit Log(result); } catch { emit Log("external call failed"); } } // Example of try / catch with contract creation // tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address") // tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("") // tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created") function tryCatchNewContract(address _owner) public { try new Foo(_owner) returns (Foo foo) { // you can use variable foo here emit Log("Foo created");//创建成功 } catch Error(string memory reason) { // catch failing revert() and require() emit Log(reason);//revert/require中的提示错误 } catch (bytes memory reason) { // catch failing assert() emit LogBytes(reason);//assert的错误 } } }
keccak256 计算输入内容的Keccak-256哈希值
主要场景
下面例子中有说明一个hash冲突的问题
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; contract HashFunction { function hash( string memory _text, uint _num, address _addr ) public pure returns (bytes32) { return keccak256(abi.encodePacked(_text, _num, _addr)); } // Example of hash collision // Hash collision can occur when you pass more than one dynamic data type // to abi.encodePacked. In such case, you should use abi.encode instead. function collision(string memory _text, string memory _anotherText) public pure returns (bytes32) { // encodePacked(AAA, BBB) -> AAABBB // encodePacked(AA, ABBB) -> AAABBB return keccak256(abi.encodePacked(_text, _anotherText)); } } contract GuessTheMagicWord { bytes32 public answer = 0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00; // Magic word is "Solidity" function guess(string memory _word) public view returns (bool) { return keccak256(abi.encodePacked(_word)) == answer; } }
(AAA, BBB) 和 (AA, ABBB) 为例
encodePacked 的结果都是
0x414141424242
所以keccak256的结果都是一样的
encode
AAA BBB
0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003414141000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034242420000000000000000000000000000000000000000000000000000000000
展开
0x
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000003
4141410000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000003
4242420000000000000000000000000000000000000000000000000000000000
AA ABBB
0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002414100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044142424200000000000000000000000000000000000000000000000000000000
展开
0x
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000002
4141000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000004
4142424200000000000000000000000000000000000000000000000000000000
可以在链下签名,链上延签,再执行一定的操作
签名步骤
1、所有参数进行hash
msgHash = hash(allparams...)
2、加上前缀再hash
ethPrefix = "\x19Ethereum Signed Message:\n32";
ethHash = hash(ethPrefix,msgHash);
3、使用私钥签名得到签名结果
signResult = sign(ethHash,prikey)
通过signResult得到r,s,v 传入到合约中
注意使用的时候,一般都会在合约里面维护一个nonces,参考UniswapV2ERC20中的permit
使用场景:
上面所说的链游提收益,是针对游戏中心化产生的积分,需要实际提到链上时,可参考meli提PCE, 代码没开源,应该也是这种方式
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; /* Signature Verification How to Sign and Verify # Signing 1. Create message to sign 2. Hash the message 3. Sign the hash (off chain, keep your private key secret) # Verify 1. Recreate hash from the original message 2. Recover signer from signature and hash 3. Compare recovered signer to claimed signer */ contract VerifySignature { /* 1. Unlock MetaMask account ethereum.enable() */ /* 2. Get message hash to sign getMessageHash( 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C, 123, "coffee and donuts", 1 ) hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd" */ function getMessageHash( address _to, uint _amount, string memory _message, uint _nonce ) public pure returns (bytes32) { return keccak256(abi.encodePacked(_to, _amount, _message, _nonce)); } /* 3. Sign message hash # using browser account = "copy paste account of signer here" ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log) # using web3 web3.personal.sign(hash, web3.eth.defaultAccount, console.log) Signature will be different for different accounts 0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b */ function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) { /* Signature is produced by signing a keccak256 hash with the following format: "\x19Ethereum Signed Message\n" + len(msg) + msg */ return keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash) ); } /* 4. Verify signature signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C amount = 123 message = "coffee and donuts" nonce = 1 signature = 0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b */ function verify( address _signer, address _to, uint _amount, string memory _message, uint _nonce, bytes memory signature ) public pure returns (bool) { bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce); bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); return recoverSigner(ethSignedMessageHash, signature) == _signer; } function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } function splitSignature(bytes memory sig) public pure returns ( bytes32 r, bytes32 s, uint8 v ) { require(sig.length == 65, "invalid signature length"); assembly { /* First 32 bytes stores the length of the signature add(sig, 32) = pointer of sig + 32 effectively, skips first 32 bytes of signature mload(p) loads next 32 bytes starting at the memory address p into memory */ // first 32 bytes, after the length prefix r := mload(add(sig, 32)) // second 32 bytes s := mload(add(sig, 64)) // final byte (first byte of the next 32 bytes) v := byte(0, mload(add(sig, 96))) } // implicitly return (r, s, v) } }
参考链接
https://learnblockchain.cn/docs/solidity/060-breaking-changes.html
unchecked { ... }
, 可以节省丢丢手续费a**b**c
被解析为a**(b**c)
。在 0.8.0 之前,它被解析为(a**b)**c
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。