这里写目录标题
这篇文章仅用于记录上课笔记!!点击 solidity 查看更多
基本数据类型:
整数,枚举,布尔
Address,contract
Fixed byte array
-
Integer(int,uint)
uint(unsigned int) 无符号整数,2的n次方减一为最大数值。可以使用type(x).max() 来查看该数据类型的最大值和最小值
部署合约之后可以看到最大值:
可以写一个increment函数来检验:当数据值的范围超出最大值之后,就不会再增加,并且会给出错误信息。
-
枚举类型(Enum type)
-
address
address含有20个字节长度,address可以转换为uint160和bytes20,表示部署合约的账号地址值。 -
contract合约关键字
用于声明合约
-
定长字节数组
使用下标访问,但是不可以使用下标写入数据。
定义方式:
bytes1 data; // 1表示这是一个字节的定长字节数组
bytes d = data[0];
- 引用类型
数组,struct(结构体),mapping映射
数组:push 方法 向数组中添加元素
pop 方法,删除数组当中的一个元素 - mapping映射
mapping(key type => value type)
如果要想映射作为函数的参数传入,那么这个函数的可见性必须是internal或者是private.不能是public。
mapping (string => string) nameToDesc;
mapping (string => uint8) nameToAge;
mapping (string => mapping(string=>uint8)) public name2age;
function setnameToAge (mapping(string=> uint8) storage data) internal{
}
contract Storage {
mapping(address => uint256) balances;
constructor(){
address deployer = msg.sender;
balances[deployer] = 100;
}
function balanceOf(address owner) public view returns(uint256){
return balances[owner];
}
function transfer(address from, address to ,uint256 amount)public{
require(balances[from] >= amount,"The address do not have enough money");
balances[from] -= amount;
balances[to] += amount;
}
}
- sd
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract ArrayTest{
uint256[4] public fixedData;
uint256[] public dynamicData;
// 定长数组 使用下标来存放数据
function setFixedData(uint8 index,uint256 value)public{
fixedData[index] = value;
}
// 动态数组 使用push方法来存入变量,不需要使用下标索引
function addDataToDynamic(uint256 value) public{
dynamicData.push(value);
}
function removeAllData(uint8 n) public{
uint256 length = dynamicData.length;
for (uint8 i = 0 ;i<length;i++){
dynamicData.pop();
}
}
// 内存当中的动态数组
// function memoryDynamic(uint8 size) public{
// uint256 [] memory temp = new uint256[](size);
// }
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
struct Student {
uint256 num;
string name;
}
contract Storage {
uint256 number;
Student [] public student;
function setStudent(uint256 _num,string memory _name) public{
Student memory st = Student(_num,_name);
// Student memory st = Student({num:_num,name:_name});
student.push(st);
}
}
引用数据类型
在solidity当中,引用数据类型的变量和其他语言并不相同,因为solidity当中含有storage这个 数据块的存储位置,所以变量的数据块会固化在存储当中,并不会指向引用的数据变量当中的内容,所以不会出现其他语言当中的引用拷贝,只会出现值拷贝。
uint256[3] public stateData1;
uint256[3] public stateData2;
function assignStateVariable()public{
stateData1 = stateData2; // 只会把stateData2的值给到stateData1,并不会产生一个指针
stateData2[0] = 5;
}
function storage2memory () public returns (uint256[3] memory){
uint256[3] memory data1 = stateData1; // 只会把磁盘当中的数据放到内存当中,不会创建一个指针
stateData1[0] = 10;
return data1;
}
- location对数据空间的分割
uint256[3] public stateData1;
uint256[3] public stateData2;
function storage2memory () public returns (uint256[3] memory){
uint256[3] memory data1 = stateData1; // 两个变量的location是memory和storage,所以是值引用,并且只会把磁盘当中的数据放到内存当中,不会创建一个指针
stateData1[0] = 10;
return data1;
}
- 判定算法
function storage2memory () public {
uint256[3] memory data;
data = stateData1; // 因为两个数据的location并不相同,所以不会出现引用拷贝,只会是值拷贝
stateData1 [0] = 9;
}
*-------------------------------------------------------
function storage2memory () public {
uint256[3] memory data;
stateData1 = data;
stateData1 [0] = 9;
}
-------------------------------------------------------------------
uint256[3] public stateData1;
uint256[3] public stateData2;
function assignStateVariable()public{
stateData1 = stateData2; // 只会把stateData2的值给到stateData1,并不会产生一个指针
stateData2[0] = 5;
}
function storage2memory () public {
uint256[3] memory data;
uint256[3] storage sdp = stateData1; // 成员变量默认的location是storage 所以是引用拷贝
sdp = stateData2;
data = sdp; // 两个变量的location不同 ,所以应该是值拷贝
stateData1 = data;
stateData1 [0] = 9;
}
https://solidity-by-example.org/
函数的调用机制
静态调用
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Caller{
uint256 public mycount;
address callee;
constructor(address _callee){
callee = _callee;
}
function fetchCallee()public{
Callee calleeContract = Callee(callee);
uint256 callee_value = calleeContract.get_number();
mycount = callee_value;
}
}
contract Callee{
uint256 number = 0;
function get_number()public view returns(uint256) {
return number;
}
function increment() public{
number += 1;
}
}
当调用函数和被调用函数不在一个合约当中时,我们需要在调用函数所在的合约当中引入被调用函数所在的合约
import “./callee.sol”;
通过接口调用
调用者不再需要被调用者的原文件,不需要import导入
需要写一个接口:
// 接口
interface CalleeI{
// 接口指向的函数
function get_number() external view returns(uint256);
}
之后在caller当中修改调用callee函数的语句:
// 使用接口CalleeI强制转换合约地址为被调用的合约
CalleeI calleeContract = CalleeI(callee);
之前使用import导入的方式当中调用callee函数的语句:
Callee calleeContract = Callee(callee);
- 上课代码
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Callee1{
uint256 count = 0;
function getCount()public view returns(uint256){
return count;
}
function increment()public{
count +=1;
}
}
contract Callee2{
uint256 count = 0;
function getCount()public view returns(uint256){
return count;
}
function increment()public{
count +=2;
}
}
调用过程中的上下文变量
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function setCalleeX(uint256 _x) public{
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
function setX(uint256 _x) public{
x = _x;
}
}
关于各个函数合约的调用者
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function setCalleeX(uint256 _x) public{
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
address public whocallCallee;
function setX(uint256 _x) public{
whocallCallee = msg.sender;
x = _x;
}
}
Caller的调用者是外部账号(eoa),Callee的调用者是Caller
利用上下文变量tx来查看触发transaction的外部账号
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
// 在调用链条上出发transaction的外部账号(也就是eoa)
address public eoa;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function setCalleeX(uint256 _x) public{
eoa = tx.origin;
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
address public whocallCallee;
address public eoa;
function setX(uint256 _x) public{
eoa = tx.origin;
whocallCallee = msg.sender;
x = _x;
}
}
this 关键字
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Caller{
address callee;
// 在调用链条上出发transaction的外部账号(也就是eoa)
address public eoa;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function callSetCalleesetX(uint256 _x) public{
// 合约内部函数之间的调用 不会产生新的message
// this关键字会使函数的调用产生新的message 这个message就是这个合约自己不再是eoa
this.setCalleeX(_x);
}
function setCalleeX(uint256 _x) public{
eoa = tx.origin;
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
contract Callee{
uint256 public x;
address public whocallCallee;
address public eoa;
function setX(uint256 _x) public{
eoa = tx.origin;
whocallCallee = msg.sender;
x = _x;
}
}
当合约当中的一个函数要调用另一个可见性为external的函数的时候,必须要使用this关键字 否则会报错
contract Caller{
address callee;
// 在调用链条上出发transaction的外部账号(也就是eoa)
address public eoa;
address public whocallCaller;
// 调用者拿到被调用者的合约地址
constructor(address _callee){
callee = _callee;
}
function callSetCalleesetX(uint256 _x) public{
// 合约内部函数之间的调用 不会产生新的message
// this关键字会使函数的调用产生新的message 这个message就是这个合约自己不再是eoa
this.setCalleeX(_x);
}
function setCalleeX(uint256 _x) external{
eoa = tx.origin;
whocallCaller = msg.sender;
// 把地址强制转换成合约对象
Callee calleeC = Callee(callee);
calleeC.setX(_x);
}
}
erc 和 rfc
函数的动态调用
Call调用语法:
因为call是address的语法,所以调用call的对象必须是一个地址,call的函数是一个字节数组,
sig是函数的签名 keccak256是一种哈希算法
把函数签名和参数列表作为参数输入 返回打包之后的列表
动态调用代码:
Callee合约:
contract Callee{
uint public x;
uint public y;
function setXAndY(uint _x,uint _y) external {
x = _x;
y = _y;
}
}
Caller合约
contract Caller{
address callee;
constructor(address _a){
callee = _a;
}
function setCalleeX(uint _x,uint _y) external{
// (函数签名,参数列表)
// 被调合约的函数的签名(只需要参数的类型 不要参数的名称) 参数列表
bytes memory data = abi.encodeWithSignature("setXAndY(uint256, uint256)",_x,_y);
(bool success,bytes memory rdata) = callee.call(data);
if(!success){
revert("setXAndY fail!"); // 终止执行
}
}
}
调用者合约当中通过函数参数传递然后进行转化的数据就是data 而这个data可以在被调用者合约当中通过上下文变量得到 验证这两个data是相同的
contract Caller{
address callee;
bytes public callee_setxy_data; // 使用编码形成的data
constructor(address _a){
callee = _a;
}
function setCalleeX(uint _x,uint _y) external{
// (函数签名,参数列表)
// 被调合约的函数的签名(只需要参数的类型 不要参数的名称) 参数列表
bytes memory data = abi.encodeWithSignature("setXAndY(uint256,uint256)",_x,_y);
callee_setxy_data = data;
(bool success,bytes memory rdata) = callee.call(data);
if(!success){
revert("setXAndY fail!"); // 终止执行
}
}
}
contract Callee{
uint public x;
uint public y;
bytes public callee_data;
function setXAndY(uint _x,uint _y) external {
callee_data = msg.data; // 调用者通过函数参数传过来的数据
x = _x;
y = _y;
}
}
备胎fallback函数
- fallback函数的可见度必须是external
- fallback函数的名称就是fallback
- 书写方式:
fallback()external{
// 函数语句
}
如果在调用者合约当中的的调用函数语句出现了错误,导致无法正常调用应该要调用的函数,就会触发被调用合约当中的fallback函数。
Gas与转账收款
Payable关键字(也是函数的一种属性),用于修饰函数,表示该函数可以接受转账,调用该函数当中的data前四位函数选择器对应这个函数时,message当中的value可以大于0,函数当中带的钱会放在value当中。
message : from to value data(前四位表示函数选择器,后边表示函数的参数)
contract Storage {
uint256 number;
function store(uint256 num) public payable {
number = num;
}
}
转账收钱
contract Caller {
address callee;
constructor(address _callee){
callee = _callee;
}
// 首先定义一个存钱的函数,才能完成下面转账的功能
function deposit () public payable {
}
// 转账操作
function callCalllee() public payable {
bytes memory data = abi.encodeWithSignature("receiveMoney()");
(bool success,bytes memory r_data) = callee.call{value:1 ether}(data);
if (!success){
revert("sdajshd Failed!");
}
}
}
contract Callee{
uint256 number;
// 使fallback函数也具有收钱的功能
fallback() external payable{
}
function receiveMoney () public payable{
}
}
如果在交易
的时候函数签名出现问题,那么就不能成功完成转账这个操作,这时就要用到fallback函数,为了转账成功,所以要给fallback函数一个payable属性,使他可以收钱。
因为有了fallback函数,所以reteiveMoney函数就不会再起到作用。所以在Caller合约当中调用函数时什么也不调用就可以,传一个空字符串就行。
10.31
合约的边界性
非合约地址不一定是合法的外部账号,所以并不一定可以完成接受转账的功能
11.5
内存动态数组和成员变量动态数组的区别
- 内存动态数组位于memory 中,成员变量动态数组位于storage中。
- 成员变量数组初始长度为0,通过push,pop的动态变化,内存动态数组在程序运行时分配固定长度,一旦分配不可变
整型溢出的解决办法
- 使用高版本
- 使用safemath库
mapping为什么不能作为public函数的参数
- 因为public函数可能被链下的其他函数和合约调用,而mapping又不能被拷贝,所以不能作为public函数的参数
引用拷贝和值拷贝
- 变量本身的指针旋转发生变化,指向其他变量
- 直接把变量的值拿过来,是数据块本身发生的拷贝
使用mapping和msg
因为java当中引用类型的赋值一定是引用拷贝,不会发生值拷贝
netmask是一个钱包 适用于以太坊和跟他兼容的区块链
web3js 访问合约需要合约地址和abi信息
dapp(decentralized application)去中心化
- 区块链,智能合约是去中心化的
- 如果一个应用的核心业务逻辑是构建在区块链和智能合约上就是DAPP
读取合约函数 getOwner 更改合约状态changeOwner
通过import接口调用合约的好处
- 不容易写错
函数的签名和参数编码 data
函数调用者传过来多少钱 value
合约的调用者 sender
触发调用链条的链下的钱包地址 tx.origin
msg变化规则(沿着调用链条):
- 函数内部调用msg不变
- 跨月合约调用,调用者向被调用者发送message,msg发生变化
- 通过this关键字调用内部函数,msg变化
返回值是struct时,如何解码?
11.07
delegatecall详解
delegatecall 语法与动态调用函数相似:
<address>.delegatecall(bytes calldata)
调用别的合约代码,访问自己成员
(bool success,bytes memory rdata) = b.delegatecall(data);
只有方法的名称发生了变化,方法的参数也跟call一样。
使用Delegatecall方法时B合约当中的msg.sender应该是A的外部账号,也就是Remix账号(不是B合约地址,也不是A的地址),因为Delegatecall修改的是A合约当中的成员变量
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract A{
uint256 public number;
address b;
constructor(address _b){
b=_b;
}
function borrowCodeFromB()public{
bytes memory data = abi.encodeWithSignature("setNumber(uint256)", 13);
(bool success,bytes memory rdata) = b.delegatecall(data);
if(!success){
revert("Fail!");
}
}
function provokeCodeFromB()public{
bytes memory data = abi.encodeWithSignature("setNumber(uint256)", 14);
(bool success,bytes memory rdata) = b.call(data);
if(!success){
revert("Fail!");
}
}
}
contract B{
uint256 public number;
function setNumber(uint256 _n)public{
number = _n;
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract A{
uint256 public number;
address public invoker;
address b;
constructor(address _b){
b=_b;
}
function borrowCodeFromB()public{
bytes memory data = abi.encodeWithSignature("setNumber(uint256)", 13);
(bool success,bytes memory rdata) = b.delegatecall(data);
if(!success){
revert("Fail!");
}
}
function invokeCodeFromB()public{
bytes memory data = abi.encodeWithSignature("setNumber(uint256)", 14);
(bool success,bytes memory rdata) = b.call(data);
if(!success){
revert("Fail!");
}
}
}
contract B{
uint256 public number;
address public invoker;
function setNumber(uint256 _n)public{
invoker = msg.sender;
number = _n;
}
}
部署合约之后发现A合约当中的invoker发生了变化但是B合约当中的invoker并没有发生变化,就是因为Delegatecall方法时调用合约B的代码在合约A当中去执行,所以修改的是A合约当中的成员变量。
但是调用invokeCodeFromB函数(因为这是一个正常的函数调用)就会发现A当中的invoker并没有发生变化,而是B合约当中的invoker发生了变化,并且invoker当中的内容是A合约的地址。
因为Delegatecall方法要求两个合约当中的plot一样的,所以如果两个合约当中的成员变量的顺序没有对应上,那么就会产生错误。invoker就无法发生正常的变化。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract A{
uint256 public number;
address public invoker;
address b;
constructor(address _b){
b=_b;
}
function borrowCodeFromB()public{
bytes memory data = abi.encodeWithSignature("setNumber(uint256)", 13);
(bool success,bytes memory rdata) = b.delegatecall(data);
if(!success){
revert("Fail!");
}
}
function invokeCodeFromB()public{
bytes memory data = abi.encodeWithSignature("setNumber(uint256)", 14);
(bool success,bytes memory rdata) = b.call(data);
if(!success){
revert("Fail!");
}
}
}
contract B{
uint256 public number;
address public invoker;
function setNumber(uint256 _n)public{
invoker = msg.sender;
number = _n;
}
}
代理模式
代理模式为了完成合约的升级
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Proxy {
uint256 public x;
address logic;
constructor(address l){
logic = l;
}
fallback() external {
(bool success,bytes memory rdata) = logic.delegatecall(msg.data);
if (!success){
revert("fail!");
}
}
}
contract Logic{
uint256 public x;
function increment( ) public {
x++;
}
}
contract Client{
address public proxy;
constructor(address p){
proxy = p;
}
function callProxyNoneExist()public{
bytes memory data = abi.encodeWithSignature("increment()");
(bool success,bytes memory rdata)=proxy.call(data);
if (!success){
revert("fail!");
}
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Proxy {
uint256 public x;
address logic;
constructor(address l){
logic = l;
}
fallback() external {
(bool success,bytes memory rdata) = logic.delegatecall(msg.data);
if (!success){
revert("fail!");
}
}
function upgradeTo(address newlogic) public {
logic = newlogic;
}
}
contract Logic{
uint256 public x;
function increment( ) public {
x++;
}
}
contract NewLogic{
uint256 public x;
function increment( ) public {
x += 5;
}
}
contract Client{
address public proxy;
constructor(address p){
proxy = p;
}
function callProxyNoneExist()public{
bytes memory data = abi.encodeWithSignature("increment()");
(bool success,bytes memory rdata)=proxy.call(data);
if (!success){
revert("fail!");
}
}
}
11.14
因为fallback没有返回值,所以调用者看不到调用的结果。
可以使用汇编语言来完成这个操作,可以使fallback把结果返回
部署一个proxy(代理)合约 再部署一个logic(逻辑)合约 再使用proxy的地址来把代理合约强制当成一个logic合约使用,此时操作的是proxy当中的数据,可以查看logic合约当中的数据被没有发生变化。
但是为了使proxy合约和logic合约当中的slot相对应,需要在logic合约当中设置一个placeholder相当于占了一个位置。为了更简洁,再次使用汇编语言完成这个操作
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
// 代理合约
contract Proxy {
// constant 常量 不占用存储槽
bytes32 private constant logicPosition = keccak256("org.zeppelinos.proxy.implementation");
uint public x = 10;
address public logic;
function upgradeTo(address newLogic)public {
bytes32 position = logicPosition;
assembly{
sstore(position,newLogic)
}
}
function mylogic() public view returns(address impl) {
bytes32 position = logicPosition;
assembly {
impl := sload(position)
}
}
constructor(address l){
logic = l;
}
fallback() external {
address _logic = logic;
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
// calldatacopy(t, f, s) - copy s bytes from calldata at position f to mem at position t
// calldatasize() - size of call data in bytes
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
// delegatecall(g, a, in, insize, out, outsize) -
// - call contract at address a
// - with input mem[in…(in+insize))
// - providing g gas
// - and output area mem[out…(out+outsize))
// - returning 0 on error (eg. out of gas) and 1 on success
let result := delegatecall(gas(), _logic, 0, calldatasize(), 0, 0)
// Copy the returned data.
// returndatacopy(t, f, s) - copy s bytes from returndata at position f to mem at position t
// returndatasize() - size of the last returndata
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
// revert(p, s) - end execution, revert state changes, return data mem[p…(p+s))
revert(0, returndatasize())
}
default {
// return(p, s) - end execution, return data mem[p…(p+s))
return(0, returndatasize())
}
}
}
}
// 逻辑合约
contract Logic{
// address public placeholder; // 为了使slot与proxy当中的slot相对应
// 去掉placeholder之后,使用汇编语言来完成两个合约当中的slot相对应
uint public x;
uint public y;
function getX() public view returns(uint256){
return x;
}
function getY() public view returns(uint256){
return y;
}
function setX(uint256 _x) public {
x = _x;
}
function setY(uint256 _y) public {
y = _y;
}
}
// 把代理合约强制当成logic合约来用,此时操作的数据是proxy当中的数据,可以查看logic合约当中的数据并没有发生变化
11.19
using的用法
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
library Math {
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
// else z = 0 (default value)
}
function add(uint256 x,uint256 y) internal pure returns(uint256){
return x+y;
}
}
contract Storage {
// 第一种方法 进行绑定
using Math for uint256; // 与整形数绑定
function computeSqrt(uint256 x) public pure returns(uint256){
uint256 r = x.sqrt();
return r;
}
// 第二种方法 不绑定
/*
function computeSqrt(uint256 x) public pure returns(uint256){
uint256 r = Math.sqrt(x);
return r;
}
*/
}
在部署合约的时候会部署两次,因为library库当中有一个可见性为public的函数,会把它当成一个合约来部署。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
library FooContract {
function foo(address addr) internal {
bytes memory cd = abi.encodeWithSignature("foo()");
(bool success,bytes memory rd) = addr.call(cd);
if(!success){
revert("Fail!");
}
}
}
contract Storage {
address fooaddr;
using FooContract for address;
function callFoo() public {
fooaddr.foo();
}
}
多继承
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract ParentContract {
uint256 number;
event Store(uint256 _old ,uint256 _new);
modifier lessthan(uint256 num ){
require(num <= 100,"failed!");
_;
}
constructor(uint256 _n){
number = _n;
}
function store(uint256 num) internal lessthan(num) {
emit Store(number,num);
number = num;
}
function retrieve() public view returns (uint256){
return number;
}
}
// 继承了ParentContract合约
contract ChildContract is ParentContract{
// 子合约的构造函数调用父合约的构造函数 并且传入一个参数
constructor( uint256 _n ) ParentContract ( _n){
}
/*
// 直接调用父合约的构造函数并且传入 45
constructor() ParentContract (45){
}
*/
function foo(uint256 num) public lessthan(num) {
emit Store(number,num);
number = num;
retrieve();
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
abstract contract A{
uint256 number;
// 当一个virtual 函数有函数体之后就可以去掉abstract
function foo(uint256 _n) external virtual;
}
// 一个抽象合约被继承 其中包括一个虚函数 那么子合约就可以实现这个虚函数
contract Child is A{
// 使用override关键字
function foo(uint256 _n) external override{
number = _n;
}
}