solidity8进阶
事件 event
事件是EVM的日志功能之上的抽象。 应用程序可以通过以太坊客户端的RPC接口订阅和监听这些事件
event Log(address indexed from, uint num);
//即使只是调用event,也不可以用view prue
function send(uint _num) external{
emit Log(msg.sender, _num);
}
indexed & anonymous
indexed 修饰事件时:将参数作为 topic 存储。
anonymous 修饰事件时:不把事件签名作为 topic 存储。
//此事件toppics中应出现 签名、add、_num
event Log(address indexed add, address sender, uint indexed _num);
//此事件topics中应出现 add、_num
event Log1(address indexed add, address sender, uint indexed _num) anonymous;
function send(uint _num) external{
emit Log(address(this), msg.sender, _num);
emit Log1(address(this), msg.sender, _num);
}
测试结果
payable
//被payable修饰的函数可以接收主币
function tes() public payable {
uint value = msg.value;
}
function getBalance() public view returns(uint){
return address(this).balance;
}
//被payable修饰的地址可以接收主币
fallback & receive
//receive:接收以太函数,一个合约最多有一个receive函数
//没有function关键字,也没有参数和返回值, 必须是 external可见性 和 payable修饰
//可以是virtual的,可以被重载也可以有修改器
receive() external payable{
}
//fallback:回退函数,合约可以最多有一个回退函数,
//没有function关键字,必须是 external可见性
//可以是virtual的,可以被重载也可以有修改器modifier
fallback() external payable{
}
//带参数写法
fallback(bytes calldata _input) external payable returns(bytes memory _output){
}
selfdestruct
selfdestruct(address payable recipient):销毁合约,把合约的剩余资金发送到指定的地址。
pragma solidity ^0.8.0;
contract Mycontract{
constructor() payable{
}
function destroy() external {
selfdestruct(payable(msg.sender));
}
function get() public pure returns(uint){
return 1;
}
}
unchecked
contract Test {
uint8 public a = type(uint8).max;
function check() external view returns(uint8 b){
b= a + 10;
}
function uncheck() external view returns(uint8 b){
unchecked{
b= a + 10;
}
}
}
发送主币的三种方式:transfer、send、call
transfer:2300 gas,交易失败后会revert
send:2300 gas,交易结果返回布尔值,不会抛异常,继续执行合约代码
call:剩余全部gas发送给_to地址使用,不会抛异常,继续执行合约代码
可以携带数据,返回值为交易结果布尔值和返回数据
如果发送地址是合约,合约有返回值,就会有返回数据
//接收主币合约
contract rec{
receive() external payable{
}
//查询合约余额
function getBal() public view returns(uint) {
return address(this).balance;
}
}
//发送主币合约
contract Mycontract{
//部署时向合约发送主币,用于测试
constructor() payable {
}
//传入rec合约地址 调用后查询rec余额
function byTransfer(address payable _to) public payable {
_to.transfer(1);
}
//传入rec合约地址 调用后查询rec余额
function bySend(address payable _to) public payable {
bool res = _to.send(1);
require(res, "send failed");
}
//传入rec合约地址 调用后查询rec余额
function byCall(address payable _to) public payable {
(bool res,) = _to.call{
value: 1}("");
require(res, "call failed");
}
}
调用其他合约
引入源码调用
pragma solidity ^0.8.0;
contract TestContract{
uint a = 1;
function get() public view returns(uint, uint){
return (a, address(this).balance);
}
function set(uint _a) public payable{
a = _a;
}
}
pragma solidity ^0.8.0;
import './TestContract.sol';
contract Mycontract{
//将调用test的set函数并将接收的主币一并发送给test
function set(address _test, uint _a) public payable{
TestContract(_test).set{
value: msg.value}(_a);
}
function seta(TestContract _test, uint _a) public payable{
_test.set{
value: msg.value}(_a);
}
function get(address _test) public view returns(uint a, uint v, uint myV){
(a, v) = TestContract(_test).get();
myV = address(this).balance;
}
function geta(TestContract _test) public view returns(uint a, uint v, uint myV){
(a, v) = _test.get();
myV = address(this).balance;
}
}
调用set发送100wei,调用seta发送200wei,测试结果
接口合约调用
pragma solidity ^0.8.0;
contract TestContract{
uint a = 1;
function get() external view returns(uint){
return a;
}
function set(uint _a) external{
a = _a;
}
}
pragma solidity ^0.8.0;
interface ITestContract{
function get() external view returns(uint);
function set(uint _a) external;
}
contract Mycontract{
function set(address _test, uint _a) public{
ITestContract(_test).set(_a);
}
function get(address _test) public view returns(uint a){
a = ITestContract(_test).get();
}
}
call方法调用
pragma solidity ^0.8.0;
contract TestContract{
uint public a = 1;
uint public bal = 0;
address public sender;
function set(uint _a) external payable{
a = _a;
bal = address(this).balance;
sender = msg.sender;
}
}
pragma solidity ^0.8.0;
contract Mycontract{
function set(address _test, uint _a) external payable{
(bool success, ) = _test.call(abi.encodeWithSignature("set(uint256)", _a));
require(success, "call failed");
}
function getBal() external view returns(uint b){
b = address(this).balance;
}
}
测试结果:
a变量修改成功,
发送的主币被Mycontract合约接收,
TestContract显示调用者是Mycontract合约
委托调用
pragma solidity ^0.8.0;
contract TestContract{
uint public a;
uint public bal;
address public sender;
function set(uint _a) external payable{
a = _a;
bal = msg.value;
sender = msg.sender;
}
}
pragma solidity ^0.8.0;
contract Mycontract{
uint public a;
uint public bal;
address public sender;
function callSet(address _test, uint _a) external payable{
(bool success,) = _test.delegatecall(abi.encodeWithSignature("set(uint256)", _a));
require(success, "call failed");
}
}
测试结果:TestContract合约的值并没有变化,余额和调用者信息也是没有的
改变的是Mycontract合约内的变量,主币也被Mycontract接收,显示调用者是函数调用者
相当于Mycontract使用了TestContract合约函数逻辑,但操作的数据是Mycontract本身的
这就要求Mycontract合约内定义的变量要和TestContract合约内保持一致,包括变量顺序
库合约
pragma solidity ^0.8.0;
library safeMath{
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
}
contract Mycontract{
//所有uint类型都可以直接使用safeMath库 声明后可直接使用 sub1 方式
using safeMath for uint;
function sub(uint _a, uint _b) external pure returns(uint){
return safeMath.add(_a, _b);
}
function sub1(uint _a, uint _b) external pure returns(uint){
return _a.add(_b);
}
}
哈希运算
keccak256(bytes memory) returns (bytes32): 计算输入参数的 Keccak-256 哈希
abi.encode(...) returns (bytes memory): ABI - 对给定的参数进行编码
abi.encodePacked(...) returns (bytes memory): 给指定的参数执行 packed encoding , 请注意,这种编码可能会有歧义!(参数和编码可能出现多对一的情况)
pragma solidity ^0.8.0;
contract Mycontract{
function encode(string memory _a, string memory _b) public pure returns(bytes memory){
return abi.encode(_a, _b);
}
function encode1(string memory _a, string memory _b) public pure returns(bytes memory){
return abi.encode(_a, _b);
}
function encodePacked(string memory _a, string memory _b) public pure returns(bytes memory){
return abi.encodePacked(_a, _b);
}
function encodePacked1(string memory _a, string memory _b) public pure returns(bytes memory){
return abi.encodePacked(_a, _b);
}
function hash(string memory _a, string memory _b) public pure returns(bytes32){
return keccak256(abi.encode(_a, _b));
}
function hash1(string memory _a, string memory _b) public pure returns(bytes32){
return keccak256(abi.encode(_a, _b));
}
function hashPacked(string memory _a, string memory _b) public pure returns(bytes32){
return keccak256(abi.encodePacked(_a, _b));
}
function hashPacked1(string memory _a, string memory _b) public pure returns(bytes32){
return keccak256(abi.encodePacked(_a, _b));
}
}
测试结果:encodePacked不会补0,会出现不同入参编码相同的情况
函数签名
pragma solidity ^0.8.0;
contract Mycontract{
event Log(bytes data);
function getSign(string memory _t) public pure returns(bytes4){
return bytes4(keccak256(bytes(_t)));
}
function transfer(address _to, uint _num) external{
emit Log(msg.data);
}
}
create2部署合约
pragma solidity ^0.8.0;
contract Mycontract{
address public owner;
constructor(address _owner){
owner = _owner;
}
}
contract Create2Factory{
event Deploy(address add);
function deploy(uint _salt) external {
Mycontract _contract = new Mycontract{
salt : bytes32(_salt)
}(msg.sender);
emit Deploy(address(_contract));
}
function get(bytes memory bytecode, uint _salt) external view returns(address){
bytes32 hash = keccak256(abi.encodePacked(
bytes1(0xff), address(this), _salt, keccak256(bytecode)
));
return address(uint160(uint(hash)));
}
function getByteCode(address _owner) external pure returns(bytes memory){
bytes memory bytecode = type(Mycontract).creationCode;
return abi.encodePacked(bytecode, abi.encode(_owner));
}
}
MultiCall批量调用
call调用中,要注意msg.sender、msg.value,如果函数中有相关的逻辑,会出现问题
pragma solidity ^0.8.0;
contract Mycontract{
function fun1() external view returns (address, uint){
return(msg.sender, 1);
}
function fun2() external view returns (address, uint){
return(msg.sender, 2);
}
function getdata1() external pure returns(bytes memory) {
return abi.encodeWithSelector(this.fun1.selector);
}
function getdata2() external pure returns(bytes memory) {
return abi.encodeWithSelector(this.fun2.selector);
}
}
contract MultiCall{
function call(address[] calldata adds, bytes[] calldata data) external view returns(bytes[] memory){
require(adds.length == data.length, "params exception");
bytes[] memory res = new bytes[](adds.length);
for(uint a = 0; a < adds.length; a++){
(bool success, bytes memory result) = adds[a].staticcall(data[a]);
require(success, "call failed");
res[a] = result;
}
return res;
}
批量委托调用
批量委托调用中,如果多次调用有payable接收主币的函数,会出现计算问题
pragma solidity ^0.8.0;
contract Mycontract{
event Log(address caller, uint i);
//由于委托调用只是使用目标合约的函数,并不会改变其他合约的变量,
//所有必须address(this).delegatecall才是合理的
function delegatecall(bytes[] calldata data) external payable returns(bytes[] memory){
bytes[] memory res = new bytes[](data.length);
for(uint a = 0; a < data.length; a++){
(bool success, bytes memory result) = address(this).delegatecall(data[a]);
require(success, "call failed");
res[a] = result;
}
return res;
}
function fun1(uint x) external{
emit Log(msg.sender, x++);
}
function fun2() external returns (uint){
emit Log(msg.sender, 666);
}
function getdata1(uint x) external pure returns(bytes memory) {
return abi.encodeWithSelector(this.fun1.selector, x);
}
function getdata2() external pure returns(bytes memory) {
return abi.encodeWithSelector(this.fun2.selector);
}
}