IC-dfx常用指令

安装dfx sdk

安装最新版:

sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"

安装指定版本:

DFX_VERSION=0.9.3 sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"

dfx常用指令

identity

1
2
3
4
5
6
7
dfx identity list #identity 列表
dfx identity get-principal #获取当前的PID
dfx ledger account-id #接收转账的account-id
dfx --identity default ledger --network ic balance #当前账户还有多少ICP
fx --identity xxxx ledger --network ic transfer --memo 0 --amount 0.5 $(dfx --identity default ledger account-id) #从一个账户转账到另一个账户
dfx identity new developer #创建新的identity
dfx identity use developer #切换identity

wallet

1
2
3
4
5
dfx identity --network ic get-wallet # 获取燃料钱包
dfx ledger --network ic create-canister --amount 0.0 $(dfx identity get-principal) #创建一个canister,创建之后没有钱包
dfx identity --network ic deploy-wallet xxxxx-xxxxx-xxxxx-xxxxx-xxx # 把钱包部署到canister里面shu
dfx wallet --network ic balance #当前钱包的cycles余额
dfx wallet --network ic send $(dfx --identity developer identity get-wallet) 80000590000 #给developer充值

deploy

1
2
dfx deploy #部署到本地
dfx deploy --network ic --with--cycles 80000590000 xxx #部署到主网

canister

1
2
3
4
dfx canister --network ic status --all #获取canister状态
dfx canister --network ic stop --all # 停止
dfx canister --network ic uninstall-code --all #删除代码
dfx canistet --network ic delete -all #删除并回收cycles

IC-Canister内存模型与StableMemory管理

1.Canister的内存模型

  • WebAssembly内存模型简介
  • 运行时内存与Statble内存
  • 运行时内存与GC选择
  • 使用Prim库获取内存状态

1.1 WebAssembly内存模型简介

canister与wasm

Motoko和Rust的代码都会编译为wasm,canister会以wasm来执行。而wasm的内存管理就会格外重要

wasm内存模型

  • 页式内存
    • webabssembly的内存管理形式为页式管理,对内存进行分页,每页64KB
  • 线性增长
    • webassembly的内存一经分配不能收回,本质上,wasm的内存时一个数组,数组每个元素表示内存页,每页64KB(65536byte),共65536页,共4G
    • 4G的原因:目前IC使用的wasm标准为wasm32,因为时32位指针域。因此最大只能存储4G(2^32个byte)

canister内存模型

​ canister目前最大可使用内存为12G

运行时内存与GC(Garbage Collection)

IC的垃圾回收很多都可以参考java的jvm虚拟机的GC算法和垃圾回收器

coping gc(也称为Minor GC):

基于拷贝算法的垃圾回收

将内存分割为from和to两块区域,当触发gc时会拷贝(from->t0)和清除(from),同时下一次GC时,有数据的就变成了from,清除完毕的区域变成了to

compacting gc:

标记整理算法的垃圾回收

便利heap,找出所有活体对象(正在活动/被活动的对象引用),标记活体对象。标记完后剩下的就是不被引用或者不再使用的对象,及内存垃圾。然清理内存垃圾并整理活体对象

Prim库

RTS:Run Time System运行时系统

ExperimentalStableMemory库

升级时要留下足够空间用来存储升级时canister内的运行时状态数据

2.StableMemory管理

  • ExperimentalStableMemory库的使用讲解
  • Bucket库的使用

Java表格处理-easyexcel框架使用和进阶

对表格对象追加操作

使用easyexcel,使用注解模板一般操作如下

注解模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.poi.ss.usermodel.FillPatternType;

/**
* Author: caijc
* Date: 2022/1/25 10:48
*/
@Data
@EqualsAndHashCode
// 头单元格样式
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 44)
@HeadFontStyle(fontHeightInPoints = 12)
public class BusinessTravelSalesDailyData {
@ExcelProperty({"统计时间", "企业名称"})
private String corpName;

@ExcelProperty({"统计时间","客户经理"})
private String manager;

@ExcelProperty({"年交易总额","交易总额"})
private Double totalAmount;

@ExcelProperty({"年交易总额","去年同期交易总额"})
private Double lastYearTotalAmount;

@ExcelProperty({"国内机票","单量总计"})
private Long dftOrderQuantity;

@ExcelProperty({"国内机票","去年同期单量"})
private Long dftLastYearOrderQuantity;

@ExcelProperty({"国内机票","毛利"})
private Double dftGrossMargin;

@ExcelProperty({"国内机票","去年同期毛利"})
private Double dftLastYearGrossMargin;
}

生成表格

1
2
3
4
5
6
7
8
9
10
List<BusinessTravelSalesDailyData> dataList = new ArrayList<>();
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
EasyExcel.write(out, BusinessTravelSalesDailyData.class)
.excelType(ExcelTypeEnum.XLSX)
.sheet(0, sheetName)
.doWrite(dataList);
} catch (Exception e) {
log.error("数据生成完毕 写入失败", e);
throw e;
}

因为在调用完doWrite()方法后,excel文件就已经生成,这时想再对表格进行操作,比如在表尾添加一个合计行,或者对已生成的数据进行额外操作,就必须再读取生成好的excel文件,进行后续操作。这样的追加操作就变得比较复杂

其实有一个比较好的方式可以很好实现追加操作

在分析源码的过程中,跟踪doWrite()方法,发现,在写入excel文件之前,会调用所有实现WorkbookWriteHandler接口的实现,所以,实现一个WorkbookWriteHandler实现类来完成追加操作

下面代码,是在表尾生成一个包含合计的单元格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 数据
List<BusinessTravelSalesDailyData> dataList = new ArrayList<>();
Integer sum = getSum(list, yearAmount);
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
EasyExcel.write(out, BusinessTravelSalesDailyData.class)
.excelType(ExcelTypeEnum.XLSX)
.registerWriteHandler(new CustomSumDataHandler(sum)) // 设置合计填充数据
.sheet(0, sheetName)
.doWrite(dataList);
} catch (Exception e) {
log.error("数据生成完毕 写入失败", e);
throw e;
}
log.info(file.getAbsolutePath());
return file;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.huoli.ctar.tmc.csa.csm.job.statsRpt.excel.handle;

import com.alibaba.excel.write.handler.WorkbookWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.huoli.ctar.tmc.vo.BusinessTravelSalesDailyEmailVo;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;

/**
* Author: caijc
* Date: 2022/2/9 14:29
* Description: 自定义合计列
*/
public class CustomSumDataHandler implements WorkbookWriteHandler {
Integer sum;

public CustomSumDataHandler(Integer sum) {
this.sum = sum;
}

@Override
public void beforeWorkbookCreate() {

}

@Override
public void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) {

}

@Override
public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {
Sheet sheet = writeWorkbookHolder.getWorkbook().getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
int sumRowIndex = lastRowNum+1;
Row row = sheet.createRow(sumRowIndex);
CellStyle cellStyle = writeWorkbookHolder.getWorkbook().createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
Cell cellA1 = row.createCell(0);
cellA1.setCellValue("总计:" + sum);
cellA1.setCellStyle(cellStyle);
CellRangeAddress cra = new CellRangeAddress(row.getRowNum(), row.getRowNum(), 0, 1);
// 合并单元格
sheet.addMergedRegion(cra);
}
}

Java表格处理-两个excel之间的数据拷贝

POI介绍

ApachePOI是用Java编写的免费开源的跨平台的JavaAPI,ApachePOI提供API给Java程序对MicrosoftOffice格式档案读和写的功能,其中使用最多的就是使用POI操作Excel文件。

maven坐标依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>

<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>

POI结构:

1
2
3
4
5
6
7
HSSF-提供读写MicrosoftExcelXLS格式档案的功能
XSSF-提供读写MicrosoftExcelOOXMLXLSX格式档案的功能
HWPF-提供读写MicrosoftWordDOC格式档案的功能
HSLF-提供读写MicrosoftPowerPoint格式档案的功能
HDGF-提供读MicrosoftVisio格式档案的功能
HPBF-提供读MicrosoftPublisher格式档案的功能
HSMF-提供读MicrosoftOutlook格式档案的功能

关于POI中基础API的使用,就不多介绍了,本文主要介绍对excel的处理-两个excel之间的数据拷贝

话不多说,现在开始!!

单元格样式个数限制

首先介绍一下POI中excel的单元格样式,工作簿Workbook中单元格样式个数是有限制的,所以在 程序中应该重复使用相同CellStyle,而不是为每个单元 格创建一个CellStyle

1
2
HSSFCellStyle - 4000个
XSSFCellStyle - 64000个
最大行数 最大列数 单元格样式个数 文本单元格最大长度 一个单元格上条件样式最大个数
HSSF 65536 256(2^8) 4000 32767 3
XSSF 1048576 16384(2^14) 64000 Integer.MAX_VALUE

其实在WorkbookcreateCellStyle()方法中,可以看到各个表格格式实现类对单元格样式的限制

HSSFCellStyle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
   /**
* The maximum number of cell styles in a .xls workbook.
* The 'official' limit is 4,000, but POI allows a slightly larger number.
* This extra delta takes into account built-in styles that are automatically
*
* See http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
*/
private static final int MAX_STYLES = 4030;

/**
* Create a new Cell style and add it to the workbook's style table.
* You can define up to 4000 unique styles in a .xls workbook.
*
* @return the new Cell Style object
* @throws IllegalStateException if the number of cell styles exceeded the limit for this type of Workbook.
*/
@Override
public HSSFCellStyle createCellStyle()
{
if(workbook.getNumExFormats() == MAX_STYLES) {
throw new IllegalStateException("The maximum number of cell styles was exceeded. " +
"You can define up to 4000 styles in a .xls workbook");
}
ExtendedFormatRecord xfr = workbook.createCellXF();
short index = (short) (getNumCellStyles() - 1);
return new HSSFCellStyle(index, xfr, this);
}

HSSFCellStyle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    /**
* Excel2007
*
* <ul>
* <li>The total number of available rows is 1M (2^20)</li>
* <li>The total number of available columns is 16K (2^14)</li>
* <li>The maximum number of arguments to a function is 255</li>
* <li>Number of conditional format conditions on a cell is unlimited
* (actually limited by available memory in Excel)</li>
* <li>Number of cell styles is 64000</li>
* <li>Length of text cell contents is 32767</li>
* <ul>
*/
EXCEL2007(0x100000, 0x4000, 255, Integer.MAX_VALUE, 64000, 32767);

// Is this right? Number formats (XSSFDataFormat) and cell styles (XSSFCellStyle) are different. What's up with the plus 1?
private static final int MAXIMUM_STYLE_ID = SpreadsheetVersion.EXCEL2007.getMaxCellStyles();

public XSSFCellStyle createCellStyle() {
if (this.getNumCellStyles() > MAXIMUM_STYLE_ID) {
throw new IllegalStateException("The maximum number of Cell Styles was exceeded. You can define up to " + MAXIMUM_STYLE_ID + " style in a .xlsx Workbook");
} else {
int xfSize = this.styleXfs.size();
CTXf xf = org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf.Factory.newInstance();
xf.setNumFmtId(0L);
xf.setFontId(0L);
xf.setFillId(0L);
xf.setBorderId(0L);
xf.setXfId(0L);
int indexXf = this.putCellXf(xf);
return new XSSFCellStyle(indexXf - 1, xfSize - 1, this, this.theme);
}
}

所以,如果在拷贝两个Workbook的数据时,数据单元格数量过多,频繁使用createCellStyle()方法创建单元格样式,会导致单元格样式超出限制。

所以,为了解决单元格样式限制问题,可以将样式缓存起来,具体方法可见如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package com.huoli.ctar.cms.poi;

import lombok.Data;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.text.SimpleDateFormat;
import java.util.*;

/**
* Author: caijc
* Date: 2022/1/5 10:28
* Description:
*/
@Data
public class POICopyHandler {

// 缓存单元格的样式索引,key是拷贝原表格的样式索引id,value是拷贝目标表格的样式id
Map<Short,Short> styleMap = new HashMap<>();
private Workbook fromWorkbook;
private Workbook toWorkbook;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

// 构造方法,将原表格的所有样式一并拷贝到目标表格,并将表格样式索引缓存
public POICopyHandler(Workbook fromWorkbook, Workbook toWorkbook) {
this.fromWorkbook = fromWorkbook;
this.toWorkbook = toWorkbook;
Sheet fromSheet = fromWorkbook.getSheetAt(0);
Sheet toSheet = fromWorkbook.getSheetAt(0);
if (toSheet == null){
toWorkbook.createSheet();
}
for (int i = fromSheet.getFirstRowNum(); i <= fromSheet.getLastRowNum(); i++) {
Row fromRow = fromSheet.getRow(i);
for (int j = fromRow.getFirstCellNum(); j < fromRow.getPhysicalNumberOfCells(); j++) {
Cell fromCell = fromRow.getCell(j);
CellStyle fromStyle = fromCell.getCellStyle();
if(!styleMap.containsKey(fromStyle.getIndex())){
CellStyle toStyle = toWorkbook.createCellStyle();
toStyle.cloneStyleFrom(fromStyle);
styleMap.put(fromStyle.getIndex(), toStyle.getIndex());
}
}
}

}

/**
* 复制行
* 此方法主要用于复制2个不同Workbook间的行
*/
public void copySingleRows(int fromSheetIndex, int toSheetIndex, int fromRowIndex, int toRowIndex) {
Sheet fromSheet = fromWorkbook.getSheetAt(fromSheetIndex);
Sheet toSheet = toWorkbook.getSheetAt(toSheetIndex);

if ((fromRowIndex == -1) || (fromRowIndex == -1)) {
return;
}

// 设置列宽
Row fromRow = fromSheet.getRow(fromRowIndex);
if (fromRow == null) {
return;
}
for (int i = fromRow.getLastCellNum(); i >= fromRow.getFirstCellNum(); i--) {
toSheet.setColumnWidth(i, fromSheet.getColumnWidth(i));
toSheet.setColumnHidden(i, false);
}
// 拷贝行并填充数据
Row toRow = toSheet.createRow(toRowIndex);
toRow.setHeight(fromRow.getHeight());
for (int j = fromRow.getFirstCellNum(); j <= fromRow.getPhysicalNumberOfCells(); j++) {
Cell fromCell = fromRow.getCell(j);
Cell toCell = toRow.createCell(j);
if (fromCell != null) {
CellStyle fromStyle = fromCell.getCellStyle();
CellStyle toStyle = toWorkbook.getCellStyleAt(styleMap.get(fromStyle.getIndex()));
if (toStyle != null){
toCell.setCellStyle(toStyle);
}
switch (fromCell.getCellTypeEnum()) {
case BOOLEAN:
toCell.setCellValue(fromCell.getBooleanCellValue());
break;
case ERROR:
toCell.setCellErrorValue(fromCell.getErrorCellValue());
break;
case FORMULA:
toCell.setCellFormula(parseFormula(fromCell.getCellFormula()));
break;
case NUMERIC:
if(DateUtil.isCellDateFormatted(fromCell)){
toCell.setCellValue(sdf.format(fromCell.getDateCellValue()));
} else{
toCell.setCellValue(fromCell.getNumericCellValue());
}
break;
case STRING:
toCell.setCellValue(fromCell.getRichStringCellValue());
break;
}
}

}
}

private String parseFormula(String pPOIFormula) {
final String cstReplaceString = "ATTR(semiVolatile)"; //$NON-NLS-1$
StringBuffer result;
int index;

result = new StringBuffer();
index = pPOIFormula.indexOf(cstReplaceString);
if (index >= 0) {
result.append(pPOIFormula, 0, index);
result.append(pPOIFormula.substring(index + cstReplaceString.length()));
} else {
result.append(pPOIFormula);
}

return result.toString();
}
}

调用示例:

1
2
POICopyHandler poiCopyHandler = new POICopyHandler(fromWorkbook, toWorkbook);
poiCopyHandler.copySingleRows(0,0,0,0);// 四个参数分别是:源Workbook的sheet索引,目标Workbook的sheet索引,源Workbook的行索引,目标Workbook的行索引

以太网络 - 油价和油量

以太网络是区块链2.0,建立了区块链生态。很多token,也只是以太网络的应用(智能合约)。以太网络进行交易的时候,除了设置对方地址,传输token的类型(以太就是ETH)和数量外,还需要设置油费。油费就是以太网络交易的手续费,这些油费被把交易打包的以太矿工获得。

油费的计算公式:油费 = 油价 * 油量。

1)油价

介绍油价之前,先介绍一下以太网络中的计量单位:

wei - 以太最小的计量单位

Kwei - 1e3 wei

Mwei - 1e6 wei

Gwei - 1e9 wei

microether - 1e12 wei

milliether - 1e15 wei

ether - 1e18 wei

矿工在打包交易的时候,会优先挑选油价高的交易。也就是说,设置高油价的交易可能会尽快被矿工接受,被打包进区块。很简单的道理,以太网络拥堵的时候,越高的油价,越容易,越快被打包。

2)油量

油量取决于以太应用的复杂程序。众所周知,以太程序是运行在以太虚拟机(EVM)上的。以太定义了跑在EVM上的每个指令耗费的油量。也就是说,程序一旦确定,相应的虚拟机上的指令也就确定,运行这段程序的油量也就确定。**如果设置的油量超过程序运行需要的油量,多余的油量会退回。如果设置的油量不够程序运行结束,以太会报告 - Out of gas(油量不够)的错误。注意的是:油量不够的情况下,首先交易没有完成,其次耗费的油费也浪费了。
**

3)油费

举个例子,如果油价是20 gwei,油量是10000的话,油费是:

201e910000/1e18 = 0.0002 ether

理解了油价和油量的概念,很容易发现,一次交易,要想又快又便宜的完成,寻找合适的油价(油量是和程序相关固定的,油量可以设置的大一些)

4)如何寻找当前合适的油价?

因为以太是个开发的生态,任何人可以查看以太当前的交易记录。所以,可以通过查看以太当前交易使用的油价来估算自己交易需要的油价。推荐etherscan.io查看交易情况。

54f317bf-1.jpg

点击最近的交易记录,可以查看详细的交易信息:

54f317bf-2.jpg

以上的示例交易中,油价是8 gwei。可以知道,目前交易使用油价8 gwei就可以。你可以多看几个交易确定油价。想尽快的交易,就在目前平均水平上加点油价。不要求速度,甚至可以在目前油价基础上减去一些。看历史的交易记录,你会发现还有好些交易是用1gwei的油价的。

5)imToken应用油价和油量的设置

imToken应用在转帐的默认界面,可以设置油量(拖动油量的进度框)。

54f317bf-3.jpg

在默认情况下,需要注意的是:

1)油价是程序固定的(这个固定的油价有可能太高(浪费),有可能太低(无法及时被矿工处理)。

2)油量即使拉到最大也有可能还是不够。特别是对于一些复杂的智能合约,油量需要300000的。

对于理解了油价和油量的小伙伴,可以直接使用高级选项,自己轻松设置。

54f317bf-4.jpg

所谓的高级设置,也就是两个选项,一个设置油价(以gwei为单位),一个设置油量。非常简单。上图中就是设置油价是8 gwei,油量是300000。

转载:https://mp.weixin.qq.com/s/khRgVcjioPNeD3DO1xV2TA

Compound vs Aave

Compound vs Aave

比较这两种Deif产品前先简单介绍一下两个产品

Compound介绍

Compound是运行在以太坊上的数字资产理财和贷款服务平台,他通过一个开源的、去中心化的Compound协议,自动建立市场化的利率定价,为用户提供类似银行的数字货币存贷业务,用户存入对应的数字货币资产会立即产生收益,也可也通过抵押的方式借出数字资产,当然要支付利息,利率根据市场供需水平浮动定价,并且按照以太坊区块产生时间,也就是大概每15s,结算一次利息,用户的存款可以随时连本带息的取出。

创始团队

image-20210320114216820

Compound总部位于旧金山,创始人是CEO Robert Leshner 和CTO Geoffrey Hayes,Robert曾是金融业从业者,是旧金山收入债券委员会的联合主席,他和CTO Geoffrey曾经一起合作创立过两家还不错的公司,一个叫Postmates,是一个提供外卖跑腿服务的APP,另一个项目叫Safe Shepard,是一个致力于保护用户隐私,防各类骚扰的信息安全平台。

融资情况

image-20210320115334296

Compound 2018年获得8.2M美元的种子轮,2019年获得25M美元的A轮融资,投资人都是全球最顶级的VC。

包括A16Z,Polychanin,Coinbase,Bain Captial

Bain Captial 就是曾经代表美国共和党与奥巴吗竞选美国总统的Romney创立的那个贝恩资本

市场数据

image-20210320120435198

截至目前2021-03-20,compound的Total supply市场存款额是约$122亿,Total Borrow借款额约$54亿。

image-20210320121107638

当前的Top3市场是Eth、USDC、Dai

利率模型

compound对不同市场采用不通的利率定价模型

以TOP1 Eth的利率模型为例

image-20210320121300795

黑线:市场资金利用率
绿线:存币市场的利率
紫线:借币市场利率

当市场的资金利用率越高,也就是借款需求越多,市场的利率越高

cToken

image-20210320122241454

用户在Compound存入任何币后,都会得到对应币种的cToken,比如图上的cBAT,cCOMP,cDai,cEth等 ,它是我们在Compound存款和提供流动性的凭证,账户有了cToken,就会按时收到利息,cToken也可也进行转账和交易,注意的是,cToken标的Token的兑换比例并不是1:1,总体上,借款量越高,cToken和标的资产的兑换比率越高

抵押清算

在我们还款前,如果市场价格波动,导致抵押率达到最大抵押率,也就是清算阈值,那么第三方清算人就会介入 ,替借款人偿还全部或部分借款,得到对应价值的抵押物,并额外获得8%的抵押物激励,也就是讲抵押物的价格打了8%的折扣计价给清算人

治理代币

2020年2月,Compound发布了治理代币COMP,拥有COMP的用户可以参与社区投票,拥有1%以上的COMP的地址可以发起提案并开启投票

image-20210320123639722

这是截至当前2021-03-20的投票权重,A16Z和Polychain权重最高

image-20210320125248788

comp的发行总量是1000万枚

image-20210320125653735

目前的comp价格为$434

Aave介绍

Aave也是一个Defi接待平台,是一个开源的,基于以太坊的协议,用户可以将数字货币存入aave,赚取利息,也可也在Aave申请抵押借币,借币的利率可以是稳定利率Stable rate,或者是浮动利率variable rate,目前aave支持24种货币。

创始团队

Aave创立与2017年,总部位于伦敦项目前生叫做ETHLend,创始人是Stani Kulechov 。

Stanni是一个英国人,从小就热爱编程,后来大学在法学院接触了Fintech社区,之后接触了比特币和以太坊智能合约,2017年11月,ETHLend通过ICO募资了价值近1700美元的ETH,出售了10亿的LEND代币。

2018年9月,项目更名为Aave,Aave是芬兰语中“幽灵Ghost”的意思, 就是现在Aave的logo的幽灵的意思。

