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

Fee splitting between relayers ; Affiliate program #12

Open
PhABC opened this issue Oct 25, 2017 · 7 comments

Comments

Projects
None yet
5 participants
@PhABC
Copy link
Contributor

commented Oct 25, 2017

Summary

The 0x protocol could allow relayers to split the fees with their affiliates if another agent help in the filling of one of their orders.

Motivation

Currently, if an order from relayer Alice is filled by a user from relayer Bob, Alice will receive 100% of the fees. Bob benefits from this since it increases his liquidity, but the incentive is somewhat weak. In addition, there is no incentive for third parties to simply act as order aggregators, since they would never receive fees. Allowing relayers to give part of the fees to their affiliates would create a stronger incentive for relayers to share their liquidity and to borrow orders from others.

In addition of creating stronger collaboration among the 0x network, it can also allow for new, interesting strategies and profitable infrastructure. For instance, some agents could simply aggregate order from all the relayers and offer the best orders available, both price wise and fee wise, to dApps. Such a service would allow dApps to listen to communicate with a much smaller number of relayers and would still be profitable for the agent running this order aggregator system.

Another interesting application would be to create a Reserve Manager based relayer where users can contribute to the liquidity pool by being affiliates. One of the main problem of the Reserve Manager strategy is that the relayer needs large reserves if they want to fill the need of a large amount of users. One obvious solution to this would be for users to deposit their funds in a smart contract and this smart contract acts as a reserve. However, there are some complications when it comes to price control attacks and fair profit sharing. With fee splitting, you can have a simpler approach to this problem ; Liquidity providers can simply create orders at a price requested by the relayer and when these orders are filled, the liquidity provider can receive part of the fees.

Specification

There are multiple ways to allow affiliates on the 0x protocol.

  1. Change the exchange.sol smart contract so that an extra argument affiliateAddress can be passed when filling an order. This argument would be outside of the signature and would be appended by the affiliates themselves. The fees could them be split between the feeRecepient and affiliateAddress at a ratio specified by feeAffiliate[feeRecepient][affiliateAddress]. If feeAffiliate[feeRecepient][affiliateAddress] returns 0x0, then the affiliateAddress was not approved by the relayer at feeRecepient. The relayer could sign the order with the affiliate address to prevent users from removing the affiliate address (proposed by @vsergeev).

  2. Create a single-party state-channel between affiliates and relayers. Alice is an affiliate and Bob is a relayer. Alice and Bob open a state channel where Bob deposits y ZRX. Alice appends her ETH address as data passed in the order filling transaction sent by her users (or as argument in fillOrder which triggers an Event). Alice shows to Bob (or Bob sees) that one of Alice's users filled one of Bob's order and referenced Alice as an affiliate. Bob sends Alice a signed message allowing her to take k ZRX. Alice can then take the latest signed message from Bob to claim her rewards. If Bob doesn't send the signed message, Alice can stop trying to fill Bob's orders or close the channel. If Bob's wants Alice to stop being an affiliate (e.g. Alice is a miner front-running real affiliates), then Bob closes the channel (with a bounty claim period).

Rationale

Currently, both suggestions do not force an order to be attached to an affiliate in order to minimize friction from the user perspective (unless relayer sign orders for each affiliate with the affiliate address). Maker or taker would otherwise be required to either resign the order or sign the affiliate address before filling an order, respectively, both of which impair user experience. I believe there are off-chain solutions affiliates and relayers can use to make sure the affiliate reward goes to the right address.

Examining both solutions ;

  1. In this case, the users will have to pay higher fees to fill an order since there is an extra state variable change and some extra computation (splitting the fees and updating 4 balances instead of 3). This might lead users (especially bot) to remove the affiliate address and set it to 0x0 to avoid paying extra fees.

However, with this solution, there is no need for an external smart contract. You need simple a new mapping variable, 1 new function ( setFeeAffiliate(address affiliateAddress, int value) ), a few lines to perform the fee splitting and adjust certain functions to accepts this new argument affiliateAddress passed when filling an order. In addition, affiliates can blacklist addresses from accessing their API if they notice an address keeps removing them as affiliate. The user would then need to move their funds, which is more expensive than simply paying the affiliate. Other off-chain solution might be possible to make it more expensive for users to not-include the affiliate address than leaving it as is.

  1. In this scenario, the exchange.sol doesn't need to be changes, but a new contract needs to be deployed that will handle state-channels. It also means that relayers might need to deposit funds in many state channels if the amount of affiliate becomes large. However, the users do not spend more gas when filling an order, nor does the relayer and affiliate since everything happens off-chain. The only time the relayer and affiliate pay extra gas is when the state-channel is created, closed and when the affiliate claims their rewards.

Here as well, users could remove the affiliateAddress from their fillOrder transaction, but now there is no real incentive to do so, since users that aren't affiliate will not earn anything from changing the affiliateAddress and that letting this affiliateAddress will not increase the gas cost of filling the order (or only marginally if Event based).

Note that modifying approach 2 slightly to act as a handshake between the relayer and affiliate could reduce fillOrder collisions. For example Alice (affiliate) says to Bob (relayer) that one of her user will fill Bob's order X, Bob sends the signed message to Alice allowing her to take reward (conditional here), Alice allow her user to fill the order, Alice provides a proof that her user filled the order and claim (perhaps can bundle proofs for each orders) reward. If Alice users don't fill the order, then Bob closes channel. Bob will not send a signed message to other affiliates then Alice, since he would have to pay them all for an order that is already "promised" to be filled by Alice. Alice will wait for the handshake before letting her users fill the order since she will not receive the reward (signed message from Bob) otherwise. This handshake version has flaws, but thinking along these lines might allow relayers and affiliate to handshake before the affiliate's users fill a trade, which could significantly reduce collisions when orders are shared by many relayers (albeit impair communication flow between relayers and affiliates).

Summary

In general, allowing fees to be split between relayers and affiliates would not only significantly increase the collaboration within the 0x network, but would also open the door to more many new interesting strategies and ideas.

@PhABC

This comment has been minimized.

Copy link
Contributor Author

commented Oct 25, 2017

Note that in the current real world, affiliates are not imposed on costumers. Affiliate URLs are very common and everybody can change the URL to their own affiliate link. Changelly, for example, allow anyone to become an affiliate, which allows you to have reduced fees when you use your own referral link. Considering the affiliate market is valued in the billions of dollar, I believe these approaches are viable options.

Improvements can definitely be made, but, in my opinion, should never impair user experience .

@vsergeev

This comment has been minimized.

Copy link

commented Oct 26, 2017

My comments: I really like the affiliate term in this context, with the original relayer being the primary relay and the secondary ones being affiliates. I'm not too fond of the non-enforceability aspect of 1, but I like the idea of a relayer explicitly opting into several affiliates with a specified split. This opens the door for dynamic splits that rewards more productive affiliates. State channels are also an interesting approach in 2, but I'm a little weary of how much of this logic would live outside of the protocol and escape standardization.

I'd like to pitch in a variant to your approach 1. This is probably very gas heavy, but I think it would address enforceability and also keep your core ideas of setting downstream fee splits.

Alice makes her original order at Relayer A, with them as the fee recipient. The basic order format is the same as it is today, and the order hash / her signature still covers the same parameters, including one fee recipient and the total maker and taker fees.

The difference is that the call to fillOrder() would now accept arrays for the following relay fee related parameters: maker fees, taker fees, fee recipients, and relay signatures. The relay signature would be a signature that covers the order hash, maker fee, taker fee, fee recipient, and, if it's the last one, the order salt. Each relay signature is signed by the preceding relay when forwarding the order.

For example, when originally posted on Relayer A, the relay fee related parameters are:

Maker Fee Taker Fee Recipient Relay Signature
5 ZRX 10 ZRX Relayer A S(H(Order Hash, 5, 10, Relayer A, Salt)) by Relayer A

Let's say Relayer B requests to share Alice's order from Relayer A, and Relayer A decides the maker and taker fee split based on the history of their relationship. Relayer A would respond to Relayer B with the following parameters:

Maker Fee Taker Fee Recipient Relay Signature
5 ZRX 10 ZRX Relayer A S(H(Order Hash, 5, 10, Relayer A)) by Relayer A
2 ZRX 2 ZRX Relayer B S(H(Order Hash, 2, 2, Relayer B, Salt)) by Relayer A

Note that the first relay signature now omits the order salt in its coverage. The exchange contract will use it for verification, but won't allow it for taking an order. Also, Relayer A will supply Relayer B with S(H(Order Hash, 2, 2, Relayer B)) by Relayer A as the right to split fees further downstream.

Now, let's say that Relayer B would like to share Alice's order with Relayer C. Relayer B would be allowed to sign off some of its fees to Relayer C, but no more than it would receive in total:

Maker Fee Taker Fee Recipient Relay Signature
5 ZRX 10 ZRX Relayer A S(H(Order Hash, 5, 10, Relayer A)) by Relayer A
2 ZRX 2 ZRX Relayer B S(H(Order Hash, 2, 2, Relayer B)) by Relayer A
1 ZRX 1 ZRX Relayer C S(H(Order Hash, 1, 1, Relayer C, Salt)) by Relayer B

Relayer B would also provide Relayer C with S(H(Order Hash, 1, 1, Relayer C)) by Relayer B, outside of the above parameters.

Finally, let's say Relayer C shares this order with Relayer D:

Maker Fee Taker Fee Recipient Relay Signature
5 ZRX 10 ZRX Relayer A S(H(Order Hash, 5, 10, Relayer A)) by Relayer A
2 ZRX 2 ZRX Relayer B S(H(Order Hash, 2, 2, Relayer B)) by Relayer A
1 ZRX 1 ZRX Relayer C S(H(Order Hash, 1, 1, Relayer C)) by Relayer B
0.5 ZRX 0.5 ZRX Relayer D S(H(Order Hash, 0.5, 0.5, Relayer D, Salt)) by Relayer C

Again, Relayer C will provide Relayer D with S(H(Order Hash, 0.5, 0.5, Relayer D)) by Relayer C, if it would like to allow it to further split fees.

If Bob were to discover this order on Relayer D and take it, the exchange contract would verify that the maker and taker fees decrease monotonically and that each relay signature belongs to the preceding address.

The fee recipients would get paid out backwards in the table, each row subtracting the accumulated fees of prior rows. For example, for the Maker Fee, Relayer D would get 0.5 ZRX, Relayer C would get 0.5 ZRX (1 - 0.5), Relayer B would get 1 ZRX (2 - 1), Relayer A would get 3 ZRX (5 - 2), which sum to the total maker fee of 5 ZRX. Alice and Bob still pay no more than the original maker and taker fees, respectively.

The order Bob receives from Relayer D intentionally omits the previous relay signatures that cover the order salt, so Bob cannot shortchange any relayers in the chain. For example, Bob could not fill the original order paying out all the fees to Relayer A, because he doesn't have its relay signature that covers the order salt. (He could, of course, go to straight to Relayer A, but he could have always done that.)

Intermediate relayers cannot forge the fees, as they can't sign the updated hash that covers them.

The added costs are as many signature verifications as there are relayers, more complex fee calculations, and more input data in the transaction. This is all obviously expensive in terms of gas, but it would allow for trustless relaying with downstream fee splitting, while only modifying or introducing a variant of fillOrder().

edit: fix typos

@PhABC PhABC added the enhancement label Oct 26, 2017

@AusIV

This comment has been minimized.

Copy link

commented Oct 26, 2017

I think @vsergeev's solution adds a lot of complexity and gas while not actually solving much. If a user is going to go to the effort of circumventing the affiliate's application, they already have the order hash and the base feeRecipient. It seems like it wouldn't take much extra effort to map the feeRecipient address back to the source relayer, then go directly to the source to get the order (given the order hash) without any affiliate signatures.

I think a simpler solution is to have the order define an affiliateFee to come out of total fee (makerFee + takerFee). If an affiliateFee is specified, but no affiliate is provided when the transaction is filled, pay the affiliate fee to the feeRecipient, but do it in two separate transactions through the token transfer proxy to ensure that gas can't be saved by not specifying an affiliate address.

This doesn't directly allow the relayer to specify different fees for different affiliates, but that's solvable too. At OpenRelay, we're developing an affiliate contract to split received fees between us and our affiliates (though without this ZEIP we're only able to split fees with the affiliate that helps create transactions, not with affiliates that help fill transactions). If a relayer wants to give different shares to different affiliates, they could have the accepted affiliate addresses be contracts that will split the affiliate fees according to different percentages for different affiliates.

I must admit my knowledge of gas consumption is somewhat limited, and it seems plausible that the EVM might reduce the gas for multiple updates to the same addresses to look like a single update, in which case this solution may not pan out.

@vsergeev

This comment has been minimized.

Copy link

commented Oct 26, 2017

@AusIV, the complexity is not just mitigating user circumvention, but it's also to support chained affiliates, while preventing them from stealing fees they're not entitled to. If we're only concerned with one affiliate, it could be simplified.

I just don't think the exchange contract is the right place to store affiliate relationship state -- that seems like it should be offchain.

I do like your idea of paying fees to a contract that splits between relayer and affiliate(s), but I think there are still some difficulties with supporting multiple affiliates, not to mention chained affiliates, without asking the maker to re-sign for each one.

Maybe a hybrid option is that there could be a formalized fee split contract interface, which the exchange contract could transferFrom() fees to, and then call split() with an affiliate ID, which would implement a custom fee split. The affiliate ID would live outside the order hash and be attached by the affiliate. The original relayer would own the fee split contract instance, which would define the split for each affiliate ID. But this might still be difficult to extend to chained affiliates. It is also easy for the user to circumvent or redirect fees, as the IDs would be public.

edit: rename affiliate token to affiliate id to avoid confusion

@AusIV

This comment has been minimized.

Copy link

commented Oct 27, 2017

I guess I'm not convinced of the need for affiliate chaining. I can see it being mildly useful, but I don't see it being valuable enough to justify the complexity.

Another issue with that proposal is that feeRecipients must be key pairs for that to work, and can't be contracts. Not only that, the private keys have to be on live, connected systems to be able to sign orders for specific affiliates. I'm fairly intent on keeping the keys that can withdraw my relay funds as isolated as possible, so having to have them on the system serving my orderbook rubs me the wrong way. (It doesn't help that I spent the afternoon consoling a friend who lost his life savings because his bitcoin private keys got stolen from a system that was supposed to be secure).

@avonian

This comment has been minimized.

Copy link

commented Oct 27, 2017

I'm excited to hear you guys have opened the floor for discussion on the subject of fee splitting between collaborating relayers, as it makes all the sense in the world if you really want the relayer protocol to gain momentum.

I'm not familiar with state channels so option 2 is a bit beyond my grasp at this stage, but with regards to option 1 I will just say that I'm a pragmatic entrepreneur/dev, and not too worried at this stage about the possibility of bad actors skimming a few affiliate fees here and there. I don't expect the affiliate fees to be particularly large since the relayer will likely be taking the lions share, as such bad actors can't get away with a whole lot before they're spotted and mitigated.

The gas argument on option 1 is fair but honestly, from a consumer side of it, thanks to relayer liquidity sharing you now have access to much better market deals, and so what's a little more gas expenditure?

My advice is don't overthink it and go with something practical, let it loose and see how it performs so you have some tangible data from which to drive the relayer protocol roadmap.

The perceived bad actor threat may not really be as much of a threat as you think, and the gas fee may not be as big a deal as you think... the proof is in the pudding :)

@PhABC

This comment has been minimized.

Copy link
Contributor Author

commented Oct 27, 2017

@vsergeev I do like your proposal, especially the signature from the relayer for each of their affiliate to ensure users don't remove it. Producing a signature is cheap computationally wise and relayers would be happy to sign the orders to their affiliate if it means they could earn some profit from their users filling it. I edited 1. to include this. However, as @AusIV mentioned, it might indeed have some security risks that relayers would not be willing to expose themselves to. I do agree with @AusIV however that I am not sure if the multi-layer relayer feature is necessary at the moment. From my experience, I often tend to over-engineer things while trying to solve problems that in practice actually never show up (or ends up being insignificant). We know that allowing at least one affiliate can come a long way, more is unclear. I therefore think a single affiliate-layer is a sufficient proposal for a first version of this affiliate feature. Perhaps in the future we will see that many people requests more than one layer and then we can revisit this proposal.

@AusIV Yes, I am pretty sure that updating the same address twice will result in lower gas consumption than updating two separate addresses, and it might be impossible in practice to have the same gas consumption when no or one affiliate is specified.

@avonian I do indeed agree with you that in practice most users will not try to remove the affiliate address, especially if some off-chain consequences are applied. In the end, if implemented properly, a user would need to move their funds or find another relayer if they remove the affiliate address, which is more expensive both in time and gas than simply letting the affiliate address on the order. I also do think that this simple solution might be fine for first implementation and refinement can be made in the future, especially since this introduces no substantial security risks for the users and relayers.

@abandeali1 abandeali1 added feature and removed enhancement labels Dec 19, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.