Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MultiPartyEscrow contract #39

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions contracts/Migrations.sol
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
pragma solidity ^0.4.21;

pragma solidity ^0.4.23;

contract Migrations {
address public owner;
uint public last_completed_migration;
address public owner;
uint public last_completed_migration;

modifier restricted() {
if (msg.sender == owner)
_;
}
constructor() public {
owner = msg.sender;
}

function Migrations() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}

function setCompleted(uint completed) restricted public {
last_completed_migration = completed;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}

function upgrade(address newAddress) restricted public {
Migrations upgraded = Migrations(newAddress);
upgraded.setCompleted(last_completed_migration);
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
221 changes: 221 additions & 0 deletions contracts/MultiPartyEscrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
pragma solidity ^0.4.24;

import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";

contract MultiPartyEscrow {

//it seems we don't need SafeMath
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need. When using the SafeMath library, an error will be thrown stopping the execution of the contract and it conforms to the existent codebase.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using SafeMath for MultiPartyEscrow is a big question. I will open an Issue and explain why. And I don't understand your last part. If there is an error in the contract it must revert, there is not other possible behavior.... Ok we continue in an issue

Copy link
Collaborator

@tiero tiero Oct 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean "codebase"? If it is signet/platform-contract then I am the first who consider using it (please make "grep SafeMath contracts/*"). And please use issue #45 for this discussion...

Copy link
Collaborator Author

@astroseger astroseger Oct 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And Marco have you read what I've wrote in #45 ? That we are already checking for it, But still I'm hesitating to use SafeMax ? What's the point to write it here?

//using SafeMath for uint256;


//TODO: we could use uint64 for replicaId and nonce (it could be cheaper to store but more expensive to operate with)

//the full ID of "atomic" payment channel = "[this, channelId, nonce]"
struct PaymentChannel {
address sender; // The account sending payments.
address recipient; // The account receiving the payments.
uint256 replicaId; // id of particular service replica
uint256 value; // Total amount of tokens deposited to the channel.
uint256 nonce; // "nonce" of the channel (by changing nonce we effectivly close the old channel ([this, channelId, oldNonce])
// and open the new channel [this, channelId, newNonce])
//!!! nonce also prevents race conditon between channelClaim and channelExtendAndAddFunds
uint256 expiration; // Timeout in case the recipient never closes.
}


mapping (uint256 => PaymentChannel) public channels;
mapping (address => uint256) public balances; //tokens which have been deposit but haven't been escrowed in the channels

uint256 public nextChannelId; //id of the next channel (and size of channels)

ERC20 public token; // Address of token contract

//TODO: optimize events. Do we need more (or less) events?
tiero marked this conversation as resolved.
Show resolved Hide resolved
event EventChannelOpen (uint256 channelId, address indexed sender, address indexed recipient, uint256 indexed replicaId);
//event EventChannelReopen (uint256 channelId, address indexed sender, address indexed recipient, uint256 indexed replicaId, uint256 nonce);
//event EventChannelTorecipient(uint256 indexed channelId, address indexed sender, address indexed recipient, uint256 amount);
//event EventChannelTosender (uint256 indexed channelId, address indexed sender, address indexed recipient, uint256 amount);

constructor (address _token)
public
{
token = ERC20(_token);
}

function deposit(uint256 value)
public
returns(bool)
{
require(token.transferFrom(msg.sender, this, value), "Unable to transfer token to the contract");
balances[msg.sender] += value;
return true;
}

function withdraw(uint256 value)
public
returns(bool)
{
require(balances[msg.sender] >= value);
require(token.transfer(msg.sender, value));
balances[msg.sender] -= value;
return true;
}

//open a channel, token should be already being deposit
//openChannel should be run only once for given sender, recipient, replicaId
//channel can be reused even after channelClaim(..., isSendback=true)
function openChannel(address recipient, uint256 value, uint256 expiration, uint256 replicaId)
public
returns(bool)
{
require(balances[msg.sender] >= value);
channels[nextChannelId] = PaymentChannel({
sender : msg.sender,
recipient : recipient,
value : value,
replicaId : replicaId,
nonce : 0,
expiration : expiration
});
balances[msg.sender] -= value;
emit EventChannelOpen(nextChannelId, msg.sender, recipient, replicaId);
nextChannelId += 1;
return true;
}



function depositAndOpenChannel(address recipient, uint256 value, uint256 expiration, uint256 replicaId)
public
returns(bool)
{
require(deposit(value));
require(openChannel(recipient, value, expiration, replicaId));
return true;
}


function _channelSendbackAndReopenSuspended(uint256 channelId)
private
{
PaymentChannel storage channel = channels[channelId];
balances[channel.sender] += channel.value;
channel.value = 0;
channel.nonce += 1;
channel.expiration = 0;
}

// the recipient can close the channel at any time by presenting a
vsbogd marked this conversation as resolved.
Show resolved Hide resolved
// signed amount from the sender. The recipient will be sent that amount. The recipient can choose:
// send the remainder to the sender (isSendback == true), or put that amount into the new channel.
function channelClaim(uint256 channelId, uint256 amount, bytes memory signature, bool isSendback)
public
{
PaymentChannel storage channel = channels[channelId];
require(amount <= channel.value);
require(msg.sender == channel.recipient);
tiero marked this conversation as resolved.
Show resolved Hide resolved

//compose the message which was signed
bytes32 message = prefixed(keccak256(abi.encodePacked(this, channelId, channel.nonce, amount)));
// check that the signature is from the channel.sender
require(recoverSigner(message, signature) == channel.sender);

balances[msg.sender] += amount;
channels[channelId].value -= amount;

if (isSendback)
{
_channelSendbackAndReopenSuspended(channelId);
}
else
{
//reopen new "channel", without sending back funds to "sender"
channels[channelId].nonce += 1;
}
tiero marked this conversation as resolved.
Show resolved Hide resolved
}


/// the sender can extend the expiration at any time
function channelExtend(uint256 channelId, uint256 newExpiration)
public
returns(bool)
{
PaymentChannel storage channel = channels[channelId];

require(msg.sender == channel.sender);
require(newExpiration > channel.expiration);

channels[channelId].expiration = newExpiration;
return true;
}

/// the sender could add funds to the channel at any time
function channelAddFunds(uint256 channelId, uint256 amount)
public
returns(bool)
{
require(balances[msg.sender] >= amount);

PaymentChannel storage channel = channels[channelId];

//TODO: we could remove this require and allow everybody to funds it
require(msg.sender == channel.sender);

channels[channelId].value += amount;
balances[msg.sender] -= amount;
return true;
}

function channelExtendAndAddFunds(uint256 channelId, uint256 newExpiration, uint256 amount)
public
{
require(channelExtend(channelId, newExpiration));
require(channelAddFunds(channelId, amount));
}

// sender can claim refund if the timeout is reached
function channelClaimTimeout(uint256 channelId)
public
{
require(msg.sender == channels[channelId].sender);
require(now >= channels[channelId].expiration);
_channelSendbackAndReopenSuspended(channelId);
}

function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);

assembly {
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte
v := and(mload(add(sig, 65)), 255)
}

if (v < 27) v += 27;

return (v, r, s);
}

function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);

return ecrecover(message, v, r, s);
}

/// builds a prefixed hash to mimic the behavior of ethSign.
function prefixed(bytes32 hash) internal pure returns (bytes32)
{
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
12 changes: 12 additions & 0 deletions migrations/3_MultiPartyEscrow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
let MultiPartyEscrow = artifacts.require("./MultiPartyEscrow.sol");
let Contract = require("truffle-contract");
let TokenAbi = require("singularitynet-token-contracts/abi/SingularityNetToken.json");
let TokenNetworks = require("singularitynet-token-contracts/networks/SingularityNetToken.json");
let TokenBytecode = require("singularitynet-token-contracts/bytecode/SingularityNetToken.json");
let Token = Contract({contractName: "SingularityNetToken", abi: TokenAbi, networks: TokenNetworks, bytecode: TokenBytecode});

module.exports = function(deployer, network, accounts) {
Token.setProvider(web3.currentProvider)
Token.defaults({from: accounts[0], gas: 4000000});
deployer.deploy(Token, {overwrite: false}).then((TokenInstance) => deployer.deploy(MultiPartyEscrow, TokenInstance.address));
};
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"eth-gas-reporter": "^0.1.9",
"fs-extra": "^5.0.0",
"moment": "^2.22.2",
"webpack-cli": "^3.0.8"
"webpack-cli": "^3.0.8",
"ethereumjs-abi": "^0.6.5",
"ethereumjs-util":"^5.2.0"
}
}
Loading