Skip to content
This repository has been archived by the owner on Dec 27, 2022. It is now read-only.

Commit

Permalink
Merge branch 'master' into add-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ArjunBhuptani committed Oct 5, 2020
2 parents 66911b2 + 4390fda commit 648b7bd
Show file tree
Hide file tree
Showing 31 changed files with 1,303 additions and 159 deletions.
1 change: 1 addition & 0 deletions modules/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@ethersproject/constants": "5.0.4",
"ethers": "5.0.14",
"keccak": "3.0.1",
"p-queue": "6.6.1",
"yargs": "16.0.3"
},
"devDependencies": {
Expand Down
106 changes: 106 additions & 0 deletions modules/contracts/src.sol/AssetTransfer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.1;
pragma experimental ABIEncoderV2;

import "./interfaces/IERC20.sol";
import "./lib/LibAsset.sol";
import "./lib/LibUtils.sol";
import "./EmergencyWithdrawable.sol";


contract AssetTransfer is EmergencyWithdrawable {

// TODO: These are ad hoc values. Confirm or find more suitable ones.
uint256 private constant ETHER_TRANSFER_GAS_LIMIT = 10000;
uint256 private constant ERC20_TRANSFER_GAS_LIMIT = 100000;
uint256 private constant ERC20_BALANCE_GAS_LIMIT = 5000;

mapping(address => uint256) private _totalTransferred;

modifier onlySelf() {
require(
msg.sender == address(this),
"AssetTransfer: Can only be called from this contract"
);
_;
}

function safelyTransferEther(address payable recipient, uint256 maxAmount)
private
returns (bool, uint256)
{
uint256 balance = address(this).balance;
uint256 amount = LibUtils.min(maxAmount, balance);
(bool success, ) = recipient.call{gas: ETHER_TRANSFER_GAS_LIMIT, value: amount}("");
return (success, success ? amount : 0);
}

function safelyTransferERC20(address assetId, address recipient, uint256 maxAmount)
private
returns (bool, uint256)
{
(bool success, bytes memory encodedReturnValue) = address(this).call{gas: ERC20_BALANCE_GAS_LIMIT}(
abi.encodeWithSignature("_getOwnERC20Balance(address)", assetId)
);
if (!success) { return (false, 0); }

uint256 balance = abi.decode(encodedReturnValue, (uint256));
uint256 amount = LibUtils.min(maxAmount, balance);
(success, ) = address(this).call{gas: ERC20_TRANSFER_GAS_LIMIT}(
abi.encodeWithSignature("_transferERC20(address,address,uint256)", assetId, recipient, amount)
);
return (success, success ? amount : 0);
}

function safelyTransfer(address assetId, address payable recipient, uint256 maxAmount)
private
returns (bool, uint256)
{
return LibAsset.isEther(assetId) ?
safelyTransferEther(recipient, maxAmount) :
safelyTransferERC20(assetId, recipient, maxAmount);

}

function _getOwnERC20Balance(address assetId)
external
onlySelf
view
returns (uint256)
{
return IERC20(assetId).balanceOf(address(this));
}

function _transferERC20(address assetId, address recipient, uint256 amount)
external
onlySelf
returns (bool)
{
return LibAsset.transferERC20(assetId, recipient, amount);
}

function totalTransferred(address assetId) public view returns (uint256) {
return _totalTransferred[assetId];
}

function registerTransfer(address assetId, uint256 amount) internal {
_totalTransferred[assetId] += amount;
}

function transferAsset(address assetId, address payable recipient, uint256 maxAmount)
internal
returns (bool)
{
(bool success, uint256 amount) = safelyTransfer(assetId, recipient, maxAmount);

if (success) {
registerTransfer(assetId, amount);
} else {
addToEmergencyWithdrawableAmount(assetId, recipient, maxAmount);
registerTransfer(assetId, maxAmount);
}

return success;
}

}
38 changes: 38 additions & 0 deletions modules/contracts/src.sol/CMCAccountant.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.1;
pragma experimental ABIEncoderV2;

import "./interfaces/ICMCAccountant.sol";
import "./interfaces/Types.sol";
import "./AssetTransfer.sol";
import "./CMCDeposit.sol";
import "./CMCWithdraw.sol";


contract CMCAccountant is
AssetTransfer,
CMCDeposit,
CMCWithdraw,
ICMCAccountant
{

function transferBalance(address assetId, Balance memory balance)
internal
{
address payable recipient;
uint256 amount;

recipient = balance.to[0];
amount = balance.amount[0];
if (amount != 0) {
transferAsset(assetId, recipient, amount);
}

recipient = balance.to[1];
amount = balance.amount[1];
if (amount != 0) {
transferAsset(assetId, recipient, amount);
}
}

}
24 changes: 5 additions & 19 deletions modules/contracts/src.sol/CMCAdjudicator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ pragma experimental ABIEncoderV2;

import "./interfaces/ICMCAdjudicator.sol";
import "./interfaces/ITransferDefinition.sol";
import "./interfaces/IERC20.sol";
import "./CMCCore.sol";
import "./CMCDeposit.sol";
import "./CMCAccountant.sol";
import "./lib/LibChannelCrypto.sol";
import "./lib/MerkleProof.sol";
import "./lib/SafeMath.sol";


/// @title Adjudicator - Dispute logic for ONE channel
contract CMCAdjudicator is CMCCore, CMCDeposit, ICMCAdjudicator {
contract CMCAdjudicator is CMCCore, CMCAccountant, ICMCAdjudicator {
using LibChannelCrypto for bytes32;
using SafeMath for uint256;

Expand Down Expand Up @@ -107,7 +107,7 @@ contract CMCAdjudicator is CMCCore, CMCDeposit, ICMCAdjudicator {
// transfer.to[0] = balance.to[0];
// transfer.to[1] = balance.to[1];
// transfer.amount[0] = balance.amount[0];
// transferAsset(transfer, assetId);
// transferBalance(assetId, transfer);
// }
}

Expand Down Expand Up @@ -179,7 +179,7 @@ contract CMCAdjudicator is CMCCore, CMCDeposit, ICMCAdjudicator {
} else {
finalBalance = cts.initialBalance;
}
transferAsset(finalBalance, cts.assetId);
transferBalance(cts.assetId, finalBalance);
}

function verifySenderIsParticipant(CoreChannelState memory ccs) internal view {
Expand Down Expand Up @@ -234,18 +234,4 @@ contract CMCAdjudicator is CMCCore, CMCDeposit, ICMCAdjudicator {
return keccak256(abi.encode(cts));
}

// TODO: Asset library

function transferAsset(Balance memory balances, address assetId) internal {
// TODO: This is quick-and-dirty to allow for basic testing.
// We should add dealing with non-standard-conforming tokens,
// unexpected reverts, avoid reentrancy, etc.
if (assetId == address(0)) {
balances.to[0].transfer(balances.amount[0]);
balances.to[1].transfer(balances.amount[1]);
} else {
require(IERC20(assetId).transfer(balances.to[0], balances.amount[0]), "oh no");
require(IERC20(assetId).transfer(balances.to[1], balances.amount[1]), "oh no");
}
}
}
3 changes: 0 additions & 3 deletions modules/contracts/src.sol/CMCCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ contract CMCCore is ICMCCore {
address internal alice;
address internal bob;

mapping(address => uint256) internal _totalDepositedA;
mapping(address => uint256) internal _totalWithdrawn;

// Prevents us from calling methods directly from the mastercopy contract
modifier onlyOnProxy {
require(masterCopy != address(0), "This contract is the mastercopy");
Expand Down
30 changes: 10 additions & 20 deletions modules/contracts/src.sol/CMCDeposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,45 @@ pragma solidity ^0.7.1;
pragma experimental ABIEncoderV2;

import "./interfaces/ICMCDeposit.sol";
import "./interfaces/Types.sol";
import "./interfaces/IERC20.sol";
import "./CMCCore.sol";
import "./AssetTransfer.sol";
import "./lib/LibAsset.sol";


/// @title Vector Channel
/// @author Arjun Bhuptani <[email protected]>
/// @notice
/// (a) A proxy to this contract is deployed per-channel using the ChannelFactory.sol contract
/// (b) Executes transactions dispute logic on a hardcoded channel factory
/// (c) Supports executing arbitrary CALLs when called w/ commitment that has 2 signatures
contract CMCDeposit is CMCCore, AssetTransfer, ICMCDeposit {

contract CMCDeposit is CMCCore, ICMCDeposit {
mapping(address => uint256) private _totalDepositedA;

receive() external payable onlyOnProxy {}

function getBalance(address assetId) public override view returns (uint256) {
return assetId == address(0)
? address(this).balance
: IERC20(assetId).balanceOf(address(this));
}

function totalDepositedA(address assetId) public override view returns (uint256) {
return _totalDepositedA[assetId];
}

// Calculated using invariant onchain properties. Note we DONT use safemath here
function totalDepositedB(address assetId) public override view returns (uint256) {
return getBalance(assetId) + _totalWithdrawn[assetId] - _totalDepositedA[assetId];
return LibAsset.getOwnBalance(assetId) + totalTransferred(assetId) - _totalDepositedA[assetId];
}

function depositA(
address assetId,
uint256 amount
)
public
override
external
payable
override
onlyOnProxy
{
if (assetId == address(0)) {
if (LibAsset.isEther(assetId)) {
require(
msg.value == amount,
"msg.value does not match the provided amount"
"CMCDeposit: msg.value does not match the provided amount"
);
} else {
require(
IERC20(assetId).transferFrom(msg.sender, address(this), amount),
"ERC20: transferFrom failed"
"CMCDeposit: ERC20 transferFrom failed"
);
}
// NOTE: explicitly do NOT use safemath here
Expand Down
35 changes: 13 additions & 22 deletions modules/contracts/src.sol/CMCWithdraw.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ pragma solidity ^0.7.1;
pragma experimental ABIEncoderV2;

import "./interfaces/ICMCWithdraw.sol";
import "./interfaces/IERC20.sol";
import "./lib/LibChannelCrypto.sol";
import "./CMCCore.sol";
import "./AssetTransfer.sol";
import "./lib/LibAsset.sol";
import "./lib/LibChannelCrypto.sol";


contract CMCWithdraw is CMCCore, AssetTransfer, ICMCWithdraw {

contract CMCWithdraw is CMCCore, ICMCWithdraw {
using LibChannelCrypto for bytes32;

mapping(bytes32 => bool) isExecuted;
Expand All @@ -24,36 +27,24 @@ contract CMCWithdraw is CMCCore, ICMCWithdraw {
uint256 nonce,
bytes memory aliceSignature,
bytes memory bobSignature
) public override onlyOnProxy {
) external override onlyOnProxy {
// Replay protection
bytes32 withdrawHash = keccak256(abi.encodePacked(recipient, assetId, amount, nonce));
require(!isExecuted[withdrawHash], "Transacation has already been executed");
require(!isExecuted[withdrawHash], "CMCWithdraw: Transaction has already been executed");
isExecuted[withdrawHash] = true;

// Validate signatures
require(alice == withdrawHash.verifyChannelMessage(aliceSignature), "CMCWithdraw: Invalid alice signature");
require(bob == withdrawHash.verifyChannelMessage(bobSignature), "CMCWithdraw: Invalid bob signature");

// Add to totalWithdrawn
_totalWithdrawn[assetId] += amount;
registerTransfer(assetId, amount);

// Execute the withdraw
if (assetId == address(0)) {
recipient.transfer(amount);
} else {
safeTransfer(assetId, recipient, amount);
}
require(
LibAsset.transfer(assetId, recipient, amount),
"CMCWithdraw: Transfer failed"
);
}

// uses uniswap transfer helper for non-confirming ERC20 tokens
// https://github.com/Uniswap/uniswap-lib/blob/master/contracts/libraries/TransferHelper.sol
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "CMCWithdraw: ERC20 transfer failed");
}
}
3 changes: 2 additions & 1 deletion modules/contracts/src.sol/ChannelFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./interfaces/IChannelFactory.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/IVectorChannel.sol";
import "./Proxy.sol";
import "./lib/LibAsset.sol";


/// @title Channel Factory - Allows us to create new channel proxy contract
Expand Down Expand Up @@ -83,7 +84,7 @@ contract ChannelFactory is IChannelFactory {
channel = createChannel(alice, bob, chainId);
// TODO: This is a bit ugly and inefficient, but alternative solutions are too.
// Do we want to keep it this way?
if (assetId != address(0)) {
if (!LibAsset.isEther(assetId)) {
require(
IERC20(assetId).transferFrom(msg.sender, address(this), amount),
"ChannelFactory: token transferFrom failed"
Expand Down
6 changes: 2 additions & 4 deletions modules/contracts/src.sol/ChannelMastercopy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ pragma experimental ABIEncoderV2;

import "./interfaces/IVectorChannel.sol";
import "./CMCCore.sol";
import "./CMCWithdraw.sol";
import "./CMCDeposit.sol";
import "./CMCAccountant.sol";
import "./CMCAdjudicator.sol";

/// @title Vector Channel
Expand All @@ -17,8 +16,7 @@ import "./CMCAdjudicator.sol";

contract ChannelMastercopy is
CMCCore,
CMCWithdraw,
CMCDeposit,
CMCAccountant,
CMCAdjudicator,
IVectorChannel
{}
Loading

0 comments on commit 648b7bd

Please sign in to comment.