智能合约语法
// 转账合约
pragma solidity ^0.8.25;
contract Asset {
// 注册事件
event RegisterEvent(string result,string name,uint256 balance);
// 交易事件
event TransactionEvent(string result,string from,string to,uint256 account);
// 账户数据:键为账户名,值为整型数组,第一个为状态值,第二个为余额
mapping (string => uint256[]) public bank;
// 注册函数
function register(string memory name,uint256 balance) public{
// 判断是否已经注册
uint256[] memory registerinfo = bank[name];
if(registerinfo.length == 0){
bank[name] = [1,balance];
emit RegisterEvent("success", name, balance);
}else {
emit RegisterEvent("fail",name,uint256(0));
}
}
// 查询
function query(string memory name) public view returns(uint256){
uint256[] memory userInfo = bank[name];
if(userInfo.length == 0){
return uint256(0);
}
return userInfo[1];
}
// 交易
function transaction(string memory from,string memory to,uint256 account) public {
// 验证账户是否存在
if(bank[from].length == 0 || bank[to].length == 0){
emit TransactionEvent("account does not exit",from,to,account);
return ;
}
// 验证转账用户余额是否充足
if(bank[from][1] < account){
emit TransactionEvent("balance is not enough", from, to, account);
return ;
}
// 转账
bank[from][1] = bank[from][1] - account;
bank[to][1] = bank[to][1] + account;
emit TransactionEvent("success", from, to, account);
}
}
代码结构
版本声明
指定智能合约的编译器版本,有如下两种表示方法:
// 表示编译器版本介于0.7.0到0.9.0之间 pragma solidity >=0.7.0 <0.9.0; // 表示使用主版本号为4的编译器的最新版本,但是也可以使用主版本号为4且在0.4.19之后的编译器 pragma solidity ^0.4.19;
合约代码
基本的智能合约应该包含合约声明、构造函数和函数
数据结构
- 整形:unit:无符号整型,int:有符号整型,还可以通过后面带数字的形式指定存储空间的大小,避免执行合约时多余的空间消耗,unit8,unit16,int8,int16等
- 布尔类型:bool a = true,bool b = false;
- 地址类型:可以记录合约地址或外部账户地址,address a = 0xcb76b7d9a3458ef540ade6068dfe2f44e8ff745f,地址可以是一个值,也可以有成员变量,比如address addre = xxxxx;unit256 b = addre.balance。所以地址可以作为一个合约的基础。
- 文本数据:string
- 字符:byte,表示单个字符
- 字符数组:bytes,不跟数字则表示动态数组,同理可以在后面跟数字限定存储空间的大小,如bytes32,但是此时是指一个字符数组。
- 结构体:
struct Person { string name; uint age; }
- 数组:动态数组,type[],静态数组,type[size]
- 键值对:当映射的值为uint时,如果键不存在则返回0,如果是数组类型时,如果键不存在则返回长度为0的数组
// 账户数据 mapping (string => uint256[]) public bank; // 注册函数 function register(string calldata name,uint256 balance) public returns(uint256[] memory){ bank["h"] = [1,23]; return bank["h"]; }
指定变量数据位置
solidity中,所有引用类型(字符串、数组等)都需要指定数据位置
- memory:内存,变量动态分配,有作用域,出了作用域就等待回收
- storage:区块链存储,在状态变量存储的位置
- calldata:只读变量,用于函数参数修饰,0.6.9之前只用于外部函数,0.6.9之后可用于任意函数,不可改,就以为不需要存储拷贝,不分配空间
控制结构
if语句
if(true){
// }else{
// }
for语句
for(unit i = 0; i < 100; i++){ // }
while语句
while(true){ // }
do while语句
do{ // }while(true);
函数
function add(uint a, uint b) public pure returns (uint) { return a + b; } // 调用 uint result = add(3, 5);
函数可见性
- public:内外部合约可见
- private:仅内部可见,合约继承者也不可见
- external:仅外部可见,内部不可见
- internal:内部可见,外部不可见
函数操作权限
- pure:纯函数,不可读取,不可写入
- view:可读取,不可写入
支付payable
通过payable修饰符声明函数可以接收gas
- 使用payable的函数说明调用函数者发送的合约将被存储到合约的余额中
- 如果没有使用payable声明,如果尝试向该合约发送gas,则会导致一个失败的交易
抽象与继承
一个函数中定义了没有函数体的函数,则它是抽象合约;
一个函数如果继承自抽象合约,却没有实现抽象函数,则它也是抽象合约
// 版本声明 pragma solidity ^0.8.0; // 合约声明 abstract contract Parent { function p() public pure virtual returns(uint); } contract Son is Parent{ function p() public pure override returns( uint) { return 12; } }
调用外部函数
contract ExternalContract { function externalFunction() public pure returns (uint) { return 42; } } contract MyContract { ExternalContract public externalContract; constructor(address externalContractAddress) { externalContract = ExternalContract(externalContractAddress); } function interactWithExternal() public view returns (uint) { return externalContract.externalFunction(); } }
事件或日志
事件定义
- 事件和日志的使用基本一样,都是通过event定义,通过emit触发
- 日志偏向于记录信息,事件偏向于传递信息,触发某些操作
- 事件在他被使用的位置向外界传递你需要传递的参数,外界接收参数的同时可以确定,代码运行到了事件执行位置
- 事件记录的数据结构花费很小
- 事件的记录智能合约不可访问
- 事件存储在交易的日志部分,但是不可以被智能合约直接访问,只能够验证区块链中是否存在该日志
- 事件最多可以有三个索引,有时候也叫主题,通过indexed修饰,被定义为索引的变量,可以成为后续检索该事件的索引
事件使用
pragma solidity ^0.8.0; contract EventExample { event NewUser(address user, string name); function addUser(string memory name) public { emit NewUser(msg.sender, name); } }
事件监听
下述两种监听代码来自别的博客
counter = web3.eth.contract(abi).at(address); counter.Increment(function (err, result) {//开始监听 if (err) { return error(err); } log("Count was incremented by address: " + result.args.who); });
var abi = /* abi 通过编译器生成 */; var ClientReceipt = web3.eth.contract(abi); var clientReceipt = ClientReceipt.at("0x1234...ab67" /* 地址 */); var event = clientReceipt.Deposit(); // 观察变化 event.watch(function(error, result){ //结果将包含`Deposit`调用给定的参数等各种信息 if (!error) console.log(result); }); // 或通过回调立即开始观察,事件是使用EVM日志内置功能的方便工具,在dapp的接口中,它可以反过来调用Javascript的监听事件的回调。 var event = clientReceipt.Deposit(function(error, result) { if (!error) console.log(result); });
事件数据结构
事件外发的数据结构

支付
下述是一个具备转账功能的合约
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract Payable { // payable 标记函数:接收以太币,接收以后存储到合约中 function deposit1() external payable { // 处理接收到的以太币 } function deposit2() external {} // payable 标记函数 function withdraw() external { // msg.sender合约的调用者 // 把当前合约的所有余额全部转到调用者 payable(msg.sender).transfer(address(this).balance); } // 通过balance属性来查看余额 function getBalance() external view returns (uint256) { return address(this).balance; } }
刚才写的合约的deposit1是payable,可以接受用户的转账:
账号拥有99.9999个以太币





异常
异常捕获
function divide(uint256 numerator, uint256 denominator) public returns (uint256) { uint256 result; try this.division(numerator, denominator) returns (uint256 quotient) { result = quotient; } catch { result = 0; } return result; }
异常抛出
异常抛出可以使用require、revert、assert
区别:
- require:可以检查错误并抛出,相当于revert和if语句的合体,发生异常后会退还剩余gas,消耗小,推荐使用
- revert:不支持检查,直接抛出,抛出后退还gas,实际上require相当于检查错误后执行了revert
- assert:断言,检查到不符合条件后直接阻断代码,抛出异常,并且不退换剩余gas,消耗大,不建议在生产环境使用
uint public num=0; function testRevert() public { num++; if (num>3){ revert(“revert返回的错误”); } num++; } function testAssert() public { num++; assert(num<13); num++; } function testRequire()public { num++ require( num<23,”require报错信息”); num++; } 总结:不管是哪个回退,都是回退到不满足条件前满足条件的状态
智能合约demo
solidity
fisco bcos
Asset.sol,需要引用Table.sol,Table.sol中声明了接口,其中的合约实现由Fisco Bcos底层实现
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.4.25; import "./Table.sol"; contract Asset { // event event RegisterEvent( int256 ret, string indexed account, uint256 indexed asset_value ); event TransferEvent( int256 ret, string indexed from_account, string indexed to_account, uint256 indexed amount ); KVTable kvTable; KVTableFactory tableFactory; string constant tableName = "t_asset"; constructor() public { // 构造函数中创建t_asset表 tableFactory = KVTableFactory(0x1002); // 资产管理表, key : account, field : asset_value // | 资产账户(主键) | 资产金额 | // |-------------------- |-------------------| // | account | asset_value | // |---------------------|-------------------| // // create table tableFactory.createTable(tableName, "account", "asset_value"); // get table address kvTable = tableFactory.openTable(tableName); } /* 描述 : 根据资产账户查询资产金额 参数 : account : 资产账户 返回值: 参数一: 成功返回0, 账户不存在返回-1 参数二: 第一个参数为0时有效,资产金额 */ function select(string memory account) public view returns (bool, uint256) { // 查询 bool result; Entry entry; (result, entry) = kvTable.get(account); uint256 asset_value = 0; if(result){ asset_value = uint256(entry.getInt("asset_value")); } return (result, asset_value); } /* 描述 : 资产注册 参数 : account : 资产账户 amount : 资产金额 返回值: 0 资产注册成功 -1 资产账户已存在 -2 其他错误 */ function register(string memory account, uint256 asset_value) public returns (int256) { int256 ret_code = 0; bool ret = true; uint256 temp_asset_value = 0; // 查询账号是否存在 (ret, temp_asset_value) = select(account); if (ret != true) { // 不存在,创建 string memory asset_value_str = uint2str(asset_value); // 插入 Entry entry = kvTable.newEntry(); entry.set("asset_value",asset_value_str); int256 count = kvTable.set(account, entry); if (count == 1) { // 成功 ret_code = 0; } else { // 失败? 无权限或者其他错误 ret_code = - 2; } } else { // 账户已存在 ret_code = - 1; } emit RegisterEvent(ret_code, account, asset_value); return ret_code; } /* 描述 : 资产转移 参数 : from_account : 转移资产账户 to_account : 接收资产账户 amount : 转移金额 返回值: 0 资产转移成功 -1 转移资产账户不存在 -2 接收资产账户不存在 -3 金额不足 -4 金额溢出 -5 其他错误 */ function transfer( string memory from_account, string memory to_account, uint256 amount ) public returns (int16) { // 查询转移资产账户信息 bool ret = true; uint256 from_asset_value = 0; uint256 to_asset_value = 0; // 转移账户是否存在? (ret, from_asset_value) = select(from_account); if (ret != true) { // 转移账户不存在 emit TransferEvent(- 1, from_account, to_account, amount); return - 1; } // 接受账户是否存在? (ret, to_asset_value) = select(to_account); if (ret != true) { // 接收资产的账户不存在 emit TransferEvent(- 2, from_account, to_account, amount); return - 2; } if (from_asset_value < amount) { // 转移资产的账户金额不足 emit TransferEvent(- 3, from_account, to_account, amount); return - 3; } if (to_asset_value + amount < to_asset_value) { // 接收账户金额溢出 emit TransferEvent(- 4, from_account, to_account, amount); return - 4; } string memory f_new_value_str = uint2str(from_asset_value - amount); // 更新转账账户 Entry entry = kvTable.newEntry(); entry.set("asset_value",f_new_value_str); int256 count = kvTable.set(from_account, entry); if (count != 1) { // 失败? 无权限或者其他错误? emit TransferEvent(- 5, from_account, to_account, amount); return - 5; } string memory to_new_value_str = uint2str(to_asset_value + amount); // 更新接收账户 entry.set("asset_value",to_new_value_str); kvTable.set(to_account, entry); emit TransferEvent(0, from_account, to_account, amount); return 0; } function uint2str(uint256 _i) internal pure returns (string memory _uintAsString) { if (_i == 0) { return "0"; } uint j = _i; uint len; while (j != 0) { len++; j /= 10; } bytes memory bstr = new bytes(len); uint k = len; while (_i != 0) { k = k-1; uint8 temp = (48 + uint8(_i - _i / 10 * 10)); bytes1 b1 = bytes1(temp); bstr[k] = b1; _i /= 10; } return string(bstr); } function safeParseInt(string memory _a) internal pure returns (uint256 _parsedInt) { return safeParseInt(_a, 0); } function safeParseInt(string memory _a, uint256 _b) internal pure returns (uint256 _parsedInt) { bytes memory bresult = bytes(_a); uint256 mint = 0; bool decimals = false; for (uint256 i = 0; i < bresult.length; i++) { if ( (uint256(uint8(bresult[i])) >= 48) && (uint256(uint8(bresult[i])) <= 57) ) { if (decimals) { if (_b == 0) break; else _b--; } mint *= 10; mint += uint256(uint8(bresult[i])) - 48; } else if (uint256(uint8(bresult[i])) == 46) { require( !decimals, "More than one decimal encountered in string!" ); decimals = true; } else { revert("Non-numeral character encountered in string!"); } } if (_b > 0) { mint *= 10 ** _b; } return mint; } }
参考
callData memory storage 的区别solidity的一些知识点(下)
solidity: 转账实现
Solidity基础六
Solidity中的错误处理和异常
深入理解Solidity——事件(Events)
Solidity之事件
Solidity中的事件和日志
2.solidity 数值类型 bool、uint、int、address、bytes32
区块链智能合约solidity的中的一些关键字
区块链2——Solidity智能合约开发
浙公网安备 33010602011771号