-
Notifications
You must be signed in to change notification settings - Fork 465
Conversation
3b0365d
to
3481e1d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still 🤔 about stuff.
@@ -75,6 +77,17 @@ describe('Reference Functions', () => { | |||
); | |||
expect(() => addFillResults(a, b)).to.throw(expectedError.message); | |||
}); | |||
|
|||
it('reverts if computing `protocolFeePaid` overflows', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙏
@@ -73,7 +73,8 @@ export function getPartialAmountFloor(numerator: BigNumber, denominator: BigNumb | |||
* Calculates partial value given a numerator and denominator rounded down. | |||
*/ | |||
export function getPartialAmountCeil(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { | |||
return safeDiv(safeAdd(safeMul(numerator, target), safeSub(denominator, new BigNumber(1))), denominator); | |||
const sub = safeSub(denominator, new BigNumber(1)); // This is computed first to simulate Solidity's order of operations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this the discrepancy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! I guess when you chain something like:
uint256 someValue = someValue1
.safeAdd(someValue2)
.safeSub(someValue3.safeAdd(someValue4))
the someValue3.safeAdd(someValue4)
expression is the first thing to get executed. I guess this isn't a huge surprise since this does follow PEMDAS
, but it's still pretty weird. We had a case that was somewhat similar to this, which is why the safeSub
needs be evaluated first.
@@ -78,6 +81,9 @@ library LibFillResults { | |||
order.takerFee | |||
); | |||
|
|||
// Compute the protocol fee for a single fill. | |||
fillResults.protocolFeePaid = tx.gasprice.safeMul(protocolFeeMultiplier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be better to just pass tx.gasprice
into this function to keep it pure if we want to go down the route of converting reference functions to ethjsvm. It also creates parity with the reference functions as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It ended up being more efficient to just entirely exclude the calculation from this function, since it could end up being inaccurate if a protocolFeeCollector
is unregistered. I'd be interested to hear your thoughts on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, I took your suggestion when I changed it back, as a bonus.
@@ -162,6 +170,11 @@ library LibFillResults { | |||
rightOrder.takerFee | |||
); | |||
|
|||
// Compute the protocol fees | |||
uint256 protocolFee = tx.gasprice.safeMul(protocolFeeMultiplier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as in calculateFillResults
(ethjsvm).
f76b518
to
ce53c71
Compare
* This commit also squashed some bugs in the reference functions. Thankfully, combinatorial testing had our back!
…oxy` will fail for non-contract proxies
valuePaid = 0; | ||
|
||
// Pay the right order's protocol fee. | ||
if (address(this).balance >= protocolFee) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: we can save some gas by setting address(this).balance
to a stack variable and then checking balance - valuePaid >= protocolFee
here. The ADDRESS
opcode costs 200 gas if I recall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out that ADDRESS
opcode only costs 2 gas as of now. With this in mind, the use of ADDRESS
will actually be cheaper here. It's specified as Gbase
in the Ethereum Yellow Paper and in the Jello Paper (and Gbase = 2 gas
in both). Is this going to change soon?
cdf5080
to
911c007
Compare
emit Fill( | ||
order.makerAddress, | ||
order.feeRecipientAddress, | ||
order.makerAssetData, | ||
order.takerAssetData, | ||
order.makerFeeAssetData, | ||
order.takerFeeAssetData, | ||
orderHash, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reordering worked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! Amir figured it out. The heuristic is that you use all of the fields from order
together and do the same with fillResults
. It also seems that maintaining the order of parameters in the event reduces stack usage.
contracts/exchange/contracts/src/interfaces/IStakingManager.sol
Outdated
Show resolved
Hide resolved
@@ -78,6 +81,9 @@ library LibFillResults { | |||
order.takerFee | |||
); | |||
|
|||
// Compute the protocol fee for a single fill. | |||
fillResults.protocolFeePaid = tx.gasprice.safeMul(protocolFeeMultiplier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really awesome stuff here, and obviously a lot of work. Just a few nits and concerns.
dc049df
to
84e88a8
Compare
84e88a8
to
dba0d84
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
import { artifacts } from '../src'; | ||
|
||
describe('Contract Size Checks', () => { | ||
const MAX_CODE_SIZE = 24576; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 👍 👍
contracts/exchange/test/assertion_wrappers/fill_order_wrapper.ts
Outdated
Show resolved
Hide resolved
7640183
to
c50c997
Compare
c50c997
to
2c1393f
Compare
Description
This PR adds protocol fees to the
Exchange
contract, implementing part of ZEIP-42. Several things had to happen for this to occur.Refundable
A new contract was added to
@0x:contracts-utils
calledRefundable
.Refundable
is a contract with no dependencies that introduces two new modifiers and a state variable calledshouldNotRefund
. Both modifiers are designed to respect therefundFinalBalance
state variable in that they will only refund ifshouldNotRefund
isfalse
.disableRefundUntilEnd
: This modifier will setshouldNotRefund
to true, call the function that it modifies, and then refund all of the ether in the contract to the sender. This will disable all nested functions that usedisableRefundUntilEnd
andrefundFinalBalance
, and this modifier will itself be disabled ifshouldNotRefund
is true.refundFinalBalance
: This modifier will call the function that it is modifying and then refund all of the ether in the contract to the sender. This will be disabled ifshouldNotRefund
is true.Payable Functions
Almost every function in the exchange had to be made
payable
. Some notable exceptions includecancelOrder
batchCancelOrders
cancelOrdersUpTo
Since these functions are now payable, they all were also modified by
refundFinalBalance
ordisableRefundUntilEnd
. The functions that were givenrefundFinalBalance
have no risk of calling a function that also usesrefundFinalBalance
and experiencing strange behavior. Similarly, functions whose behavior would be affected by refunds in nested calls are modified bydisableRefundUntilEnd
.Protocol Fees
Protocol fees were added to the internal functions
_fillOrder
and_matchOrders
, so every function that calls these functions or has the potential to call them will potentially pay protocol fees.fillOrder
protocol fees are half of the price ofmatchOrders
protocol fees, and these fees will be paid on a per-fill basis. For example, a call tobatchFillOrders
withk
orders that are filled will payk * protocolFeePerFill
, whereprotocolFeePerFill = exchange.protocolFeeMultiplier * tx.gasprice
.New state that was added to the exchange (like
protocolFeeMultiplier
from the above example), including:protocolFeeMultiplier
: This is the number that will be multiplied totx.gasprice
to calculate the protocol fee of a single fill.protocolFeeCollector
: This is the contract that will receiveprotocolFee
payments.Both of these state variables are protected by the
onlyOwner
modifier, and will thus not be mutable by normal addresses.Glorious Solidity Tests
One of my favorite parts of writing this PR was discovering that writing tests in Solidity is actually surprisingly nice. The tests that I wrote are in the form:
The assertions that are made are generally made by leveraging contract inheritance's ability to override important internal functions. These overridden functions are made to change state in ways that indicate internal behavior.
Both
Protocol Fees
andRefundable
received good Solidity test treatment that runs quickly and makes strong assertions about their behavior.Testing instructions
yarn build:contracts && yarn test:contracts && yarn lint:contracts
(orycall
for those with my.bash_profile
sourced :))Types of changes
Checklist:
[WIP]
if necessary.