1,摘要

本文以住房租赁积分管理系统为例,给大家演示CLOUD IDE如何编译调试solidity智能合约,并以此为例,给大家分享以太坊SOLIDITY跟蚂蚁区块链SOLIDITY语言的差异点。

2,住房租赁积分管理系统的背景和目标

背景

基于区块链智能合约的住房租赁积分管理系统旨在有效地服务于公众群体、市民,真正让那些为城市当前发展做出努力的人有房住、租得起,让那些为城市建设长远发展做出贡献的人有房产、买得起。为此,建立住房租赁积分制度,从住房租赁市场主体属性、政策激励、租赁行为三方面,运用区块链、大数据等前沿技术,建立科学、有效的住房租赁积分全生命周期管理机制,营造活力、健康、有序、可持续的住房租赁生态。

目标

通过使用区块链平台作为底层数据支撑,确保隐私保护和数据不可篡改,达到公平、公正、透明的目的;利用积分决策引擎,结合住房租赁积分评价体系,产出应用于住房租赁场景的综合积分方案。该方案具备横向扩展性,在底层技术、数据不变的基础上,未来能够支撑经济信用积分、绿色生活积分等诸多积分体系的建设,从而扩展形成城市信用体系,应用于如医疗教育、金融借贷、绿色生活等诸多场景。

3,合约设计

权限管理

在住房租赁积分管理系统中,主要分为 4 种角色,分别是管理者、操作者、观察者以及市民。管理者、操作者、观察者是智能合约的操作人员,每个角色可以对应多名人员。市民是租赁积分智能合约服务的对象,是整个智能合约的核心用户。

角色操作权限

  • 管理者是合约的超级管理员,能够对管理者、操作者以及观察者进行添加、删除、查询操作。
  • 操作者可以查询操作者信息。
  • 观察者可以查询观察者信息。

积分操作权限

  • 管理者:具有积分奖励、积分查询、积分转移、积分违规扣除、积分权益兑换等积分操作权限。
  • 操作者:操作者是由管理者设置的积分操作角色,该角色可以是具体的人员也可以是系统服务,具有积分奖励、积分转移、积分违规扣除、积分权益兑换等积分操作权限。
  • 观察者:观察者是由管理者设置的积分查询角色,该角色可以是具体的人员也可以是系统服务,具有积分查询的操作权限。
  • 市民:市民是住房租赁积分系统的用户,是住房租赁积分系统的实际权益对象。
    • 市民可以通过组织机构的租赁积分外部服务系统代理的观察者权限查看自己的积分状况。
    • 租赁积分外部服务系统会根据市民的合规表现,通过操作者的权限进行积分奖励或积分违规扣除。
    • 市民可以通过租赁积分外部服务系统代理的操作者权限进行积分权益兑换或积分转移操作。

积分管理

租赁积分智能合约提供的服务方法包括 积分奖励、积分查询、积分权益兑换、积分违规扣除、积分转移 以及积分达到阈值后触发 积分事件。

积分奖励(awardScore)

在市民租赁房屋的过程中,针对租赁的时间、租赁过程中产生的行为,为市民进行积分累计。

积分查询(queryScore)

市民可通过租赁积分外部服务系统代理的观察者权限查看自己的积分状况。

积分权益兑换(exchangeScore)

市民的积分达到指定阈值后可以享有相应的权益,如租房优惠、买房资格、落户资格等。

积分违规扣除(deductScore)

在市民租赁房屋的过程中,针对租赁过程中产生的错误行为,对市民惩罚性的扣减积分。住房租赁积分作为正向激励的积分体系,鼓励民众积极向上。对于积分主体的伪造资料行为,视情节严重程度扣减积分、取消积分资格以及收回积分对应的权益。

积分事件(SCORE_EQUITY_NOTICE)

当积分权益实时增加时,会触发积分权益检查。若积分达到了指定的阈值,则会产生相应的积分权益通知事件。通过外部服务系统通知到具体的市民。

积分转移(transferScore)

当市民因工作关系、家庭因素需要到其他城市生活时,可以将该城市的积分转移到目标城市的积分系统。为满足该合约的调用,目标合约需要有对应的积分奖励方法(awardScore),且需要为发起合约提供操作权限,即将发起合约的地址设置为操作者或管理员。

4,智能合约代码

pragma solidity ^0.4.20;contract LeasingScoreManager {identity[] adminList;identity[] observerList;identity[] calculatorList;//用于记录管理数组被置位元素uint adminDeled;uint observerDeled;uint calculatorDeled;mapping (bytes32 => uint) leasingScore;enum ScoreAction {//奖励积分, 因相关行为,进行积分奖励ActionAwardScore,//积分权益兑换,花费指定积分ActionExchangeScore,//扣除积分, 因违规等, 扣除积分ActionDeductScore,//查询积分ActionQueryScore//积分转移, 将积分转移到其他的合约。}enum UserRole {RoleAdmin, //管理员RoleCalculator, //积分操作员RoleObserver, //积分查看人员RolePlayer //积分参与者}//是否为有效的操作人event VALID(bool valid, UserRole role);//admin/observer/calculator 用户存在event USER_EXIST(bool exist, UserRole role);//积分事件event SCORE_OPERATOR(ScoreAction action, string describe);//积分操作错误event SCORE_ERROR(ScoreAction action, string describe);//积分权益通知event SCORE_EQUITY_NOTICE(string action, uint score, string describe);constructor() public {adminList.push(msg.sender);adminList.push(this);}function indexAdmin(identity admin) view returns (uint) {for (uint i = 0; i < adminList.length; i++) {if (adminList[i] == admin) {return i;}}return adminList.length;}function validAdmin(identity admin) view returns (bool) {return indexAdmin(admin) < adminList.length;}function indexCalculator(identity calculator) view returns (uint) {for (uint i = 0; i < calculatorList.length; i++) {if (calculatorList[i] == calculator) {return i;}}return calculatorList.length;}function validCalculator(identity calculator) view returns (bool) {return indexCalculator(calculator) < calculatorList.length;}function indexObserver(identity observer) view returns (uint) {for (uint i = 0; i < observerList.length; i++) {if (observerList[i] == observer) {return i;}}return observerList.length;}function validObserver(identity observer) view returns (bool) {return indexObserver(observer) < observerList.length;}modifier onlyAdmin {bool isValid = validAdmin(msg.sender);emit VALID(isValid, UserRole.RoleAdmin);require(isValid);_;}modifier onlyCalculatorOrAdmin {bool isValid = validAdmin(msg.sender);if(isValid) {emit VALID(isValid, UserRole.RoleAdmin);} else {isValid = validCalculator(msg.sender);emit VALID(isValid, UserRole.RoleCalculator);}require(isValid);_;}modifier onlyObserverOrAdmin {bool isValid = validAdmin(msg.sender);if(isValid) {emit VALID(isValid, UserRole.RoleAdmin);} else {isValid = validObserver(msg.sender);emit VALID(isValid, UserRole.RoleObserver);}require(isValid);_;}function addAdmin(identity admin) public onlyAdmin {bool isExist = validAdmin(admin);emit USER_EXIST(isExist, UserRole.RoleAdmin);require(!isExist);if(adminDeled > 0) {uint deled = 1;for (uint i = 0; i < adminList.length; i++) {if(deled&adminDeled != 0) {adminList[i] = admin;adminDeled ^= deled;break;}deled <<= 1;}} else {adminList.push(admin);}}function removeAdmin(identity admin) public onlyAdmin {uint index = indexAdmin(admin);bool isValid = index != adminList.length;emit USER_EXIST(isValid, UserRole.RoleAdmin);require(isValid);delete adminList[index];adminDeled ^= 1 << index;}function queryAdmins() view public onlyAdmin returns (identity[]) {return adminList;}function addCalculator(identity calculator) public onlyAdmin {bool isExist = validCalculator(calculator);emit USER_EXIST(isExist, UserRole.RoleCalculator);require(!isExist);if(calculatorDeled > 0) {uint deled = 1;for (uint i = 0; i < calculatorList.length; i++) {if(deled&calculatorDeled != 0) {calculatorList[i] = calculator;calculatorDeled ^= deled;break;}deled <<= 1;}} else {calculatorList.push(calculator);}}function removeCalculator(identity calculator) public onlyAdmin {uint index = indexCalculator(calculator);bool isValid = index < calculatorList.length;emit USER_EXIST(isValid, UserRole.RoleCalculator);require(isValid);delete calculatorList[index];calculatorDeled ^= 1 << index;}function queryCalculators() view public onlyCalculatorOrAdmin returns (identity[]) {return calculatorList;}function addObserver(identity observer) public onlyAdmin {bool isExist = validObserver(observer);emit USER_EXIST(isExist, UserRole.RoleObserver);require(!isExist);if(observerDeled > 0) {uint deled = 1;for (uint i = 0; i < observerList.length; i++) {if(deled&observerDeled != 0) {observerList[i] = observer;observerDeled ^= deled;break;}deled <<= 1;}} else {observerList.push(observer);}}function removeObserver(identity observer) public onlyAdmin {uint index = indexCalculator(observer);bool isValid = index < observerList.length;emit USER_EXIST(isValid, UserRole.RoleObserver);require(isValid);delete observerList[index];observerDeled ^= 1 << index;}function queryObservers() view public onlyObserverOrAdmin returns (identity[]) {return observerList;}function checkScoreEquity(uint balance, uint score) {uint total = balance + score;if(total >= 100 && balance < 100) {emit SCORE_EQUITY_NOTICE("RentConcessions", total, "Citizens enjoy a 90% discount on rental housing");}if(total >= 200 && balance < 200) {emit SCORE_EQUITY_NOTICE("RentConcessions_1", total, "Citizens enjoy a 80% discount on rental housing");}if(total >= 300 && balance < 300) {emit SCORE_EQUITY_NOTICE("PurchaseDiscount", total, "Citizens enjoy a 90% discount on purchase housing");}}//积分奖励function awardScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin {uint balance = leasingScore[player];leasingScore[player] = balance + score;emit SCORE_OPERATOR(ScoreAction.ActionAwardScore, describe);checkScoreEquity(balance, score);}//积分权级兑换,为消费者主动意愿,若不够花,则不扣除积分,且发送失败事件。function exchangeScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin returns (bool) {emit SCORE_OPERATOR(ScoreAction.ActionExchangeScore, describe);if(leasingScore[player] >= score) {leasingScore[player] -= score;return true;}emit SCORE_ERROR(ScoreAction.ActionExchangeScore, "Score not enough to exchange");return false;}//积分扣除,为消费者被动意愿,进行强制积分扣除,若积分不够,则清零,且发送失败时间function deductScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin {emit SCORE_OPERATOR(ScoreAction.ActionDeductScore, describe);uint balance = leasingScore[player];if(balance >= score) {leasingScore[player] -= score;} else {if(balance != 0) {leasingScore[player] = 0;}emit SCORE_ERROR(ScoreAction.ActionDeductScore, "Score not enough to deduct");}}//积分查询function queryScore(bytes32 player, string describe) view public onlyObserverOrAdmin returns (uint) {emit SCORE_OPERATOR(ScoreAction.ActionQueryScore, describe);return leasingScore[player];}//拼接字符串function stringAdd(string a, string b) returns(string){bytes memory _a = bytes(a);bytes memory _b = bytes(b);bytes memory res = new bytes(_a.length + _b.length);for(uint i = 0;i < _a.length;i++)res[i] = _a[i];for(uint j = 0;j < _b.length;j++)res[_a.length+j] = _b[j];  return string(res);}//积分转移,将积分从一个一个合约转移到另一个合约function transferScore(bytes32 player, uint score, LeasingScoreManager toContract, string describe) public onlyCalculatorOrAdmin {//判断积分是否够用并扣除describe =  stringAdd("Transfer score: ", describe);require(exchangeScore(player, score, describe));toContract.awardScore(player, score, describe);}
}

