Skip to content

Commit

Permalink
Added smoke test for canceling subscription on VRFv2 (#11587)
Browse files Browse the repository at this point in the history
* Added smoke test for canceling subscription on VRFv2

* Added smoke test for canceling subscription on VRFv2

* Extended v2 coordinator contract model with WaitForSubscriptionCanceledEvent

* Added smoke test for owner canceling subscription on VRFv2

---------

Co-authored-by: David Kneisly <[email protected]>
Co-authored-by: Ilja Pavlovs <[email protected]>
  • Loading branch information
3 people authored Dec 19, 2023
1 parent 233445a commit d4ab877
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 0 deletions.
2 changes: 2 additions & 0 deletions integration-tests/contracts/contract_vrf_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ type VRFCoordinatorV2 interface {
Address() string
GetSubscription(ctx context.Context, subID uint64) (vrf_coordinator_v2.GetSubscription, error)
PendingRequestsExist(ctx context.Context, subID uint64) (bool, error)
OwnerCancelSubscription(subID uint64) (*types.Transaction, error)
CancelSubscription(subID uint64, to common.Address) (*types.Transaction, error)
FindSubscriptionID(subID uint64) (uint64, error)
WaitForRandomWordsFulfilledEvent(requestID []*big.Int, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsFulfilled, error)
WaitForRandomWordsRequestedEvent(keyHash [][32]byte, subID []uint64, sender []common.Address, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested, error)
WaitForSubscriptionCanceledEvent(subID []uint64, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2SubscriptionCanceled, error)
}

type VRFCoordinatorV2_5 interface {
Expand Down
39 changes: 39 additions & 0 deletions integration-tests/contracts/ethereum_vrfv2_contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,25 @@ func (v *EthereumVRFCoordinatorV2) PendingRequestsExist(ctx context.Context, sub
return pendingRequestExists, nil
}

// OwnerCancelSubscription cancels subscription,
// return funds to the subscription owner,
// down not check if pending requests for a sub exist,
// outstanding requests may fail onchain
func (v *EthereumVRFCoordinatorV2) OwnerCancelSubscription(subID uint64) (*types.Transaction, error) {
opts, err := v.client.TransactionOpts(v.client.GetDefaultWallet())
if err != nil {
return nil, err
}
tx, err := v.coordinator.OwnerCancelSubscription(
opts,
subID,
)
if err != nil {
return nil, err
}
return tx, v.client.ProcessTransaction(tx)
}

// CancelSubscription cancels subscription by Sub owner,
// return funds to specified address,
// checks if pending requests for a sub exist
Expand Down Expand Up @@ -300,6 +319,26 @@ func (v *EthereumVRFCoordinatorV2) WaitForRandomWordsRequestedEvent(keyHash [][3
}
}

func (v *EthereumVRFCoordinatorV2) WaitForSubscriptionCanceledEvent(subID []uint64, timeout time.Duration) (*vrf_coordinator_v2.VRFCoordinatorV2SubscriptionCanceled, error) {
eventsChannel := make(chan *vrf_coordinator_v2.VRFCoordinatorV2SubscriptionCanceled)
subscription, err := v.coordinator.WatchSubscriptionCanceled(nil, eventsChannel, subID)
if err != nil {
return nil, err
}
defer subscription.Unsubscribe()

for {
select {
case err := <-subscription.Err():
return nil, err
case <-time.After(timeout):
return nil, fmt.Errorf("timeout waiting for SubscriptionCanceled event")
case sub := <-eventsChannel:
return sub, nil
}
}
}

// GetAllRandomWords get all VRFv2 randomness output words
func (v *EthereumVRFConsumerV2) GetAllRandomWords(ctx context.Context, num int) ([]*big.Int, error) {
words := make([]*big.Int, 0)
Expand Down
187 changes: 187 additions & 0 deletions integration-tests/smoke/vrfv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"context"
"math/big"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/kelseyhightower/envconfig"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink-testing-framework/blockchain"
"github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext"
"github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions"
"github.com/smartcontractkit/chainlink/integration-tests/actions/vrfv2_actions/vrfv2_config"

Expand Down Expand Up @@ -113,6 +115,191 @@ func TestVRFv2Basic(t *testing.T) {
require.Equal(t, 1, w.Cmp(big.NewInt(0)), "Expected the VRF job give an answer bigger than 0")
}
})

t.Run("Canceling Sub And Returning Funds", func(t *testing.T) {
testConfig := vrfv2Config
subIDsForCancelling, err := vrfv2_actions.CreateFundSubsAndAddConsumers(
env,
testConfig,
linkToken,
vrfv2Contracts.Coordinator,
vrfv2Contracts.LoadTestConsumers,
1,
)
require.NoError(t, err)
subIDForCancelling := subIDsForCancelling[0]

testWalletAddress, err := actions.GenerateWallet()
require.NoError(t, err)

testWalletBalanceLinkBeforeSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), testWalletAddress.String())
require.NoError(t, err)

subscriptionForCancelling, err := vrfv2Contracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling)
require.NoError(t, err, "error getting subscription information")

subBalanceLink := subscriptionForCancelling.Balance

l.Info().
Str("Subscription Amount Link", subBalanceLink.String()).
Uint64("Returning funds from SubID", subIDForCancelling).
Str("Returning funds to", testWalletAddress.String()).
Msg("Canceling subscription and returning funds to subscription owner")

tx, err := vrfv2Contracts.Coordinator.CancelSubscription(subIDForCancelling, testWalletAddress)
require.NoError(t, err, "Error canceling subscription")

subscriptionCanceledEvent, err := vrfv2Contracts.Coordinator.WaitForSubscriptionCanceledEvent([]uint64{subIDForCancelling}, time.Second*30)
require.NoError(t, err, "error waiting for subscription canceled event")

cancellationTxReceipt, err := env.EVMClient.GetTxReceipt(tx.Hash())
require.NoError(t, err, "error getting tx cancellation Tx Receipt")

txGasUsed := new(big.Int).SetUint64(cancellationTxReceipt.GasUsed)
cancellationTxFeeWei := new(big.Int).Mul(txGasUsed, cancellationTxReceipt.EffectiveGasPrice)

l.Info().
Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()).
Str("Effective Gas Price", cancellationTxReceipt.EffectiveGasPrice.String()).
Uint64("Gas Used", cancellationTxReceipt.GasUsed).
Msg("Cancellation TX Receipt")

l.Info().
Str("Returned Subscription Amount Link", subscriptionCanceledEvent.Amount.String()).
Uint64("SubID", subscriptionCanceledEvent.SubId).
Str("Returned to", subscriptionCanceledEvent.To.String()).
Msg("Subscription Canceled Event")

require.Equal(t, subBalanceLink, subscriptionCanceledEvent.Amount, "SubscriptionCanceled event LINK amount is not equal to sub amount while canceling subscription")

testWalletBalanceLinkAfterSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), testWalletAddress.String())
require.NoError(t, err)

//Verify that sub was deleted from Coordinator
_, err = vrfv2Contracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling)
require.Error(t, err, "error not occurred when trying to get deleted subscription from old Coordinator after sub migration")

subFundsReturnedLinkActual := new(big.Int).Sub(testWalletBalanceLinkAfterSubCancelling, testWalletBalanceLinkBeforeSubCancelling)

l.Info().
Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()).
Str("Sub Funds Returned Actual - Link", subFundsReturnedLinkActual.String()).
Str("Sub Balance - Link", subBalanceLink.String()).
Msg("Sub funds returned")

require.Equal(t, 0, subBalanceLink.Cmp(subFundsReturnedLinkActual), "Returned LINK funds are not equal to sub balance that was cancelled")
})

t.Run("Owner Canceling Sub And Returning Funds While Having Pending Requests", func(t *testing.T) {
testConfig := vrfv2Config

// Underfund subscription to force fulfillments to fail
testConfig.SubscriptionFundingAmountLink = float64(0.000000000000000001) // 1 Juel

subIDsForCancelling, err := vrfv2_actions.CreateFundSubsAndAddConsumers(
env,
testConfig,
linkToken,
vrfv2Contracts.Coordinator,
vrfv2Contracts.LoadTestConsumers,
1,
)
require.NoError(t, err)

subIDForCancelling := subIDsForCancelling[0]

subscriptionForCancelling, err := vrfv2Contracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling)
require.NoError(t, err, "Error getting subscription information")

vrfv2_actions.LogSubDetails(l, subscriptionForCancelling, subIDForCancelling, vrfv2Contracts.Coordinator)

// No GetActiveSubscriptionIds function available - skipping check

pendingRequestsExist, err := vrfv2Contracts.Coordinator.PendingRequestsExist(testcontext.Get(t), subIDForCancelling)
require.NoError(t, err)
require.False(t, pendingRequestsExist, "Pending requests should not exist")

// Request randomness - should fail due to underfunded subscription
randomWordsFulfilledEventTimeout := 5 * time.Second
_, err = vrfv2_actions.RequestRandomnessAndWaitForFulfillment(
vrfv2Contracts.LoadTestConsumers[0],
vrfv2Contracts.Coordinator,
vrfv2Data,
subIDForCancelling,
testConfig.RandomnessRequestCountPerRequest,
testConfig,
randomWordsFulfilledEventTimeout,
l,
)
require.Error(t, err, "Error should occur while waiting for fulfilment due to low sub balance")

pendingRequestsExist, err = vrfv2Contracts.Coordinator.PendingRequestsExist(testcontext.Get(t), subIDForCancelling)
require.NoError(t, err)
require.True(t, pendingRequestsExist, "Pending requests should exist after unfilfulled requests due to low sub balance")

walletBalanceLinkBeforeSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), defaultWalletAddress)
require.NoError(t, err)

subscriptionForCancelling, err = vrfv2Contracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling)
require.NoError(t, err, "Error getting subscription information")
subBalanceLink := subscriptionForCancelling.Balance

l.Info().
Str("Subscription Amount Link", subBalanceLink.String()).
Uint64("Returning funds from SubID", subIDForCancelling).
Str("Returning funds to", defaultWalletAddress).
Msg("Canceling subscription and returning funds to subscription owner")

// Call OwnerCancelSubscription
tx, err := vrfv2Contracts.Coordinator.OwnerCancelSubscription(subIDForCancelling)
require.NoError(t, err, "Error canceling subscription")

subscriptionCanceledEvent, err := vrfv2Contracts.Coordinator.WaitForSubscriptionCanceledEvent([]uint64{subIDForCancelling}, time.Second*30)
require.NoError(t, err, "error waiting for subscription canceled event")

cancellationTxReceipt, err := env.EVMClient.GetTxReceipt(tx.Hash())
require.NoError(t, err, "error getting tx cancellation Tx Receipt")

txGasUsed := new(big.Int).SetUint64(cancellationTxReceipt.GasUsed)
cancellationTxFeeWei := new(big.Int).Mul(txGasUsed, cancellationTxReceipt.EffectiveGasPrice)

l.Info().
Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()).
Str("Effective Gas Price", cancellationTxReceipt.EffectiveGasPrice.String()).
Uint64("Gas Used", cancellationTxReceipt.GasUsed).
Msg("Cancellation TX Receipt")

l.Info().
Str("Returned Subscription Amount Link", subscriptionCanceledEvent.Amount.String()).
Uint64("SubID", subscriptionCanceledEvent.SubId).
Str("Returned to", subscriptionCanceledEvent.To.String()).
Msg("Subscription Canceled Event")

require.Equal(t, subBalanceLink, subscriptionCanceledEvent.Amount, "SubscriptionCanceled event LINK amount is not equal to sub amount while canceling subscription")

walletBalanceLinkAfterSubCancelling, err := linkToken.BalanceOf(testcontext.Get(t), defaultWalletAddress)
require.NoError(t, err)

// Verify that subscription was deleted from Coordinator contract
_, err = vrfv2Contracts.Coordinator.GetSubscription(testcontext.Get(t), subIDForCancelling)
l.Info().
Str("Expected error message", err.Error())
require.Error(t, err, "Error did not occur when fetching deleted subscription from the Coordinator after owner cancelation")

subFundsReturnedLinkActual := new(big.Int).Sub(walletBalanceLinkAfterSubCancelling, walletBalanceLinkBeforeSubCancelling)
l.Info().
Str("Wallet Balance Before Owner Cancelation", walletBalanceLinkBeforeSubCancelling.String()).
Str("Cancellation Tx Fee Wei", cancellationTxFeeWei.String()).
Str("Sub Funds Returned Actual - Link", subFundsReturnedLinkActual.String()).
Str("Sub Balance - Link", subBalanceLink.String()).
Str("Wallet Balance After Owner Cancelation", walletBalanceLinkAfterSubCancelling.String()).
Msg("Sub funds returned")

require.Equal(t, 0, subBalanceLink.Cmp(subFundsReturnedLinkActual), "Returned LINK funds are not equal to sub balance that was cancelled")

// Again, there is no GetActiveSubscriptionIds method on the v2 Coordinator contract, so we can't double check that the cancelled
// subID is no longer in the list of active subs
})
}

func TestVRFv2MultipleSendingKeys(t *testing.T) {
Expand Down

0 comments on commit d4ab877

Please sign in to comment.