Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Feature/staking/protocol fees #2130

Merged
merged 4 commits into from
Sep 9, 2019
Merged

Conversation

jalextowle
Copy link
Contributor

@jalextowle jalextowle commented Sep 5, 2019

Description

This pull request introduces WETH support to the staking contract, implementing part of ZEIP-42. Despite the fact that supporting a new asset typically increases the complexity of a system, in this case, only the payProtocolFee and finalizeFees functions were made more complex.

payProtocolFee

The logic is now entirely compatible with ETH or WETH, and this works whether the maker is registered in a staking pool or not. These changes are also unit tested in the file test/protocol_fees.ts.

finalizeFees

Only a small change needed to be made to this function. Now this function will unwrap WETH and withdraw all of the unwrapped ETH to the staking contract. After this, finalization can carry on normally, so this is not a very big change.

Testing instructions

yall

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@coveralls
Copy link

coveralls commented Sep 5, 2019

Coverage Status

Coverage remained the same at 75.804% when pulling 1d5c175 on feature/staking/protocol-fees into abd479d on 3.0.

@jalextowle jalextowle force-pushed the feature/staking/protocol-fees branch from b521a4d to 73dfb7d Compare September 5, 2019 23:06
@jalextowle jalextowle marked this pull request as ready for review September 5, 2019 23:07
@hysz hysz self-assigned this Sep 5, 2019
@jalextowle jalextowle force-pushed the feature/staking/protocol-fees branch from 73dfb7d to 724f9ab Compare September 6, 2019 00:55
@jalextowle jalextowle changed the base branch from 3.0 to feature/staking/NewMechanicsSolidityOnly-Squashed-Tests-Squashed September 6, 2019 00:58
@jalextowle jalextowle changed the base branch from feature/staking/NewMechanicsSolidityOnly-Squashed-Tests-Squashed to feature/staking/NewMechanicsSolidityOnly-Squashed-Tests September 6, 2019 00:58
@jalextowle jalextowle changed the base branch from feature/staking/NewMechanicsSolidityOnly-Squashed-Tests to feature/staking/NewMechanicsSolidityOnly-Squashed September 6, 2019 00:58
Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work bud! Very concise implementation and test coverage lvl 💯


// Transfer the protocol fee to this address if it should be paid in WETH.
if (msg.value == 0) {
erc20Proxy.transferFrom(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is more directed to architecture opposed to the logic, but I'm wondering if we should deploy a separate feeProxy.

(i)
When users set their allowance on the ERC20 proxy they permit our exchange contract to transfer tokens on their behalf. The user then explicitly consents to the transfer of token X in amount Y when they sign an order.

Here we're overloading the ERC20 proxy to collect a hidden or implicit fee. We can broadcast to our community that there now exists a fee, but it's still a passive agreement that the average user may not understand.

(ii)
The ERC20 proxy is super OP. It can transfer any ERC20 that the user has set allowance for. In this code, we really only need access to WETH.

(iii)
This also raises a question around the proxy used to stake ZRX. On one hand, this doesn't break (i) because the user still explicitly agrees to the transfer by calling stake(). However (ii) does apply because in that scenario we really only need access to ZRX.

I propose we have either (i) a wethFeeProxy and a zrxStakeProxy, or (ii) a combined stakingProxy that users can set allowances for WETH and ZRX on. Note that this can just be the generic ERC20 Proxy, but redeployed.

@abandeali1 curious to get your thoughts.

Copy link
Member

@abandeali1 abandeali1 Sep 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings on it. I think I'm leaning towards having a separate wethAssetProxy but continuing to use the current ERC20Proxy for ZRX.

The good news is that we don't need to decide now since this can literally be a fork of the current ERC20Proxy. Let's just rename this variable wethAssetProxy (might as well rename ZrxVault.erc20Proxy to zrxAssetProxy while we're at it).

contracts/staking/contracts/src/interfaces/IStructs.sol Outdated Show resolved Hide resolved
// Ensure that the correct number of logs were recorded.
expect(receipt.logs.length).to.be.eq(1);

// Ensure that the correct log was recorded.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we could move this into an actor or assertion wrapper, to follow similar patterns used elsewhere in our tests. Would also reduce a lot of the code that's repeated in these tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I can work on that. It should be a very simple actor for now.

contract TestStaking is
Staking
{
// Stub out `payProtocolFee` to be the naive payProtocolFee function so that tests will
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we say a bit more here? I'm still not sure why tests would fail against the actual implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up just removing it since they do work with the current impl

import "../src/Staking.sol";


contract TestProtocolFees is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I really like this pattern 💯

} from '../src';

// tslint:disable:no-unnecessary-type-assertion
blockchainTests('Protocol Fee Unit Tests', env => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid coverage!

Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super clean overall, just had a few nits!

address constant internal WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

// abi.encodeWithSelector(IAssetData(address(0)).ERC20Token.selector, WETH_ADDRESS)
bytes constant internal WETH_ASSET_DATA = hex"f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these values are hard coded, it could be good to assert that this actually equals abi.encodeWithSelector(IAssetData(address(0)).ERC20Token.selector, WETH_ADDRESS) in the constructor of this contract.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a merge blocker, but it's probably a good idea to add comments here with all of the testnet WETH addresses/assetDatas. We should also add WETH to the contract-addresses package now that it is used within the protocol.

@@ -18,6 +18,8 @@

pragma solidity ^0.5.9;

import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import isn't being used at the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will actually be used now, since the check is in the constructor.

contracts/staking/contracts/src/interfaces/IStructs.sol Outdated Show resolved Hide resolved
poolIdByMakerAddress[makerAddress] = poolId;
}

function getActivePoolsByEpoch()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add a getter for this in the staking contract if it doesn't already exist. Alternatively, should everything just be made public? If we go that route we can probably do it in another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, a task has been made to implement this change in a separate PR, where naming conventions will also be updated.

contracts/staking/test/protocol_fees.ts Outdated Show resolved Hide resolved
contract TestStaking is
Staking
{
// Stub out `payProtocolFee` to be the naive payProtocolFee function so that tests will
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on creating some internal getter function for WETH_ASSET_DATA (getWethAssetData), using that in the actual payProtocolFee, and then stubbing it out in this contract? That way we can test with actual WETH transfers. It's probably a couple extra gas but the tradeoff feels worth it to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could actually be a cool pattern to use for all deployment constants. Not sure if we have any similar situations with any of the other constants at the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, I'm a big fan and I think that we should do it on an as-needed basis.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the integration tests aren't being implemented in this PR, I'm going to hold off on this until it's needed.

contracts/staking/test/protocol_fees.ts Outdated Show resolved Hide resolved
@jalextowle jalextowle force-pushed the feature/staking/protocol-fees branch from 73d598b to f52f929 Compare September 6, 2019 23:45
@jalextowle jalextowle changed the base branch from feature/staking/NewMechanicsSolidityOnly-Squashed to 3.0 September 6, 2019 23:49
@jalextowle jalextowle force-pushed the feature/staking/protocol-fees branch 3 times, most recently from 85d99fd to d270581 Compare September 7, 2019 09:28
@jalextowle jalextowle force-pushed the feature/staking/protocol-fees branch from d270581 to 2fdd4e9 Compare September 9, 2019 17:25
Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like how you employed the actor pattern. Nice work! (~‾▿‾)~

@@ -217,8 +263,9 @@ contract MixinExchangeFees is
);

// store pool stats
uint256 protocolFeeBalance = protocolFeesThisEpochByPool[poolId];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an optimization?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it actually makes it less efficient. I think that it's a holdover from how it would have worked before WETH protocol fees.

// Stub out `payProtocolFee` to be the naive payProtocolFee function so that tests will
// not fail for WETH protocol fees. These tests will fail otherwise because many of them
// will transfer
function payProtocolFee(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this, since it's commented out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I'll remove.

@jalextowle jalextowle merged commit c21bce9 into 3.0 Sep 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants