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

FUN-1234 (feat): Chainlink Functions premium fees in USD denomination #12000

Closed
wants to merge 19 commits into from

Conversation

justinkaseman
Copy link
Contributor

@justinkaseman justinkaseman commented Feb 12, 2024

Changes:

  • Adds operationFee that pays to the FunctionsCoordinator contract's owner
  • FunctionsCoordinator now takes a LINK/USD feed in the constructor
  • A new Commitment & OracleRequest structure that uses the operationFee
  • The LogPollerWrapper uses the Coordinator's typeAndVersion to select between which OracleRequest to watch for

Copy link
Contributor

I see that you haven't updated any README files. Would it make sense to do so?

contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol Outdated Show resolved Hide resolved
return (uint256(usdPerUnitLink), s_linkToUsdFeed.decimals());
}

function _getJuelsFromUsd(uint256 amountUsd) private view returns (uint96) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I need to work through this math by hand to verify.

Copy link
Contributor

Choose a reason for hiding this comment

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

I doubled checked. I think this is good.

Copy link
Contributor

Choose a reason for hiding this comment

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

We also have tests that triple and quadruple check this, right?

Copy link
Contributor Author

@justinkaseman justinkaseman Feb 19, 2024

Choose a reason for hiding this comment

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

The tests are in this PR, yes. They should also be reviewed.

@@ -186,6 +186,10 @@ func StartNewChainWithContracts(t *testing.T, nClients int) (*bind.TransactOpts,
linkEthFeedAddr, _, _, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(owner, b, 18, big.NewInt(5_000_000_000_000_000))
require.NoError(t, err)

Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed we don't have additional tests that cover both the previous contract version & new contracts. My guess is this is gonna be flagged for lack of coverage by SonarQube. I think we need to write tests which cover both in order to get this PR merged.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I think the SonarQube coverage requirement may have been temporarily paused for other reasons? Either way, I'd really prefer we test with both versions of the contract here just to be safe.
I'm also curious if we can simulate a coordinator contract upgrade in our 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.

There is a test with an upgrade, though it doesn't go from version to version, because previously we only held on to one wrapper.

I think it's a good idea to change this test to actually move through breaking changes

Copy link
Contributor Author

@justinkaseman justinkaseman Feb 16, 2024

Choose a reason for hiding this comment

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

I took an attempt with this, but the types got messy.
I'm opening a new ticket to ensure that this happens, even though it won't be in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

okay. In the mean time, we should properly test this process on staging.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you can, please preserve existing tests without changes and only add new ones. We can remove obsolete ones later, once prod is fully migrated.

core/services/relay/evm/functions/logpoller_wrapper.go Outdated Show resolved Hide resolved
core/services/relay/evm/functions/logpoller_wrapper.go Outdated Show resolved Hide resolved
core/services/relay/evm/functions/logpoller_wrapper.go Outdated Show resolved Hide resolved

func (l *logPollerWrapper) logsToResponses(coordinator coordinator, responseLogs []logpoller.Log) ([]evmRelayTypes.OracleResponse, error) {
var responses []evmRelayTypes.OracleResponse

Copy link
Contributor

@KuphJr KuphJr Feb 15, 2024

Choose a reason for hiding this comment

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

Why not set the parsingContract here first based on the version, then remove the 2 duplicated code blocks below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because we don't have a robust way to handle the two different types of wrappers here. Go is very strict about typing. We need a wrapper of wrappers of sorts. Something we will add to our tech debt and tackle soon.

KuphJr
KuphJr previously approved these changes Feb 19, 2024
return nil, err
}
return nameEncoded, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you very much for all the additional tests above!

…12066)

* fix: test race

* Revert "(test): Run LogPollerWrapperTest v1/v2 tests sequentially"

This reverts commit 1ba2c06.

* chore: refactor logpoller_wrapper by injecting a coordinator dep

* fix: remove duplicates abitype definitions

* fix: lint sonarqube reliability issue

* fix: race conditions due to share variables

---------

Co-authored-by: Justin Kaseman <[email protected]>
func init() {
err := initAbiTypes()
if err != nil {
panic(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we introducing a panic? This does not seem like the best practice.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I am not quite sure I understand why this function is here at all...

Copy link
Contributor

Choose a reason for hiding this comment

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

We can probably run initAbiTypes() inside NewLogPollerWrapper() and also not panic.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I don't love the panic either, my initial reasons were:
The init() function can not return an error, so if anything fails this is the only way. The benefits of initialising this here is that it will run after variables are declare and before anything else is executed (this is specially important because these are pkg variables that can be reference from outside). Another pro: It will only run once compare to re-declaring the types on each request.

We can probably run initAbiTypes() inside NewLogPollerWrapper() and also not panic.

I thought about this but, wouldn't we leave the door open to someone referencing these pkg variables before they are initialised? 🤔

I think there is an alternative that could work too without panic and is defining a structure like:

type validAbiTypes struct{
    uint32Type abi.Type,
    ...
}

that contain all the valid types as part of logpuller_wrapper, this could be initialised in the NewLogPollerWrapper and injected down to the V1 and V2 implementations of the Coordinator.

will explore these two last options and have something for today

if errActive != nil || errProposed != nil {
l.lggr.Errorw("LogPollerWrapper: Failed to register filters", "errorActive", errActive, "errorProposed", errProposed)
var activeCoordinator Coordinator
switch {
Copy link
Contributor

Choose a reason for hiding this comment

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

This section looks cleaner. Thanks!

@@ -60,28 +55,42 @@ func newSubscriber(expectedCalls int) *subscriber {
return sub
}

func addr(t *testing.T, lastByte string) []byte {
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that passing t was not actually the issue which caused the race condition, can we revert this change?

return contractAddr, nil
}

type setupResponse struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Good way to clean this up.

Copy link
Contributor

@bolekk bolekk left a comment

Choose a reason for hiding this comment

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

A bunch of minor comments and questions.
One small bug (cc @agparadiso please verify)

msg.sender,
commitment
FunctionsResponse.Commitment({
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a comment here explaining that we re-pack everything into an older Commitment struct compatible with the Router?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good suggestion, I agree that it'll make it more clear

s_feePool += commitment.donFee;
s_feePool += commitment.donFeeJuels;
// Pay the operation fee to the Coordinator owner
s_withdrawableTokens[_owner()] += commitment.operationFeeJuels;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there already a proposal for concrete values of all 3 fees in prod? Can we discuss that in the Config sheet maybe?

Copy link
Contributor Author

@justinkaseman justinkaseman Feb 20, 2024

Choose a reason for hiding this comment

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

It's in another PR in tooling. It will be added to the Config sheet once finalized.

emit RequestBilled({
requestId: requestId,
juelsPerGas: juelsPerGas,
l1FeeShareWei: l1FeeShareWei,
callbackCostJuels: callbackCostJuels,
totalCostJuels: gasOverheadJuels + callbackCostJuels + commitment.donFee + commitment.adminFee
donFeeJuels: commitment.donFeeJuels,
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this affect Atlas or other data collection? Any other event changes that we are coordinating (or will) with other teams?)

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 not. I already double checked that Atlas only uses Router events.

commitment = FunctionsResponse.Commitment({
adminFee: request.adminFee,
commitment = FunctionsResponse.CommitmentWithOperationFee({
adminFeeJuels: request.adminFee,
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we are deprecating adminFee (right?), could we save all this effort with a new Commitment struct by putting the new operationFee into adminFee field? Still passing a zero to the router so everything matches there. Not the most elegant approach but maybe a lot simpler?

Copy link
Contributor

@KuphJr KuphJr Feb 20, 2024

Choose a reason for hiding this comment

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

I hadn't considered this. I'd be very curious to know if this is possible as it would be great to eliminate the changes to the log_poller_wrapper.

@@ -186,6 +186,10 @@ func StartNewChainWithContracts(t *testing.T, nClients int) (*bind.TransactOpts,
linkEthFeedAddr, _, _, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(owner, b, 18, big.NewInt(5_000_000_000_000_000))
require.NoError(t, err)

Copy link
Contributor

Choose a reason for hiding this comment

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

If you can, please preserve existing tests without changes and only add new ones. We can remove obsolete ones later, once prod is fully migrated.

if err != nil {
l.lggr.Error("LatestEvents: creating a contract instance for parsing failed")
Copy link
Contributor

Choose a reason for hiding this comment

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

Keep this log line (with an appropriate message).

resultsReq = append(resultsReq, requests...)
responses, err := coordinator.LogsToResponses(responseLogs)
if err != nil {
return nil, nil, err
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here - log something with a "LatestEvents" prefix

l.lggr.Error("LogPollerWrapper: cannot update activeCoordinator to zero address")
return
}

if activeCoordinator == l.activeCoordinator && proposedCoordinator == l.proposedCoordinator {
if (l.activeCoordinator != nil && l.activeCoordinator.Address() == activeCoordinatorAddress) &&
(l.proposedCoordinator != nil && l.proposedCoordinator.Address() == proposedCoordinatorAddress) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If proposedCoordinator == nil we never get inside this block but often we should. @agparadiso

Copy link
Contributor

Choose a reason for hiding this comment

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

ok changed to:

if (l.activeCoordinator != nil && l.activeCoordinator.Address() == activeCoordinatorAddress) &&
		(l.proposedCoordinator == nil || l.proposedCoordinator.Address() == proposedCoordinatorAddress) {
		l.lggr.Debug("LogPollerWrapper: no changes to routes")
		return
	}

that would contemplate both scenarios


return c.logPoller.RegisterFilter(
logpoller.Filter{
Name: logpoller.FilterName("FunctionsLogPollerWrapper", c.address.String(), "-v", "2"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Move string to a const used by both files. Also you could encode "-v2" in the prefix.

return
}

var proposedCoordinator Coordinator
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a helper method to avoid code duplication?

@justinkaseman
Copy link
Contributor Author

Closing in favor of #12104

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.

5 participants