通过这次命名,Stani希望未来Aave未来的发展不局限于Eth,2020年1月,Aave通过了OpenZepplin的安全审计,并且主网上线。

市场数据

image-20210320132812406

image-20210320133754647

目前Aave v2的总市场规模约35亿、Aave v2的总市场规模约20亿

利率模型

Aave的借款利率是根据资金利用率挂钩的,他的特色

aToken

aToken与对应标的token始终保持等值,比如一个aDai始终等值一个DAI,所以,由于利息的存在,通过重定向,存款用户账户的余额会实时增长。

它的特色是有两种可以随时切换的利率模式,稳定利率Stable rate,或者是浮动利率Variable rate

Stable利率和Variable利率都是随着资金利用率的变化而动态变化

借款需求越高,流动资金越少,利率就越高

两种利率一的不同点是:Stable利率会实时变化、按Variable利率借款计算确认后基本上就不变了,除非满足基本的再平衡条件才会改变,如下:

  • 当前资金利用率高于95%
  • 总加权平均借款利率小于25%

image-20210320134324509

闪电贷

这是面向开发者的功能,申请人可以无抵押快速借币,但是用户需要在同一个区块中完成借款和还款,否则交易失效,以太坊虚拟机EVM的状态回滚 。

闪电贷的总结就是三步:借钱、用钱、连本带息还钱

优点:没有债务违约风险、无需抵押、不限制贷款额度

应用场景:套利、倒仓、抵押物交换、自清算

治理代币

Aave有两层治理模式,使用两种治理代币。

一种叫协议治理:它使用LEND代币投票,主要是决定协议中智能合约的升级,算法参数的调整等提案

land的总发行量是13亿,其中10亿在早期ICO中卖掉了。

另一种叫借贷池治理 Pool’s Governance:它使用aToken来投票,每个市场都是独立的治理投票:它主要决定一些关于借贷参数,比如抵押比率的升级调整

Aave V2

Aave v2推出了很多新功能

一、无国界金融

1、用抵押还款

如果用户想用部分抵押品还款,他们将首先需要提取抵押品,用它来购买借入的资产,然后最终偿还债务并解锁所有已存入的抵押品,需要花费至少4个步骤。

而 V2 中允许用户通过在 1 笔交易中直接用抵押品付款来去除杠杆化或平仓。

2、债务标记和信用委托

用户的债务状况将被标记 (用户将收到代表其债务的代币)。债务的标记化使 Aave 协议中的本地信用委托成为可能,用户可以通过冷钱包进行本地头寸管理和针对特定用户的流动性挖矿策略。

3、固定利率存款

存款的可预测利率为流动性提供者提供明确的收入保证,不受市场变化的约束。

4、稳定的借贷率

为了进一步确保利率的可预测性,借款人将能够使用改良版的稳定借款利率,可以将借款利率锁定在一定的时间段内,以确保融资成本的可预测性。

5、私募市场

为了满足机构的需求,《 Aave协议》现在将允许政府开放私募市场以支持各种代币化资产。

RealT与Aave协议之间的合作正在进行中,以进一步推动DeFi并为以太坊提供抵押。

6、优化代币

ATokens V2集成了EIP 2612以实现无气认证,短期目标是推动采用aToken。

7、GAS费优化

优化 Gas 费,实现对本机 GasToke n 支持,进一步帮助用户降低交易成本。

二、安全

8、提升协议安全性

Aave V2建立在Aave V1安全性的强大基础上。内部设计已经简化,体系结构也得到了改进,使其更易于形式验证。

Aave与自动验证技术的领导者Certora和Consensys Diligence以及其他顶级审核员紧密合作,为协议及其用户带来最高的安全标准。

三、本地交易功能

9、债务交易

作为DeFi用户,波动的利率难以管理,Aave协议V2将为任何人提供将其债务头寸从一种资产自然地交易到另一种资产的能力。比如,你可以借贷Dai,在USDC借贷变得更便宜时,可将您的债务头寸更改为USDC,所有这些都在一次交易中完成。

10、抵押交易

用户可以使用Aave支持的所有货币与他们的存入资产进行交易,即使是抵押品也可以交易。

11、保证金交易

通过“保证金借款”,用户可以直接使用所支持的任何资产的多头和空头头寸,而无需使用第三方服务。

另一方面, 通过保证金贷款,流动性提供者现在能够增加其存款的比重来抓住机会。

四、社区治理

Aave社区是Aave协议的重心,V2引入了特定的治理功能。

12、投票委派

任何AAVE持有人都可以安全地将其投票权委派给任何其他地址。

13、冷钱包投票

分布式治理的痛苦点之一是需要将令牌从一个地方移到另一个地方以参与治理,这带来了安全性问题和用户体验的摩擦。用户现在将能够从其冷钱包签名消息以参与Aave治理

14、将控制权分配给用户

V2 是Aave协议“流动性挖掘”的起点,用户可以参与 V2 的讨论。

Aave vs Compound

Aave能否超越Compound,本文将通过比较几个重要指标来进行分析。

代币设计

AAVE在代币设计方面比Compound具有另一个吸引人的优势,即80%的协议费用用于购买和销毁AAVE代币。在Compound协议下,收入用于创建平台储备金,这些收入由社区管理。

COMP代币总供应量价值约为1.53亿美元,占Compound平台总供应量的2.2%(= TVL +未偿还贷款总额)。

相比之下,Aave平台的总发行量中约有30%是其本机代币AAVE(V1中为0.43亿美元,V2中为7.36亿美元)。根据协议本身,这些仅是用户存入的作为借入稳定币的抵押品。实际上,该协议不允许用户借用其治理代币,以避免潜在的治理攻击,因为有人可能会使用借来的AAVE代币来获得额外的投票权。

Aave和Compound用户

image-20210320142528869

图片来源duneanalytics;

根据Dune Analytics的数据,就唯一的钱包地址数量而言,Compound的用户仍然比Aave多。

当然,这个度量标准也不是绝对的,因为理论上用户可以拥有多个钱包。因此,有可能人为地夸大了该度量标准,无法1:1转换为实际用户数。尽管如此,它仍然提供了有用的信息:Compound已拥有超过30万个钱包地址。明显高于Aave的4万多个地址(注意:Dune Analytics数据可能仅包括V1用户。如果包括V2用户,则这个数字可能会增加)。

借贷额

image-20210320142747477

图片来源duneanalytics;

在贷款量方面,Compound也优于Aave,市场份额接近77%。 Aave的23%份额分配在其V1和V2之间。然而,Compound在整个2020年保持约80%至90%的贷款份额,因此其在2021年的表现略有下降。因此,趋势可能再次发生变化。

协议与创新

两者在概念上都是非常相似的协议,但事实证明,Aave在实施附加服务方面更快、更具有创新性。 Aave支持20多种不同的资产,而Compound仅支持12种。Aave协议还提供稳定的利率,而Compound不提供

Aave引领了DeFi的发展,包括闪电贷款 和信贷委托保险库。去年12月份,Compound宣布计划推出Compound链,这是一个独立的 区块链,可在多个区块链中提供货币市场功能。

Aave v2发布的一系列创新功能也是一大优势

价格表现

2020年8月份,Aave代币当时仍未更名(前身LEND),并很快就达到了5亿美元的市值,即便如此,我们仍在怀疑AAVE是否能发展成为Compound杀手。不出半年,Aave市值增长了八倍,超过60亿美元。

AAVE已远远超过了COMP。今年年初,COMP的股价约为145美元左右,而AAVE的股价仅在88美元附近。截至2月初,COMP和AAVE的股价均在500美元左右。AAVE自1月初以来增长了450%以上,而COMP的增长幅度相对较小(但仍然是可观的)为240%。

结论

