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

feat(spoke-pool): Allow relayer and depositor to agree to update relay fee #27

Merged
merged 9 commits into from
Feb 9, 2022

Conversation

nicholaspai
Copy link
Member

No description provided.

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

Looks awesome! A few minor questions/comments

require(relayFills[relayHash] < totalRelayAmount, "filled relay");

// Depositor should have signed a hash of the relayer fee % to update to.
bytes32 expectedDepositorMessageHash = keccak256(abi.encode(newRelayerFeePct));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need more than this to avoid replay attacks. For instance, if I agree to a relayer fee of 23% on a small relay, then I send a large relay, someone could take my signed message from the small relay and replay it to get a large fee from me in the in the large relay.

A few additional fields I think we need: depositId, chainId. We might also want include a constant value in front, like "ACROSS-V2 FEE 1.0" or something that we could increment if we ever updated spoke pools and ended up resetting the depositId counter.

Copy link
Member Author

Choose a reason for hiding this comment

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

ah good point these are easy to include

@@ -268,6 +272,61 @@ abstract contract SpokePool is Testable, Lockable, MultiCaller {
);
}

function increaseRelayFee(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the relayer be able to use this method to actually fill the relay? If not, how does it get connected with a particular fill? Or is the idea that we would swap out the relayerFeePct for every FilledRelay that was submitted afterward?

Copy link
Member Author

Choose a reason for hiding this comment

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

The relayer in this implementation would call this method separately and it would not add to the fill. We could add an additonal method increaseRelayFeeAndFillRelay (bad name) that would call fillRelay AND modify the fee WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

relayer in this implementation would call this method separately and it would not add to the fill

How would this work? Wouldn't the subsequent fill still be using the old relayer fee (since that's the only one that would match the hash)? So in that case, too much money would be sent to the recipient, no?

We don't have to do it this way, but the way I had conceptualized this was that we'd have a fill method that's identical to the existing one with two additional args: bytes memory signature, uint64 updatedRelayerFeePct. The signature would be verified against the provided fee, and the updated fee fee would be emitted in the FilledRelay event (we could also include a second event to signify that this fee was overridden or something).

Copy link
Member Author

Choose a reason for hiding this comment

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

So the relay hash would stay the same, with the old relayer fee, but the event would just change? (or have an additional one emitted)

I agree wth you that fillRelay could be overridden with a separate interface

Copy link
Contributor

@mrice32 mrice32 Feb 8, 2022

Choose a reason for hiding this comment

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

Yeah, in the solution I had been thinking of, the relay hash/data would always be the same, the signature would be provided essentially as an override to the originally-set relayer fee.

So the event would be different and the amount sent to the recipient would be different (i.e. the relayFeePct used throughout the method).

Copy link
Member Author

Choose a reason for hiding this comment

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

interesting let me try this

Copy link
Member

Choose a reason for hiding this comment

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

ye, what @mrice32 describes fits with my understanding of this method. you can actually just overload the fillRelay method with an extra param which optionally lets you change the originally provided relayFeePct.

Copy link
Member Author

Choose a reason for hiding this comment

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

The only problem with overloading is that overloading doesn't play nicely with ethers like it does with web3. Do you have a strong opinion about overloading versus creating a new function name?

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

Looks good! Minor comments

bytes depositorSignature;
}

function fillRelayUpdatedFee(
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: since this method is public, should it be moved up next to the fillRelay?

bytes32 expectedDepositorMessageHash = keccak256(
abi.encode("ACROSS-V2-FEE-1.0", updatedRelayerFeeData.newRelayerFeePct, depositId, originChainId)
);
require(expectedDepositorMessageHash == updatedRelayerFeeData.depositorMessageHash, "incorrect new fee");
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need them to pass in the message hash? Aren't we already generating it from other data they've provided?

Copy link
Member Author

Choose a reason for hiding this comment

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

ah true!

relayAmount: totalRelayAmount
});
bytes32 relayHash = _getRelayHash(relayData);
_fillRelay(relayHash, relayData, updatedRelayerFeeData.newRelayerFeePct, maxTokensToSend, repaymentChain);
Copy link
Contributor

Choose a reason for hiding this comment

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

+1!

uint256 repaymentChain;
}

