-
Notifications
You must be signed in to change notification settings - Fork 44
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
astroseger
merged 7 commits into
singnet:master
from
astroseger:develop-multiparty-escrow-contracts
Oct 4, 2018
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6e72b8c
MultiPartyEscrow contract
astroseger 161045f
add ethereumjs-abi and ethereumjs-util into package.json
astroseger cd0950d
add comment that nonce prevents race condtions
astroseger 9978042
few comments and variable renaming
astroseger 31c416f
rename _channel_sendback_and_reopen_suspended and remove isValidSigna…
astroseger 52ca590
changeToThisUglyFormat
astroseger fff3de3
remove openChannelByRecipient logic
astroseger File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
//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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved here
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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?