从目前的数据来看,由于Aave的快速增长,它将来在各个方面都可能取代Compound成为DeFi最受欢迎的借贷协议,这匹黑马已在许多指标上遥遥领先。它的代币设计对投资者来说也更具吸引力。

区块链Defi新项目记录

区块链Defi新项目记录

Fei protocol

Fei Protocol 支持创建基于以太坊的去中心化、可扩展且公平的稳定币,它的取名灵感来自于雅浦岛上著名的石头货币(FEI)——希望 FEI 表现出与石头货币相同的稳定性、简单性和普适性。

FEI 采用了一种「直接激励」的新型稳定实现机制,它资本效率更高、分配公平、而且完全去中心化,该协议使用协议控制的资产价值(PCV,新概念,下文详细讲到)来维持二级市场的流动性,实现 FEI 的挂钩发行与价值稳定。

Fei Protocol 设计了协议控制的资产价值 (PCV) 模型来解决TVL通胀问题

Fei Protocol 如何运作

与 TVL 下的 IOU 不同,PCV 中资用户不可以随时撤走存入的资产,当然 PCV 中的治理代币可积累更深层次的价值捕获并承担相应的责任,而 Fei Protocol 就是 PCV 的理想应用

联合曲线和其他激励措施为 PCV 池提供资金,该池大约等于 FEI 的整个用户流通量,它将由 ETH 联合曲线资助的 100%PCV 分配给以 ETH / FEI 的 Uniswap 池(将来不排除将 PCV 重新分配给其他平台)

激励措施

FEI 采用了一种「直接激励」的新稳定实现机制:直接激励是指激励稳定币交易活动和激励稳定币使用的行为,奖励和惩罚将价格推向锚定目标。通常这将包括至少一个作为枢纽的激励性交易所,所有其他交易所和二级市场都可以通过激励性交易所进行套利,这有助于稳定币在整个生态系统中维持挂钩

在创世阶段,购买Fei的价格会以一定的折扣进行奖励,同时Fei的价格也在提升,一直到$1维持不变,所有早期的PCV储备就是这样来的

治理代币

Fei Protocol 将在一开始就以完全去中心化的 DAO 启动,而 TRIBE 代币就是 DAO 的治理凭证

Genesis Group(最开始创世阶段买入Fei的群体) 的完成将启动 TRIBE 代币的初始发售,Uniswap 上将有 FEI 和 TRIBE 同时发行,TRIBE 完全稀释后的估值将设置为与 Genesis Group 筹集的 ETH 数量相同值。且代币将存储在发展基金中,它将在四年内进行线性解锁,以保证足够的流动性。

Float protocol

官网:https://floatprotocol.com/
推特:https://twitter.com/FloatProtocol
电报:https://t.me/officialfloatprotocol
官方doc:https://docs-float.gitbook.io/docs/

项目解读:(来自金色财经:https://www.jinse.com/blockchain/1022681.html)

float定义自己是一个基于web3的浮动、低波动性货币(Floating, Low-volatility Currency for Web3),团队为匿名团队。

项目中有两个代币,float和bank,其中:

float是“稳定币”,但团队声称, float并不锚定1美元,而是浮动的,“它像大多数主要的法定货币一样,旨在随时间浮动和改变价值”。初始的目标价格为1.618美元,目标价格会随时市场情况而变化。

bank承担着float价值稳定(包括通胀时降低float价值和通缩时提振float价值)以及治理的功能

稳定机制

每24小时,系统会触发rebase,通过rebase来重新稳定float价格,rebase的过程,是用户支付ETH(v1,v2中计划引入其他资产,但是v1仅有eth,下文中都以eth代替)和BANK来向协议获得新的FLOAT(通胀时),或协议使用ETH和BANK来回购FLOAT(通缩时)

如果FLOAT的价格偏离其目标价格:

通胀时,系统通过铸造FLOAT来改变供应量进而影响价格,具体实现形式为荷兰式拍卖(套利者有机会以低于市场价格的价格购买新的FLOAT),系统受到的BANK会被销毁,ETH会进入金库
通缩时,系统通过购买并燃烧FLOAT来改版供应链进而影响价格, 具体实现形式为反向荷兰式拍卖(套利者有机会以高于市场价格的价格出售FLOAT),同时销毁回购的FLOAT
这样,通过套利者来改变市场上的供应,进而达到稳定。

VAULT(金库)

在float的机制中,金库(vault)里锁定了ETH(或v2中计划引入的其他基础“抵押物”)。在初始阶段,合约中的这部分ETH并不会用来做其他事情,只是等待在通缩阶段回购FLOAT(不代表之后不会),金库是在通缩阶段能将FLOAT拉回目标价的关键储备。

VAULT FACTOR(金库充足率)

VAULT FACTOR = 金库中锁定的ETH市值 / 流通中的FLOAT目标价格市值。

以下表格介绍了在通胀阶段和通缩阶段两种情形下,叠加金库充足率本身出于盈余或不足状态时,发生拍卖时BANK价值和VAULT FACTOR的变化:

假设当前vault factor 为 a ,则a-1>0时,vault factor为盈余状态,反之为不足状态

治理代币

分发的具体信息如下:

第1年总共会铸造168,000 BANK。预期分配如下:

社区分发:50%,其中阶段1分配37.5%,阶段2分配12.5%

团队令牌:5%(在初次铸币仪式结束后分发,并锁定12个月)。

财政部(treasury):10%(在社区分发第二阶段开始时铸造,并由DAO控制)。

发行后的流动性激励措施:35%(5%的初始流通量铸币,30%由DAO控制,目标是进行各种流动性激励措施和合伙关系)。

1年后,BANK可能会有通胀,届时会由DAO来控制。

第1阶段(从2月7日星期日22:00 UTC开始,持续6周,至3月21日结束)

项目采取白名单制,目前白名单由参与Snapshot.page上任何协议的链下治理和/或Compound,MakerDao或MolochDao上链上治理的地址的代表,Yearn、UMA的、YAM、Curve的delegates,具体是否在白名单可以通过官网链接钱包后查看。

在白名单内的用户可以使用USDT、DAI、USDC进行挖矿,每天将分配1,500.00 BANK,或每个池500 BANK。总共每周将分发10,500 BANK,6周一共63,000BANK。

每个池中最多可以存入10,000个token,即单个白名单地址的投入上限是3万U,目前TVL并不高,还不到3500万U,按照目前BANK的$266的价格,日化收益率在1.15%左右

第2阶段(从3月21日星期日22:00 UTC开始,持续2周)

第二阶段旨在向顶级DeFI协议社区提供广泛的分发,并实现BANK令牌的初始价格发现。

相对第一阶段,将不再有白名单的限制。此外,将添加新的非稳定币池,每天总计分发1500BANK(所有池子)

完整的公告将在第2阶段开始前1周(即3月14日)提供。

此外,在第2阶段,系统还将激励BANK-ETH的流动资金池,以便更好地发现价格。

可能存在的风险

合约目前未经审计(虽然1阶段的合约是snx的fork,且出问题的可能性不大);

合约内存在较多复杂的交互及组合,复杂性带来更多危险的可能性;

VAULT内锁定的ETH丢失风险;

项目未能如期上线的风险。

Bigdata protocol

项目信息:

主页:https://bigdataprotocol.com/
所在链:Ethereum
平台币:BDP
上线日期:2021/3/04

项目解读:

区块链世界里,可以分为三种需求:刚性需求(比如借贷、交易、流动性挖矿)+弱需求(比如defi里面的指数产品)+伪需求(没有应用场景)。

1.做的是大数据。在区块链领域,能不能算是大的需求?

大数据在区块链里面,介于弱需求和伪需求之间,价值有待验证。

2.代币模型

BDP(8000万枚)相当于平台币,BALPHA(18000枚)(暂叫数据管理币)可能会去消耗它。代币分配见官网。

bigdata主要想让

3.Bigdata最初成为神矿,之所以叫“神矿”,是因为一上线锁仓量就冲到几十亿美金。在其流动性矿池staking的币,发现有OCEAN、SRM、TOMOE。这些小币种,几乎都不了解它。这种项目,上了一些很冷门的大多数人不知道的币,这里面就可能有问题。可能会出现,项目方手里有这些小币种,在项目里去流动性挖矿。如果大家都不买这些币或者很少的人抵押这些币,那么你就可以挖到很多币。如果大家都去买这些币的话,那么你就可以高价套现,这是套路的一种方式,注意风险。

4.bigdata早期价格US$14.93,截止目前US$3.73,跌了将近4-5倍,早期进场的人,心里是有一个价值预期的,未来会烧掉挖出来的币bAlpha,所以对它未来的通缩有一定的心理期望,但是这种期望是无法长期维系的,一旦有很长的下跌周期,很多人是拿不住的,会跌的比较快。但后面能否救得活,还有是有一定可能,该协议在上线初期,就已被爆出 SBF、神鱼、Cream.Finance 和 SWAG.Finance 创始人黄立成等都参与了该协议的挖矿,资本的介入,也是因为大户的介入,才使得它的热度和tvl才这么高

Ganache搭建一个以太坊投票dapp的Demo

工具及技术框架

  • 智能合约编写工具:Remix,个人喜欢用remix写智能合约,写好智能合约直接部署测试,把写好的合约代码再放到拷贝出来放到项目中进行编译

  • 前端页面编写工具:vscode

  • 环境:node:v14.17.0、npm:6.14.13

  • 依赖包:"ganache-cli": "^6.12.2", "solc": "^0.4.22","web3": "^1.3.6"

    关于几个依赖包的简要介绍:

Ganache CLI

Ganache CLI是以太坊开发工具Truffle套件的一部分,是以太坊开发私有区块链的Ganache命令行版本,用于测试和开发的快速以太坊RPC客户端,用于测试和开发的快速以太坊RPC客户端。ganache-cli是用Javascript编写的,并通过npm作为Node包进行分发。安装之前首先要确保安装了Node.js(> = v6.11.5)。

旨在快速搭建一个小demo,就没有用geth客户端去搭建一个本地私有链,直接用Ganache建一个干净纯粹的模拟节点进行测试,跟在Remix上用JavaScript VM测试合约是一个原理

solc

solidity编写的以太坊智能合约可通过命令行编译工具solc来进行编译,成为以太坊虚拟机中的代码。solc编译后最终部署到链上形成我们所见到的各种智能合约。

web3

web3.js是一个库集合,你可以使用HTTP或IPC连接本地或远程以太它节点进行交互。 web3的JavaScript库能够与以太坊区块链交互。 它可以检索用户帐户,发送交易,与智能合约交互等。

以上的三个依赖包具体的信息就不详细介绍

工程结构

首先使用node init初始化一个node项目,这里取名字为simple_voting_dapp

项目根目录安装依赖:npm i ganache-cli@6.12.2 solc@0.4.22 web3@1.3.6

安装完成后package.json应该是如下信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "simple_voting_dapp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"ganache-cli": "^6.12.2",
"solc": "^0.4.22",
"web3": "^1.3.6"
}
}

接着创建如下目录结构及代码

image-20210629174325035

投票合约

本例子中的投票合约是一个非常简单的合约,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
pragma solidity ^0.4.22;

contract Voting {
bytes32[] public canditateList;
mapping (bytes32 => uint8) public votesReceived;
constructor() public {
canditateList = getBytes32ArrayForInput();
}

function validateCandidate(bytes32 candiateName) internal view returns(bool){
for(uint8 i = 0;i<canditateList.length; i++){
if(candiateName == canditateList[i])
return true;
}
return false;
}
function vote(bytes32 candiateListName) public payable returns(bytes32){
require(validateCandidate(candiateListName));
votesReceived[candiateListName] +=1;
}

function totalVotesFor(bytes32 candiateName) public view returns(uint8){
require(validateCandidate(candiateName));
return votesReceived[candiateName];
}

function getBytes32ArrayForInput() pure public returns (bytes32[3] b32Arr) {
b32Arr = [bytes32("Candidate"), bytes32("Alice"), bytes32("Cary")];
}
}

变量说明

canditateList:投票的对象

votesReceived:投票的对象—>获得的总票数。一个映射

构造函数

调用getBytes32ArrayForInput函数初始化三个投票对象至canditateList中

函数

validateCandidate:一个internal、view的函数,返回目标是否在投票对象列表中

vote:给制定投票对象进行投票

totalVotesFor:查看指定对象的获得票数

编译脚本compile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const fs = require('fs-extra'); 
const path = require('path');
const solc = require('solc');

// cleanup 清除之前已编译的文件
const compiledDir = path.resolve(__dirname, '../compiled');
fs.removeSync(compiledDir);
fs.ensureDirSync(compiledDir);
// compile 开始编译合约
const contractPath = path.resolve(__dirname, '../../contract',
'voting.sol');
const contractSource = fs.readFileSync(contractPath, 'utf8');
const result = solc.compile(contractSource, 1);

// check errors 检查错误
if (Array.isArray(result.errors) && result.errors.length) {
throw new Error(result.errors[0]);
}
// save to disk 保存到指定目录compiled目录中
Object.keys(result.contracts).forEach( name => {
const contractName = name.replace(/^:/, '');
const filePath = path.resolve(__dirname, '../compiled',
`${contractName}.json`);
fs.outputJsonSync(filePath, result.contracts[name]);
console.log(`save compiled contract ${contractName} to
${filePath}`);
});

部署脚本deploy.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const path = require('path');
const Web3 = require('web3');
// 连接到ganache启动的节点
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
// 1. 拿到 abi 和 bytecode
const contractPath = path.resolve(__dirname, '../compiled/Voting.json');
const { interface, bytecode } = require(contractPath);
(async () => {
// 2. 获取钱包里面的账户
const accounts = await web3.eth.getAccounts();
console.log('部署合约的账户:', accounts[0]);
// 3. 创建合约实例并且部署
console.time('合约部署耗时');
var result = await new
web3.eth.Contract(JSON.parse(interface))
.deploy({data: bytecode})
.send({
from: accounts[0],
gas: 1500000,
gasPrice: '30000000000000'
})
.then(function(newContractInstance){
console.log('合约部署成功: ' + newContractInstance.options.address) // instance with the new contract address
});
console.timeEnd('合约部署耗时');
})();

部署合约

1.手动启动ganache程序,运行一个节点,保持终端不关闭

.\node_modules\.bin\ganache-cli

2.编译voting.sol投票合约

node .\contract_workflow\scripts\compile.js

contract_workflow目录下面会出现Voting.json文件,后面index.js会用到该文件中的内容

3.部署合约

node .\contract_workflow\scripts\deploy.js

会出现以下信息

部署合约的账户: 0x8C6ba0616f05909eCb334C1a3707C38a8d7bDd0F

合约部署成功: 0xaCCBC818BC9224A6da2902eeF82c3d14afC82aB5

合约部署耗时: 345.211ms

网页交互代码index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html>

<head>
<title>Voting DApp</title>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>

<body class="container">
<h1>A Simple Voting Application</h1>
<div class="table-responsive"></div>
<table class="table table-bordered">
<thead>
<tr>
<th>Candidate</th>
<th>Votes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Candidate</td>
<td id="candidate-1"></td>
</tr>
<tr>
<td>Alice</td>
<td id="candidate-2"></td>
</tr>
<tr>
<td>Cary</td>
<td id="candidate-3"></td>
</tr>
</tbody>
</table>
</div>
<input type="text" id="candidate" />
<a href="#" onclick="vote()" class="btn btn-primary">Vote</a>
</body>
<script src="https://cdn.jsdelivr.net/gh/ethereum/web3.js/dist/web3.min.js">
</script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js">
</script>
<script src="./index.js"></script>
</html>