function _emitFillRelay(FillRelayEventData memory eventData) internal {
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 extra method here just to avoid stack too deep errors?

Copy link
Member Author

Choose a reason for hiding this comment

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

exactly


function _fillRelay(
bytes32 relayHash,
RelayData memory relayData,
Copy link
Member

Choose a reason for hiding this comment

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

is there a gas difference between this being memory and calldata?

Copy link
Member Author

Choose a reason for hiding this comment

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

according to this answer, calldata is cheaper.

Also aside, when testing I noticed that declaring a variable as memory does not contribute to the "stack too deep error", while calldata does?

Copy link
Member Author

Choose a reason for hiding this comment

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

Note that calldata is only avaialble for external methods. source

_fillRelay(relayHash, relayData, relayerFeePct, maxTokensToSend, repaymentChain);
}

function _fillRelay(
Copy link
Member

Choose a reason for hiding this comment

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

do you have a section for internal methods? should probs be located there.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

) internal {
// We limit the relay fees to prevent the user spending all their funds on fees.
require(
updatableRelayerFeePct <= 0.5e18 &&
Copy link
Member

Choose a reason for hiding this comment

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

I think one of these checks is redundant. if you validate relayData.realizedLpFeePct and (updatableRelayerFeePct + relayData.realizedLpFeePct) < 1e18 then it's impossible for updatableRelayerFeePct to be >0.5e18.

Copy link
Member Author

Choose a reason for hiding this comment

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

what if realizedLpFeePct = 0 but updatableRelayerFeePct = 0.6?

Copy link
Member

Choose a reason for hiding this comment

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

then just check that: updatableRelayerFeePct <= 0.5e18 && relayData.realizedLpFeePct <= 0.5e18?

Copy link
Member Author

Choose a reason for hiding this comment

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

true

relayData.recipient
// Needed to resolve stack too deep compile-time error
_emitFillRelay(
FillRelayEventData(relayHash, relayData, updatableRelayerFeePct, fillAmountPreFees, repaymentChain)
Copy link
Member

Choose a reason for hiding this comment

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

dont emit RelayData. we dont want to emit structs. ideally what you had before was better so etherscan can parse it.

Copy link
Member

Choose a reason for hiding this comment

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

I take that back. your method you call does this 🙏

Copy link
Member Author

Choose a reason for hiding this comment

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

haha yep this is just unreadable to get around stack too deep

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

LGTM!

// above keccak156 hash), then this will revert.
bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(expectedDepositorMessageHash);
require(
SignatureChecker.isValidSignatureNow(depositor, ethSignedMessageHash, depositorSignature),
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor thing we should be aware of: since this allows for ERC1271 "contract signatures", this can make a call out to the caller under the hood. Shouldn't be a problem, just wanted to mention it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmm thats a good point. As long as we reentrancy guard this method it should be ok though? I suppose also the calling contrct could call this contract back into fillRelay which I don't think could cause issues, but we could easily remedy by moving all this signature verification logic AFTER _fillRelay. This would also be more consistent perhaps with Check-effects-interaction pattern. WDYT?

Copy link
Contributor

@mrice32 mrice32 Feb 9, 2022

Choose a reason for hiding this comment

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

Reading more into the method, I really don't think this is a problem, and it's arguable that it's even an interaction, because it makes a staticcall (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/63b466901fb015538913f811c5112a2775042177/contracts/utils/cryptography/SignatureChecker.sol#L35), meaning no state can be modified anywhere inside that call (and subcalls). It's more akin to making a balanceOf call, which I think is considered a check rather than an interaction.

I think this is safe.

@nicholaspai nicholaspai changed the title feat(spoke-pool): Allow relayer and depositor to agree to increase relay fee feat(spoke-pool): Allow relayer and depositor to agree to update relay fee Feb 9, 2022
@nicholaspai
Copy link
Member Author

Remaining comment here: #27 (comment)

@chrismaree wdyt?

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

LGTM!

// above keccak156 hash), then this will revert.
bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(expectedDepositorMessageHash);
require(
SignatureChecker.isValidSignatureNow(depositor, ethSignedMessageHash, depositorSignature),
Copy link
Contributor

@mrice32 mrice32 Feb 9, 2022

Choose a reason for hiding this comment

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

Reading more into the method, I really don't think this is a problem, and it's arguable that it's even an interaction, because it makes a staticcall (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/63b466901fb015538913f811c5112a2775042177/contracts/utils/cryptography/SignatureChecker.sol#L35), meaning no state can be modified anywhere inside that call (and subcalls). It's more akin to making a balanceOf call, which I think is considered a check rather than an interaction.

I think this is safe.

@nicholaspai nicholaspai merged commit 25ff460 into master Feb 9, 2022
@nicholaspai nicholaspai deleted the npai/speed-up branch February 9, 2022 19:57
pxrl added a commit that referenced this pull request Feb 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants