熟悉比特币和以太坊的人应该都知道,在比特币中有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的缓存刷新比较慢,并不是部署了合约后马上就能搜索到的。以上代码都实测没问题。

一个简单的以太坊合约让imtoken支持多签的更多相关文章

  1. go打造以太坊合约测试框架

    传送门: 柏链项目学院 1 以太坊智能合约编译 以太坊智能合约编写使用solidity语言,一般情况下我们会在remix环境下进行编译测试,在线环境相对比较稳定.如果不想用在线环境,那我们就需要自己动 ...

  2. 以太坊智能合约Hello World示例程序

    简介 以太坊(Ethereum)是一提供个智能合约(smart contract)功能的公共区块链(BlockChain)平台. 本文介绍了一个简单的以太坊智能合约的开发过程. 开发环境 在以太坊上开 ...

  3. [币严区块链]简单易懂的以太坊(ETH)智能合约开发入门教程

    以太坊(Ethereum)是一提供个智能合约(smart contract)功能的公共区块链(BlockChain)平台. 本文介绍了一个简单的以太坊智能合约的开发过程. 开发环境 在以太坊上开发应用 ...

  4. c#实战开发:用.net core开发一个简单的Web以太坊钱包 (六)

    今天就来开发一个C# 版的简易钱包 先回顾以前的内容 c#实战开发:以太坊Geth 命令发布智能合约 (五) c#实战开发:以太坊Geth 常用命令 (四) c#实战开发:以太坊钱包快速同步区块和钱包 ...

  5. 如何在以太坊上搭建一个Dapp?

    原创: 前哨小兵甲 区块链前哨 昨天 策划|Tina作者|Mahesh Murthy俗话说,实践出真知!对于开发人员来说,最好的学习办法就是亲自动手做一个小项目.所以,接下来我们将会以一个投票程序为例 ...

  6. 区块链入门到实战(27)之以太坊(Ethereum) – 智能合约开发

    智能合约的优点 与传统合同相比,智能合约有一些显著优点: 不需要中间人 费用低 代码就是规则 区块链网络中有多个备份,不用担心丢失 避免人工错误 无需信任,就可履行协议 匿名履行协议 以太坊(Ethe ...

  7. 基于以太坊开发的类似58同城的DApp开发与应用案例

    今天,Origin开发团队很高兴地宣布在以太坊Rinkeby测试网络上推出Origin Protocol Demo DApp ! 在这个DApp中,你可以在不同垂直行业的solidarity econ ...

  8. C#以太坊基础入门

    在这一部分,我们将使用C#开发一个最简单的.Net控制台应用,来接入以太坊节点,并打印 所连接节点旳版本信息.通过这一部分的学习,你将掌握以下技能: 如何使用节点仿真器 如何在命令行访问以太坊节点 如 ...

  9. Geth命令用法-参数详解 and 以太坊源码文件目录

    本文是对以太坊客户端geth命令的解析 命令用法 geth [选项] 命令 [命令选项] [参数-] 版本 1.7.3-stable 命令 account 管理账户 attach 启动交互式JavaS ...

随机推荐

  1. Spring Bean 生命周期测试

    本文代码GitHub地址 Bean的生命周期是开始创建到销毁的过程.需要实现相关的类BeanNameAware   ,DisposableBean, InitializingBean ,并注册Inst ...

  2. Javascript高级编程学习笔记(96)—— WebGL(2) 类型化视图

    类型化视图 类型化视图一般也被认为是一种类型化数组. 因为其元素必须是某种特定的数据类型,类型化视图都继承自 Dataview Int8Array: 表示8位二补整数(即二进制补数) Uint8Arr ...

  3. Oracle AWR报告生成和性能分析

    目录 一.AWE报告生成步骤 1.1 工具选择 1.2 自动创建快照 1.3 手工创建快照 1.4 生成AWR报告 二.AWR报告分析 2.1 AWR之DB Time 2.2 AWR之load_pro ...

  4. 【Android Studio安装部署系列】十二、Android studio代码混淆

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 为什么需要代码混淆呢?原因很简单,你的apk很容易被反编译出来,你写的代码都会被看到,因此我们需要在编译过程中对代码进行一定程度的混 ...

  5. okhttputils【 Android 一个改善的okHttp封装库】使用(一)

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 本文使用的OKHttp封装库是张鸿洋(鸿神)写的,因为在项目中一直使用这个库,所以对于一些常用的请求方式都验证过,所以特此整理下. ...

  6. Jenkins集群搭建

    Jenkins的目的是加快CI/CD的步伐,集群的搭建也不是必须的,当一台服务器的构建速度受到限制下,可以考虑使用主从并发构建,来加快构建速度.作为一款超级管家的角色,Jenkins的资料非常多,Je ...

  7. springboot~Compiler时开启插件的注解功能

    对于IJ这个IDE工具来说,我们会安装一些插件来帮助我们更好的进行开发,像lombok就是一款不错的插件,使用注解的方式在项目编译时帮助我们生成代码,像getter,setter,tostring等等 ...

  8. Flink-Kafka-Connector Flink结合Kafka实战

    戳更多文章: 1-Flink入门 2-本地环境搭建&构建第一个Flink应用 3-DataSet API 4-DataSteam API 5-集群部署 6-分布式缓存 7-重启策略 8-Fli ...

  9. 微信小程序开发05-日历组件的实现

    接上文:微信小程序开发04-打造自己的UI库 github地址:https://github.com/yexiaochai/wxdemo 我们这里继续实现我们的日历组件,这个日历组件稍微有点特殊,算是 ...

  10. 深入理解Linux内核 学习笔记(3)

    第三章 进程 可以看到很多熟悉的结构体 进程状态: 可运行状态(TASK_ RUNNING) 进程要么在CPU上执行,要么准备执行. 可巾断的等待状态(TASK_ INTERRUPTIBLE) 进程被 ...