文档: https://solidity-by-example.org/
视频教程: https://www.youtube.com/watch?v=xv9OmztShIw&list=PLO5VPQH6OWdVQwpQfw9rZ67O6Pjfo6q-p
说明:
本文内容: Function Modifier
, Events
, Constructor
, Inheritance
, Shadowing Inherited State Variables
, Calling Parent Contracts
, Visibility
, Interface
Function Modifier
Function Modifier
是可以在函数调用之前和/或之后运行的代码. 可以类比AOP.
修饰符可用于:
- 限制访问
- 验证输入
- 防止重入攻击. 简单来说重入攻击是利用智能合约的漏洞反复调用智能合约从而非法获利的一种攻击方式.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;contract FunctionModifier {// We will use these variables to demonstrate how to use// modifiers.address public owner;uint public x = 10;bool public locked;constructor() {// Set the transaction sender as the owner of the contract.owner = msg.sender;}// Modifier to check that the caller is the owner of// the contract.modifier onlyOwner() {require(msg.sender == owner, "Not owner");// Underscore is a special character only used inside// a function modifier and it tells Solidity to// execute the rest of the code._;}// Modifiers can take inputs. This modifier checks that the// address passed in is not the zero address.modifier validAddress(address _addr) {require(_addr != address(0), "Not valid address");_;}// 调用过一次修改成别的地址之后, 就没法修改回来了. 会报错的. function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {owner = _newOwner;}// Modifiers can be called before and / or after a function.// This modifier prevents a function from being called while// it is still executing.modifier noReentrancy() {require(!locked, "No reentrancy");locked = true;_;locked = false;}function decrement(uint i) public noReentrancy {x -= i;if (i > 1) {decrement(i - 1);}}
}
Events
events
允许记录到以太坊区块链。
events
的一些用例是:
- 监听事件, 更新用户接口
- 一种廉价的存储形式
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;contract Event {// Event declaration// Up to 3 parameters can be indexed.// Indexed parameters helps you filter the logs by the indexed parameterevent Log(address indexed sender, string message);event AnotherLog();function test() public {emit Log(msg.sender, "Hello World!");emit Log(msg.sender, "Hello EVM!");emit AnotherLog();}
}
要到控制台去看日志输出.
Inheritance
继承关系.
Solidity支持多继承。Contract
可以通过使用is
关键字继承其他合同。
要被子智能合约覆盖的函数必须声明为虚函数(virtual
)。
要重写父函数的函数必须使用关键字override
。
继承顺序很重要。您必须按照从most base-like
到most derived
的顺序列出父合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;contract A {function foo() virtual public pure returns(string memory){return "A";}
}contract B is A {function foo() virtual override public pure returns(string memory){return "B";}
}contract C is A {// 如果没有儿子合约(D和E)继承C了的话,就不需要virtual了。function foo() override virtual public pure returns(string memory){return "C";}
}contract D is B, C {function foo() public pure override(B, C) virtual returns(string memory){return super.foo();}
}// 注意对比, D.foo()返回的是"C", 而E.foo()返回的是"B".
// 关键在于继承顺序, 而非override里面的顺序.
contract E is C, B {function foo() public pure override(B, C) virtual returns(string memory){return super.foo();}
}// 但是要继承父和爷合约的话必须从左到右从大到小. 上面也说过了.
contract F is A, B {function foo() public pure override(A, B) virtual returns(string memory){return super.foo();}
}
Constructor
构造函数是可选函数,在智能合约创建时执行。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;// 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;}
}// There are 2 ways to initialize parent contract with parameters.// Pass the parameters here in the inheritance list.
contract B is X("Input to X"), Y("Input to Y") {}contract C is X, Y {// Pass the parameters here in the constructor,// similar to function modifiers.constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
}// Parent constructors are always called in the order of inheritance
// regardless of the order of parent contracts listed in the
// constructor of the child contract.// Order of constructors called:
// 1. X
// 2. Y
// 3. D
contract D is X, Y {constructor() X("X was called") Y("Y was called") {}
}// Order of constructors called:
// 1. X
// 2. Y
// 3. E
contract E is X, Y {constructor() Y("Y was called") X("X was called") {}
}
Shadowing Inherited State Variables
意思是隐藏继承的状态变量. 与函数不同,状态变量state variable
不能通过在子契约中重新声明来重写。让我们学习如何正确地重写继承的状态变量.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;contract A {string public name = "Contract A";function getName() public view returns(string memory) {return name;}
}// 0.6之后这种形式就被ban了.
// contract B is A {
// string public name = "Contract B";
// }contract C is A {constructor() {name = "Contract C";}
}
Calling Parent Contracts
父契约可以直接调用,也可以使用关键字super
调用。通过使用关键字super
,将调用所有的直接父契约. 前面也已经演示过了.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;/* Inheritance treeA/ \
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();}
}
// 顺序是C -> B -> A, 也是有讲究的.
// D声明写成`D is C, B`就是B, C, A顺序了
[{"from": "0x540d7E428D5207B30EE03F2551Cbb5751D3c7569","topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab","event": "Log","args": {"0": "C.bar called","message": "C.bar called"}},{"from": "0x540d7E428D5207B30EE03F2551Cbb5751D3c7569","topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab","event": "Log","args": {"0": "B.bar called","message": "B.bar called"}},{"from": "0x540d7E428D5207B30EE03F2551Cbb5751D3c7569","topic": "0xcf34ef537ac33ee1ac626ca1587a0a7e8e51561e5514f8cb36afa1c5102b3bab","event": "Log","args": {"0": "A.bar called","message": "A.bar called"}}
]
Visibility
函数和状态变量必须声明它们是否可以被其他智能合约访问。
函数可以声明为:
public
---任何合约和账户都可以调用.private
---只能在本合约内用.internal
---同一合约和儿子合约可以调用.external
---仅其他契约和帐户可以调用
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;contract Base {// Private function can only be called// - inside this contract// Contracts that inherit this contract cannot call this function.function privateFunc() private pure returns(string memory){return "private function called";}function testPrivateFunc() pure public returns(string memory){return string(abi.encodePacked(privateFunc(), "test"));}// Internal function can be called// - inside this contract// - inside contracts that inherit this contractfunction internalFunc() internal pure returns(string memory){return "internal function called";}function testInternalFunc() public pure virtual returns(string memory){return string(abi.encodePacked(internalFunc(), "test"));}function publicFunc() public pure returns(string memory){return "public function called";}// External functions can only be called// - by other contracts and accountsfunction externalFunc() external pure returns(string memory){return "external function called";}// This function will not compile since we're trying to call// an external function here.// function testExternalFunc() public pure returns (string memory) {// // 报错: DeclarationError: Undeclared identifier.// return externalFunc();// }string private privateVar = "my private variable";string internal internalVar = "my internal variable";string public publicVar = "my public variable";// State variables cannot be external so this code won't compile.// 报错: ParserError: Expected identifier but got 'external'// string external externalVar = "my external variable";
}contract Child is Base {// Inherited contracts do not have access to private functions// and state variables.// 报错: DeclarationError: Undeclared identifier.// function testPrivateFunc() public pure returns (string memory) {// return privateFunc();// }function testInternalFunc() public pure override returns(string memory) {return string(abi.encodePacked(internalFunc(), " child test"));}
}
注意这些函数都是用public
或者external
修饰的.
Interface
可以通过声明接口interface
与其他合约进行交互。
interface
特点:
- 没有具体实现
- 可以从其他接口继承
- 所有声明的功能必须是
external
修饰的 - 无法声明构造函数
- 无法声明状态变量
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;// Counter合约,用于计数
contract Counter {uint public count;// 增加计数的函数function increment() external {count += 1;}
}// 计数器接口,定义了获取计数和增加计数的函数
interface ICounter {function count() external view returns (uint);function increment() external;
}// MyContract合约,包含与ICounter合约交互的函数
contract MyContract {// 通过传递的_counter地址调用ICounter合约的increment函数function incrementCounter(address _counter) external {ICounter(_counter).increment();}// 通过传递的_counter地址调用ICounter合约的count函数function getCount(address _counter) external view returns(uint){return ICounter(_counter).count();}
}// UniswapV2Factory接口,用于获取Uniswap交易对的地址
interface UniswapV2Factory {function getPair(address tokenA,address tokenB) external view returns(address pair);
}// UniswapV2Pair接口,用于获取Uniswap交易对的储备量信息
interface UniswapV2Pair {function getReserves()external view returns(uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}// UniswapExample合约,用于获取Uniswap交易对的储备量信息
contract UniswapExample {// Uniswap工厂合约地址address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;// DAI代币地址address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;// WETH代币地址address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;// 获取Uniswap交易对的储备量信息function getTokenReserves() external view returns (uint, uint){// 获取DAI-WETH交易对地址address pair = UniswapV2Factory(factory).getPair(dai, weth);// 调用UniswapV2Pair接口获取储备量信息(uint reserve0, uint reserve1, ) = UniswapV2Pair(pair).getReserves();return (reserve0, reserve1);}
}
uniswap
还没研究过, 在Remix用的时候是报错的.
不过Counter
的用法倒是清楚.
注意填写的address
, 是Counter
合约的地址.
用右边Counter
合约的地址执行左边的MyCounter
的incrementCounter
, 会增加右边Counter
合约的count
.