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

On-chain order generation by smart contracts #7

Closed
abandeali1 opened this issue Sep 15, 2017 · 11 comments
Closed

On-chain order generation by smart contracts #7

abandeali1 opened this issue Sep 15, 2017 · 11 comments

Comments

@abandeali1
Copy link
Member

abandeali1 commented Sep 15, 2017

Summary

Up until now, the focus of discussion has been on how to generate an order on behalf of a contract off-chain. However, there is a definite value in allowing a contract to generate an order on-chain (e.g a DAO voting on the creation of an order).

Motivation

Currently, there is no way for smart contracts to generate orders (see issue #1 ).

Specification

The following additions can be added to the Exchange contract:

mapping(bytes32 => bool) public isOrderApproved;

function approveOrder(
    address[5] orderAddresses,
    uint[6] orderValues)
    public
    returns (bool)
{
    require(orderAddresses[0] == msg.sender);
    bytes32 orderHash = getOrderHash(orderAddresses, orderValues);
    isOrderApproved[orderHash] = true;
    return true;
}

Then, when verifying an order signature, we check:

require(isValidSignature(...) || isOrderApproved[order.orderHash]);

Rationale

This allows any contract to approve an order on-chain in place of a signature.

@retotrinkler
Copy link

Hi, whats the eta for this?

@fabioberger
Copy link

Instead of keeping a separate mapping, we could also designate 2^256-1 as a special case and add the approved orderHash to the filled map. Upon the first partial fill, this value would be overwritten.

Any entry in the filled mapping is known to either have a valid signature or be approved.

This isn't a great separation of concern, however it does reduce the gas cost of the initial fill:

  • Contract approving an order -> 20k
  • First order partial fill -> 5k (instead of 20k without this optimization)

@abandeali1
Copy link
Member Author

abandeali1 commented Oct 20, 2017

Although that would be an efficiency gain, it's a bit inconsistent with how we've been special casing 2^256-1. I also think that long term, there's more value in this: #11

@abandeali1
Copy link
Member Author

@retotrinkler this will be included in the next version, but we are still deciding on the other features that will be added.

@izqui
Copy link

izqui commented Jan 4, 2018

What is the status of this? I'm considering doing an EIP for an ecosystem-wide smart contract where contracts can 'sign a hash'. The interface looks like this:

contract ISignHolder {
    /**
    * @notice Returns whether `who` ever signed `hash`. Even if she cancelled, this will return true.
    */
    function signed(address who, bytes32 hash) public view returns (bool);

    /**
     * @notice Returns if `who` signed `hash` and hasnt cancelled the signature.
     */
    function isSigned(address who, bytes32 hash) public view returns (bool);
}

and this would be an initial implementation:

contract SignHolder is ISignHolder {
    mapping (address => mapping (bytes32 => bool)) signatures;
    mapping (address => mapping (bytes32 => bool)) cancels;

    function sign(bytes32 hash) external { signatures[msg.sender][hash] = true; }
    function cancel(bytes32 hash) external { cancels[msg.sender][hash] = true; }

    function signed(address who, bytes32 hash) public view returns (bool) {
        return signatures[who][hash];
    }

    function isSigned(address who, bytes32 hash) public view returns (bool) {
        return signatures[who][hash] && !cancels[who][hash];
    }
}

Then from contracts like the 0x Exchange you would directly check whether a contract has signed a hash, as an alternative for ecrecover when the code size of an address is greater than 0.

contract EIPSign {
    ISignHolder constant SIGN_HOLDER = ISignHolder(0x1234); // TODO: Address

    function check(address who, bytes32 hash) internal view returns (bool) {
        if (msg.sender == who) return true; // Implicit authorization by sender
        return SIGN_HOLDER.signed(who, hash);
    }
}

I feel like this is better than making a specific implementation just for 0x. In terms of gas costs, it would be slightly more expensive than just checking in the contract, as you need to add the cost for a CALL to the SignHolder. However I think this is better, specially thinking that account abstraction is not that far away.

@abandeali1
Copy link
Member Author

Interesting. What do you think is the benefit of standardizing this? It seems to me that signatures are typically used for application/protocol specific messages, so putting them in a global scope might not be that useful. The other problems I see are:

  • Users have no incentive to sign messages this way at an increased cost. For example, If this contract is deployed in parallel to a 0x version, it would be always be cheaper to use the 0x version for validating a 0x order.
  • cancel adds a race condition that seems very difficult, if not impossible, to remove. I'd prefer removing it altogether.

@izqui
Copy link

izqui commented Jan 4, 2018

What I think is important is having a global way for contracts to signal they approve something, the equivalent as signing an arbitrary payload with a private key. The type of payloads being signed would defer depending on the protocol, in the same way that ECDSA signatures work the same independently of the data being signed.

Another more limited advantage of keeping it generic, is that data could be 'signed' before knowing on what application/protocol it is relevant for. As well as signing data that isn't relevant to any protocol, like expressing conformity to an agreement by 'signing' its hash.

Regarding costs, it would be as cheap for the order maker, taking the order would add around 700 gas (IMO not a deal-breaker for its benefits).

@abandeali1
Copy link
Member Author

700 gas doesn't seem like a big deal, but that does make the maker's order slightly less attractive to a taker (and in general we want to cut out any extra gas from transactions that happen frequently).

I do see value in standardizing this, but my preference would be to first check the internal 0x mapping and fall back to the global signature contract. This would mean that the global contract can have false negatives, but I think that's going to be hard to avoid anyways.

@izqui
Copy link

izqui commented Jan 5, 2018

I think that is a good compromise :) If 'order signatures' can't be cancelled on the 0x mapping nor the global signing contract, then just seeing that a the hash is present in either of those could be enough to know that the order has been approved. You could even make it a public function such as checkContractOrderSignature(bytes32 orderHash, address signer) view returns (bool) that checks both and once it returns true, you know that will be true for ever.

@silentinfotechllp
Copy link

@abandeali1 Is there any reference implementation for this strategy where we can define the approveOrder, isValidSignature and isOrderApproved?

@recmo
Copy link

recmo commented Feb 26, 2018

@izqui The current PR for signature generalization is not 0x specific and could be re-used by other projects, or even be promoted to a standard. Please check the MixinSignatureValidator contract.

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

6 participants