5,合约编译/部署/测试

5.1 账号信息

参考《蚂蚁区块链第8课 如何创建新的账户,获取私钥和identity标识?》 文章获得辉哥需要的账号的解密私钥和identity。

角色定义如下,其中的私钥已被辉哥篡改过了,防止被误用。

管理者 - test002  identity: 0xd6b1f9e8a0da740fa04245a41b78eba7be9214cf96e7f6594899706e64050d20
管理者 - ella  identity: 0xfb2cb45b6b443241e38145b6445a6e0ebee0410d19e71d9fd0adf5fc382d49e5
私钥:0x2ac3bc8673454b6de00fc8815b5f2676084d2e6c74d3a0fdf34b5e63ead6e019
操作员 - duncanwang identity: 0x4983bcbaf60b9c90dc9d9a0b38a8931aad9a444acaa2adcbc61c5e3e218c49e1
私钥:0x46f9c8c5037a92d7fd811b350a42dc63591fb772ea2c104b89ccb48e1784c76c
观察员 - ouyang identity: 0xe26ef3b9bb0244244935f0176e2e4b5b623be3634276ee897810a380ae8c1314
私钥:0x2bc3bc8673454a6de00fc8815b5f2676084d2e6c74d3a0fdf34b5e63ead6e019
市民 - dingheng identity: 0xccaa69e5e5583f2e34885450669c518a6febcdec9040a4d0a2d41cd9f8f8ca40
私钥:0x2bc3bc8673454a6de00fc8815b5f2676084d2e6c74d3a0fdf34b5e63ead6e019

因为目前(2019.04.06)蚂蚁BAAS系统存在一个BUG,导致ella等账号不能被用于智能合约部署,辉哥目前可使用的账号只有test002和duncanwang账号,辉哥在测试用例设计上做了一个小调整。

5.2 合约编译/部署

参考《蚂蚁区块链第5课 如何配置Cloud IDE证书并进行Solidity智能合约调试?》 完成智能合约的编译和部署,采用默认的test002账号。
【告警】在蚂蚁BAAS系统中,相同的合约名称只能使用一次。
如果该合约已部署,可按“已部署合约”根据已部署的智能合约名称来调用合约。

5.3 模拟测试(基本函数测试成功)

账号:test002
账号identity: 0xd6b1f9e8a0da740fa04245a41b78eba7be9214cf96e7f6594899706e64050d20
创建 LeasingScoreManager,其合约identity: 0x64d555c5c89575982d319fe7ddd2eb1741a5d3c4386fac168ed5440958ed5044

(1)管理员test002做账户设置操作

<1> 管理员test002部署合约(上海合约)后增加自己为管理员
function addAdmin(identity admin) public onlyAdmin
addAdmin(0xd6b1f9e8a0da740fa04245a41b78eba7be9214cf96e7f6594899706e64050d20)

结果:

tx hash: 0x842c8d85080878942a9fa0c1f356f3284305cb0317ce5aca2ed43291d8cd1a80
log:
VALID(bool,uint8): true,0
USER_EXIST(bool,uint8): false,0

<2> 管理员test002增加操作员 duncanwang
function addCalculator(identity calculator) public onlyAdmin
addCalculator(0x4983bcbaf60b9c90dc9d9a0b38a8931aad9a444acaa2adcbc61c5e3e218c49e1)

结果:

tx hash: 0xe68c2fa466885221dba293e95f803e511ba46e9e111ef1e44e685a2df2e2269b
log:
VALID(bool,uint8): true,0
USER_EXIST(bool,uint8): false,1

<3> 管理员test002增加观察员 duncanwang
function addObserver(identity observer) public onlyAdmin
addObserver(0x4983bcbaf60b9c90dc9d9a0b38a8931aad9a444acaa2adcbc61c5e3e218c49e1)

结果:未记录。

账号切换到duncanwang账号:

(2) 操作员duncanwang给市民dingheng增加100积分,理由:在马路上勇敢扶起一位摔倒的80岁老奶奶。

function awardScore(bytes32 player, uint score, string describe)
awardScore(“dingheng”,100, “理由:dingheng在马路上勇敢扶起一位摔倒的80岁老奶奶。”)

输出信息:

tx hash:0x591daac5d038de4d9a3f437dd8aeea466772b28562792225be3d5af2903ef122
log
VALID(bool,uint8): true,1
SCORE_OPERATOR(uint8,string):0,理由:dingheng在马路上勇敢扶起一位摔倒的80岁老奶奶。
SCORE_EQUITY_NOTICE(string,uint256,string): RentConcessions,100,Citizens enjoy a 90% discount on rental housing

(3) 操作员duncanwang收到市民dingheng申请,理由:需要租房1年,花费80积分。

function exchangeScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin returns (bool)
exchangeScore(“dingheng”, 80, “理由:需要租房1年,花费80积分。”)

输出信息:

tx hash:0xd7ce3c91a3b6e3c05117616c4d6e4ad1bc05e5ba8dd016189223a81dbdc0b68a
log
VALID(bool,uint8): true,1
SCORE_OPERATOR(uint8,string): 1,理由:需要租房1年,花费80积分。

(4)操作员duncanwang发现市民dingheng房租到期后,未及时退租。扣10个积分,电话催丁恒来退租。

function deductScore(bytes32 player, uint score, string describe) public onlyCalculatorOrAdmin
deductScore(“dingheng”, 10, “理由:dingheng房租到期后,未及时退租。扣10个积分。”)

输出信息:

tx hash:0x970e9ad61dfd9e264cd1bd12ca0d8426d1bbe9a178b0ec88d82d6a30ec2c5c99
log:
VALID(bool,uint8): true,1
SCORE_OPERATOR(uint8,string): 2,理由:dingheng房租到期后,未及时退租。扣10个积分。

6,蚂蚁BAAS Solidity语法差异精要

6.1 合约数据类型

蚂蚁区块链合约平台基本支持 Solidity 所有的数据类型,但是对于一些用户编写的合约的输入参数类型并没有完全的支持,比如参数输入中二维数组的输入。同时,蚂蚁区块链合约平台提供了 identity 类型来标注每一个用户的身份,不支持原生 Solidity 中的 address 类型,identity 的长度为 32 字节。
蚂蚁BAAS的identity就是账号名称的SHA256算法产生的32字节内容。
例如,在站长工具网站http://tool.chinaz.com/tools/hash.aspx中输入账号名字duncanwang,获得其identity为0x4983bcbaf60b9c90dc9d9a0b38a8931aad9a444acaa2adcbc61c5e3e218c49e1

更多信息,可查看 Solidity 官网关于类型的文档(英文)。

6.2 合约关键字

蚂蚁区块链合约平台支持的 Solidity 语法基本与官方文档一致,具体语法介绍可参看 Solidity 官方文档(英文)。

本文主要介绍 Solidity 合约函数构造的常用 权限关键字 和 修饰关键字。

6.3 平台接口函数

蚂蚁区块链合约平台为合约开发者提供一些新的特性接口支持,包括对区块数据获取、交易数据获取、加密方法的支持。

区块数据接口函数

  • block.blockhash(uint blockNumber) returns (bytes32):传入 blockNumber,返回块的哈希值。
  • block.gaslimit (uint):系统中的 gas 最大值。
  • block.number (uint):当前块高度。
  • block.timestamp (uint):当前区块创建的时间戳。
  • now (uint):block.timestamp 的别名。

交易数据接口函数

  • msg.data (bytes):用户的输入数据。
  • msg.gas (uint):用户交易中的 gas 值。
  • msg.sender (identity):用户交易中的发送方。
  • msg.sig (bytes4):用户交易输入数据的前四字节。
  • msg.value (uint):用户交易中的 gas 值。

蚂蚁区块链合约平台还支持查询交易哈希,示例如下:

tx.txhash (identity)

可通过此方法获取当前交易的哈希值。

