熟悉比特币和以太坊的人应该都知道,在比特币中有2种类型的地址,1开头的是P2PKH,就是个人地址,3开头的是P2SH,一般是一个多签地址。所以在原生上比特币就支持多签。多签的一个优势就是可以多方对一笔付款达成共识,才能支付成功。比如3个人合伙开公司,他们的对外付款是比特币,为了防止管理财务的人作恶,于是他们可以创建2/3多签的地址,每个人持有一个私钥,对于每一笔付款,必须任意2个人都签名了才能支付出去。

比特币上的这个多签地址在以太坊上是没有原生支持的!以太坊最大的优点是支持图灵完备的智能合约,所以多签功能需要靠智能合约来实现。

为了简化代码,我们的需求是这样的:创建一个AB两个用户创建2/2的多签合约,该合约支持指定的ERC20 Token的支付。当某需要对外付款时,A用户调用合约,发起对C的转账n个Token,B用户也必须调用合约,发起对C的转账n个Token,只有A和B都调用了合约后,合约才会真的付款。其他用户发起转账无效。

根据以上需求,我改了一款极其简单的多签合约。代码如下:

pragma solidity ^0.4.24;

interface Token {
function balanceOf(address _owner) public view returns (uint256 );
function transfer(address _to, uint256 _value) public ;
} contract MultiSig {
address private addrA;
address private addrB;
address private addrToken; struct Permit {
bool addrAYes;
bool addrBYes;
} mapping (address => mapping (uint => Permit)) private permits; event Transfer(address indexed from, address indexed to, uint256 value); constructor(address a, address b, address tokenAddress) public{
addrA = a;
addrB = b;
addrToken = tokenAddress;
}
function getAddrs() public view returns(address, address,address) {
return (addrA, addrB,addrToken);
}
function transferTo(address to, uint amount) public{
Token token = Token(addrToken);
require(token.balanceOf(this) >= amount); if (msg.sender == addrA) {
permits[to][amount].addrAYes = true;
} else if (msg.sender == addrB) {
permits[to][amount].addrBYes = true;
} else {
require(false);
} if (permits[to][amount].addrAYes == true && permits[to][amount].addrBYes == true) {
token.transfer(to, amount);
permits[to][amount].addrAYes = false;
permits[to][amount].addrBYes = false;
}
emit Transfer(msg.sender, to, amount);
}
}

以上代码十分简陋,功能十分有限,而且需要在etherscan或者remix上调用,对用户来说十分不友好,于是我想到了可以按ERC20标准接口对这个多签合约进行改造。改造后的合约看起来像是一个Token,但是本质上是一个多签地址。A B用户都可以使用imtoken或者KCash之类的支持ERC20的钱包APP进行多签,而不需要任何复杂的技能。所以我改写后的多签合约如下:

pragma solidity ^0.4.24;

interface IERC20 {
function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function totalSupply() external view returns (uint256); function balanceOf(address who) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value);
} contract MultiSig is IERC20 {
address private addrA;
address private addrB;
address private addrToken; struct Permit {
bool addrAYes;
bool addrBYes;
} mapping (address => mapping (uint => Permit)) private permits; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); uint public totalSupply = 10*10**26;
uint8 constant public decimals = 18;
string constant public name = "MutiSigPTN";
string constant public symbol = "MPTN"; function approve(address spender, uint256 value) external returns (bool){
return false;
} function transferFrom(address from, address to, uint256 value) external returns (bool){
return false;
} function totalSupply() external view returns (uint256){
IERC20 token = IERC20(addrToken);
return token.totalSupply();
} function allowance(address owner, address spender) external view returns (uint256){
return 0;
} constructor(address a, address b, address tokenAddress) public{
addrA = a;
addrB = b;
addrToken = tokenAddress;
}
function getAddrs() public view returns(address, address,address) {
return (addrA, addrB,addrToken);
}
function transfer(address to, uint amount) public returns (bool){
IERC20 token = IERC20(addrToken);
require(token.balanceOf(this) >= amount); if (msg.sender == addrA) {
permits[to][amount].addrAYes = true;
} else if (msg.sender == addrB) {
permits[to][amount].addrBYes = true;
} else {
require(false);
} if (permits[to][amount].addrAYes == true && permits[to][amount].addrBYes == true) {
token.transfer(to, amount);
permits[to][amount].addrAYes = false;
permits[to][amount].addrBYes = false;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function balanceOf(address _owner) public view returns (uint) {
IERC20 token = IERC20(addrToken);
if (_owner==addrA || _owner==addrB){
return token.balanceOf(this);
}
return 0;
}
}

这里我需要特别指出的是decimals这个属性必须与所支持的ERC20 Token一致,这样钱包才会算出正确的转账金额。另外imtoken的缓存刷新比较慢,并不是部署了合约后马上就能搜索到的。以上代码都实测没问题。

最新文章

  1. linux下配置yun源
  2. DatePickerDialog、AutoCompleteTextView
  3. mysql:SQL语句操作数据库中表和字段的COMMENT值
  4. Hadoop streaming模式获取jobconf参数
  5. android SQLite使用SQLiteOpenHelper类对数据库进行操作
  6. MyBatis之六:缓存
  7. 【玩转微信公众平台之六】 搭建新浪SAEserver
  8. HTTP协议1之协议详解--转
  9. spring JdbcTemplate 在itest 开源测试管理项目中的浅层(5个使用场景)封装
  10. [转]webstorm中js文件被识别成txt类型
  11. [转帖]御界预警:3700余台SQL服务器被入侵挖矿 或导致严重信息泄露事件
  12. 常见排序算法JAVA实现
  13. 简单(基本)的风光摄影照片后期处理-新手教程-ps照片后期基本处理
  14. Hadoop生态圈-CentOs7.5单机部署ClickHouse
  15. PHP在linux读取word文档
  16. NLP-训练个model出来写诗
  17. uva10003
  18. 结合Python代码介绍音符起始点检测 (onset detection)
  19. C++异常安全
  20. JavaWeb学习笔记(三)—— Servlet

热门文章

  1. Python爬虫9-request包介绍及应用
  2. mpvue小程序开发之 wx.getUserInfo获取用户信息授权
  3. 知名区块链人脸识别公司iFace Chain [爱妃链] 支招,如何防止钱包数字币被盗...
  4. 开源图像标注工具labelme的安装使用及汉化
  5. PhotoPickerDemo【PhotoPicker0.9.8的个性化修改以及使用(内部glide版本号是3.7.0)】
  6. LindDotNetCore~Ocelot实现微服务网关
  7. 通用查询设计思想(2)- 基于ADO.Net的设计
  8. 图解Go语言内存分配
  9. 博主新建Linux学习交流群,欢迎广大大神入驻~
  10. Java基础系列-equals方法和hashCode方法