
49万USDT钱包资产被转走之到底是不是朋友干的?
话题背景wu351256@discord 有谁知道我的代币为什么会在我不知情的情况下下被转走,我查记录上面说是触发了什么智能合约。我是新手有大神带我理解一下嘛? 如果是触发了什么智能合约怎么找到这个合约并且解除合约进入正题@bmlcwenwu 是不是点了什么钓鱼链接?赶快去解除授权 @42069Imtoken里边有教程,先解除授权 @夜与昼 这种币被盗了,报警说不立案,imtoken客服说报警,投诉无门,欲哭无泪 @42069 国内加密货币属于非法,警察不立案。 @夜与昼 对啊,国内,我被盗了49万usdt夜与昼 @42069 这么多U,没想到用冷钱包吗 @夜与昼 我最后一次转账用我朋友的id下载的app,现在就是不知道会不会是他盗的 @42069 什么app? @夜与昼 Imtoken钱包 @42069 不是假的吧 @夜与昼 用他的id后,商城里下载的,登录进去给他转了一笔钱,一个星期后钱包被盗 @42069 登录之后退出id了吗?只是商城登录id没事 夜与昼登录后退出了,登录的时候还双重认证了, *[2:39 PM]*Icloud就会自动登陆 *[2:39 PM]*Iclou...
Etherscan事务交易详细分类
TransferERC21https://cn.etherscan.com/tx/0xc261caa5556785d46fbf5f715f4e5686288304a5a510d34336d763b060dc37b6Swap案例1: https://cn.etherscan.com/tx/0xf09f3b3dcd29552dac6fd14040bb457f3f462ab497dd4a885fcf1bcc026f10ff https://cn.etherscan.com/tx/0x23fdb4a895877af5d2091d541d05f6ae874267c8d82e73bb7f186d8839781c92ApproveERC20ERC721ERC1159https://cn.etherscan.com/tx/0x00ff492f7c93e50b47c1c341d935e5d5cac201455d7d7f8a491fd5f0a7d93746MintERC721MethodID: 0xa0712d68Function: mint(uint256 _mintAmount) MethodI...

以太坊签名验签原理揭秘
签名三大作用讨论密码学中的签名时,我们其实是在讨论所有权、有效性和完整性证明。举例来说,这些签名可以用来:证明你拥有地址的私钥(即认证功能);确保信息(例如,邮件)没有被篡改;验证你下载的 文件是合法有效的。签名基础原理:基于数学公式输入:一个输入消息、一个私钥和一个(通常情况下是秘密的)随机数,就可以得到一串数字作为输出值,也就是签名。 输出:使用另一个数学公式可以进行反向计算,在不知道私钥和随机数的情况下进行验证(译者注:即验证该签名是否出自跟某个公钥对应的私钥)。 这类算法有很多,如 RSA 和 AES,但是以太坊(和比特币)采用的都是椭圆曲线数字签名算法(ECDSA)。请注意,ECDSA 只是签名算法。与 RSA 和 AES 不同,这种算法不能用于加密。以太坊采用的是 secp256k1曲线。 签名方案由哈希算法和签名算法组成。以太坊选择的签名算法是secp256k1,哈希算法选择了keccak256,这是一个从字节串。不可逆计算通过椭圆曲线点乘算法(elliptic curve point manipulation),我们可以使用私钥计算出一个不可逆向计算的值(译者注:...
The infinite past has the present as its destination, and the infinite future has the present as its origin.



49万USDT钱包资产被转走之到底是不是朋友干的?
话题背景wu351256@discord 有谁知道我的代币为什么会在我不知情的情况下下被转走,我查记录上面说是触发了什么智能合约。我是新手有大神带我理解一下嘛? 如果是触发了什么智能合约怎么找到这个合约并且解除合约进入正题@bmlcwenwu 是不是点了什么钓鱼链接?赶快去解除授权 @42069Imtoken里边有教程,先解除授权 @夜与昼 这种币被盗了,报警说不立案,imtoken客服说报警,投诉无门,欲哭无泪 @42069 国内加密货币属于非法,警察不立案。 @夜与昼 对啊,国内,我被盗了49万usdt夜与昼 @42069 这么多U,没想到用冷钱包吗 @夜与昼 我最后一次转账用我朋友的id下载的app,现在就是不知道会不会是他盗的 @42069 什么app? @夜与昼 Imtoken钱包 @42069 不是假的吧 @夜与昼 用他的id后,商城里下载的,登录进去给他转了一笔钱,一个星期后钱包被盗 @42069 登录之后退出id了吗?只是商城登录id没事 夜与昼登录后退出了,登录的时候还双重认证了, *[2:39 PM]*Icloud就会自动登陆 *[2:39 PM]*Iclou...
Etherscan事务交易详细分类
TransferERC21https://cn.etherscan.com/tx/0xc261caa5556785d46fbf5f715f4e5686288304a5a510d34336d763b060dc37b6Swap案例1: https://cn.etherscan.com/tx/0xf09f3b3dcd29552dac6fd14040bb457f3f462ab497dd4a885fcf1bcc026f10ff https://cn.etherscan.com/tx/0x23fdb4a895877af5d2091d541d05f6ae874267c8d82e73bb7f186d8839781c92ApproveERC20ERC721ERC1159https://cn.etherscan.com/tx/0x00ff492f7c93e50b47c1c341d935e5d5cac201455d7d7f8a491fd5f0a7d93746MintERC721MethodID: 0xa0712d68Function: mint(uint256 _mintAmount) MethodI...

以太坊签名验签原理揭秘
签名三大作用讨论密码学中的签名时,我们其实是在讨论所有权、有效性和完整性证明。举例来说,这些签名可以用来:证明你拥有地址的私钥(即认证功能);确保信息(例如,邮件)没有被篡改;验证你下载的 文件是合法有效的。签名基础原理:基于数学公式输入:一个输入消息、一个私钥和一个(通常情况下是秘密的)随机数,就可以得到一串数字作为输出值,也就是签名。 输出:使用另一个数学公式可以进行反向计算,在不知道私钥和随机数的情况下进行验证(译者注:即验证该签名是否出自跟某个公钥对应的私钥)。 这类算法有很多,如 RSA 和 AES,但是以太坊(和比特币)采用的都是椭圆曲线数字签名算法(ECDSA)。请注意,ECDSA 只是签名算法。与 RSA 和 AES 不同,这种算法不能用于加密。以太坊采用的是 secp256k1曲线。 签名方案由哈希算法和签名算法组成。以太坊选择的签名算法是secp256k1,哈希算法选择了keccak256,这是一个从字节串。不可逆计算通过椭圆曲线点乘算法(elliptic curve point manipulation),我们可以使用私钥计算出一个不可逆向计算的值(译者注:...
Share Dialog
Share Dialog
The infinite past has the present as its destination, and the infinite future has the present as its origin.

Subscribe to Renaissance Labs

Subscribe to Renaissance Labs
主要功能点:1)投注;2)开奖;3)退奖;4)获取奖池奖金;5)返回当前期数;6)返回中奖者地址;7)返回参与彩民的地址;
合约主要包含有四个属性:
contract Lottery {
address manager; // 管理员
address[] players; // 投了注的彩民
address winner; // 上期彩票的胜出者
uint256 round = 1; // 第几期
}
管理员属性可以在创建合约的时候进行初始化。
constructor() public {
manager = msg.sender;
}
假设每次投注只能投1个以太币。
// 投注
function play() public payable {
require(msg.value == 1 ether);
players.push(msg.sender);
}
开奖就是在投注彩民数组players中随机选出一个彩民,然后将合约的余额转账到该彩民的地址上。
这里需要先定义一个修饰器,用于限定只有管理员角色有权执行开奖的方法。
modifier onlyManager() {
require(manager == msg.sender);
_;
}
下面是开奖方法的实现逻辑:
function kaijiang() public payable {
// 生成players数组的随机下标
bytes memory v1 = abi.encodePacked(block.difficulty, now, players.length);
bytes32 v2 = keccak256(v1);
uint v3 = uint256(v2) % players.length;
// 获取中奖者
winner = players[v3];
// 把奖池的金额转账给中奖者
winner.transfer(address(this).balance);
// 清空plays
delete players;
// 期数加1
round++;
}
只有管理员才可以发起退奖操作。
// 退奖
function tuiJiang() public onlyManager {
require(players.length != 0);
// 把奖池的金额退还给每一个玩家
for (uint i = 0; i < players.length; i++) {
players[i].transfer(1 ether);
}
// 清空plays
delete players;
// 期数加1
round++;
}
值得注意的是,上面开奖和退奖方法中,都会执行
transfer函数执行转账操作。上面代码会存在代码“重入”的风险。所以作为改善措施,应该按照Checks-Effects-Interactions模式编写函数代码。
优化后的代码:
function kaijiang() public payable {
// 生成随机下标
bytes memory v1 = abi.encodePacked(block.difficulty, now, players.length);
bytes32 v2 = keccak256(v1);
uint v3 = uint256(v2) % players.length;
// 获取中奖者
winner = players[v3];
// 清空plays
delete players;
// 期数加1
round++;
// 把奖池的金额转账给中奖者
winner.transfer(address(this).balance);
}
// 退奖
function tuiJiang() public onlyManager {
require(players.length != 0);
// 清空plays
delete players;
// 期数加1
round++;
// 把奖池的金额退还给每一个玩家
for (uint i = 0; i < players.length; i++) {
players[i].transfer(1 ether);
}
}
上面代码将transfer操作放在方法最后执行。另外,上面代码通过delete players删除所有参与的玩家信息,这样会存在一定的风险:如果数组较大,可能会超过区块gas限制,从而引发out of gas异常。其实对于动态数组而言,使用length属性修改数组大小也可以达到同样效果。
players.lenght = 0;
// 获取奖金池的金额
function getAmount() public view returns(uint256) {
return address(this).balance;
}
// 获取管理员地址
function getManagerAddress() public view returns(address) {
return manager;
}
// 返回当前期数
function getRound() public view returns(uint256) {
return round;
}
// 返回中奖者地址
function getWinner() public view returns(address) {
return winner;
}
// 返回参与彩民的地址
function getPlays() public view returns(address[]) {
return players;
}
二、众筹合约 主要功能点:1)创建众筹合约;2)获取所有众筹合约;3)获取发起者的众筹合约;4)获取参与者的众筹合约;5)参与众筹;6)获取已筹集到的金额;7)获取所有参与者;8)退款;9)众筹成功后,发起花费请求;10)审批花费请求;11)完成花费请求;12)获取众筹的剩余时间;13)获取参与者的数量;14)获取花费申请的详情;
2.1 创建众筹合约
第一步:定义合约。
contract Funding {
// 合约发起者
address public manager;
// 众筹项目名称
string public projectName;
// 目标金额
uint public targetMoney;
// 支持金额
uint public supportMoney;
// 项目结束时间,单位秒
uint public endTime;
// 参与者
address payable[] investors;
constructor(string memory _projectName, uint _targetMoney, uint _supportMoney, uint _duration, address _creator) public {
manager = _creator;
projectName = _projectName;
targetMoney = _targetMoney;
supportMoney = _supportMoney;
endTime = now + _duration;
}
}
第二步:定义一个工厂合约,该合约保存了所有的众筹实例,并提供了操作众筹合约的方法;
contract FundingFactory {
// 平台管理员
address public platformManager;
// 所有的众筹合约
address[] allFundings;
// 自己创建的合约集合,key代表合约发起者地址,value代表合约机制的集合
mapping(address => address[]) creatorFundings;
constructor() public {
platformManager = msg.sender;
}
}
第三步:定义发起众筹的方法;
function createFunding(string memory _projectName, uint _targetMoney, uint _supportMoney, uint _duration) public {
Funding funding = new Funding(_projectName, _targetMoney, _supoortMoney, _duration, msg.sender);
allFundings.push(address(funding));
creatorFundings[msg.sender].push(address(funding));
}
接着在FundingFactory中添加其他操作合约的方法。
// 获取所有众筹合约
function getAllFundings() public view returns(address[] memory) {
return allFundings;
}
// 获取发起者的众筹合约
function getCreatorFundings() public view returns(address[] memory) {
return creatorFundings[msg.sender];
}
第一步:为了便于维护参与者的众筹记录,我们定义一个合约,专门用来记录所有参与者参与过的众筹合约。
contract SupportFunding {
// 记录参与过的众筹
mapping(address => address[]) supportFundings;
function setSupportFunding(address _supportor, address funding) public {
supportFundings[_supportor].push(funding);
}
function getSupportFunding(address _supportor) public {
return supportFundings[_supportor];
}
}
第二步:在FundingFactory中增加supportFunding属性,并提供获取参与者参与过的合约方法;
SupportFunding supportFunding;
// 获取参与者参与过的众筹合约
function getSupportFunding() public view return(address[] memory) {
return supportFunding.getFundings(msg.sender);
}
第一步:在Funding合约增加一个supportFunding属性,在该合约的构造函数中对该属性执行初始化;
SupportFunding supportFunding;
constructor(string memory _projectName, uint _targetMoney, uint _supportMoney, uint _duration, address _creator, SupportFunding _supportFunding) public {
manager = _creator;
projectName = _projectName;
targetMoney = _targetMoney;
supportMoney = _supportMoney;
endTime = now + _duration;
supportFunding = _supportFunding;
}
}
第二步:定义参与众筹方法;
// 记录是否投资人,key代表参与者,value代表是否投资人
mapping(address => bool) isInvestors;
// 参与众筹
function invest() public payable returns(uint) {
// 约束条件:发送币的数量必须要等于支持金额
require(msg.value == supportMoney);
// 记录投资人
investors.push(msg.sender);
// 记录参与者为投资人
isInvestors[msg.sender] = true;
// 记录参与者参与过的合约(当前合约)
supportFunding.setFunding(msg.sender, address(this));
}
// 获取已经筹集到的金额,即当前合约的余额
function getBalance() public view returns(uint) {
return address(this).balance;
}
// 获取所有的参与者
function getInvestors() public view returns(address payable[] memory) {
return investors;
}
如果合约还没结束,允许投资人退出众筹。所以我们在Funding合约中定义一个修饰器,该修饰器用于添加方法的约束条件。
modifier onlyManager {
require(msg.sender == manager);
}
然后定义退款方法,并使用上面定义好的修饰器。
// 退款
function refund() onlyManager public {
// 循环遍历所有投资人,并向其转账
for (uint i = 0; i < investors.length; i++) {
investors[i].transfer(supportMoney);
}
// 清空投资人数组
delete investors;
}
如果众筹成功,管理员在使用众筹资金前,需要发起花费。
第一步:在Funding合约中定义一个结构体,用于记录花费的详情。
// 花费申请状态,0代表申请中,1代表已批准,2代表已完成
enum RequestStatus {
Voting, Approved, Completed
}
// 记录花费申请信息
struct Request {
// 花费目的
string purpose;
// 申请花费的金额
uint cost;
// 转账给商家的地址
address payable seller;
// 花费申请赞同的票数
uint approveCount;
// 申请状态
RequestStatus status;
// 参与者的投票状态,true代表已经投票,false代表还没投票
mapping(address => bool) hasVoted;
}
第二步:在Funding合约中增加一个属性,该属性用于记录管理员发起的花费请求。
Request[] requests
第三步:定义申请花费的方法;
// 发起花费申请
function createRequest(string memory _purpose, uint _cost, address payable _seller) onlyManager public {
Request memory req = Request({
purpose: _purpose,
cost: _cose * 10**18, // 将花费金额转换成单位wei
seller: _seller,
approveCount: 0,
status: RequestStatus.Voting
});
requests.push(req);
}
审核通过条件:1)消息发起者是投资人;2)投资人之前没有投过票;
// 对指定索引的申请进行审核
// 参数i代表数组的索引
function approveRequest(uint i) public {
Request storage req = requests[i];
// 检查是否是投资人
require(isInvestors[msg.sender] == true);
// 检查是否投过票
require(req.hasVoted[msg.sender] == false);
// 赞同数增加
req.approveCount++;
// 记录该投资人已经投过票
req.hasVoted[msg.sender] = true;
// 如果票数过半,更新申请状态为Approved
if (req.approveCount * 2 > investors.length) {
req.status = RequestStatus.Approved;
}
}
结束花费申请的条件:1)合约余额必须要大于等于申请的花费金额;2)赞同票数要过半;
// 完成花费
function finalizeRequest(uint i) onlyManager public {
Request storage req = requests[i];
// 合约的余额必须要足够支付花费
require(address(this).balance >= req.cost);
// 申请状态必须为已通过
require(req.status == RequestStatus.Approved);
// 向商家转账
req.seller.transfer(req.cose);
// 更新申请状态为已完成
req.status = RequestStatus.Completed;
}
function getLeftTime() public view returns(uint) {
return endTime - now;
}
function getRequestCount() public view returns(uint) {
return requests.length;
}
function getRequest(uint i) public view returns(string memory, uint, address, uint, RequestStatus) {
Request storage req = requests[i];
return(req.purpose, req.cost, req.seller, req.approveCount, req.status);
}
到这里为止,整个众筹合约的核心代码已经完成。
该合约实现了根据每一个学员的计算分数指标m和n计算出他们的最终成绩。
首先在合约中定义一个结构体,用于封装学员相关的信息。
contract SmartScore {
struct Score {
// 学生ID
uint uid;
// 课程ID
uint cid;
// m为被其他学生发现并成功评价的实验操作错误数量
uint m;
// n为成功评价其他学生实验操作错误数量
uint n;
// 分数
uint score;
}
}
定义一个计算学员成绩的函数。函数入参是一个数组类型,而数组中每一个元素也是一个长度为4的一维数组,分别用于存储学员ID,课程ID,分数指标m,分数指标n、以及最终成绩。
// 设置完成后,计算学生成绩
function calc(uint[4][] memory data) public pure returns(string memory) {
Score[] memory scores = new ScoreUnsupported embed;
uint i = 0;
for (i = 0; i < data.length; i++) {
uint uid = data[i][0];
uint cid = data[i][1];
uint m = data[i][2];
uint n = data[i][3];
Score memory score = Score(uid, cid, m, n, 0);
scores[i] = score;
}
// 找出最高分数,以n-m的值作为参考值
Score memory maxScore = scores[0];
for (i = 1; i < scores.length; i++) {
int a1 = int(maxScore.n - maxScore.m);
int a2 = int(scores[i].n - scores[i].m);
if(a2 > a1) {
maxScore = scores[i];
}
}
// 计算alpha参数的公式为:100 = 80 + (n - m) * alpha
uint alpha = 20 / (maxScore.n - maxScore.m);
// 计算每个学生的成绩
for (i = 0; i < scores.length; i++ ){
scores[i].score = 80 + (scores[i].n - scores[i].m) * alpha;
}
// 返回学员的成绩,返回格式: uid_cid_score#uid_cid_score#uid_cid_score#...
string memory result = "";
for (i = 0; i < scores.length; i++ ){
Score memory sc = scores[i];
result = result.concat(uint2str(sc.uid).concat("_").concat(uint2str(sc.cid)).concat("_").concat(uint2str(sc.score)));
if (i != scores.length - 1) {
result = result.concat("#");
}
}
return result;
}
因为solidity不支持uint和string类型之间的强制类型转换,因此这里我们自定义了一个uint2str函数,用于将一个uint类型变量转换成string类型变量。
uint2str函数的实现如下:
function uint2str(uint i) internal pure returns (string memory c) {
if (i == 0) {
return "0";
}
uint j = i;
uint length;
while (j != 0){
length++;
j /= 10;
}
bytes memory b = new bytes(length);
uint k = length - 1;
while (i != 0){
b[k--] = byte(uint8(48 + i % 10));
i /= 10;
}
return string(b);
}
上面代码就是将uint类型的变量中每一个数字转换成对应的ascii码后,再封装到bytes里面,最终再将bytes转换成string类型。
另外,合约里面还需要实现字符串的拼接。遗憾的是,solidity并没有提供字符串拼接的工具。所以需要我们自己来实现字符串的拼接。
library StringUtils {
function concat(string memory self, string memory s) internal pure returns (string memory) {
bytes memory b = new bytes(_a.length + _b.length);
bytes memory _a = bytes(self);
bytes memory _b = bytes(s);
uint k = 0;
uint i = 0;
for (i = 0; i < _a.length; i++) {
b[k++] = _a[i];
}
for (i = 0; i < _b.length; i++) {
b[k++] = _b[i];
}
return string(b);
}
}
这里我们定义了一个库合约,用于提供字符串拼接的函数。然后在SmartScore合约中通过using..for语法将库合约引入进来。
using StringUtils for string;
主要功能点:1)卖方发布商品;2)读取商品信息;3)投标;4)揭标;5)仲裁者确定中标者;6)获取赢家信息;7)获取参与竞标的人数;8)管理投标合约资金的发放;
发布商品者(卖方):发布商品; 投标者或出价者(买方):进行投标和揭标操作; 仲裁者:负责确定中标者;
// 竞拍合约
contract Auction {
// 用于统计竞标商品数量,作为ID
uint public productIndex;
// 该mapping存储了商品Id与竞标获得者地址的对应关系
mapping(uint => address payable) productIdInStore;
// 该mapping存储了竞标获得者参与过的拍卖商品之间的关系
mapping(address => mapping(uint => Product)) stores;
// 竞标商品的状态,0代表开始拍卖,1代表交易成功,2代表交易不成功
enum ProductStatus {
Open, Sold, Unsold
}
// 竞标商品的使用状态,0代表未使用过,1代表使用过
enum ProductCondition {
New, Used
}
// 竞标人信息
struct Bid {
// 投标人
address bidder;
// 竞标商品ID
uint productId;
// 虚拟投标价格
uint value;
//是否已经揭标
bool revealed;
}
// 商品信息
struct Product {
// 商品id
uint id;
// 商品名称
string name;
// 商品类别
string category ;
// 图片Hash
string imageLink ;
// 图片描述信息的Hash
string descLink;
// 竞标开始时间
uint auctionStartTime;
// 竞标结束时间
uint auctionEndTime;
// 竞标初始价格
uint startPrice;
// 出价最高者
address payable highestBidder;
// 赢家得标的价格
uint highestBid ;
// 竞标价格第二名
uint secondHighestBid ;
// 竞标总人数
uint totalBids ;
// 竞标商品的状态
ProductStatus status;
// 竞标商品的新旧标识
ProductCondition condition;
// 存储所有竞标人的信息
mapping(address => mapping(bytes => Bid)) bids;
}
}
// 发布商品
function addProductToStore(string memory _name, string memory _category, string memory _imageLink, string memory _descLink, uint _auctionStartTime, uint _auctionEndTime ,uint _startPrice, uint _productCondition) public {
// 开始时间需要小于结束时间
require(_auctionStartTime < _auctionEndTime, "开始时间不能晚于结束时间");
// 商品ID自增
productIndex += 1;
// 创建Product实例
Product memory product = Product(productIndex,_name,_category,_imageLink,_descLink,_auctionStartTime,_auctionEndTime,_startPrice,address(0x0),0,0,0,ProductStatus.Open,ProductCondition(_productCondition));
// 保存商品
stores[msg.sender][productIndex] = product;
productIdInStore[productIndex] = msg.sender;
}
// 通过商品ID读取商品信息
function getProduct(uint _productId) public view returns (uint,string memory, string memory,string memory,string memory,uint ,uint,uint, ProductStatus, ProductCondition) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.id, product.name,product.category,product.imageLink,product.descLink,product.auctionStartTime,product.auctionEndTime,product.startPrice,product.status,product.condition);
}
投标必须要满足以下条件: 1)必须在竞拍时间内; 2)投标价格必须大于等于商品的拍卖价格; 3)投标人没有该商品的投标记录;
// 投标, 其中_bid参数代表投标人的ID,由外部生成传入
function bid(uint _productId, bytes memory _bid) payable public returns (bool) {
Product storage product = stores[productIdInStore[_productId]][_productId];
require(now >= product.auctionStartTime, "商品竞拍时间未到,暂未开始,请等待...");
require(now <= product.auctionEndTime,"商品竞拍已经结束");
require(msg.value >= product.startPrice,"设置的虚拟价格不能低于开标价格");
require(product.bids[msg.sender][_bid].bidder == address (0x0), "bidder的值必须为空");
// 设置投标人
product.bids[msg.sender][_bid] = Bid(msg.sender, _productId, msg.value, false);
// 投标人数递增
product.totalBids += 1;
// 返回投标成功
return true;
}
竞标结束后,由买方公告价格。最后以买方公告价格的最高者作为赢家。
// 揭标(买方公告价格)
function revealBid(uint _productId, string memory _amount, bytes memory _bid) public {
// 通过商品ID获取商品信息
Product storage product = stores[productIdInStore[_productId]][_productId];
// 检查条件:投标必须要结束了
require(now > product.auctionEndTime,"竞标尚未结束,未到公告价格时间");
// 获取投标人信息
Bid memory bidInfo = product.bids[msg.sender][_bid];
// 检查条件:投标人不为空
require(bidInfo.bidder > address (0x0), "钱包地址不存在");
// 检查条件:必须是未揭标状态
require(bidInfo.revealed == false, "已经揭标");
// 退款金额
uint refund;
// 公告价格
uint amount = stringToUint(_amount);
// 如果买方设置的虚拟价格低于公告价格,则执行退款操作
if (bidInfo.value < amount) {
refund = bidInfo.value;
} else {
// 如果第一个参与公告价格者,那么更新商品竞标信息,并将虚拟价格与实际竞标价格之间的差额退还给买方
if (address(product.highestBidder) == address (0x0)) {
// 设置当前出价者为最高价的竞拍者
product.highestBidder = msg.sender;
// 将公告价格作为最高价格
product.highestBid = amount;
// 将商品的起始拍卖价格作为第二高价格
product.secondHighestBid = product.startPrice;
// 将虚拟价格与公告价格之间的差额作为退款
refund = bidInfo.value - amount;
} else {
// 如果参与者不是第一个揭标,那么分为三种情况:
// 第一种情况:实际竞标价大于商品的最高竞标价;
// 第二种情况:实际竞标价小于商品的最高竞标价,但是大于第二高竞标价;
// 第三种情况:实际竞标价小于第二高竞标价;
if (amount > product.highestBid) {
// 将原来的最高价竞拍者修改为第二高价竞拍者
product.secondHighestBid = product.highestBid;
// 将原来最高竞拍价退还给最高价竞拍者
product.highestBidder.transfer(product.highestBid);
// 将当前出价者作为最高价竞拍者
product.highestBidder = msg.sender;
// 将当前出价作为最高价
product.highestBid = amount;
// 将虚拟价格与公告价格之间的差额作为退款
refund = bidInfo.value - amount;
} else if (amount > product.secondHighestBid) {
product.secondHighestBid = amount;
// 退还所有竞标款
refund = amount;
} else {
// 如果出价比第二高价还低的话,直接退还竞标款
refund = amount;
}
}
// 更新状态为已揭标
product.bids[msg.sender][_bid].revealed = true;
// 退款
if (refund > 0){
msg.sender.transfer(refund);
}
}
}
下面是stringToUint函数的实现:
function stringToUint(string memory s) pure private returns (uint) {
bytes memory b = bytes(s);
uint result = 0 ;
for (uint i = 0; i < b.length; i++ ){
if (uint(uint8(b[i])) >= 48 && uint(uint8(b[i])) <= 57){
result = result * 10 + (uint(uint8(b[i])) - 48);
}
}
return result;
}
仲裁者可以确定最终的中标方。
function finalizeAuction(uint _productId) public {
Product memory product = stores[productIdInStore[_productId]][_productId];
require(now > product.auctionEndTime, "当前时间必须大于竞拍结束时间");
require(product.status == ProductStatus.Open, "竞拍状态必须是开始状态");
require(product.highestBidder != msg.sender, "仲裁者不能够是最高出价者");
require(productIdInStore[_productId] != msg.sender, "仲裁者不能够是卖方");
// 如果竞拍人数为0,则无需做任何操作
if (product.totalBids == 0) {
product.status = ProductStatus.Unsold;
} else {
// 创建托管合约实例
Escrow escrow = (new Escrow).value(product.secondHighestBid)(_productId, product.highestBidder, productIdInStore[_productId], msg.sender);
// 竞拍合约包含了托管合约的引用
productEscrow[_productId] = address(escrow);
// 更新状态
product.status = ProductStatus.Sold;
// 中标方只需要支付第二高投标价格,差额会退还给中标方
uint refund = product.highestBid - product.secondHighestBid;
// 退还差额
product.highestBidder.transfer(refund);
}
// 更新商品信息
stores[productIdInStore[_productId]][_productId] = product;
}
上面代码创建了一个Escrow合约实例,该合约主要负责管理合约资金的发放。
contract Escrow {
// 商品ID
uint public productId;
// 买房(中标者)
address payable public buyer;
// 卖方
address payable public seller;
// 仲裁者
address public arbiter;
// 投标金额
uint public amount;
// 是否已经将投标金额支付给卖方或买方
bool public isDisbursed;
// 是否同意支付给卖方
mapping(address => bool) isReleased;
// 同意支付人数
uint public releaseCount;
// 是否同意退还给买方
mapping(address => bool) isRefunded;
// 同意退还人数
uint public refundCount;
event CreateEscrow(uint _productId, address _buyer, address _seller, address _arbiter);
event UnlockAmount(uint _productId, string _operation, address _operator);
event DisburseAmount(uint _productId, uint _amount, address _beneficiary);
constructor(uint _productId, address _buyer, address _seller, address _arbiter) payable public {
productId = _productId;
buyer = _buyer;
seller = _seller;
arbiter = _arbiter;
amount = msg.value;
isRefunded= false;
emit CreateEscrow(_productId, _buyer, _seller, _arbiter);
}
// 获取Escrow合约的详情
function getEscrowInfo() public view returns(address, address, address, bool, uint, uint) {
return(buyer, seller, arbiter, isDisbursed, releaseCount, refundCount);
}
// 只要有两位任意买方、卖方或仲裁者同意,那么就会向卖方发放投标金额
function releaseToSeller(address caller) public {
require(!isDisbursed, "支付状态必须是未支付");
if ((caller == buyer || caller == seller || caller == arbiter) && !isReleased[caller]) {
isReleased[caller] = true;
releaseCount += 1;
emit UnlockAmount(productId, "release", caller);
}
if (releaseCount == 2) {
seller.transfer(amount);
isDisbursed = true;
emit DisburseAmount(productId, amount, seller);
}
}
// 只要有两位任意买方、卖方或仲裁者同意,那么就会向买方退还投标金额
function refundToBuyer(address caller) public {
require(!isDisbursed, "支付状态必须是未支付");
if ((caller == buyer || caller == seller || caller == arbiter) && !isFunded[caller]) {
isRefunded[caller] = true;
refundCount += 1;
emit UnlockAmount(productId, "refund", caller);
}
if (releaseCount == 2) {
buyer.transfer(amount);
isDisbursed = true;
emit DisburseAmount(productId, amount, buyer);
}
}
}
// 获取竞标赢家信息
function getWinnerInfo(uint _productId) public view returns (address, uint ,uint) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.highestBidder, product.highestBid, product.secondHighestBid);
}
// 获取竞拍人数
function getTotalBids(uint _productId) public view returns(uint) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.highestBidder, product.highestBid, product.secondHighestBid);
}
上面就是竞标合约的所有代码介绍。
主要功能点:1)投注;2)开奖;3)退奖;4)获取奖池奖金;5)返回当前期数;6)返回中奖者地址;7)返回参与彩民的地址;
合约主要包含有四个属性:
contract Lottery {
address manager; // 管理员
address[] players; // 投了注的彩民
address winner; // 上期彩票的胜出者
uint256 round = 1; // 第几期
}
管理员属性可以在创建合约的时候进行初始化。
constructor() public {
manager = msg.sender;
}
假设每次投注只能投1个以太币。
// 投注
function play() public payable {
require(msg.value == 1 ether);
players.push(msg.sender);
}
开奖就是在投注彩民数组players中随机选出一个彩民,然后将合约的余额转账到该彩民的地址上。
这里需要先定义一个修饰器,用于限定只有管理员角色有权执行开奖的方法。
modifier onlyManager() {
require(manager == msg.sender);
_;
}
下面是开奖方法的实现逻辑:
function kaijiang() public payable {
// 生成players数组的随机下标
bytes memory v1 = abi.encodePacked(block.difficulty, now, players.length);
bytes32 v2 = keccak256(v1);
uint v3 = uint256(v2) % players.length;
// 获取中奖者
winner = players[v3];
// 把奖池的金额转账给中奖者
winner.transfer(address(this).balance);
// 清空plays
delete players;
// 期数加1
round++;
}
只有管理员才可以发起退奖操作。
// 退奖
function tuiJiang() public onlyManager {
require(players.length != 0);
// 把奖池的金额退还给每一个玩家
for (uint i = 0; i < players.length; i++) {
players[i].transfer(1 ether);
}
// 清空plays
delete players;
// 期数加1
round++;
}
值得注意的是,上面开奖和退奖方法中,都会执行
transfer函数执行转账操作。上面代码会存在代码“重入”的风险。所以作为改善措施,应该按照Checks-Effects-Interactions模式编写函数代码。
优化后的代码:
function kaijiang() public payable {
// 生成随机下标
bytes memory v1 = abi.encodePacked(block.difficulty, now, players.length);
bytes32 v2 = keccak256(v1);
uint v3 = uint256(v2) % players.length;
// 获取中奖者
winner = players[v3];
// 清空plays
delete players;
// 期数加1
round++;
// 把奖池的金额转账给中奖者
winner.transfer(address(this).balance);
}
// 退奖
function tuiJiang() public onlyManager {
require(players.length != 0);
// 清空plays
delete players;
// 期数加1
round++;
// 把奖池的金额退还给每一个玩家
for (uint i = 0; i < players.length; i++) {
players[i].transfer(1 ether);
}
}
上面代码将transfer操作放在方法最后执行。另外,上面代码通过delete players删除所有参与的玩家信息,这样会存在一定的风险:如果数组较大,可能会超过区块gas限制,从而引发out of gas异常。其实对于动态数组而言,使用length属性修改数组大小也可以达到同样效果。
players.lenght = 0;
// 获取奖金池的金额
function getAmount() public view returns(uint256) {
return address(this).balance;
}
// 获取管理员地址
function getManagerAddress() public view returns(address) {
return manager;
}
// 返回当前期数
function getRound() public view returns(uint256) {
return round;
}
// 返回中奖者地址
function getWinner() public view returns(address) {
return winner;
}
// 返回参与彩民的地址
function getPlays() public view returns(address[]) {
return players;
}
二、众筹合约 主要功能点:1)创建众筹合约;2)获取所有众筹合约;3)获取发起者的众筹合约;4)获取参与者的众筹合约;5)参与众筹;6)获取已筹集到的金额;7)获取所有参与者;8)退款;9)众筹成功后,发起花费请求;10)审批花费请求;11)完成花费请求;12)获取众筹的剩余时间;13)获取参与者的数量;14)获取花费申请的详情;
2.1 创建众筹合约
第一步:定义合约。
contract Funding {
// 合约发起者
address public manager;
// 众筹项目名称
string public projectName;
// 目标金额
uint public targetMoney;
// 支持金额
uint public supportMoney;
// 项目结束时间,单位秒
uint public endTime;
// 参与者
address payable[] investors;
constructor(string memory _projectName, uint _targetMoney, uint _supportMoney, uint _duration, address _creator) public {
manager = _creator;
projectName = _projectName;
targetMoney = _targetMoney;
supportMoney = _supportMoney;
endTime = now + _duration;
}
}
第二步:定义一个工厂合约,该合约保存了所有的众筹实例,并提供了操作众筹合约的方法;
contract FundingFactory {
// 平台管理员
address public platformManager;
// 所有的众筹合约
address[] allFundings;
// 自己创建的合约集合,key代表合约发起者地址,value代表合约机制的集合
mapping(address => address[]) creatorFundings;
constructor() public {
platformManager = msg.sender;
}
}
第三步:定义发起众筹的方法;
function createFunding(string memory _projectName, uint _targetMoney, uint _supportMoney, uint _duration) public {
Funding funding = new Funding(_projectName, _targetMoney, _supoortMoney, _duration, msg.sender);
allFundings.push(address(funding));
creatorFundings[msg.sender].push(address(funding));
}
接着在FundingFactory中添加其他操作合约的方法。
// 获取所有众筹合约
function getAllFundings() public view returns(address[] memory) {
return allFundings;
}
// 获取发起者的众筹合约
function getCreatorFundings() public view returns(address[] memory) {
return creatorFundings[msg.sender];
}
第一步:为了便于维护参与者的众筹记录,我们定义一个合约,专门用来记录所有参与者参与过的众筹合约。
contract SupportFunding {
// 记录参与过的众筹
mapping(address => address[]) supportFundings;
function setSupportFunding(address _supportor, address funding) public {
supportFundings[_supportor].push(funding);
}
function getSupportFunding(address _supportor) public {
return supportFundings[_supportor];
}
}
第二步:在FundingFactory中增加supportFunding属性,并提供获取参与者参与过的合约方法;
SupportFunding supportFunding;
// 获取参与者参与过的众筹合约
function getSupportFunding() public view return(address[] memory) {
return supportFunding.getFundings(msg.sender);
}
第一步:在Funding合约增加一个supportFunding属性,在该合约的构造函数中对该属性执行初始化;
SupportFunding supportFunding;
constructor(string memory _projectName, uint _targetMoney, uint _supportMoney, uint _duration, address _creator, SupportFunding _supportFunding) public {
manager = _creator;
projectName = _projectName;
targetMoney = _targetMoney;
supportMoney = _supportMoney;
endTime = now + _duration;
supportFunding = _supportFunding;
}
}
第二步:定义参与众筹方法;
// 记录是否投资人,key代表参与者,value代表是否投资人
mapping(address => bool) isInvestors;
// 参与众筹
function invest() public payable returns(uint) {
// 约束条件:发送币的数量必须要等于支持金额
require(msg.value == supportMoney);
// 记录投资人
investors.push(msg.sender);
// 记录参与者为投资人
isInvestors[msg.sender] = true;
// 记录参与者参与过的合约(当前合约)
supportFunding.setFunding(msg.sender, address(this));
}
// 获取已经筹集到的金额,即当前合约的余额
function getBalance() public view returns(uint) {
return address(this).balance;
}
// 获取所有的参与者
function getInvestors() public view returns(address payable[] memory) {
return investors;
}
如果合约还没结束,允许投资人退出众筹。所以我们在Funding合约中定义一个修饰器,该修饰器用于添加方法的约束条件。
modifier onlyManager {
require(msg.sender == manager);
}
然后定义退款方法,并使用上面定义好的修饰器。
// 退款
function refund() onlyManager public {
// 循环遍历所有投资人,并向其转账
for (uint i = 0; i < investors.length; i++) {
investors[i].transfer(supportMoney);
}
// 清空投资人数组
delete investors;
}
如果众筹成功,管理员在使用众筹资金前,需要发起花费。
第一步:在Funding合约中定义一个结构体,用于记录花费的详情。
// 花费申请状态,0代表申请中,1代表已批准,2代表已完成
enum RequestStatus {
Voting, Approved, Completed
}
// 记录花费申请信息
struct Request {
// 花费目的
string purpose;
// 申请花费的金额
uint cost;
// 转账给商家的地址
address payable seller;
// 花费申请赞同的票数
uint approveCount;
// 申请状态
RequestStatus status;
// 参与者的投票状态,true代表已经投票,false代表还没投票
mapping(address => bool) hasVoted;
}
第二步:在Funding合约中增加一个属性,该属性用于记录管理员发起的花费请求。
Request[] requests
第三步:定义申请花费的方法;
// 发起花费申请
function createRequest(string memory _purpose, uint _cost, address payable _seller) onlyManager public {
Request memory req = Request({
purpose: _purpose,
cost: _cose * 10**18, // 将花费金额转换成单位wei
seller: _seller,
approveCount: 0,
status: RequestStatus.Voting
});
requests.push(req);
}
审核通过条件:1)消息发起者是投资人;2)投资人之前没有投过票;
// 对指定索引的申请进行审核
// 参数i代表数组的索引
function approveRequest(uint i) public {
Request storage req = requests[i];
// 检查是否是投资人
require(isInvestors[msg.sender] == true);
// 检查是否投过票
require(req.hasVoted[msg.sender] == false);
// 赞同数增加
req.approveCount++;
// 记录该投资人已经投过票
req.hasVoted[msg.sender] = true;
// 如果票数过半,更新申请状态为Approved
if (req.approveCount * 2 > investors.length) {
req.status = RequestStatus.Approved;
}
}
结束花费申请的条件:1)合约余额必须要大于等于申请的花费金额;2)赞同票数要过半;
// 完成花费
function finalizeRequest(uint i) onlyManager public {
Request storage req = requests[i];
// 合约的余额必须要足够支付花费
require(address(this).balance >= req.cost);
// 申请状态必须为已通过
require(req.status == RequestStatus.Approved);
// 向商家转账
req.seller.transfer(req.cose);
// 更新申请状态为已完成
req.status = RequestStatus.Completed;
}
function getLeftTime() public view returns(uint) {
return endTime - now;
}
function getRequestCount() public view returns(uint) {
return requests.length;
}
function getRequest(uint i) public view returns(string memory, uint, address, uint, RequestStatus) {
Request storage req = requests[i];
return(req.purpose, req.cost, req.seller, req.approveCount, req.status);
}
到这里为止,整个众筹合约的核心代码已经完成。
该合约实现了根据每一个学员的计算分数指标m和n计算出他们的最终成绩。
首先在合约中定义一个结构体,用于封装学员相关的信息。
contract SmartScore {
struct Score {
// 学生ID
uint uid;
// 课程ID
uint cid;
// m为被其他学生发现并成功评价的实验操作错误数量
uint m;
// n为成功评价其他学生实验操作错误数量
uint n;
// 分数
uint score;
}
}
定义一个计算学员成绩的函数。函数入参是一个数组类型,而数组中每一个元素也是一个长度为4的一维数组,分别用于存储学员ID,课程ID,分数指标m,分数指标n、以及最终成绩。
// 设置完成后,计算学生成绩
function calc(uint[4][] memory data) public pure returns(string memory) {
Score[] memory scores = new ScoreUnsupported embed;
uint i = 0;
for (i = 0; i < data.length; i++) {
uint uid = data[i][0];
uint cid = data[i][1];
uint m = data[i][2];
uint n = data[i][3];
Score memory score = Score(uid, cid, m, n, 0);
scores[i] = score;
}
// 找出最高分数,以n-m的值作为参考值
Score memory maxScore = scores[0];
for (i = 1; i < scores.length; i++) {
int a1 = int(maxScore.n - maxScore.m);
int a2 = int(scores[i].n - scores[i].m);
if(a2 > a1) {
maxScore = scores[i];
}
}
// 计算alpha参数的公式为:100 = 80 + (n - m) * alpha
uint alpha = 20 / (maxScore.n - maxScore.m);
// 计算每个学生的成绩
for (i = 0; i < scores.length; i++ ){
scores[i].score = 80 + (scores[i].n - scores[i].m) * alpha;
}
// 返回学员的成绩,返回格式: uid_cid_score#uid_cid_score#uid_cid_score#...
string memory result = "";
for (i = 0; i < scores.length; i++ ){
Score memory sc = scores[i];
result = result.concat(uint2str(sc.uid).concat("_").concat(uint2str(sc.cid)).concat("_").concat(uint2str(sc.score)));
if (i != scores.length - 1) {
result = result.concat("#");
}
}
return result;
}
因为solidity不支持uint和string类型之间的强制类型转换,因此这里我们自定义了一个uint2str函数,用于将一个uint类型变量转换成string类型变量。
uint2str函数的实现如下:
function uint2str(uint i) internal pure returns (string memory c) {
if (i == 0) {
return "0";
}
uint j = i;
uint length;
while (j != 0){
length++;
j /= 10;
}
bytes memory b = new bytes(length);
uint k = length - 1;
while (i != 0){
b[k--] = byte(uint8(48 + i % 10));
i /= 10;
}
return string(b);
}
上面代码就是将uint类型的变量中每一个数字转换成对应的ascii码后,再封装到bytes里面,最终再将bytes转换成string类型。
另外,合约里面还需要实现字符串的拼接。遗憾的是,solidity并没有提供字符串拼接的工具。所以需要我们自己来实现字符串的拼接。
library StringUtils {
function concat(string memory self, string memory s) internal pure returns (string memory) {
bytes memory b = new bytes(_a.length + _b.length);
bytes memory _a = bytes(self);
bytes memory _b = bytes(s);
uint k = 0;
uint i = 0;
for (i = 0; i < _a.length; i++) {
b[k++] = _a[i];
}
for (i = 0; i < _b.length; i++) {
b[k++] = _b[i];
}
return string(b);
}
}
这里我们定义了一个库合约,用于提供字符串拼接的函数。然后在SmartScore合约中通过using..for语法将库合约引入进来。
using StringUtils for string;
主要功能点:1)卖方发布商品;2)读取商品信息;3)投标;4)揭标;5)仲裁者确定中标者;6)获取赢家信息;7)获取参与竞标的人数;8)管理投标合约资金的发放;
发布商品者(卖方):发布商品; 投标者或出价者(买方):进行投标和揭标操作; 仲裁者:负责确定中标者;
// 竞拍合约
contract Auction {
// 用于统计竞标商品数量,作为ID
uint public productIndex;
// 该mapping存储了商品Id与竞标获得者地址的对应关系
mapping(uint => address payable) productIdInStore;
// 该mapping存储了竞标获得者参与过的拍卖商品之间的关系
mapping(address => mapping(uint => Product)) stores;
// 竞标商品的状态,0代表开始拍卖,1代表交易成功,2代表交易不成功
enum ProductStatus {
Open, Sold, Unsold
}
// 竞标商品的使用状态,0代表未使用过,1代表使用过
enum ProductCondition {
New, Used
}
// 竞标人信息
struct Bid {
// 投标人
address bidder;
// 竞标商品ID
uint productId;
// 虚拟投标价格
uint value;
//是否已经揭标
bool revealed;
}
// 商品信息
struct Product {
// 商品id
uint id;
// 商品名称
string name;
// 商品类别
string category ;
// 图片Hash
string imageLink ;
// 图片描述信息的Hash
string descLink;
// 竞标开始时间
uint auctionStartTime;
// 竞标结束时间
uint auctionEndTime;
// 竞标初始价格
uint startPrice;
// 出价最高者
address payable highestBidder;
// 赢家得标的价格
uint highestBid ;
// 竞标价格第二名
uint secondHighestBid ;
// 竞标总人数
uint totalBids ;
// 竞标商品的状态
ProductStatus status;
// 竞标商品的新旧标识
ProductCondition condition;
// 存储所有竞标人的信息
mapping(address => mapping(bytes => Bid)) bids;
}
}
// 发布商品
function addProductToStore(string memory _name, string memory _category, string memory _imageLink, string memory _descLink, uint _auctionStartTime, uint _auctionEndTime ,uint _startPrice, uint _productCondition) public {
// 开始时间需要小于结束时间
require(_auctionStartTime < _auctionEndTime, "开始时间不能晚于结束时间");
// 商品ID自增
productIndex += 1;
// 创建Product实例
Product memory product = Product(productIndex,_name,_category,_imageLink,_descLink,_auctionStartTime,_auctionEndTime,_startPrice,address(0x0),0,0,0,ProductStatus.Open,ProductCondition(_productCondition));
// 保存商品
stores[msg.sender][productIndex] = product;
productIdInStore[productIndex] = msg.sender;
}
// 通过商品ID读取商品信息
function getProduct(uint _productId) public view returns (uint,string memory, string memory,string memory,string memory,uint ,uint,uint, ProductStatus, ProductCondition) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.id, product.name,product.category,product.imageLink,product.descLink,product.auctionStartTime,product.auctionEndTime,product.startPrice,product.status,product.condition);
}
投标必须要满足以下条件: 1)必须在竞拍时间内; 2)投标价格必须大于等于商品的拍卖价格; 3)投标人没有该商品的投标记录;
// 投标, 其中_bid参数代表投标人的ID,由外部生成传入
function bid(uint _productId, bytes memory _bid) payable public returns (bool) {
Product storage product = stores[productIdInStore[_productId]][_productId];
require(now >= product.auctionStartTime, "商品竞拍时间未到,暂未开始,请等待...");
require(now <= product.auctionEndTime,"商品竞拍已经结束");
require(msg.value >= product.startPrice,"设置的虚拟价格不能低于开标价格");
require(product.bids[msg.sender][_bid].bidder == address (0x0), "bidder的值必须为空");
// 设置投标人
product.bids[msg.sender][_bid] = Bid(msg.sender, _productId, msg.value, false);
// 投标人数递增
product.totalBids += 1;
// 返回投标成功
return true;
}
竞标结束后,由买方公告价格。最后以买方公告价格的最高者作为赢家。
// 揭标(买方公告价格)
function revealBid(uint _productId, string memory _amount, bytes memory _bid) public {
// 通过商品ID获取商品信息
Product storage product = stores[productIdInStore[_productId]][_productId];
// 检查条件:投标必须要结束了
require(now > product.auctionEndTime,"竞标尚未结束,未到公告价格时间");
// 获取投标人信息
Bid memory bidInfo = product.bids[msg.sender][_bid];
// 检查条件:投标人不为空
require(bidInfo.bidder > address (0x0), "钱包地址不存在");
// 检查条件:必须是未揭标状态
require(bidInfo.revealed == false, "已经揭标");
// 退款金额
uint refund;
// 公告价格
uint amount = stringToUint(_amount);
// 如果买方设置的虚拟价格低于公告价格,则执行退款操作
if (bidInfo.value < amount) {
refund = bidInfo.value;
} else {
// 如果第一个参与公告价格者,那么更新商品竞标信息,并将虚拟价格与实际竞标价格之间的差额退还给买方
if (address(product.highestBidder) == address (0x0)) {
// 设置当前出价者为最高价的竞拍者
product.highestBidder = msg.sender;
// 将公告价格作为最高价格
product.highestBid = amount;
// 将商品的起始拍卖价格作为第二高价格
product.secondHighestBid = product.startPrice;
// 将虚拟价格与公告价格之间的差额作为退款
refund = bidInfo.value - amount;
} else {
// 如果参与者不是第一个揭标,那么分为三种情况:
// 第一种情况:实际竞标价大于商品的最高竞标价;
// 第二种情况:实际竞标价小于商品的最高竞标价,但是大于第二高竞标价;
// 第三种情况:实际竞标价小于第二高竞标价;
if (amount > product.highestBid) {
// 将原来的最高价竞拍者修改为第二高价竞拍者
product.secondHighestBid = product.highestBid;
// 将原来最高竞拍价退还给最高价竞拍者
product.highestBidder.transfer(product.highestBid);
// 将当前出价者作为最高价竞拍者
product.highestBidder = msg.sender;
// 将当前出价作为最高价
product.highestBid = amount;
// 将虚拟价格与公告价格之间的差额作为退款
refund = bidInfo.value - amount;
} else if (amount > product.secondHighestBid) {
product.secondHighestBid = amount;
// 退还所有竞标款
refund = amount;
} else {
// 如果出价比第二高价还低的话,直接退还竞标款
refund = amount;
}
}
// 更新状态为已揭标
product.bids[msg.sender][_bid].revealed = true;
// 退款
if (refund > 0){
msg.sender.transfer(refund);
}
}
}
下面是stringToUint函数的实现:
function stringToUint(string memory s) pure private returns (uint) {
bytes memory b = bytes(s);
uint result = 0 ;
for (uint i = 0; i < b.length; i++ ){
if (uint(uint8(b[i])) >= 48 && uint(uint8(b[i])) <= 57){
result = result * 10 + (uint(uint8(b[i])) - 48);
}
}
return result;
}
仲裁者可以确定最终的中标方。
function finalizeAuction(uint _productId) public {
Product memory product = stores[productIdInStore[_productId]][_productId];
require(now > product.auctionEndTime, "当前时间必须大于竞拍结束时间");
require(product.status == ProductStatus.Open, "竞拍状态必须是开始状态");
require(product.highestBidder != msg.sender, "仲裁者不能够是最高出价者");
require(productIdInStore[_productId] != msg.sender, "仲裁者不能够是卖方");
// 如果竞拍人数为0,则无需做任何操作
if (product.totalBids == 0) {
product.status = ProductStatus.Unsold;
} else {
// 创建托管合约实例
Escrow escrow = (new Escrow).value(product.secondHighestBid)(_productId, product.highestBidder, productIdInStore[_productId], msg.sender);
// 竞拍合约包含了托管合约的引用
productEscrow[_productId] = address(escrow);
// 更新状态
product.status = ProductStatus.Sold;
// 中标方只需要支付第二高投标价格,差额会退还给中标方
uint refund = product.highestBid - product.secondHighestBid;
// 退还差额
product.highestBidder.transfer(refund);
}
// 更新商品信息
stores[productIdInStore[_productId]][_productId] = product;
}
上面代码创建了一个Escrow合约实例,该合约主要负责管理合约资金的发放。
contract Escrow {
// 商品ID
uint public productId;
// 买房(中标者)
address payable public buyer;
// 卖方
address payable public seller;
// 仲裁者
address public arbiter;
// 投标金额
uint public amount;
// 是否已经将投标金额支付给卖方或买方
bool public isDisbursed;
// 是否同意支付给卖方
mapping(address => bool) isReleased;
// 同意支付人数
uint public releaseCount;
// 是否同意退还给买方
mapping(address => bool) isRefunded;
// 同意退还人数
uint public refundCount;
event CreateEscrow(uint _productId, address _buyer, address _seller, address _arbiter);
event UnlockAmount(uint _productId, string _operation, address _operator);
event DisburseAmount(uint _productId, uint _amount, address _beneficiary);
constructor(uint _productId, address _buyer, address _seller, address _arbiter) payable public {
productId = _productId;
buyer = _buyer;
seller = _seller;
arbiter = _arbiter;
amount = msg.value;
isRefunded= false;
emit CreateEscrow(_productId, _buyer, _seller, _arbiter);
}
// 获取Escrow合约的详情
function getEscrowInfo() public view returns(address, address, address, bool, uint, uint) {
return(buyer, seller, arbiter, isDisbursed, releaseCount, refundCount);
}
// 只要有两位任意买方、卖方或仲裁者同意,那么就会向卖方发放投标金额
function releaseToSeller(address caller) public {
require(!isDisbursed, "支付状态必须是未支付");
if ((caller == buyer || caller == seller || caller == arbiter) && !isReleased[caller]) {
isReleased[caller] = true;
releaseCount += 1;
emit UnlockAmount(productId, "release", caller);
}
if (releaseCount == 2) {
seller.transfer(amount);
isDisbursed = true;
emit DisburseAmount(productId, amount, seller);
}
}
// 只要有两位任意买方、卖方或仲裁者同意,那么就会向买方退还投标金额
function refundToBuyer(address caller) public {
require(!isDisbursed, "支付状态必须是未支付");
if ((caller == buyer || caller == seller || caller == arbiter) && !isFunded[caller]) {
isRefunded[caller] = true;
refundCount += 1;
emit UnlockAmount(productId, "refund", caller);
}
if (releaseCount == 2) {
buyer.transfer(amount);
isDisbursed = true;
emit DisburseAmount(productId, amount, buyer);
}
}
}
// 获取竞标赢家信息
function getWinnerInfo(uint _productId) public view returns (address, uint ,uint) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.highestBidder, product.highestBid, product.secondHighestBid);
}
// 获取竞拍人数
function getTotalBids(uint _productId) public view returns(uint) {
Product memory product = stores[productIdInStore[_productId]][_productId];
return (product.highestBidder, product.highestBid, product.secondHighestBid);
}
上面就是竞标合约的所有代码介绍。
<100 subscribers
<100 subscribers
No activity yet