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

Standard deposit contract interface #13

Closed
abandeali1 opened this issue Nov 1, 2017 · 1 comment
Closed

Standard deposit contract interface #13

abandeali1 opened this issue Nov 1, 2017 · 1 comment

Comments

@abandeali1
Copy link
Member

abandeali1 commented Nov 1, 2017

Summary

This proposal would allow anyone to build and trade out of a custom deposit contract that conforms to a standard interface.

Motivation

It is currently not possible to add constraints on how and when tokens are moved with the 0x protocol. For certain relayer strategies, it is optimal to make sure tokens are locked for the duration of trading, for example.

In addition, trading tokens from a deposit contract results in fewer state changes and is generally more gas efficient than using the ERC20 transferFrom method for every single token transfer.

Specification

This proposal would require changes to the Exchange contract that allow it to interact with deposit contracts, as well as the addition of deposit contract addresses to the order message format (for maker, taker, and feeRecipient).

A sample implementation of a deposit contract could look like this:

pragma solidity 0.4.11;

import "./base/Token.sol";
import "./base/SafeMath.sol";

contract StandardDeposit is SafeMath {

    // token => owner => balance
    mapping (address => mapping (address => uint256)) balances;

    // This would be hard code in production
    address exchange;

    modifier onlyExchange() {
        require(msg.sender == exchange);
        _;
    }

    function StandardDeposit(address _exchange) {
        exchange = _exchange;
    }

    // Deposit and withdraw are not part of the standard and are here for illustrative purposes.
    function deposit(
        address token, 
        uint256 value) 
        public 
    {
        balances[token][msg.sender] = safeAdd(balances[token][msg.sender], value);
        require(Token(token).transferFrom(msg.sender, address(this), value));
    }

    function withdraw(
        address token, 
        uint256 value) 
        public 
    {
        balances[token][msg.sender] = safeSub(balances[token][msg.sender], value);
        require(Token(token).transfer(msg.sender, value));
    }

    // Used to add to internal token balances. Part of the standard interface.
    function addTokenBalance(
        address token, 
        address owner, 
        uint256 value) 
        onlyExchange
        returns (bool) 
    {
        balances[token][owner] = safeAdd(balances[token][owner], value);
        return true;
    }

    // Used to subtract from internal token balances. Part of the standard interface.
    function subtractTokenBalance(
        address token, 
        address owner, 
        uint256 value) 
        onlyExchange
        returns (bool) 
    {
        balances[token][owner] = safeSub(balances[token][owner], value);
        return true;
    }

    // Used to transfer tokens to external account. Part of the standard interface.
    function transfer(
        address token, 
        address to, 
        uint256 value) 
        onlyExchange
        returns (bool) 
    {
        return Token(token).transfer(to, value);
    }
    
    // Every deposit contract would define its own rules for fills and cancels. 
    // This could be made more generic by passing in a byte array that contains the signature of the Exchange function being called, the caller, and all parameters of the function call.
    // The Exchange contract would only execute the function if validation is successful.
    // Part of the standard interface.
    function isValidFill(        
        address[5] orderAddresses,
        address caller,
        uint256[6] orderValues,
        uint256 fillTakerTokenAmount)
        public
        constant 
        returns (bool) 
    {
        return true;
    }
    
    function isValidCancel(
        address[5] orderAddresses,
        address caller,
        uint256[6] orderValues,
        uint256 cancelTakerTokenAmount)
        public
        constant
        returns (bool)
    {
        return true;
    }
}

This interface would allow for trades between different deposit contracts, within a single deposit contract, or between a deposit contract and regular account.

A trade between 2 deposit contracts:

if (makerDepositContract.isValidFill(...) && takerDepositContract.isValidFill(...)) {
    require(makerDepositContract.subtractTokenBalance(makerToken, maker, filledMakerTokenAmount));
    require(takerDepositContract.subtractTokenBalance(takerToken, taker, filledTakerTokenAmount));
    require(makerDepositContract.transfer(makerToken, takerDepositContract, filledMakerTokenAmount));
    require(takerDepositContract.transfer(takerToken, makerDepositContract, filledTakerTokenAmount));
    require(makerDepositContract.addTokenBalance(takerToken, maker, filledTakerTokenAmount));
    require(takerDepositContract.addTokenBalance(makerToken, taker, filledMakerTokenAmount));
}

A trade within a single deposit contract:

if (depositContract.isValidFill(...)) {
    require(depositContract.subtractTokenBalance(makerToken, maker, filledMakerTokenAmount));
    require(depositContract.addTokenBalance(makerToken, taker, filledMakerTokenAmount));
    require(depositContract.subtractTokenBalance(takerToken, taker, filledTakerTokenAmount));
    require(depositContract.addTokenBalance(takerToken, maker, filledTakerTokenAmount));
}

A trade between a deposit contract and regular account:

if (makerDepositContract.isValidFill(...)) {
    require(makerDepositContract.subtractTokenBalance(makerToken, maker, filledMakerTokenAmount));
    require(makerDepositContract.transfer(makerToken, taker, filledMakerTokenAmount));
    require(makerDepositContract.addTokenBalance(takerToken, maker, filledTakerTokenAmount));
    require(transferViaTokenTransferProxy(takerToken, taker, makerDepositContract, filledTakerTokenAmount));
}

Higher level functions can be implemented for each of these scenarios if desired.

Rationale

This would allow relayers to define any arbitrary rules on how a trader withdraws, deposits, fills and cancels orders.

@MicahZoltu
Copy link

MicahZoltu commented Nov 1, 2017

I tend to prefer the approach in #1 rather than this one. In general, I'm against unnecessarily prescriptive standards and one that requires deposit contracts to conform to a certain shape is more prescriptive than modifying the exchange to allow for maker to be a different address from signer.

I also prefer that users each have a separate wallet rather than a centralized shared wallet. This way, users can just transfer tokens directly to the wallet rather than having to approve the deposit contract and then telling the deposit contract to withdraw tokens from the user, or tell some third contract to transfer tokens from user to deposit contract. Centralized deposit contracts lead to the inevitable problem where users send tokens to the wallet thinking that is the proper course of action and then end up losing those assets to a black hole, or to opportunists who liquidate them (depending on implementation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants