Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Oozyx committed Nov 28, 2024
1 parent 86f83f4 commit 5aa92bd
Show file tree
Hide file tree
Showing 5 changed files with 4,915 additions and 0 deletions.
64 changes: 64 additions & 0 deletions integration-tests/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,70 @@ func SetupOCRv2Contracts(
return ocrInstances, nil
}

// SetupDualAggregatorContracts deploys a number of DualAggregator contracts and configures them with defaults
func SetupDualAggregatorContracts(
l zerolog.Logger,
seth *seth.Client,
ocrContractsConfig ocr.OffChainAggregatorsConfig,
linkTokenAddress common.Address,
transmitters []string,
ocrOptions contracts.OffchainOptionsDualAggregator,
) ([]contracts.OffchainAggregatorV2, error) {
var ocrInstances []contracts.OffchainAggregatorV2

if ocrContractsConfig == nil {
return nil, fmt.Errorf("you need to pass non-nil OffChainAggregatorsConfig to setup OCR contracts")
}

if !ocrContractsConfig.UseExistingOffChainAggregatorsContracts() {
for contractCount := 0; contractCount < ocrContractsConfig.NumberOfContractsToDeploy(); contractCount++ {
ocrInstance, err := contracts.DeployDualAggregator(
l,
seth,
linkTokenAddress,
ocrOptions,
)
if err != nil {
return nil, fmt.Errorf("OCRv2 instance deployment have failed: %w", err)
}
ocrInstances = append(ocrInstances, &ocrInstance)
if (contractCount+1)%ContractDeploymentInterval == 0 { // For large amounts of contract deployments, space things out some
time.Sleep(2 * time.Second)
}
}
} else {
for _, address := range ocrContractsConfig.OffChainAggregatorsContractsAddresses() {
ocrInstance, err := contracts.LoadOffchainAggregatorV2(l, seth, address)
if err != nil {
return nil, fmt.Errorf("OCRv2 instance loading have failed: %w", err)
}
ocrInstances = append(ocrInstances, &ocrInstance)
}

if !ocrContractsConfig.ConfigureExistingOffChainAggregatorsContracts() {
return ocrInstances, nil
}
}

// Gather address payees
var payees []string
for range transmitters {
payees = append(payees, seth.Addresses[0].Hex())
}

// Set Payees
for contractCount, ocrInstance := range ocrInstances {
err := ocrInstance.SetPayees(transmitters, payees)
if err != nil {
return nil, fmt.Errorf("error settings OCR payees: %w", err)
}
if (contractCount+1)%ContractDeploymentInterval == 0 { // For large amounts of contract deployments, space things out some
time.Sleep(2 * time.Second)
}
}
return ocrInstances, nil
}

// ConfigureOCRv2AggregatorContracts sets configuration for a number of OCRv2 contracts
func ConfigureOCRv2AggregatorContracts(
contractConfig *contracts.OCRv2Config,
Expand Down
17 changes: 17 additions & 0 deletions integration-tests/contracts/contract_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,23 @@ type OffchainOptions struct {
Description string // A short description of what is being reported
}

type OffchainOptionsDualAggregator struct {
MaximumGasPrice uint32 // The highest gas price for which transmitter will be compensated
ReasonableGasPrice uint32 // The transmitter will receive reward for gas prices under this value
MicroLinkPerEth uint32 // The reimbursement per ETH of gas cost, in 1e-6LINK units
LinkGweiPerObservation uint32 // The reward to the oracle for contributing an observation to a successfully transmitted report, in 1e-9LINK units
LinkGweiPerTransmission uint32 // The reward to the transmitter of a successful report, in 1e-9LINK units
MinimumAnswer *big.Int // The lowest answer the median of a report is allowed to be
MaximumAnswer *big.Int // The highest answer the median of a report is allowed to be
BillingAccessController common.Address // The access controller for billing admin functions
RequesterAccessController common.Address // The access controller for requesting new rounds
Decimals uint8 // Answers are stored in fixed-point format, with this many digits of precision
Description string // A short description of what is being reported
SecondaryProxy common.Address // The address of the secondary proxy
CutoffTime uint32 // The cutoff time
MaxSyncIterations uint32 // The maximum number of sync iterations
}

// https://uploads-ssl.webflow.com/5f6b7190899f41fb70882d08/603651a1101106649eef6a53_chainlink-ocr-protocol-paper-02-24-20.pdf
type OffChainAggregatorConfig struct {
DeltaProgress time.Duration // The duration in which a leader must achieve progress or be replaced
Expand Down
4,661 changes: 4,661 additions & 0 deletions integration-tests/contracts/ethereum/DualAggregator.go

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions integration-tests/contracts/ethereum_contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,23 @@ func DefaultOffChainAggregatorOptions() OffchainOptions {
}
}

func DefaultOffChainDualAggregatorOptions() OffchainOptionsDualAggregator {
return OffchainOptionsDualAggregator{
MaximumGasPrice: uint32(3000),
ReasonableGasPrice: uint32(10),
MicroLinkPerEth: uint32(500),
LinkGweiPerObservation: uint32(500),
LinkGweiPerTransmission: uint32(500),
MinimumAnswer: big.NewInt(1),
MaximumAnswer: big.NewInt(50000000000000000),
Decimals: 8,
Description: "Test OCR",
SecondaryProxy: common.Address{},
CutoffTime: uint32(100),
MaxSyncIterations: uint32(100),
}
}

// DefaultOffChainAggregatorConfig returns some base defaults for configuring an OCR contract
func DefaultOffChainAggregatorConfig(numberNodes int) OffChainAggregatorConfig {
s := []int{1}
Expand Down Expand Up @@ -631,6 +648,44 @@ func DeployOffchainAggregatorV2(l zerolog.Logger, seth *seth.Client, linkTokenAd
}, nil
}

func DeployDualAggregator(l zerolog.Logger, seth *seth.Client, linkTokenAddress common.Address, offchainOptions OffchainOptionsDualAggregator) (EthereumOffchainAggregatorV2, error) {
contractAbi, err := contractsethereum.DualAggregatorMetaData.GetAbi()
if err != nil {
return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to get OffChain Aggregator v2 ABI: %w", err)
}
seth.ContractStore.AddABI("DualAggregator", *contractAbi)
seth.ContractStore.AddBIN("DualAggregator", common.FromHex(contractsethereum.DualAggregatorMetaData.Bin))

ocrDeploymentData2, err := seth.DeployContract(seth.NewTXOpts(), "DualAggregator", *contractAbi, common.FromHex(contractsethereum.DualAggregatorMetaData.Bin),
linkTokenAddress,
offchainOptions.MinimumAnswer,
offchainOptions.MaximumAnswer,
offchainOptions.BillingAccessController,
offchainOptions.RequesterAccessController,
offchainOptions.Decimals,
offchainOptions.Description,
offchainOptions.SecondaryProxy,
offchainOptions.CutoffTime,
offchainOptions.MaxSyncIterations,
)

if err != nil {
return EthereumOffchainAggregatorV2{}, fmt.Errorf("OCRv2 instance deployment have failed: %w", err)
}

ocr2, err := ocr2aggregator.NewOCR2Aggregator(ocrDeploymentData2.Address, wrappers.MustNewWrappedContractBackend(nil, seth))
if err != nil {
return EthereumOffchainAggregatorV2{}, fmt.Errorf("failed to instantiate OCRv2 instance: %w", err)
}

return EthereumOffchainAggregatorV2{
client: seth,
contract: ocr2,
address: &ocrDeploymentData2.Address,
l: l,
}, nil
}

func (e *EthereumOffchainAggregatorV2) Address() string {
return e.address.Hex()
}
Expand Down
118 changes: 118 additions & 0 deletions integration-tests/smoke/ocr2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,39 @@ func TestOCRv2Basic(t *testing.T) {
}
}

func TestDualAggregatorBasic(t *testing.T) {
t.Parallel()

noMedianPlugin := map[string]string{string(env.MedianPlugin.Cmd): ""}
medianPlugin := map[string]string{string(env.MedianPlugin.Cmd): "chainlink-feeds"}
for _, test := range []ocr2test{
{"legacy", noMedianPlugin, false},
{"legacy-chain-reader", noMedianPlugin, true},
{"plugins", medianPlugin, false},
{"plugins-chain-reader", medianPlugin, true},
} {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
l := logging.GetTestLogger(t)

testEnv, aggregatorContracts, sethClient := prepareDualAggregatorSmokeTestEnv(t, test, l, 5)

err := testEnv.MockAdapter.SetAdapterBasedIntValuePath("ocr2", []string{http.MethodGet, http.MethodPost}, 10)
require.NoError(t, err)
err = actions.WatchNewOCRRound(l, sethClient, 2, contracts.V2OffChainAgrregatorToOffChainAggregatorWithRounds(aggregatorContracts), time.Minute*5)
require.NoError(t, err)

roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(2))
require.NoError(t, err, "Error getting latest OCR answer")
require.Equal(t, int64(10), roundData.Answer.Int64(),
"Expected latest answer from OCR contract to be 10 but got %d",
roundData.Answer.Int64(),
)
})
}
}

// Tests that just calling requestNewRound() will properly induce more rounds
func TestOCRv2Request(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -223,6 +256,91 @@ func prepareORCv2SmokeTestEnv(t *testing.T, testData ocr2test, l zerolog.Logger,
return testEnv, aggregatorContracts, sethClient
}

func prepareDualAggregatorSmokeTestEnv(t *testing.T, testData ocr2test, l zerolog.Logger, firstRoundResult int) (*test_env.CLClusterTestEnv, []contracts.OffchainAggregatorV2, *seth.Client) {
config, err := tc.GetConfig([]string{"Smoke"}, tc.OCR2)
if err != nil {
t.Fatal(err)
}

privateNetwork, err := actions.EthereumNetworkConfigFromConfig(l, &config)
require.NoError(t, err, "Error building ethereum network config")

clNodeCount := 6

testEnv, err := test_env.NewCLTestEnvBuilder().
WithTestInstance(t).
WithTestConfig(&config).
WithPrivateEthereumNetwork(privateNetwork.EthereumNetworkConfig).
WithMockAdapter().
WithCLNodes(clNodeCount).
WithCLNodeOptions(test_env.WithNodeEnvVars(testData.env)).
WithStandardCleanup().
Build()
require.NoError(t, err)

evmNetwork, err := testEnv.GetFirstEvmNetwork()
require.NoError(t, err, "Error getting first evm network")

sethClient, err := utils.TestAwareSethClient(t, config, evmNetwork)
require.NoError(t, err, "Error getting seth client")

nodeClients := testEnv.ClCluster.NodeAPIs()
bootstrapNode, workerNodes := nodeClients[0], nodeClients[1:]

linkContract, err := actions.LinkTokenContract(l, sethClient, config.OCR2)
require.NoError(t, err, "Error loading/deploying link token contract")

err = actions.FundChainlinkNodesFromRootAddress(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(workerNodes), big.NewFloat(*config.Common.ChainlinkNodeFunding))
require.NoError(t, err, "Error funding Chainlink nodes")

t.Cleanup(func() {
// ignore error, we will see failures in the logs anyway
_ = actions.ReturnFundsFromNodes(l, sethClient, contracts.ChainlinkClientToChainlinkNodeWithKeysAndAddress(testEnv.ClCluster.NodeAPIs()))
})

// Gather transmitters
var transmitters []string
for _, node := range workerNodes {
addr, err := node.PrimaryEthAddress()
if err != nil {
require.NoError(t, fmt.Errorf("error getting node's primary ETH address: %w", err))
}
transmitters = append(transmitters, addr)
}

dualAggregatorOffChainOptions := contracts.DefaultOffChainDualAggregatorOptions()
aggregatorContracts, err := actions.SetupDualAggregatorContracts(l, sethClient, config.OCR2, common.HexToAddress(linkContract.Address()), transmitters, dualAggregatorOffChainOptions)
require.NoError(t, err, "Error deploying OCRv2 aggregator contracts")

if sethClient.ChainID < 0 {
t.Errorf("negative chain ID: %d", sethClient.ChainID)
}
err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, testEnv.MockAdapter, "ocr2", 5, uint64(sethClient.ChainID), false, testData.chainReaderAndCodec) //nolint:gosec // G115 false positive
require.NoError(t, err, "Error creating OCRv2 jobs")

if !config.OCR2.UseExistingOffChainAggregatorsContracts() || (config.OCR2.UseExistingOffChainAggregatorsContracts() && config.OCR2.ConfigureExistingOffChainAggregatorsContracts()) {
ocrOffChainOptions := contracts.DefaultOffChainAggregatorOptions()
ocrV2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffChainOptions)
require.NoError(t, err, "Error building OCRv2 config")

err = actions.ConfigureOCRv2AggregatorContracts(ocrV2Config, aggregatorContracts)
require.NoError(t, err, "Error configuring OCRv2 aggregator contracts")
}

assertCorrectNodeConfiguration(t, l, clNodeCount, testData, testEnv)

err = actions.WatchNewOCRRound(l, sethClient, 1, contracts.V2OffChainAgrregatorToOffChainAggregatorWithRounds(aggregatorContracts), time.Minute*5)
require.NoError(t, err, "Error watching for new OCR2 round")
roundData, err := aggregatorContracts[0].GetRound(testcontext.Get(t), big.NewInt(1))
require.NoError(t, err, "Getting latest answer from OCR contract shouldn't fail")
require.Equal(t, int64(firstRoundResult), roundData.Answer.Int64(),
"Expected latest answer from OCR contract to be 5 but got %d",
roundData.Answer.Int64(),
)

return testEnv, aggregatorContracts, sethClient
}

func assertCorrectNodeConfiguration(t *testing.T, l zerolog.Logger, totalNodeCount int, testData ocr2test, testEnv *test_env.CLClusterTestEnv) {
expectedNodesWithConfiguration := totalNodeCount - 1 // minus bootstrap node
var expectedPatterns []string
Expand Down

0 comments on commit 5aa92bd

Please sign in to comment.