6.4 不支持TOKEN转账相关函数

【告警】不支持TOKEN,不支持地址相关的转账函数了。

.balance (uint256): 以 Wei 为单位的 地址类型 的余额。

.transfer(uint256 amount): 向 地址类型 发送数量为 amount 的 Wei,失败时抛出异常,发送 2300 gas 的矿工费,不可调节。

.send(uint256 amount) returns (bool): 向 地址类型 发送数量为 amount 的 Wei,失败时返回 false,发送 2300 gas 的矿工费用,不可调节。

.call(...) returns (bool): 发出低级函数 CALL,失败时返回 false,发送所有可用 gas,可调节。

.callcode(...) returns (bool): 发出低级函数 CALLCODE,失败时返回 false,发送所有可用 gas,可调节。

.delegatecall(...) returns (bool): 发出低级函数 DELEGATECALL,失败时返回 false,发送所有可用 gas,可调节。

6.5 支持合约间调用

蚂蚁区块链合约平台主要支持两种合约调用方式,分别为 calldelegatecall

call

call 类型的合约调用主要是通过调用其他合约代码获取某个方法的调用结果。同时,在执行该合约时,被调用合约的变量会被修改(即修改的是被调用合约的内存),调用执行成功时返回 true,失败则返回 false。如果被调用合约不存在,则返回执行调用合约的 fallback 函数。

函数原型

id.call(bytes4(keccak256(data), args)) returns (bool result)

请求参数

参数 必选 类型 说明
id identity 被调用合约 ID
data string 被调用合约方法签名
args var 被调用合约方法传入参数

返回值

参数 必选 类型 说明
result bool 方法返回值,成功为 true,否则为 false。

delegatecall

delegatecall 类型的合约调用也是调用其他合约的方法,与 call 不同的是,delegatecall 修改的是合约调用方的内存。此方法的主要目的在于让合约能够在不传输自身状态(如 balancestorage)的情况下使用其他合约的代码。delegatecall 不支持传输 value。

函数原型

id.delegatecall(bytes4(keccak256(data), args)) returns (bool result)

请求参数

参数 必选 类型 说明
id identity 被调用合约 ID
data string 被调用合约方法签名
args var 被调用合约方法传入参数

返回值

参数 必选 类型 说明
result bool 方法返回值,成功为 true,否则为 false。

7,参考

(1) Solidity 合约开发 https://tech.antfin.com/docs/2/101909
(2)Solidity官方中文文档 https://solidity-cn.readthedocs.io/zh/develop/

蚂蚁区块链第11课 以租房积分管理系统为例讲透蚂蚁Solidity语言差异精要相关推荐

  1. 蚂蚁区块链第9课 SSL/TLS工作原理及在蚂蚁BAAS中的应用

    1,摘要 辉哥在学习蚂蚁BAAS系统时,发现了一堆证书或者公私钥名称,包括trustCa,ca.crt,client.crt,client.key,pub.txt,MyPKCS12.p12等等文件,不 ...

  2. 蚂蚁区块链第8课 如何创建新的账户,获取私钥和identity标识?

    1,摘要 在创建TEE硬件隐私合约链(标准合约链)的时候,配套已经创建了一个证书对和2个公私钥对,对应了这1个账号(duncanwang).不理解辉哥说什么的参考<蚂蚁区块链第4课 如何创建TE ...

  3. 蚂蚁区块链第10课 可信计算及TEE硬件隐私合约链智能合约开发实践

    1,摘要 本文介绍可信计算分类INTEL SGX技术和ARM TRUSTZONE技术技术方案概要,以及应用INTEL SGX技术的蚂蚁区块链TEE硬件隐私链的智能合约开发实践. 2,可信计算和2种技术 ...

  4. 蚂蚁区块链第13课 如何搭建一个DAPP应用(以姓名年龄为例)

    1,摘要 本文介绍如何使用Cloud IDE完成name-age智能合约的编译,获取其ABI和二进制码信息. 接着,本文介绍基于EXPRESS框架搭建的前端页面,完成已部署智能合约的调用,完成了相关功 ...

  5. 蚂蚁区块链第22课 蚂蚁区块链溯源服务功能从开通到实现

    1. 摘要 本文介绍蚂蚁区块链溯源服务的特点,开通和配置流程,接入淘宝扫码的流程. 辉哥认为,区块链溯源要锚定实物和数字世界的关系,但各个环节又只能在各自平行线操作,很难做到绝对溯源.况且,蚂蚁溯源区 ...

  6. 蚂蚁区块链第7课 零知识证明隐私保护原理和蚂蚁BAAS接口调用实现

    1,摘要 本文试图普及隐私保护和零知识证明的相关技术知识,尝试使用更简单的描述来理解复杂的数学算法和技术原理.同时,也提供了蚂蚁区块链已经实现的隐私保护的接口函数说明. 本文涉及的专业知识有零知识证明 ...

  7. 蚂蚁区块链第12课 如何使用命令行编译工具solcjs编译Solidity智能合约?

    1,摘要 蚂蚁区块链合约平台支持 Solidity 智能合约,针对合约源代码的编译,可以直接通过蚂蚁区块链 Cloud IDE 合约开发环境进行合约编译.部署.测试和调试. 本文介绍由蚂蚁区块链平台提 ...

  8. 蚂蚁区块链第15课 JS SDK概述及API接口速查

    1,摘要 本文讲解蚂蚁BAAS的JavaScript SDK概述,说明JS SDK对应的API接口速查.其他语言包SDK参考官网其他章节说明即可. 2,JS SDK 说明 JavaScript SDK ...

  9. 蚂蚁区块链第2课 如何申请获得100万创新大赛参赛资格?

    1,摘要 本文针对"链创.未来"蚂蚁区块链创新大赛的内容和报名申请流程做介绍,便于感兴趣,有实力的团队申请报名参加. 2,介绍 2019年1月4日在上海举办的蚂蚁金服ATEC城市峰 ...

最新文章

  1. 自动驾驶是汽车行业的未来 但这并不代表驾驶者能完全解放双手
  2. 韶关无线电厂台式计算机,中国芯片30年的历史 它背后有哪些不为人知的故事?...
  3. 修改2440里面的FriendlyARM
  4. matlab相机标定 外参数,相机外参数的标定.doc
  5. Delphi/C#之父:Anders Hejlsberg
  6. js 操作Listbox js 获取Listbox选择的值的代码
  7. Cacti设置流量阀值实现邮件报警
  8. JS + HTml 时钟代码实现
  9. 基于MVC模式的企业公文处理信息系统的设计与实现
  10. 最常用的scrum工具、敏捷开发工具、看板工具
  11. Python爬虫入门教程:豆瓣Top电影爬取
  12. 萌宠历险记html5游戏在线玩,7724萌宠历险记
  13. python下划线怎么输入_Python中下划线的使用方法有哪些
  14. 2021-06-10-APP PUSH推送机制
  15. 解决D3DCompiler_47.dll文件丢失找不到问题
  16. 云计算平台项目团队组织架构与缘起
  17. 干货|Android免Root最全Hook插件(免Root Hook任意App)
  18. 书摘—你不可不知的心理策略
  19. js-视频播放插件Video.js简单使用
  20. stm32的简易小项目之震动感应灯

热门文章

  1. 实名推荐的神器,你安排上了几款?
  2. 《程序员的自我修养》学习心得——第七章 动态链接※※※
  3. LeetCode Weekly Contest 199
  4. 回收站删除了怎么恢复
  5. Spring切面通知执行的顺序(Advice Order)
  6. 学习GNU Emacs命令速查表(三)
  7. Moviepy音视频开发:视频转gif动画或jpg图片exe图形化工具开发案例
  8. CSS颜色:RGB颜色/HEX颜色/HSL颜色(网页颜色完全总结)
  9. 基于stm32f103的智能风扇系统
  10. 开发者该如何抓住微信小游戏的风口?听Cocos创始人王哲详解(上篇)