网页交互index.js

要注意contractAddrabi两个变量的值,

contractAddr是在部署合约时获取的

abi是从Voting.json中的interface获取的,打开Voting.json即可看到interface对象内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// const path = require('path');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
// const contractPath = path.resolve(__dirname, '../contract_workflow/compiled/Voting.json');
// 部署的只能合约地址
contractAddr = "0xaCCBC818BC9224A6da2902eeF82c3d14afC82aB5";
// abi内容,从Voting.json中拷贝interface对象的内容,由于内容过程,这里就省略了
abi = ""
contractInstance = new web3.eth.Contract(JSON.parse(abi),contractAddr);
candidates = { "Candidate": "candidate-1", "Alice": "candidate-2", "Cary": "candidate-3" };

let accounts;
web3.eth.getAccounts().then(result => accounts = result);

function vote() {
let candidateName = $("#candidate").val();
try {
contractInstance.methods.vote(stringtoHex(candidateName)).send({from: accounts[0] })
.on('transactionHash', function(hash){
console.log("hash:" + hash)
contractInstance.methods.totalVotesFor(stringtoHex(candidateName)).call().then(result => $("#" + candidates[candidateName]).html(result));
})
.on('confirmation', function(confirmationNumber, receipt){
// console.log(confirmationNumber,receipt)
})
.on('receipt', function(receipt){
// receipt example
// console.log("receipt" +receipt);
})
.on('error', console.error);
} catch (err) {
console.log(err)
}
}
// 需转成16进制的才能给合约中byte32的数据传参,且前缀要加上0x
var stringtoHex = function (str) {
var val = "";
for (var i = 0; i < str.length; i++) {
if (val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
return "0x"+val
}

$(document).ready(function () {
candidateNames = Object.keys(candidates);
for (var i = 0; i < candidateNames.length; i++) {
let name = candidateNames[i];
contractInstance.methods.totalVotesFor(stringtoHex(name)).call().then(result => $("#" + candidates[name]).html(result));


}
});

在本地打开src/index.html,出现以下界面

image-20210629174325035

在输入框中输入对应投票对象的名字,点击Vote按钮即可为指定对象增加票数

至此,一个完整的demo就完成了!!

小结

本项目是一个比较基础的区块链demo,但是还有很多要优化的地方

  • 基于ganache启动的节点在关闭后,需要重新部署合约
  • 部署合约编译部署分为了两步,很繁琐
  • 部署合约后要手动修改index.js中合约地址和abi

所以自动化的流程还是比较繁琐的

但是通过这个小demo,可以学到的东西还是很多的,比如如何用node通过ganache启动一个本地私有节点、如何编译合约、部署合约、调用合约

后期会介绍一个更自动化、更简便的框架Truffle去实现以上步骤,通过Truffle框架把以上很多编译,部署的脚本都帮我们完成了,所以学了这个demo后,学习truffle会更加得心应手

感谢

感谢尚硅谷的区块链教程:https://www.bilibili.com/video/BV1NJ411D7rf

Java8新特性-新时间与日期API

总结一下java8中的新特性新时间与日期API

LocalDate、LocalTime、LocalDateTime 类的实 例是不可变的对象,分别表示使用 ISO-8601日 历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间息。也不包含与时区相关的信息。

LocalDateTime常用方法

LocalDate / LocalTime用法类似,只不过LocalDate 包含年月日LocalTime只包含时分秒

方法名 返回值类型 解释
now( ) static LocalDateTime 从默认时区的系统时钟获取当前日期
of(int year, int month, int dayOfMonth, int hour, int minute, int second) static LocalDateTime 从年,月,日,小时,分钟和秒获得 LocalDateTime的实例,将纳秒设置为零
plus(long amountToAdd, TemporalUnit unit) LocalDateTime 返回此日期时间的副本,并添加指定的数量
get(TemporalField field) int 从此日期时间获取指定字段的值为 int
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test1(){
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);// 2020-12-10T11:48:43.387

LocalDateTime ld2 = LocalDateTime.of(2020, 12, 10, 11, 11, 10);
System.out.println(ld2);// 2020-12-10T11:11:10

LocalDateTime ldt3 = ld2.plusYears(20);
System.out.println(ldt3);// 2040-12-10T11:11:10

LocalDateTime ldt4 = ld2.minusMonths(2);
System.out.println(ldt4);// 2020-10-10T11:11:10

System.out.println(ldt.getYear());// 2020
System.out.println(ldt.getMonthValue());// 12
System.out.println(ldt.getDayOfMonth());// 10
System.out.println(ldt.getHour());// 11
System.out.println(ldt.getMinute());// 48
System.out.println(ldt.getSecond());// 43
}

时间戳Instant

Instant:用于“时间戳”的运算。它是以Unix元年(传统 的设定为UTC时区1970年1月1日午夜时分)开始 所经历的描述进行运算,也就是UTC时间或者GMT格林威治时间

GMT=UTC
UTC+8=北京时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//2. Instant : 时间戳。 (使用 Unix 元年  1970年1月1日 00:00:00 所经历的毫秒值)
@Test
public void test2(){
Instant ins = Instant.now(); //默认使用 UTC 时区
System.out.println(ins);// 2020-12-10T03:50:25.814Z

OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt);// 2020-12-10T11:50:25.814+08:00

System.out.println(ins.getNano());// 814000000

Instant ins2 = Instant.ofEpochSecond(5);
System.out.println(ins2);// 1970-01-01T00:00:05Z
}

时间 / 日期 差

  • Duration:计算两个时间之间的间隔
  • Period:计算两个日期之间的间隔
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Duration : 用于计算两个“时间”间隔
//Period : 用于计算两个“日期”间隔
@Test
public void test3(){
Instant ins1 = Instant.now();

System.out.println("--------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}

Instant ins2 = Instant.now();
// 获取两个时间间隔
Duration duration = Duration.between(ins1, ins2);
System.out.println("所耗费时间为(单位毫秒):" + duration.toMillis());// 所耗费时间为(单位秒):1001

System.out.println("----------------------------------");

LocalDate ld1 = LocalDate.now();
System.out.println("现在日期为:"+ ld1);// 现在日期为:2020-12-10
LocalDate ld2 = LocalDate.of(2011, 1, 1);
// 获取两个日期间隔
Period pe = Period.between(ld2, ld1);
System.out.println("年间隔" + pe.getYears());// 9
System.out.println("月间隔" + pe.getMonths());// 11
System.out.println("天间隔" + pe.getDays());// 9
}

时间校正器

  • TemporalAdjuster : 时间校正器。有时我们可能需要获 取例如:将日期调整到“下个周日”等操作。
  • TemporalAdjusters : 该类通过静态方法提供了大量的常 用 TemporalAdjuster 的实现。

TemporalAdjuster是一个函数接口,因此还可也用lambda表达式实现里面的接口

1
2
3
4
5
6
@FunctionalInterface
public interface TemporalAdjuster {

Temporal adjustInto(Temporal temporal);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void test4(){
// 当前日期
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
// 本月第12天
LocalDateTime ldt2 = ldt.withDayOfMonth(12);
System.out.println(ldt2);
// 下一个周日
LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
System.out.println(ldt3);

//自定义:下一个工作日
LocalDateTime ldt5 = ldt.with((l) -> {
LocalDateTime ldt4 = (LocalDateTime) l;

DayOfWeek dow = ldt4.getDayOfWeek();

if(dow.equals(DayOfWeek.FRIDAY)){
return ldt4.plusDays(3);
}else if(dow.equals(DayOfWeek.SATURDAY)){
return ldt4.plusDays(2);
}else{
return ldt4.plusDays(1);
}
});

System.out.println(ldt5);

}

解析与格式化

java.time.format.DateTimeFormatter 类:该类提供了三种 格式化方法:

  • 预定义的标准格式
  • 语言环境相关的格式
  • 自定义的格式
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test5(){
// DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");

LocalDateTime ldt = LocalDateTime.now();
String strDate = ldt.format(dtf);

System.out.println(strDate);

LocalDateTime newLdt = ldt.parse(strDate, dtf);
System.out.println(newLdt);
}

DateTimeFormatter时间工具类,转自DateTimeFormatter时间工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
 
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

/**
* @author xiaobu
* @version JDK1.8.0_171
* @date on 2019/1/11 11:52
* @description V1.0 LocalDateTime工具类
* SQL -> Java
* date -> LocalDate
* time -> LocalTime
* timestamp -> LocalDateTime
*/
public class DateTimeUtils {

public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HHmmss");
public static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
public static final DateTimeFormatter SHORT_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter SHORT_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss");
public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter LONG_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS");

/**
* 返回当前的日期
*/
public static LocalDate getCurrentLocalDate() {
return LocalDate.now();
}

/**
* 返回当前时间
*/
public static LocalTime getCurrentLocalTime() {
return LocalTime.now();
}

/**
* 返回当前日期时间
*/
public static LocalDateTime getCurrentLocalDateTime() {
return LocalDateTime.now();
}

/**
* yyyy-MM-dd
*/
public static String getCurrentDateStr() {
return LocalDate.now().format(DATE_FORMATTER);
}

/**
* yyMMdd
*/
public static String getCurrentShortDateStr() {
return LocalDate.now().format(SHORT_DATE_FORMATTER);
}

public static String getCurrentMonthStr() {
return LocalDate.now().format(YEAR_MONTH_FORMATTER);
}

/**
* yyyy-MM-dd HH:mm:ss
*/
public static String getCurrentDateTimeStr() {
return LocalDateTime.now().format(DATETIME_FORMATTER);
}


public static String getCurrentLongDateTimeStr(){
return LocalDateTime.now().format(LONG_DATETIME_FORMATTER);
}

/**
* yyMMddHHmmss
*/
public static String getCurrentShortDateTimeStr() {
return LocalDateTime.now().format(SHORT_DATETIME_FORMATTER);
}

/**
* HHmmss
*/
public static String getCurrentTimeStr() {
return LocalTime.now().format(TIME_FORMATTER);
}

public static String getCurrentDateStr(String pattern) {
return LocalDate.now().format(DateTimeFormatter.ofPattern(pattern));
}

public static String getCurrentDateTimeStr(String pattern) {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(pattern));
}

public static String getCurrentTimeStr(String pattern) {
return LocalTime.now().format(DateTimeFormatter.ofPattern(pattern));
}

public static LocalDate parseLocalDate(String dateStr, String pattern) {
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
}

public static LocalDateTime parseLocalDateTime(String dateTimeStr, String pattern) {
return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern(pattern));
}

public static LocalTime parseLocalTime(String timeStr, String pattern) {
return LocalTime.parse(timeStr, DateTimeFormatter.ofPattern(pattern));
}

public static String formatLocalDate(LocalDate date, String pattern) {
return date.format(DateTimeFormatter.ofPattern(pattern));
}

public static String formatLocalDateTime(LocalDateTime datetime, String pattern) {
return datetime.format(DateTimeFormatter.ofPattern(pattern));
}

public static String formatLocalTime(LocalTime time, String pattern) {
return time.format(DateTimeFormatter.ofPattern(pattern));
}

public static LocalDate parseLocalDate(String dateStr) {
return LocalDate.parse(dateStr, DATE_FORMATTER);
}

public static LocalDateTime parseLocalDateTime(String dateTimeStr) {
return LocalDateTime.parse(dateTimeStr, DATETIME_FORMATTER);
}

public static LocalDateTime parseLongLocalDateTime(String longDateTimeStr){
return LocalDateTime.parse(longDateTimeStr, LONG_DATETIME_FORMATTER);
}

public static LocalTime parseLocalTime(String timeStr) {
return LocalTime.parse(timeStr, TIME_FORMATTER);
}

public static String formatLocalDate(LocalDate date) {
return date.format(DATE_FORMATTER);
}

public static String formatLocalDateTime(LocalDateTime datetime) {
return datetime.format(DATETIME_FORMATTER);
}

public static String formatLocalTime(LocalTime time) {
return time.format(TIME_FORMATTER);
}

/**
* 日期相隔秒
*/
public static long periodHours(LocalDateTime startDateTime,LocalDateTime endDateTime){
return Duration.between(startDateTime, endDateTime).get(ChronoUnit.SECONDS);
}

/**
* 日期相隔天数
*/
public static long periodDays(LocalDate startDate, LocalDate endDate) {
return startDate.until(endDate, ChronoUnit.DAYS);
}

/**
* 日期相隔周数
*/
public static long periodWeeks(LocalDate startDate, LocalDate endDate) {
return startDate.until(endDate, ChronoUnit.WEEKS);
}

/**
* 日期相隔月数
*/
public static long periodMonths(LocalDate startDate, LocalDate endDate) {
return startDate.until(endDate, ChronoUnit.MONTHS);
}

/**
* 日期相隔年数
*/
public static long periodYears(LocalDate startDate, LocalDate endDate) {
return startDate.until(endDate, ChronoUnit.YEARS);
}

/**
* 是否当天
*/
public static boolean isToday(LocalDate date) {
return getCurrentLocalDate().equals(date);
}
/**
* 获取当前毫秒数
*/
public static Long toEpochMilli(LocalDateTime dateTime) {
return dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}

/**
* 判断是否为闰年
*/
public static boolean isLeapYear(LocalDate localDate){
return localDate.isLeapYear();
}
}

时区

Java 8不仅分离了日期和时间,也把时区分离出来了。现在有一系列单独的类如ZoneId来处理特定时区,ZoneDateTime类来表示某时区下的时间。这在Java 8以前都是 GregorianCalendar类来做的。下面这个例子展示了如何把本时区的时间转换成另一个时区的时间。

获取所有时区id

1
Set<String> set = ZoneId.getAvailableZoneIds();
1
2
3
4
5
6
7
8
9
// ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期
@Test
public void test7(){
LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(ldt);

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
System.out.println(zdt);
}

Java8新特性-Optional类

Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

常用方法:

  • Optional.of(T t) : 创建一个 Optional 实例
  • Optional.empty() : 创建一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
  • isPresent() : 判断是否包含值
  • orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
  • orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
  • map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
  • flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

1.of方法

创建一个 Optional 实例

1
2
3
4
5
6
@Test
public void test1(){
Optional<Employee> op = Optional.of(new Employee());
Employee emp = op.get();
System.out.println(emp);
}

2.ofNullable & empty

若 t 不为 null,创建 Optional 实例,否则创建空实例

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test2(){
Optional<Employee> op = Optional.ofNullable(null);
if(op.isPresent()){
System.out.println(op.get());
}

System.out.println(op.get());

Optional<Employee> op = Optional.empty();
System.out.println(op.get());
}

3.orElse & orElseGet

  • orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
  • orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test3(){
Optional<Employee> op = Optional.ofNullable(new Employee());

if(op.isPresent()){
System.out.println(op.get());
}

Employee emp = op.orElse(new Employee("张三"));
System.out.println(emp);

Employee emp2 = op.orElseGet(() -> new Employee());
System.out.println(emp2);
}

4.map & flatMap

  • map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
  • flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
1
2
3
4
5
6
7
8
9
10
@Test
public void test4(){
Optional<Employee> op = Optional.of(new Employee(101, "张三", 18, 9999.99));

Optional<String> op2 = op.map(Employee::getName);
System.out.println(op2.get());

Optional<String> op3 = op.flatMap((e) -> Optional.of(e.getName()));
System.out.println(op3.get());
}