diff --git a/.circleci/config.yml b/.circleci/config.yml index 4934c654cdb..1ba0a99cc13 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,6 +11,9 @@ jobs: - run: name: Docker build command: DOCKER_TAG=circleci make docker + - run: + name: Docker build non-root + command: DOCKER_TAG=circleci-nonroot CHAINLINK_USER=chainlink make docker - run: name: Docker push, if applicable command: | diff --git a/.github/workflows/cleanup-selfhosted-gha.yml b/.github/workflows/cleanup-selfhosted-gha.yml new file mode 100644 index 00000000000..9a176a5ed5b --- /dev/null +++ b/.github/workflows/cleanup-selfhosted-gha.yml @@ -0,0 +1,21 @@ +name: Cleanup Selfhosted Runners +on: + workflow_dispatch: + inputs: + githubToken: + description: 'This token should have access for modifying and listing github runners respective github repository' + required: true + dummyRunner: + description: 'A "dummy runner" in the context of https://github.com/philips-labs/terraform-aws-github-runner#usages Specifically, "This offline runner will ensure your builds will not fail immediately and stay queued until there is a runner to pick it up."' + required: false +jobs: + cleanup: + name: Cleanup + runs-on: ubuntu-latest + steps: + - name: Run GHA self hosted runner cleanup + uses: smartcontractkit/gha-cleanup@v0.0.1 + with: + githubRepository: smartcontractkit/chainlink + githubToken: ${{ github.event.inputs.githubToken }} + dummyRunner: ${{ github.event.inputs.dummyRunner }} diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 9405e8fbbba..724fb586613 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -81,6 +81,7 @@ jobs: integration: name: Integration tests running ./compose ${{ matrix.test }} against ${{ matrix.node }} runs-on: [self-hosted, sdlc-ghr-prod] + timeout-minutes: 17 strategy: matrix: test: ['test', 'test:ts'] diff --git a/GNUmakefile b/GNUmakefile index af5b33a7e65..76cc8d85bca 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -11,6 +11,7 @@ GO_LDFLAGS := $(shell tools/bin/ldflags) GOFLAGS = -ldflags "$(GO_LDFLAGS)" DOCKERFILE := core/chainlink.Dockerfile DOCKER_TAG ?= latest +CHAINLINK_USER ?= root TAGGED_REPO := $(REPO):$(DOCKER_TAG) ECR_REPO := "$(AWS_ECR_URL)/chainlink:$(DOCKER_TAG)" @@ -93,12 +94,14 @@ docker: ## Build the docker image. --build-arg BUILDER=$(BUILDER) \ --build-arg ENVIRONMENT=$(ENVIRONMENT) \ --build-arg COMMIT_SHA=$(COMMIT_SHA) \ + --build-arg CHAINLINK_USER=$(CHAINLINK_USER) \ -t $(TAGGED_REPO) \ . .PHONY: dockerpush dockerpush: ## Push the docker image to ecr docker push $(ECR_REPO) + docker push $(ECR_REPO)-nonroot help: @echo "" diff --git a/README.md b/README.md index edb398099f9..53b82d583fa 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,14 @@ By default this will start on port 6688, where it exposes a [REST API](https://g Once your node has started, you can view your current jobs with: ```bash -chainlink jobs list +chainlink job_specs list # v1 jobs +chainlink jobs list # v2 jobs ``` View details of a specific job with: ```bash -chainlink jobs show "$JOB_ID" +chainlink job_specs show "$JOB_ID # v1 jobs" ``` To find out more about the Chainlink CLI, you can always run `chainlink help`. @@ -176,14 +177,15 @@ go test -parallel=1 ./... 2. Install the dependencies: ```bash -cd evm -yarn install +yarn +yarn setup:contracts ``` 3. Run tests: ```bash -yarn run test-sol +cd evm-contracts +yarn test ``` ### Use of Go Generate diff --git a/VERSION b/VERSION index 85b7c695b80..c81aa44afbf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.6 +0.9.7 diff --git a/core/adapters/adapter.go b/core/adapters/adapter.go index 765f7974986..c64c45485f2 100644 --- a/core/adapters/adapter.go +++ b/core/adapters/adapter.go @@ -47,6 +47,8 @@ var ( TaskTypeCompare = models.MustNewTaskType("compare") // TaskTypeQuotient is the identifier for the Quotient adapter. TaskTypeQuotient = models.MustNewTaskType("quotient") + // TaskTypeResultCollect is the identifier for the ResultCollect adapter. + TaskTypeResultCollect = models.MustNewTaskType("resultcollect") ) // BaseAdapter is the minimum interface required to create an adapter. Only core @@ -145,6 +147,8 @@ func FindNativeAdapterFor(task models.TaskSpec) BaseAdapter { return &Compare{} case TaskTypeQuotient: return &Quotient{} + case TaskTypeResultCollect: + return &ResultCollect{} default: return nil } diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index 0530d1905a5..d2a1d8812c1 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -114,7 +114,7 @@ func (ba *Bridge) responseToRunResult(body []byte, input models.RunInput) models return models.NewRunOutputComplete(data) } - return models.NewRunOutputCompleteWithResult(brr.Data.String()) + return models.NewRunOutputCompleteWithResult(brr.Data.String(), input.ResultCollection()) } func (ba *Bridge) postToExternalAdapter( diff --git a/core/adapters/compare.go b/core/adapters/compare.go index 5753b71a117..13c6bdeee40 100644 --- a/core/adapters/compare.go +++ b/core/adapters/compare.go @@ -38,33 +38,33 @@ func (c *Compare) Perform(input models.RunInput, _ *store.Store) models.RunOutpu switch c.Operator { case "eq": - return models.NewRunOutputCompleteWithResult(c.Value == prevResult) + return models.NewRunOutputCompleteWithResult(c.Value == prevResult, input.ResultCollection()) case "neq": - return models.NewRunOutputCompleteWithResult(c.Value != prevResult) + return models.NewRunOutputCompleteWithResult(c.Value != prevResult, input.ResultCollection()) case "gt": value, desired, err := getValues(prevResult, c.Value) if err != nil { return models.NewRunOutputError(err) } - return models.NewRunOutputCompleteWithResult(desired < value) + return models.NewRunOutputCompleteWithResult(desired < value, input.ResultCollection()) case "gte": value, desired, err := getValues(prevResult, c.Value) if err != nil { return models.NewRunOutputError(err) } - return models.NewRunOutputCompleteWithResult(desired <= value) + return models.NewRunOutputCompleteWithResult(desired <= value, input.ResultCollection()) case "lt": value, desired, err := getValues(prevResult, c.Value) if err != nil { return models.NewRunOutputError(err) } - return models.NewRunOutputCompleteWithResult(desired > value) + return models.NewRunOutputCompleteWithResult(desired > value, input.ResultCollection()) case "lte": value, desired, err := getValues(prevResult, c.Value) if err != nil { return models.NewRunOutputError(err) } - return models.NewRunOutputCompleteWithResult(desired >= value) + return models.NewRunOutputCompleteWithResult(desired >= value, input.ResultCollection()) default: return models.NewRunOutputError(ErrOperatorNotSpecified) } diff --git a/core/adapters/eth_bool.go b/core/adapters/eth_bool.go index 474982aaea6..664cd42f883 100644 --- a/core/adapters/eth_bool.go +++ b/core/adapters/eth_bool.go @@ -25,10 +25,10 @@ func (e *EthBool) TaskType() models.TaskType { // "0x0000000000000000000000000000000000000000000000000000000000000000" func (*EthBool) Perform(input models.RunInput, _ *store.Store) models.RunOutput { if boolean(input.Result().Type) { - return models.NewRunOutputCompleteWithResult(evmTrue) + return models.NewRunOutputCompleteWithResult(evmTrue, input.ResultCollection()) } - return models.NewRunOutputCompleteWithResult(evmFalse) + return models.NewRunOutputCompleteWithResult(evmFalse, input.ResultCollection()) } func boolean(t gjson.Type) bool { diff --git a/core/adapters/eth_format.go b/core/adapters/eth_format.go index 04aaecc6eb4..82599846ab1 100644 --- a/core/adapters/eth_format.go +++ b/core/adapters/eth_format.go @@ -32,7 +32,7 @@ func (*EthBytes32) Perform(input models.RunInput, _ *store.Store) models.RunOutp hex = hex[:utils.EVMWordHexLen] } - return models.NewRunOutputCompleteWithResult(utils.AddHexPrefix(hex)) + return models.NewRunOutputCompleteWithResult(utils.AddHexPrefix(hex), input.ResultCollection()) } // EthInt256 holds no fields @@ -55,7 +55,7 @@ func (*EthInt256) Perform(input models.RunInput, _ *store.Store) models.RunOutpu return models.NewRunOutputError(err) } - return models.NewRunOutputCompleteWithResult(hexutil.Encode(value)) + return models.NewRunOutputCompleteWithResult(hexutil.Encode(value), input.ResultCollection()) } // EthUint256 holds no fields. @@ -78,5 +78,5 @@ func (*EthUint256) Perform(input models.RunInput, _ *store.Store) models.RunOutp return models.NewRunOutputError(err) } - return models.NewRunOutputCompleteWithResult(hexutil.Encode(value)) + return models.NewRunOutputCompleteWithResult(hexutil.Encode(value), input.ResultCollection()) } diff --git a/core/adapters/eth_tx.go b/core/adapters/eth_tx.go index 3cf9922bd88..61a47f48bdd 100644 --- a/core/adapters/eth_tx.go +++ b/core/adapters/eth_tx.go @@ -1,10 +1,16 @@ package adapters import ( + "math/big" + "reflect" + "strconv" + + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/smartcontractkit/chainlink/core/logger" strpkg "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" + "github.com/tidwall/gjson" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -26,9 +32,15 @@ type EthTx struct { FromAddress common.Address `json:"fromAddress,omitempty"` FromAddresses []common.Address `json:"fromAddresses,omitempty"` FunctionSelector models.FunctionSelector `json:"functionSelector"` - DataPrefix hexutil.Bytes `json:"dataPrefix"` - DataFormat string `json:"format"` - GasLimit uint64 `json:"gasLimit,omitempty"` + // DataPrefix is typically a standard first argument + // to chainlink callback calls - usually the requestID + DataPrefix hexutil.Bytes `json:"dataPrefix"` + DataFormat string `json:"format"` + GasLimit uint64 `json:"gasLimit,omitempty"` + + // Optional list of desired encodings for ResultCollectKey arguments. + // i.e. ["uint256", "bytes32"] + ABIEncoding []string `json:"abiEncoding"` // MinRequiredOutgoingConfirmations only works with bulletprooftxmanager MinRequiredOutgoingConfirmations uint64 `json:"minRequiredOutgoingConfirmations,omitempty"` @@ -89,7 +101,22 @@ func (e *EthTx) pickFromAddress(input models.RunInput, store *strpkg.Store) (com } func (e *EthTx) insertEthTx(input models.RunInput, store *strpkg.Store) models.RunOutput { - txData, err := getTxData(e, input) + var ( + txData, encodedPayload []byte + err error + ) + if e.ABIEncoding != nil { + // The requestID is present as the first element of DataPrefix (from the oracle event log). + // We prepend it as a magic first argument of for the consumer contract. + data, errPrepend := input.Data().PrependAtArrayKey(models.ResultCollectionKey, e.DataPrefix[:32]) + if errPrepend != nil { + return models.NewRunOutputError(err) + } + // Encode the calldata for the consumer contract. + txData, err = getTxDataUsingABIEncoding(e.ABIEncoding, data.Get(models.ResultCollectionKey).Array()) + } else { + txData, err = getTxData(e, input) + } if err != nil { err = errors.Wrap(err, "insertEthTx failed while constructing EthTx data") return models.NewRunOutputError(err) @@ -103,7 +130,18 @@ func (e *EthTx) insertEthTx(input models.RunInput, store *strpkg.Store) models.R logger.Error(err) return models.NewRunOutputError(err) } - encodedPayload := utils.ConcatBytes(e.FunctionSelector.Bytes(), e.DataPrefix, txData) + + if e.ABIEncoding != nil { + // Encode the calldata for the operator/oracle contract. Note that the last argument is nested calldata, calldata + // for the consumer contract. + // [hash(fulfillOracleRequest2...)[:4]] [..............................dataPrefix...............................] [call data] + // [hash(fulfillOracleRequest2...)[:4]] [requestID] [payment] [callbackAddress] [callbackFunctionId] [expiration] [call data] + // 6 = requestID + payment + callbackAddress + callbackFunctionId + expiration + offset itself + payloadOffset := utils.EVMWordUint64(utils.EVMWordByteLen * 6) + encodedPayload = append(append(append(e.FunctionSelector.Bytes(), e.DataPrefix...), payloadOffset...), utils.EVMEncodeBytes(txData)...) + } else { + encodedPayload = append(append(e.FunctionSelector.Bytes(), e.DataPrefix...), txData...) + } var gasLimit uint64 if e.GasLimit == 0 { @@ -177,6 +215,129 @@ func getConfirmedTxHash(ethTxID int64, db *gorm.DB, minRequiredOutgoingConfirmat } +var ( + ErrInvalidABIEncoding = errors.New("invalid abi encoding") + // A base set of supported types, expand as needed. + // The corresponding go type is the type we need to pass into abi.Arguments.PackValues. + solidityTypeToGoType = map[string]reflect.Type{ + "int256": reflect.TypeOf(big.Int{}), + "uint256": reflect.TypeOf(big.Int{}), + "bool": reflect.TypeOf(false), + "bytes32": reflect.TypeOf([32]byte{}), + "bytes4": reflect.TypeOf([4]byte{}), + "bytes": reflect.TypeOf([]byte{}), + "address": reflect.TypeOf(common.Address{}), + } + jsonTypes = map[gjson.Type]map[string]struct{}{ + gjson.String: { + "bytes32": {}, + "bytes4": {}, + "bytes": {}, + "address": {}, + "uint256": {}, + "int256": {}, + }, + gjson.True: { + "bool": {}, + }, + gjson.False: { + "bool": {}, + }, + gjson.Number: { + "uint256": {}, + "int256": {}, + }, + } + supportedSolidityTypes []string +) + +func init() { + for k := range solidityTypeToGoType { + supportedSolidityTypes = append(supportedSolidityTypes, k) + } +} + +// Note we need to include the data prefix handling here because +// if dynamic types (such as bytes) are used, the offset will be affected. +func getTxDataUsingABIEncoding(encodingSpec []string, jsonValues []gjson.Result) ([]byte, error) { + var arguments abi.Arguments + if len(jsonValues) != len(encodingSpec) { + return nil, errors.Errorf("number of collectors %d != number of types in ABI encoding %d", len(jsonValues), len(encodingSpec)) + } + var values = make([]interface{}, len(jsonValues)) + for i, argType := range encodingSpec { + if _, supported := solidityTypeToGoType[argType]; !supported { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "%v is unsupported, supported types are %v", argType, supportedSolidityTypes) + } + if _, ok := jsonTypes[jsonValues[i].Type][argType]; !ok { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "can't convert %v to %v", jsonValues[i].Value(), argType) + } + t, err := abi.NewType(argType, "", nil) + if err != nil { + return nil, errors.Errorf("err %v on arg type %s index %d", err, argType, i) + } + arguments = append(arguments, abi.Argument{ + Type: t, + }) + + switch jsonValues[i].Type { + case gjson.String: + if argType == "uint256" || argType == "int256" { + v, err := strconv.ParseInt(jsonValues[i].String(), 10, 64) + if err != nil { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "can't convert %v to %v", jsonValues[i].Value(), argType) + } + values[i] = big.NewInt(v) + continue + } + // Only supports hex strings. + b, err := hexutil.Decode(jsonValues[i].String()) + if err != nil { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "can't convert %v to %v, bytes should be 0x-prefixed hex strings", jsonValues[i].Value(), argType) + } + if argType == "bytes32" { + if len(b) != 32 { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "can't convert %v to %v", jsonValues[i].Value(), argType) + } + var arg [32]byte + copy(arg[:], b) + values[i] = arg + } else if argType == "address" { + if !common.IsHexAddress(jsonValues[i].String()) || len(b) != 20 { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "invalid address %v", jsonValues[i].String()) + } + values[i] = common.HexToAddress(jsonValues[i].String()) + } else if argType == "bytes4" { + if len(b) != 4 { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "can't convert %v to %v", jsonValues[i].Value(), argType) + } + var arg [4]byte + copy(arg[:], b) + values[i] = arg + } else if argType == "bytes" { + values[i] = b + } + case gjson.Number: + values[i] = big.NewInt(jsonValues[i].Int()) // JSON specs can't actually handle 256bit numbers only 64bit? + case gjson.False, gjson.True: + // Note we can potentially use this cast strategy to support more types + if reflect.TypeOf(jsonValues[i].Value()).ConvertibleTo(solidityTypeToGoType[argType]) { + values[i] = reflect.ValueOf(jsonValues[i].Value()).Convert(solidityTypeToGoType[argType]).Interface() + } else { + return nil, errors.Wrapf(ErrInvalidABIEncoding, "can't convert %v to %v", jsonValues[i].Value(), argType) + } + default: + // Complex types, array or object. Support as needed + return nil, errors.Wrapf(ErrInvalidABIEncoding, "can't convert %v to %v", jsonValues[i].Value(), argType) + } + } + packedArgs, err := arguments.PackValues(values) + if err != nil { + return nil, err + } + return utils.ConcatBytes(packedArgs), nil +} + // getTxData returns the data to save against the callback encoded according to // the dataFormat parameter in the job spec func getTxData(e *EthTx, input models.RunInput) ([]byte, error) { @@ -189,13 +350,20 @@ func getTxData(e *EthTx, input models.RunInput) ([]byte, error) { if err != nil { return []byte{}, err } + // If data format is "bytes" then we have dynamic types, + // which involve specifying the location of the data portion of the arg. + // i.e. callback(reqID bytes32, bytes arg) if e.DataFormat == DataFormatBytes || len(e.DataPrefix) > 0 { + // If we do not have a data prefix (reqID), encoding is: + // [4byte fs][0x00..20][arg 1]. payloadOffset := utils.EVMWordUint64(utils.EVMWordByteLen) if len(e.DataPrefix) > 0 { + // If we have a data prefix (reqID), encoding is: + // [4byte fs][0x00..40][reqID][arg1] payloadOffset = utils.EVMWordUint64(utils.EVMWordByteLen * 2) - return utils.ConcatBytes(payloadOffset, output), nil + return append(payloadOffset, output...), nil } - return utils.ConcatBytes(payloadOffset, output), nil + return append(payloadOffset, output...), nil } - return utils.ConcatBytes(output), nil + return output, nil } diff --git a/core/adapters/eth_tx_internal_test.go b/core/adapters/eth_tx_internal_test.go new file mode 100644 index 00000000000..519e1507e1d --- /dev/null +++ b/core/adapters/eth_tx_internal_test.go @@ -0,0 +1,206 @@ +package adapters + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + Uint256 abi.Type + Int256 abi.Type + Bool abi.Type + Bytes32 abi.Type + Bytes4 abi.Type + Address abi.Type + Bytes abi.Type +) + +func init() { + // Static types + Uint256, _ = abi.NewType("uint256", "", nil) + Int256, _ = abi.NewType("int256", "", nil) + Bool, _ = abi.NewType("bool", "", nil) + Bytes32, _ = abi.NewType("bytes32", "", nil) + Bytes4, _ = abi.NewType("bytes4", "", nil) + Address, _ = abi.NewType("address", "", nil) + + // Dynamic types + Bytes, _ = abi.NewType("bytes", "", nil) +} + +func TestGetTxData(t *testing.T) { + var tt = []struct { + name string + abiEncoding []string + argTypes abi.Arguments // Helpers to assert the unpacking works. + args []interface{} + errLike string + assertion func(t *testing.T, vals []interface{}) + }{ + { + name: "uint256", + abiEncoding: []string{"uint256"}, + argTypes: abi.Arguments{{Type: Uint256}}, + args: []interface{}{1234}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 1) + assert.Equal(t, big.NewInt(1234), vals[0]) + }, + }, + { + name: "int256", + abiEncoding: []string{"int256"}, + argTypes: abi.Arguments{{Type: Int256}}, + args: []interface{}{-1234}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 1) + assert.Equal(t, big.NewInt(-1234), vals[0]) + }, + }, + { + name: "ints as strings", + abiEncoding: []string{"int256", "uint256"}, + argTypes: abi.Arguments{{Type: Int256}, {Type: Uint256}}, + args: []interface{}{"-1234", "1234"}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 2) + assert.Equal(t, big.NewInt(-1234), vals[0]) + assert.Equal(t, big.NewInt(1234), vals[1]) + }, + }, + { + name: "multiple int256", + abiEncoding: []string{"int256", "int256"}, + argTypes: abi.Arguments{{Type: Int256}, {Type: Int256}}, + args: []interface{}{-1234, 10923810298}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 2) + assert.Equal(t, big.NewInt(-1234), vals[0]) + assert.Equal(t, big.NewInt(10923810298), vals[1]) + }, + }, + { + name: "bool", + abiEncoding: []string{"bool"}, + argTypes: abi.Arguments{{Type: Bool}}, + args: []interface{}{true}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 1) + assert.Equal(t, true, vals[0]) + }, + }, + { + name: "bytes32", + abiEncoding: []string{"bytes32"}, + argTypes: abi.Arguments{{Type: Bytes32}}, + args: []interface{}{"0x0000000000000000000000000000000000000000000000000000000000000001"}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 1) + b, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000001") + var expected [32]byte + copy(expected[:], b[:]) + assert.Equal(t, expected, vals[0]) + }, + }, + { + name: "address", + abiEncoding: []string{"address"}, + argTypes: abi.Arguments{{Type: Address}}, + args: []interface{}{"0x32Be343B94f860124dC4fEe278FDCBD38C102D88"}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 1) + addr := common.HexToAddress("32Be343B94f860124dC4fEe278FDCBD38C102D88") + assert.Equal(t, addr, vals[0]) + }, + }, + { + name: "invalid address", + abiEncoding: []string{"address"}, + argTypes: abi.Arguments{{Type: Address}}, + args: []interface{}{"0x32Be343B94f860124dC4fEe278FDCBD38C102D"}, + errLike: "invalid address", + }, + { + name: "bytes", + abiEncoding: []string{"bytes"}, + argTypes: abi.Arguments{{Type: Bytes}}, + args: []interface{}{"0x00000000000000000000000000000000000000000000000000000000000000010101"}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 1) + b, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000010101") + assert.Equal(t, b, vals[0]) + }, + }, + { + name: "bytes4", + abiEncoding: []string{"bytes4"}, + argTypes: abi.Arguments{{Type: Bytes4}}, + args: []interface{}{"0x01010101"}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 1) + b, _ := hex.DecodeString("01010101") + var expected [4]byte + copy(expected[:], b[:]) + assert.Equal(t, expected, vals[0]) + }, + }, + { + name: "multiple bytes", + abiEncoding: []string{"bytes", "bytes"}, + argTypes: abi.Arguments{{Type: Bytes}, {Type: Bytes}}, + args: []interface{}{"0x00000000000000000000000000000000000000000000000000000000000000010101", "0x0000000000000000000000000000000000000000000000000000000000000001"}, + assertion: func(t *testing.T, vals []interface{}) { + require.Len(t, vals, 2) + b1, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000010101") + b2, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000001") + assert.Equal(t, b1, vals[0]) + assert.Equal(t, b2, vals[1]) + }, + }, + { + name: "type mismatch", + abiEncoding: []string{"uint256"}, + args: []interface{}{"0x0123"}, + errLike: "can't convert 0x0123 to uint256", + }, + { + name: "invalid bytes32", + abiEncoding: []string{"bytes32"}, + args: []interface{}{"0x0123"}, + errLike: "can't convert 0x0123 to bytes32", // Could consider relaxing this to just <= 32? + }, + { + name: "unsupported type", + abiEncoding: []string{"uint8"}, + args: []interface{}{18}, + errLike: "uint8 is unsupported", // Could consider relaxing this to just <= 32? + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + j := models.JSON{} + d, err := j.Add(models.ResultCollectionKey, tc.args) + require.NoError(t, err) + b, err := getTxDataUsingABIEncoding(tc.abiEncoding, + d.Get(models.ResultCollectionKey).Array()) + if tc.errLike != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errLike) + return + } + require.NoError(t, err) + // We should be able to decode and get back the same args we specified. + vals, err := tc.argTypes.UnpackValues(b) + require.NoError(t, err) + tc.assertion(t, vals) + }) + } +} diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 015795c6af7..1e7ff8444a9 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -25,6 +25,44 @@ func TestEthTxAdapter_Perform_BPTXM(t *testing.T) { functionSelector := models.HexToFunctionSelector("0x70a08231") // balanceOf(address) dataPrefix := hexutil.MustDecode("0x88888888") + t.Run("multiword using ABI encoding", func(t *testing.T) { + adapter := adapters.EthTx{ + ToAddress: toAddress, + GasLimit: gasLimit, + FunctionSelector: functionSelector, + DataPrefix: hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000001"), + ABIEncoding: []string{"bytes32", "uint256", "bool", "bytes"}, + } + jobRunID := models.NewID() + taskRunID := cltest.MustInsertTaskRun(t, store) + input := models.NewRunInputWithResult(jobRunID, taskRunID, "0x9786856756", models.RunStatusUnstarted) + d, err := input.Data().Add(models.ResultCollectionKey, []interface{}{12, false, "0x1234"}) + require.NoError(t, err) + runOutput := adapter.Perform(input.CloneWithData(d), store) + require.NoError(t, runOutput.Error()) + assert.Equal(t, models.RunStatusPendingOutgoingConfirmations, runOutput.Status()) + etrt, err := store.FindEthTaskRunTxByTaskRunID(input.TaskRunID().UUID()) + require.NoError(t, err) + + assert.Equal(t, taskRunID.UUID(), etrt.TaskRunID) + require.NotNil(t, etrt.EthTx) + assert.Nil(t, etrt.EthTx.Nonce) + assert.Equal(t, toAddress, etrt.EthTx.ToAddress) + assert.Equal(t, "70a08231"+ // function selector + "0000000000000000000000000000000000000000000000000000000000000001"+ // requestID == 1 + "00000000000000000000000000000000000000000000000000000000000000c0"+ // normal offset for other args + "00000000000000000000000000000000000000000000000000000000000000c0"+ // length of nested txdata + "0000000000000000000000000000000000000000000000000000000000000001"+ // requestID == 1 + "000000000000000000000000000000000000000000000000000000000000000c"+ // 12 + "0000000000000000000000000000000000000000000000000000000000000000"+ // false + "0000000000000000000000000000000000000000000000000000000000000080"+ // location of array = 32 * 4 + "0000000000000000000000000000000000000000000000000000000000000002"+ // length + "1234000000000000000000000000000000000000000000000000000000000000", // contents + hex.EncodeToString(etrt.EthTx.EncodedPayload)) + assert.Equal(t, gasLimit, etrt.EthTx.GasLimit) + assert.Equal(t, models.EthTxUnstarted, etrt.EthTx.State) + }) + t.Run("with valid data and empty DataFormat writes to database and returns run output pending outgoing confirmations", func(t *testing.T) { adapter := adapters.EthTx{ ToAddress: toAddress, diff --git a/core/adapters/http.go b/core/adapters/http.go index 49c6f0e4a69..b97419c4141 100644 --- a/core/adapters/http.go +++ b/core/adapters/http.go @@ -167,7 +167,7 @@ func sendRequest(input models.RunInput, request *http.Request, config utils.HTTP return models.NewRunOutputError(errors.New(responseBody)) } - return models.NewRunOutputCompleteWithResult(responseBody) + return models.NewRunOutputCompleteWithResult(responseBody, input.ResultCollection()) } // QueryParameters are the keys and values to append to the URL diff --git a/core/adapters/json_parse.go b/core/adapters/json_parse.go index 5df56fa14f6..93adbe4f947 100644 --- a/core/adapters/json_parse.go +++ b/core/adapters/json_parse.go @@ -62,7 +62,7 @@ func (jpa *JSONParse) Perform(input models.RunInput, _ *store.Store) models.RunO return moldErrorOutput(js, jpa.Path, input) } - return models.NewRunOutputCompleteWithResult(last.Interface()) + return models.NewRunOutputCompleteWithResult(last.Interface(), input.ResultCollection()) } func dig(js *simplejson.Json, path []string) (*simplejson.Json, error) { @@ -86,7 +86,7 @@ func moldErrorOutput(js *simplejson.Json, path []string, input models.RunInput) if _, err := getEarlyPath(js, path); err != nil { return models.NewRunOutputError(err) } - return models.NewRunOutputCompleteWithResult(nil) + return models.NewRunOutputCompleteWithResult(nil, input.ResultCollection()) } func getEarlyPath(js *simplejson.Json, path []string) (*simplejson.Json, error) { diff --git a/core/adapters/multiply.go b/core/adapters/multiply.go index b4bbe275ab6..8c3a9fe1886 100644 --- a/core/adapters/multiply.go +++ b/core/adapters/multiply.go @@ -31,5 +31,5 @@ func (m *Multiply) Perform(input models.RunInput, _ *store.Store) models.RunOutp if m.Times != nil { dec = dec.Mul(*m.Times) } - return models.NewRunOutputCompleteWithResult(dec.String()) + return models.NewRunOutputCompleteWithResult(dec.String(), input.ResultCollection()) } diff --git a/core/adapters/no_op.go b/core/adapters/no_op.go index fc9f901b1cd..6b865c995a9 100644 --- a/core/adapters/no_op.go +++ b/core/adapters/no_op.go @@ -16,7 +16,7 @@ func (noa *NoOp) TaskType() models.TaskType { // Perform returns the input func (noa *NoOp) Perform(input models.RunInput, _ *store.Store) models.RunOutput { val := input.Result().Value() - return models.NewRunOutputCompleteWithResult(val) + return models.NewRunOutputCompleteWithResult(val, input.ResultCollection()) } // NoOpPendOutgoing adapter type holds no fields diff --git a/core/adapters/quotient.go b/core/adapters/quotient.go index 618b872d99b..8ff350bae51 100644 --- a/core/adapters/quotient.go +++ b/core/adapters/quotient.go @@ -58,5 +58,5 @@ func (q *Quotient) Perform(input models.RunInput, _ *store.Store) models.RunOutp if q.Dividend != nil { i = new(big.Float).Quo(q.Dividend, i) } - return models.NewRunOutputCompleteWithResult(i.String()) + return models.NewRunOutputCompleteWithResult(i.String(), input.ResultCollection()) } diff --git a/core/adapters/random.go b/core/adapters/random.go index 4178ff3aa00..fa467723bb9 100644 --- a/core/adapters/random.go +++ b/core/adapters/random.go @@ -85,7 +85,7 @@ func (ra *Random) Perform(input models.RunInput, store *store.Store) models.RunO "VRFCoordinator.fulfillRandomnessRequest", solidityProof)) } return models.NewRunOutputCompleteWithResult(fmt.Sprintf("0x%x", - vrfCoordinatorArgs)) + vrfCoordinatorArgs), input.ResultCollection()) } // getInputs parses the JSON input for the values needed by the random adapter, diff --git a/core/adapters/random_test.go b/core/adapters/random_test.go index 83f6822d813..26241fc3110 100644 --- a/core/adapters/random_test.go +++ b/core/adapters/random_test.go @@ -4,6 +4,8 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/internal/cltest" tvrf "github.com/smartcontractkit/chainlink/core/internal/cltest/vrf" @@ -40,7 +42,8 @@ func TestRandom_Perform(t *testing.T) { require.NoError(t, result.Error(), "while running random adapter") proofArg := hexutil.MustDecode(result.Result().String()) var wireProof []byte - err = models.VRFFulfillMethod().Inputs.Unpack(&wireProof, proofArg) + out, err := models.VRFFulfillMethod().Inputs.Unpack(proofArg) + wireProof = abi.ConvertType(out[0], []byte{}).([]byte) require.NoError(t, err, "failed to unpack VRF proof from random adapter") var onChainResponse vrf.MarshaledOnChainResponse require.Equal(t, copy(onChainResponse[:], wireProof), diff --git a/core/adapters/result_collect.go b/core/adapters/result_collect.go new file mode 100644 index 00000000000..ed7e11fe470 --- /dev/null +++ b/core/adapters/result_collect.go @@ -0,0 +1,25 @@ +package adapters + +import ( + "github.com/smartcontractkit/chainlink/core/store" + "github.com/smartcontractkit/chainlink/core/store/models" +) + +type ResultCollect struct{} + +func (r ResultCollect) TaskType() models.TaskType { + return TaskTypeResultCollect +} + +func (r ResultCollect) Perform(input models.RunInput, store *store.Store) models.RunOutput { + updatedCollection := make([]interface{}, 0) + for _, c := range input.ResultCollection().Array() { + updatedCollection = append(updatedCollection, c.Value()) + } + updatedCollection = append(updatedCollection, input.Result().Value()) + ro, err := input.Data().Add(models.ResultCollectionKey, updatedCollection) + if err != nil { + return models.NewRunOutputError(err) + } + return models.NewRunOutputComplete(ro) +} diff --git a/core/adapters/result_collect_test.go b/core/adapters/result_collect_test.go new file mode 100644 index 00000000000..848b83208d1 --- /dev/null +++ b/core/adapters/result_collect_test.go @@ -0,0 +1,69 @@ +package adapters_test + +import ( + "fmt" + "testing" + + "github.com/smartcontractkit/chainlink/core/adapters" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" +) + +func TestResultCollect_Perform(t *testing.T) { + var tt = []struct { + name string + json string + expectedResultCollection []gjson.Result + expectedResult gjson.Result + }{ + { + name: "empty add bool", + json: `{"result":false}`, + expectedResultCollection: gjson.ParseBytes([]byte(`[false]`)).Array(), + expectedResult: gjson.ParseBytes([]byte(`false`)), + }, + { + name: "exists add bool", + json: fmt.Sprintf(`{"result":false,"%s":[false,true]}`, models.ResultCollectionKey), + expectedResultCollection: gjson.ParseBytes([]byte(`[false,true,false]`)).Array(), + expectedResult: gjson.ParseBytes([]byte(`false`)), + }, + { + name: "exists add int", + json: fmt.Sprintf(`{"result":20,"%s":[false]}`, models.ResultCollectionKey), + expectedResultCollection: gjson.ParseBytes([]byte(`[false,20]`)).Array(), + expectedResult: gjson.ParseBytes([]byte(`20`)), + }, + { + name: "exists add float", + json: fmt.Sprintf(`{"result":20.214,"%s":[false]}`, models.ResultCollectionKey), + expectedResultCollection: gjson.ParseBytes([]byte(`[false,20.214]`)).Array(), + expectedResult: gjson.ParseBytes([]byte(`20.214`)), + }, + { + name: "exists non-array", + json: fmt.Sprintf(`{"result":20.214,"%s":false}`, models.ResultCollectionKey), + expectedResultCollection: gjson.ParseBytes([]byte(`[false,20.214]`)).Array(), + expectedResult: gjson.ParseBytes([]byte(`20.214`)), + }, + } + + for _, tc := range tt { + test := tc + t.Run(test.name, func(t *testing.T) { + t.Parallel() + past := cltest.NewRunInputWithString(t, test.json) + adapter := adapters.ResultCollect{} + result := adapter.Perform(past, nil) + assert.NoError(t, result.Error()) + require.Len(t, result.ResultCollection().Array(), len(test.expectedResultCollection)) + for i, r := range result.ResultCollection().Array() { + assert.Equal(t, test.expectedResultCollection[i].Value(), r.Value()) + } + assert.Equal(t, test.expectedResult.Value(), result.Result().Value()) + }) + } +} diff --git a/core/chainlink.Dockerfile b/core/chainlink.Dockerfile index 47987384cc5..fb9eebae0d7 100644 --- a/core/chainlink.Dockerfile +++ b/core/chainlink.Dockerfile @@ -63,13 +63,17 @@ RUN make chainlink-build # Final layer: ubuntu with chainlink binary FROM ubuntu:18.04 +ARG CHAINLINK_USER=root ENV DEBIAN_FRONTEND noninteractive RUN apt-get update && apt-get install -y ca-certificates - -WORKDIR /root - COPY --from=1 /go/bin/chainlink /usr/local/bin/ +RUN if [ ${CHAINLINK_USER} != root ]; then \ + useradd --uid 14933 --create-home ${CHAINLINK_USER}; \ + fi +USER ${CHAINLINK_USER} +WORKDIR /home/${CHAINLINK_USER} + EXPOSE 6688 ENTRYPOINT ["chainlink"] CMD ["local", "node"] diff --git a/core/cmd/app.go b/core/cmd/app.go index 08fd7bf7310..3eb43be5ad3 100644 --- a/core/cmd/app.go +++ b/core/cmd/app.go @@ -5,7 +5,7 @@ import ( "os" "regexp" - "github.com/smartcontractkit/chainlink/core/store" + "github.com/smartcontractkit/chainlink/core/static" "github.com/urfave/cli" ) @@ -24,7 +24,7 @@ func removeHidden(cmds ...cli.Command) []cli.Command { func NewApp(client *Client) *cli.App { app := cli.NewApp() app.Usage = "CLI for Chainlink" - app.Version = fmt.Sprintf("%v@%v", store.Version, store.Sha) + app.Version = fmt.Sprintf("%v@%v", static.Version, static.Sha) app.Flags = []cli.Flag{ cli.BoolFlag{ Name: "json, j", @@ -79,7 +79,6 @@ func NewApp(client *Client) *cli.App { Name: "attempts", Aliases: []string{"txas"}, Usage: "Commands for managing Ethereum Transaction Attempts", - Hidden: !client.Config.Dev(), Subcommands: []cli.Command{ { Name: "list", @@ -152,8 +151,8 @@ func NewApp(client *Client) *cli.App { }, { - Name: "jobs", - Usage: "Commands for managing Jobs", + Name: "job_specs", + Usage: "Commands for managing Job Specs (jobs V1)", Subcommands: []cli.Command{ { Name: "archive", @@ -181,37 +180,40 @@ func NewApp(client *Client) *cli.App { Usage: "Show a specific Job's details", Action: client.ShowJobSpec, }, + }, + }, + { + Name: "jobs", + Usage: "Commands for managing Jobs (V2)", + Subcommands: []cli.Command{ { - Name: "createocr", - Usage: "Create an off-chain reporting job", - Action: client.CreateOCRJobSpec, + Name: "create", + Usage: "Create a V2 job", + Action: client.CreateJobV2, }, { - Name: "deletev2", - Usage: "Delete a v2 job", + Name: "delete", + Usage: "Delete a V2 job", Action: client.DeleteJobV2, }, { Name: "run", - Usage: "Trigger an off-chain reporting job run", - Action: client.TriggerOCRJobRun, + Usage: "Trigger a V2 job run", + Action: client.TriggerPipelineRun, }, }, }, - { Name: "keys", Usage: "Commands for managing various types of keys used by the Chainlink node", Subcommands: []cli.Command{ cli.Command{ - Name: "eth", - Usage: "Local commands for administering the node's Ethereum keys", - Hidden: !client.Config.Dev(), + Name: "eth", + Usage: "Local commands for administering the node's Ethereum keys", Subcommands: cli.Commands{ { Name: "create", Usage: "Create an key in the node's keystore alongside the existing key; to create an original key, just run the node", - Hidden: !client.Config.Dev(), Action: client.CreateExtraKey, }, { @@ -222,9 +224,8 @@ func NewApp(client *Client) *cli.App { }, }, cli.Command{ - Name: "p2p", - Usage: "Remote commands for administering the node's p2p keys", - Hidden: !client.Config.Dev(), + Name: "p2p", + Usage: "Remote commands for administering the node's p2p keys", Subcommands: cli.Commands{ { Name: "create", @@ -256,9 +257,8 @@ func NewApp(client *Client) *cli.App { }, }, cli.Command{ - Name: "ocr", - Usage: "Remote commands for administering the node's off chain reporting keys", - Hidden: !client.Config.Dev(), + Name: "ocr", + Usage: "Remote commands for administering the node's off chain reporting keys", Subcommands: cli.Commands{ { Name: "create", @@ -294,7 +294,6 @@ func NewApp(client *Client) *cli.App { Usage: format(`Local commands for administering the database of VRF proof keys. These commands will not affect the extant in-memory keys of any live node.`), - Hidden: !client.Config.Dev(), Subcommands: cli.Commands{ { Name: "create", diff --git a/core/cmd/local_client.go b/core/cmd/local_client.go index 9719a3466a6..daf832559bf 100644 --- a/core/cmd/local_client.go +++ b/core/cmd/local_client.go @@ -22,6 +22,7 @@ import ( "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/bulletprooftxmanager" "github.com/smartcontractkit/chainlink/core/services/chainlink" + "github.com/smartcontractkit/chainlink/core/static" strpkg "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/migrations" "github.com/smartcontractkit/chainlink/core/store/models" @@ -48,7 +49,7 @@ func (cli *Client) RunNode(c *clipkg.Context) error { updateConfig(cli.Config, c.Bool("debug"), c.Int64("replay-from-block")) logger.SetLogger(cli.Config.CreateProductionLogger()) - logger.Infow("Starting Chainlink Node " + strpkg.Version + " at commit " + strpkg.Sha) + logger.Infow("Starting Chainlink Node " + static.Version + " at commit " + static.Sha) app := cli.AppFactory.NewApplication(cli.Config, func(app chainlink.Application) { store := app.GetStore() diff --git a/core/cmd/remote_client.go b/core/cmd/remote_client.go index bcb0a3e39a4..b6a795feb8d 100644 --- a/core/cmd/remote_client.go +++ b/core/cmd/remote_client.go @@ -215,9 +215,25 @@ func (cli *Client) CreateJobSpec(c *clipkg.Context) (err error) { return err } -// CreateOCRJobSpec creates an OCR job spec +// ArchiveJobSpec soft deletes a job and its associated runs. +func (cli *Client) ArchiveJobSpec(c *clipkg.Context) error { + if !c.Args().Present() { + return cli.errorOut(errors.New("Must pass the job id to be archived")) + } + resp, err := cli.HTTP.Delete("/v2/specs/" + c.Args().First()) + if err != nil { + return cli.errorOut(err) + } + _, err = cli.parseResponse(resp) + if err != nil { + return cli.errorOut(err) + } + return nil +} + +// CreateV2JobSpec creates a V2 job // Valid input is a TOML string or a path to TOML file -func (cli *Client) CreateOCRJobSpec(c *clipkg.Context) (err error) { +func (cli *Client) CreateJobV2(c *clipkg.Context) (err error) { if !c.Args().Present() { return cli.errorOut(errors.New("Must pass in TOML or filepath")) } @@ -227,14 +243,14 @@ func (cli *Client) CreateOCRJobSpec(c *clipkg.Context) (err error) { return cli.errorOut(err) } - request, err := json.Marshal(models.CreateOCRJobSpecRequest{ + request, err := json.Marshal(models.CreateJobSpecRequest{ TOML: tomlString, }) if err != nil { return cli.errorOut(err) } - resp, err := cli.HTTP.Post("/v2/ocr/specs", bytes.NewReader(request)) + resp, err := cli.HTTP.Post("/v2/jobs", bytes.NewReader(request)) if err != nil { return cli.errorOut(err) } @@ -268,27 +284,11 @@ func (cli *Client) CreateOCRJobSpec(c *clipkg.Context) (err error) { return nil } -// ArchiveJobSpec soft deletes a job and its associated runs. -func (cli *Client) ArchiveJobSpec(c *clipkg.Context) error { - if !c.Args().Present() { - return cli.errorOut(errors.New("Must pass the job id to be archived")) - } - resp, err := cli.HTTP.Delete("/v2/specs/" + c.Args().First()) - if err != nil { - return cli.errorOut(err) - } - _, err = cli.parseResponse(resp) - if err != nil { - return cli.errorOut(err) - } - return nil -} - func (cli *Client) DeleteJobV2(c *clipkg.Context) error { if !c.Args().Present() { return cli.errorOut(errors.New("Must pass the job id to be archived")) } - resp, err := cli.HTTP.Delete("/v2/ocr/specs/" + c.Args().First()) + resp, err := cli.HTTP.Delete("/v2/jobs/" + c.Args().First()) if err != nil { return cli.errorOut(err) } @@ -299,12 +299,12 @@ func (cli *Client) DeleteJobV2(c *clipkg.Context) error { return nil } -// TriggerOCRJobRun triggers an off-chain reporting job run based on a job ID -func (cli *Client) TriggerOCRJobRun(c *clipkg.Context) error { +// TriggerPipelineRun triggers a V2 job run based on a job ID +func (cli *Client) TriggerPipelineRun(c *clipkg.Context) error { if !c.Args().Present() { return cli.errorOut(errors.New("Must pass the job id to trigger a run")) } - resp, err := cli.HTTP.Post("/v2/ocr/specs/"+c.Args().First()+"/runs", nil) + resp, err := cli.HTTP.Post("/v2/jobs/"+c.Args().First()+"/runs", nil) if err != nil { return cli.errorOut(err) } diff --git a/core/cmd/remote_client_test.go b/core/cmd/remote_client_test.go index cc18da27ce3..56d6964dd83 100644 --- a/core/cmd/remote_client_test.go +++ b/core/cmd/remote_client_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli" + "gopkg.in/guregu/null.v4" ) func TestClient_ListETHKeys(t *testing.T) { @@ -71,7 +72,7 @@ func TestClient_IndexJobSpecs(t *testing.T) { require.Nil(t, client.IndexJobSpecs(cltest.EmptyCLIContext())) jobs := *r.Renders[0].(*[]models.JobSpec) - assert.Equal(t, 2, len(jobs)) + require.Equal(t, 2, len(jobs)) assert.Equal(t, j1.ID, jobs[0].ID) } @@ -1134,14 +1135,14 @@ func TestClient_RunOCRJob_HappyPath(t *testing.T) { err = tree.Unmarshal(&ocrJobSpecFromFile) require.NoError(t, err) - jobID, _ := app.AddJobV2(context.Background(), ocrJobSpecFromFile) + jobID, _ := app.AddJobV2(context.Background(), ocrJobSpecFromFile, null.String{}) set := flag.NewFlagSet("test", 0) set.Parse([]string{strconv.FormatInt(int64(jobID), 10)}) c := cli.NewContext(nil, set, nil) require.NoError(t, client.RemoteLogin(c)) - require.NoError(t, client.TriggerOCRJobRun(c)) + require.NoError(t, client.TriggerPipelineRun(c)) } func TestClient_RunOCRJob_MissingJobID(t *testing.T) { @@ -1155,7 +1156,7 @@ func TestClient_RunOCRJob_MissingJobID(t *testing.T) { c := cli.NewContext(nil, set, nil) require.NoError(t, client.RemoteLogin(c)) - assert.EqualError(t, client.TriggerOCRJobRun(c), "Must pass the job id to trigger a run") + assert.EqualError(t, client.TriggerPipelineRun(c), "Must pass the job id to trigger a run") } func TestClient_RunOCRJob_JobNotFound(t *testing.T) { @@ -1170,5 +1171,5 @@ func TestClient_RunOCRJob_JobNotFound(t *testing.T) { c := cli.NewContext(nil, set, nil) require.NoError(t, client.RemoteLogin(c)) - assert.EqualError(t, client.TriggerOCRJobRun(c), "500 Internal Server Error; no job found with id 1 (most likely it was deleted)") + assert.EqualError(t, client.TriggerPipelineRun(c), "500 Internal Server Error; no job found with id 1 (most likely it was deleted)") } diff --git a/core/cmd/renderer.go b/core/cmd/renderer.go index 127c3b199d4..c2be08cc892 100644 --- a/core/cmd/renderer.go +++ b/core/cmd/renderer.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/olekukonko/tablewriter" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/models/ocrkey" @@ -15,8 +16,6 @@ import ( "github.com/smartcontractkit/chainlink/core/store/presenters" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" - - "github.com/olekukonko/tablewriter" ) // Renderer implements the Render method. @@ -103,12 +102,6 @@ func (rt RendererTable) renderJobs(jobs []models.JobSpec) error { func (rt RendererTable) renderConfiguration(cp presenters.ConfigPrinter) error { table := rt.newTable([]string{"Key", "Value"}) - - table.Append([]string{ - "ACCOUNT_ADDRESS", - cp.AccountAddress, - }) - schemaT := reflect.TypeOf(orm.ConfigSchema{}) cpT := reflect.TypeOf(cp.EnvPrinter) cpV := reflect.ValueOf(cp.EnvPrinter) diff --git a/core/cmd/renderer_test.go b/core/cmd/renderer_test.go index eac8a2b9a65..34e95da8fd3 100644 --- a/core/cmd/renderer_test.go +++ b/core/cmd/renderer_test.go @@ -12,7 +12,6 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/presenters" "github.com/smartcontractkit/chainlink/core/web" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -35,7 +34,6 @@ func TestRendererTable_RenderJobs(t *testing.T) { assert.NoError(t, r.Render(&jobs)) output := buffer.String() - assert.Regexp(t, regexp.MustCompile("Job[a-f0-9]{32}"), output) assert.Contains(t, output, "noop") } diff --git a/core/internal/cltest/client.go b/core/internal/cltest/client.go index a98be67d20b..eccc58735e5 100644 --- a/core/internal/cltest/client.go +++ b/core/internal/cltest/client.go @@ -31,6 +31,8 @@ type SimulatedBackendClient struct { chainId int } +var _ eth.Client = (*SimulatedBackendClient)(nil) + func (c *SimulatedBackendClient) Dial(context.Context) error { return nil } @@ -40,8 +42,6 @@ func (c *SimulatedBackendClient) Close() { c.b.Close() } -var _ eth.Client = (*SimulatedBackendClient)(nil) - // checkEthCallArgs extracts and verifies the arguments for an eth_call RPC func (c *SimulatedBackendClient) checkEthCallArgs( args []interface{}) (*eth.CallArgs, *big.Int, error) { @@ -160,8 +160,11 @@ func (c *SimulatedBackendClient) GetERC20Balance(address common.Address, contrac return nil, errors.Wrapf(err, "while calling ERC20 balanceOf method on %s "+ "for balance of %s", contractAddress, address) } - balance = new(big.Int) - return balance, balanceOfABI.Unpack(balance, "balanceOf", b) + err = balanceOfABI.UnpackIntoInterface(balance, "balanceOf", b) + if err != nil { + return nil, errors.New("unable to unpack balance") + } + return balance, nil } func (c *SimulatedBackendClient) GetLINKBalance(linkAddress common.Address, address common.Address) (*assets.Link, error) { @@ -249,21 +252,50 @@ func (c *SimulatedBackendClient) BalanceAt(ctx context.Context, account common.A return c.b.BalanceAt(ctx, account, blockNumber) } +type headSubscription struct { + close chan struct{} + subscription ethereum.Subscription +} + +var _ ethereum.Subscription = (*headSubscription)(nil) + +func (h *headSubscription) Unsubscribe() { + h.subscription.Unsubscribe() + h.close <- struct{}{} +} + +func (h *headSubscription) Err() <-chan error { return h.subscription.Err() } + // SubscribeToNewHeads registers a subscription for push notifications of new // blocks. -func (c *SimulatedBackendClient) SubscribeNewHead(ctx context.Context, channel chan<- *models.Head) (ethereum.Subscription, error) { +func (c *SimulatedBackendClient) SubscribeNewHead( + ctx context.Context, + channel chan<- *models.Head, +) (ethereum.Subscription, error) { + subscription := &headSubscription{close: make(chan struct{})} ch := make(chan *types.Header) go func() { - for h := range ch { - if h == nil { - channel <- nil - } else { - channel <- &models.Head{Number: h.Number.Int64(), Hash: NewHash()} + for { + select { + case h := <-ch: + switch h { + case nil: + channel <- nil + default: + channel <- &models.Head{Number: h.Number.Int64(), Hash: NewHash()} + } + case <-subscription.close: + return } } - close(channel) }() - return c.b.SubscribeNewHead(ctx, ch) + var err error + subscription.subscription, err = c.b.SubscribeNewHead(ctx, ch) + if err != nil { + return nil, errors.Wrapf(err, "could not subscribe to new heads on "+ + "simulated backend") + } + return subscription, err } func (c *SimulatedBackendClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { @@ -282,7 +314,9 @@ func (c *SimulatedBackendClient) SendTransaction(ctx context.Context, tx *types. return nil } - return c.b.SendTransaction(ctx, tx) + err = c.b.SendTransaction(ctx, tx) + c.b.Commit() + return err } func (c *SimulatedBackendClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { @@ -302,7 +336,7 @@ func (c *SimulatedBackendClient) PendingCodeAt(ctx context.Context, account comm } func (c *SimulatedBackendClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { - panic("unimplemented") + return c.b.EstimateGas(ctx, call) } func (c *SimulatedBackendClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 6dceac39103..655bf2e9aab 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -39,6 +39,7 @@ import ( "github.com/smartcontractkit/chainlink/core/store/presenters" "github.com/smartcontractkit/chainlink/core/utils" "github.com/smartcontractkit/chainlink/core/web" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" "github.com/DATA-DOG/go-txdb" "github.com/ethereum/go-ethereum/accounts" @@ -58,7 +59,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tidwall/gjson" "go.uber.org/zap/zapcore" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) const ( @@ -1005,29 +1006,6 @@ func WaitForRuns(t testing.TB, j models.JobSpec, store *strpkg.Store, want int) return jrs } -// WaitForRuns waits for the wanted number of completed runs then returns a slice of the JobRuns -func WaitForCompletedRuns(t testing.TB, j models.JobSpec, store *strpkg.Store, want int) []models.JobRun { - t.Helper() - g := gomega.NewGomegaWithT(t) - - var jrs []models.JobRun - var err error - if want == 0 { - g.Consistently(func() []models.JobRun { - err = store.DB.Where("status = 'completed'").Find(&jrs).Error - assert.NoError(t, err) - return jrs - }, DBWaitTimeout, DBPollingInterval).Should(gomega.HaveLen(want)) - } else { - g.Eventually(func() []models.JobRun { - err = store.DB.Where("status = 'completed'").Find(&jrs).Error - assert.NoError(t, err) - return jrs - }, DBWaitTimeout, DBPollingInterval).Should(gomega.HaveLen(want)) - } - return jrs -} - // AssertRunsStays asserts that the number of job runs for a particular job remains at the provided values func AssertRunsStays(t testing.TB, j models.JobSpec, store *strpkg.Store, want int) []models.JobRun { t.Helper() @@ -1059,20 +1037,6 @@ func WaitForRunsAtLeast(t testing.TB, j models.JobSpec, store *strpkg.Store, wan } } -func WaitForEthTxCount(t testing.TB, store *strpkg.Store, want int) []models.EthTx { - t.Helper() - g := gomega.NewGomegaWithT(t) - - var txes []models.EthTx - var err error - g.Eventually(func() []models.EthTx { - err = store.DB.Order("nonce desc").Find(&txes).Error - assert.NoError(t, err) - return txes - }, DBWaitTimeout, DBPollingInterval).Should(gomega.HaveLen(want)) - return txes -} - func WaitForEthTxAttemptsForEthTx(t testing.TB, store *strpkg.Store, ethTx models.EthTx) []models.EthTxAttempt { t.Helper() g := gomega.NewGomegaWithT(t) @@ -1164,7 +1128,7 @@ func ParseISO8601(t testing.TB, s string) time.Time { // NullableTime will return a valid nullable time given time.Time func NullableTime(t time.Time) null.Time { - return null.Time{Time: t, Valid: true} + return null.TimeFrom(t) } // ParseNullableTime given a time string parse it into a null.Time @@ -1481,3 +1445,23 @@ func RandomizeNonce(t *testing.T, s *strpkg.Store) { err := s.DB.Exec(`UPDATE keys SET next_nonce = ?`, n).Error require.NoError(t, err) } + +func MakeConfigDigest(t *testing.T) ocrtypes.ConfigDigest { + t.Helper() + b := make([]byte, 16) + /* #nosec G404 */ + _, err := rand.Read(b) + if err != nil { + t.Fatal(err) + } + return MustBytesToConfigDigest(t, b) +} + +func MustBytesToConfigDigest(t *testing.T, b []byte) ocrtypes.ConfigDigest { + t.Helper() + configDigest, err := ocrtypes.BytesToConfigDigest(b) + if err != nil { + t.Fatal(err) + } + return configDigest +} diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 57a57a94dfc..32602819bdf 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -17,7 +17,9 @@ import ( "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/internal/mocks" "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/services/eth/contracts" "github.com/smartcontractkit/chainlink/core/services/fluxmonitor" + "github.com/smartcontractkit/chainlink/core/services/pipeline" strpkg "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" @@ -662,3 +664,19 @@ func MustInsertJobSpec(t *testing.T, s *strpkg.Store) models.JobSpec { require.NoError(t, s.CreateJob(&j)) return j } + +func NewRoundStateForRoundID(store *strpkg.Store, roundID uint32, latestAnswer *big.Int) contracts.FluxAggregatorRoundState { + return contracts.FluxAggregatorRoundState{ + ReportableRoundID: roundID, + EligibleToSubmit: true, + LatestAnswer: latestAnswer, + AvailableFunds: store.Config.MinimumContractPayment().ToInt(), + PaymentAmount: store.Config.MinimumContractPayment().ToInt(), + } +} + +func MustInsertUnfinishedPipelineTaskRun(t *testing.T, store *strpkg.Store, pipelineRunID int64) pipeline.TaskRun { + p := pipeline.TaskRun{PipelineRunID: pipelineRunID} + require.NoError(t, store.DB.Create(&p).Error) + return p +} diff --git a/core/internal/cltest/util.go b/core/internal/cltest/util.go index ff7b6534479..d5b2bed2d23 100644 --- a/core/internal/cltest/util.go +++ b/core/internal/cltest/util.go @@ -9,3 +9,16 @@ func MustHexDecodeString(s string) []byte { } return a } + +func MustHexDecode32ByteString(s string) [32]byte { + a, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + if len(a) != 32 { + panic("not 32 bytes") + } + var res [32]byte + copy(res[:], a[:]) + return res +} diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 2ff3e623e03..643ede99324 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -2,6 +2,8 @@ package internal_test import ( "bytes" + "context" + "encoding/hex" "encoding/json" "fmt" "io" @@ -10,9 +12,19 @@ import ( "net/http" "net/http/httptest" "strings" + "sync/atomic" "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + goEthereumEth "github.com/ethereum/go-ethereum/eth" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/link_token_interface" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/multiwordconsumer_wrapper" + "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/operator_wrapper" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/internal/cltest" @@ -800,7 +812,7 @@ func TestIntegration_FluxMonitor_Deviation(t *testing.T) { *args.Get(0).(*hexutil.Bytes) = cltest.MakeRoundStateReturnData(2, true, 10000, 7, 0, availableFunds, minPayment, 1) }). Return(nil). - Once() + Twice() // Have server respond with 102 for price when FM checks external price // adapter for deviation. 102 is enough deviation to trigger a job run. @@ -1006,3 +1018,207 @@ func TestIntegration_FluxMonitor_NewRound(t *testing.T) { rpcClient.AssertExpectations(t) sub.AssertExpectations(t) } + +func TestIntegration_MultiwordV1(t *testing.T) { + gethClient := new(mocks.GethClient) + rpcClient := new(mocks.RPCClient) + sub := new(mocks.Subscription) + + config, cleanup := cltest.NewConfig(t) + defer cleanup() + app, cleanup := cltest.NewApplicationWithConfigAndKey(t, config, + eth.NewClientWith(rpcClient, gethClient), + ) + defer cleanup() + app.Config.Set(orm.EnvVarName("DefaultHTTPAllowUnrestrictedNetworkAccess"), true) + confirmed := int64(23456) + safe := confirmed + int64(config.MinRequiredOutgoingConfirmations()) + inLongestChain := safe - int64(config.GasUpdaterBlockDelay()) + + sub.On("Err").Return(nil) + sub.On("Unsubscribe").Return(nil).Maybe() + gethClient.On("ChainID", mock.Anything).Return(app.Store.Config.ChainID(), nil) + chchNewHeads := make(chan chan<- *models.Head, 1) + rpcClient.On("EthSubscribe", mock.Anything, mock.Anything, "newHeads"). + Run(func(args mock.Arguments) { chchNewHeads <- args.Get(1).(chan<- *models.Head) }). + Return(sub, nil) + gethClient.On("SendTransaction", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + tx, ok := args.Get(1).(*types.Transaction) + require.True(t, ok) + assert.Equal(t, cltest.MustHexDecodeString( + "0000000000000000000000000000000000000000000000000000000000000001"+ // reqID + "00000000000000000000000000000000000000000000000000000000000000c0"+ // fixed offset + "0000000000000000000000000000000000000000000000000000000000000060"+ // length 3 * 32 + "0000000000000000000000000000000000000000000000000000000000000001"+ // reqID + "3130302e31000000000000000000000000000000000000000000000000000000"+ // bid + "3130302e31350000000000000000000000000000000000000000000000000000"), // ask + tx.Data()[4:]) + gethClient.On("TransactionReceipt", mock.Anything, mock.Anything). + Return(&types.Receipt{TxHash: tx.Hash(), BlockNumber: big.NewInt(confirmed)}, nil) + }). + Return(nil).Once() + rpcClient.On("CallContext", mock.Anything, mock.Anything, "eth_getBlockByNumber", mock.Anything, false). + Run(func(args mock.Arguments) { + head := args.Get(1).(**models.Head) + *head = cltest.Head(inLongestChain) + }). + Return(nil) + + gethClient.On("BlockByNumber", mock.Anything, big.NewInt(inLongestChain)). + Return(cltest.BlockWithTransactions(), nil) + + err := app.StartAndConnect() + require.NoError(t, err) + priceResponse := `{"bid": 100.10, "ask": 100.15}` + mockServer, assertCalled := cltest.NewHTTPMockServer(t, http.StatusOK, "GET", priceResponse) + defer assertCalled() + spec := string(cltest.MustReadFile(t, "testdata/multiword_v1_web.json")) + spec = strings.Replace(spec, "https://bitstamp.net/api/ticker/", mockServer.URL, 2) + j := cltest.CreateSpecViaWeb(t, app, spec) + jr := cltest.CreateJobRunViaWeb(t, app, j) + _ = cltest.WaitForJobRunStatus(t, app.Store, jr, models.RunStatusPendingOutgoingConfirmations) + app.EthBroadcaster.Trigger() + cltest.WaitForEthTxAttemptCount(t, app.Store, 1) + + // Feed the subscriber a block head so the transaction completes. + newHeads := <-chchNewHeads + newHeads <- cltest.Head(safe) + // Job should complete successfully. + _ = cltest.WaitForJobRunToComplete(t, app.Store, jr) + jr2, err := app.Store.ORM.FindJobRun(jr.ID) + require.NoError(t, err) + assert.Equal(t, 9, len(jr2.TaskRuns)) + // We expect 2 results collected, the bid and ask + assert.Equal(t, 2, len(jr2.TaskRuns[8].Result.Data.Get(models.ResultCollectionKey).Array())) +} + +func assertPrices(t *testing.T, usd, eur, jpy []byte, consumer *multiwordconsumer_wrapper.MultiWordConsumer) { + var tmp [32]byte + copy(tmp[:], usd) + haveUsd, err := consumer.Usd(nil) + require.NoError(t, err) + assert.Equal(t, tmp[:], haveUsd[:]) + copy(tmp[:], eur) + haveEur, err := consumer.Eur(nil) + require.NoError(t, err) + assert.Equal(t, tmp[:], haveEur[:]) + copy(tmp[:], jpy) + haveJpy, err := consumer.Jpy(nil) + require.NoError(t, err) + assert.Equal(t, tmp[:], haveJpy[:]) +} + +func setupMultiWordContracts(t *testing.T) (*bind.TransactOpts, common.Address, *link_token_interface.LinkToken, *multiwordconsumer_wrapper.MultiWordConsumer, *operator_wrapper.Operator, *backends.SimulatedBackend) { + key, err := crypto.GenerateKey() + require.NoError(t, err, "failed to generate ethereum identity") + user := bind.NewKeyedTransactor(key) + sb := new(big.Int) + sb, _ = sb.SetString("100000000000000000000", 10) + genesisData := core.GenesisAlloc{ + user.From: {Balance: sb}, // 1 eth + } + gasLimit := goEthereumEth.DefaultConfig.Miner.GasCeil * 2 + b := backends.NewSimulatedBackend(genesisData, gasLimit) + linkTokenAddress, _, linkContract, err := link_token_interface.DeployLinkToken(user, b) + require.NoError(t, err) + b.Commit() + + operatorAddress, _, operatorContract, err := operator_wrapper.DeployOperator(user, b, linkTokenAddress, user.From) + require.NoError(t, err) + b.Commit() + + var empty [32]byte + consumerAddress, _, consumerContract, err := multiwordconsumer_wrapper.DeployMultiWordConsumer(user, b, linkTokenAddress, operatorAddress, empty) + require.NoError(t, err) + b.Commit() + + // The consumer contract needs to have link in it to be able to pay + // for the data request. + _, err = linkContract.Transfer(user, consumerAddress, big.NewInt(1000)) + require.NoError(t, err) + return user, consumerAddress, linkContract, consumerContract, operatorContract, b +} + +func TestIntegration_MultiwordV1_Sim(t *testing.T) { + // Simulate a consumer contract calling to obtain ETH quotes in 3 different currencies + // in a single callback. + config, cleanup := cltest.NewConfig(t) + defer cleanup() + user, _, _, consumerContract, operatorContract, b := setupMultiWordContracts(t) + app, cleanup := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, b) + defer cleanup() + app.Config.Set("ETH_HEAD_TRACKER_MAX_BUFFER_SIZE", 100) + app.Config.Set("MIN_OUTGOING_CONFIRMATIONS", 1) + + _, err := operatorContract.SetAuthorizedSender(user, app.Store.KeyStore.Accounts()[0].Address, true) + require.NoError(t, err) + b.Commit() + + // Fund node account with ETH. + n, err := b.NonceAt(context.Background(), user.From, nil) + require.NoError(t, err) + tx := types.NewTransaction(n, app.Store.KeyStore.Accounts()[0].Address, big.NewInt(1000000000000000000), 21000, big.NewInt(1), nil) + signedTx, err := user.Signer(types.HomesteadSigner{}, user.From, tx) + require.NoError(t, err) + err = b.SendTransaction(context.Background(), signedTx) + require.NoError(t, err) + b.Commit() + + err = app.StartAndConnect() + require.NoError(t, err) + + var call int64 + response := func() string { + defer func() { atomic.AddInt64(&call, 1) }() + switch call { + case 0: + return `{"USD": 614.64}` + case 1: + return `{"EUR": 507.07}` + case 2: + return `{"JPY":63818.86}` + } + require.Fail(t, "only 3 calls expected") + return "" + } + mockServer := cltest.NewHTTPMockServerWithAlterableResponse(t, response) + spec := string(cltest.MustReadFile(t, "testdata/multiword_v1_runlog.json")) + spec = strings.Replace(spec, "{url}", mockServer.URL, 1) + spec = strings.Replace(spec, "{url}", mockServer.URL, 1) + spec = strings.Replace(spec, "{url}", mockServer.URL, 1) + j := cltest.CreateSpecViaWeb(t, app, spec) + + var specID [32]byte + by, err := hex.DecodeString(j.ID.String()) + require.NoError(t, err) + copy(specID[:], by[:]) + _, err = consumerContract.SetSpecID(user, specID) + require.NoError(t, err) + + user.GasPrice = big.NewInt(1) + user.GasLimit = 1000000 + _, err = consumerContract.RequestMultipleParameters(user, "", big.NewInt(1000)) + require.NoError(t, err) + b.Commit() + + var empty [32]byte + assertPrices(t, empty[:], empty[:], empty[:], consumerContract) + + tick := time.NewTicker(100 * time.Millisecond) + defer tick.Stop() + go func() { + for range tick.C { + app.EthBroadcaster.Trigger() + b.Commit() + } + }() + cltest.WaitForRuns(t, j, app.Store, 1) + jr, err := app.Store.JobRunsFor(j.ID) + require.NoError(t, err) + cltest.WaitForEthTxAttemptCount(t, app.Store, 1) + + // Job should complete successfully. + _ = cltest.WaitForJobRunStatus(t, app.Store, jr[0], models.RunStatusCompleted) + assertPrices(t, []byte("614.64"), []byte("507.07"), []byte("63818.86"), consumerContract) +} diff --git a/core/internal/gethwrappers/generated/flags_wrapper/flags_wrapper.go b/core/internal/gethwrappers/generated/flags_wrapper/flags_wrapper.go index 7653faa19aa..f22ad61ff23 100644 --- a/core/internal/gethwrappers/generated/flags_wrapper/flags_wrapper.go +++ b/core/internal/gethwrappers/generated/flags_wrapper/flags_wrapper.go @@ -154,7 +154,7 @@ func bindFlags(address common.Address, caller bind.ContractCaller, transactor bi // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_Flags *FlagsRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_Flags *FlagsRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _Flags.Contract.FlagsCaller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_Flags *FlagsRaw) Transact(opts *bind.TransactOpts, method string, params // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_Flags *FlagsCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_Flags *FlagsCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _Flags.Contract.contract.Call(opts, result, method, params...) } @@ -192,12 +192,17 @@ func (_Flags *FlagsTransactorRaw) Transact(opts *bind.TransactOpts, method strin // // Solidity: function checkEnabled() view returns(bool) func (_Flags *FlagsCaller) CheckEnabled(opts *bind.CallOpts) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _Flags.contract.Call(opts, out, "checkEnabled") - return *ret0, err + var out []interface{} + err := _Flags.contract.Call(opts, &out, "checkEnabled") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // CheckEnabled is a free data retrieval call binding the contract method 0xdc7f0124. @@ -218,12 +223,17 @@ func (_Flags *FlagsCallerSession) CheckEnabled() (bool, error) { // // Solidity: function getFlag(address subject) view returns(bool) func (_Flags *FlagsCaller) GetFlag(opts *bind.CallOpts, subject common.Address) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _Flags.contract.Call(opts, out, "getFlag", subject) - return *ret0, err + var out []interface{} + err := _Flags.contract.Call(opts, &out, "getFlag", subject) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // GetFlag is a free data retrieval call binding the contract method 0x357e47fe. @@ -244,12 +254,17 @@ func (_Flags *FlagsCallerSession) GetFlag(subject common.Address) (bool, error) // // Solidity: function getFlags(address[] subjects) view returns(bool[]) func (_Flags *FlagsCaller) GetFlags(opts *bind.CallOpts, subjects []common.Address) ([]bool, error) { - var ( - ret0 = new([]bool) - ) - out := ret0 - err := _Flags.contract.Call(opts, out, "getFlags", subjects) - return *ret0, err + var out []interface{} + err := _Flags.contract.Call(opts, &out, "getFlags", subjects) + + if err != nil { + return *new([]bool), err + } + + out0 := *abi.ConvertType(out[0], new([]bool)).(*[]bool) + + return out0, err + } // GetFlags is a free data retrieval call binding the contract method 0x7d723cac. @@ -270,12 +285,17 @@ func (_Flags *FlagsCallerSession) GetFlags(subjects []common.Address) ([]bool, e // // Solidity: function hasAccess(address _user, bytes _calldata) view returns(bool) func (_Flags *FlagsCaller) HasAccess(opts *bind.CallOpts, _user common.Address, _calldata []byte) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _Flags.contract.Call(opts, out, "hasAccess", _user, _calldata) - return *ret0, err + var out []interface{} + err := _Flags.contract.Call(opts, &out, "hasAccess", _user, _calldata) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // HasAccess is a free data retrieval call binding the contract method 0x6b14daf8. @@ -296,12 +316,17 @@ func (_Flags *FlagsCallerSession) HasAccess(_user common.Address, _calldata []by // // Solidity: function owner() view returns(address) func (_Flags *FlagsCaller) Owner(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _Flags.contract.Call(opts, out, "owner") - return *ret0, err + var out []interface{} + err := _Flags.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // Owner is a free data retrieval call binding the contract method 0x8da5cb5b. @@ -322,12 +347,17 @@ func (_Flags *FlagsCallerSession) Owner() (common.Address, error) { // // Solidity: function raisingAccessController() view returns(address) func (_Flags *FlagsCaller) RaisingAccessController(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _Flags.contract.Call(opts, out, "raisingAccessController") - return *ret0, err + var out []interface{} + err := _Flags.contract.Call(opts, &out, "raisingAccessController") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // RaisingAccessController is a free data retrieval call binding the contract method 0x2e1d859c. diff --git a/core/internal/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go b/core/internal/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go index 6d0997e562f..ce03d2fe6a6 100644 --- a/core/internal/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go +++ b/core/internal/gethwrappers/generated/flux_aggregator_wrapper/flux_aggregator_wrapper.go @@ -154,7 +154,7 @@ func bindFluxAggregator(address common.Address, caller bind.ContractCaller, tran // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_FluxAggregator *FluxAggregatorRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_FluxAggregator *FluxAggregatorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _FluxAggregator.Contract.FluxAggregatorCaller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_FluxAggregator *FluxAggregatorRaw) Transact(opts *bind.TransactOpts, meth // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_FluxAggregator *FluxAggregatorCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_FluxAggregator *FluxAggregatorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _FluxAggregator.Contract.contract.Call(opts, result, method, params...) } @@ -192,12 +192,17 @@ func (_FluxAggregator *FluxAggregatorTransactorRaw) Transact(opts *bind.Transact // // Solidity: function allocatedFunds() view returns(uint128) func (_FluxAggregator *FluxAggregatorCaller) AllocatedFunds(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "allocatedFunds") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "allocatedFunds") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // AllocatedFunds is a free data retrieval call binding the contract method 0xd4cc54e4. @@ -218,12 +223,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) AllocatedFunds() (*big.Int, // // Solidity: function availableFunds() view returns(uint128) func (_FluxAggregator *FluxAggregatorCaller) AvailableFunds(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "availableFunds") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "availableFunds") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // AvailableFunds is a free data retrieval call binding the contract method 0x46fcff4c. @@ -244,12 +254,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) AvailableFunds() (*big.Int, // // Solidity: function decimals() view returns(uint8) func (_FluxAggregator *FluxAggregatorCaller) Decimals(opts *bind.CallOpts) (uint8, error) { - var ( - ret0 = new(uint8) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "decimals") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + } // Decimals is a free data retrieval call binding the contract method 0x313ce567. @@ -270,12 +285,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) Decimals() (uint8, error) { // // Solidity: function description() view returns(string) func (_FluxAggregator *FluxAggregatorCaller) Description(opts *bind.CallOpts) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "description") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "description") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // Description is a free data retrieval call binding the contract method 0x7284e416. @@ -296,12 +316,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) Description() (string, error // // Solidity: function getAdmin(address _oracle) view returns(address) func (_FluxAggregator *FluxAggregatorCaller) GetAdmin(opts *bind.CallOpts, _oracle common.Address) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "getAdmin", _oracle) - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "getAdmin", _oracle) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // GetAdmin is a free data retrieval call binding the contract method 0x64efb22b. @@ -322,12 +347,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) GetAdmin(_oracle common.Addr // // Solidity: function getAnswer(uint256 _roundId) view returns(int256) func (_FluxAggregator *FluxAggregatorCaller) GetAnswer(opts *bind.CallOpts, _roundId *big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "getAnswer", _roundId) - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "getAnswer", _roundId) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetAnswer is a free data retrieval call binding the contract method 0xb5ab58dc. @@ -348,12 +378,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) GetAnswer(_roundId *big.Int) // // Solidity: function getOracles() view returns(address[]) func (_FluxAggregator *FluxAggregatorCaller) GetOracles(opts *bind.CallOpts) ([]common.Address, error) { - var ( - ret0 = new([]common.Address) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "getOracles") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "getOracles") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + } // GetOracles is a free data retrieval call binding the contract method 0x40884c52. @@ -380,16 +415,25 @@ func (_FluxAggregator *FluxAggregatorCaller) GetRoundData(opts *bind.CallOpts, _ UpdatedAt *big.Int AnsweredInRound *big.Int }, error) { - ret := new(struct { + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "getRoundData", _roundId) + + outstruct := new(struct { RoundId *big.Int Answer *big.Int StartedAt *big.Int UpdatedAt *big.Int AnsweredInRound *big.Int }) - out := ret - err := _FluxAggregator.contract.Call(opts, out, "getRoundData", _roundId) - return *ret, err + + outstruct.RoundId = out[0].(*big.Int) + outstruct.Answer = out[1].(*big.Int) + outstruct.StartedAt = out[2].(*big.Int) + outstruct.UpdatedAt = out[3].(*big.Int) + outstruct.AnsweredInRound = out[4].(*big.Int) + + return *outstruct, err + } // GetRoundData is a free data retrieval call binding the contract method 0x9a6fc8f5. @@ -422,12 +466,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) GetRoundData(_roundId *big.I // // Solidity: function getTimestamp(uint256 _roundId) view returns(uint256) func (_FluxAggregator *FluxAggregatorCaller) GetTimestamp(opts *bind.CallOpts, _roundId *big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "getTimestamp", _roundId) - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "getTimestamp", _roundId) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // GetTimestamp is a free data retrieval call binding the contract method 0xb633620c. @@ -448,12 +497,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) GetTimestamp(_roundId *big.I // // Solidity: function latestAnswer() view returns(int256) func (_FluxAggregator *FluxAggregatorCaller) LatestAnswer(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "latestAnswer") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "latestAnswer") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // LatestAnswer is a free data retrieval call binding the contract method 0x50d25bcd. @@ -474,12 +528,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) LatestAnswer() (*big.Int, er // // Solidity: function latestRound() view returns(uint256) func (_FluxAggregator *FluxAggregatorCaller) LatestRound(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "latestRound") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "latestRound") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // LatestRound is a free data retrieval call binding the contract method 0x668a0f02. @@ -506,16 +565,25 @@ func (_FluxAggregator *FluxAggregatorCaller) LatestRoundData(opts *bind.CallOpts UpdatedAt *big.Int AnsweredInRound *big.Int }, error) { - ret := new(struct { + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "latestRoundData") + + outstruct := new(struct { RoundId *big.Int Answer *big.Int StartedAt *big.Int UpdatedAt *big.Int AnsweredInRound *big.Int }) - out := ret - err := _FluxAggregator.contract.Call(opts, out, "latestRoundData") - return *ret, err + + outstruct.RoundId = out[0].(*big.Int) + outstruct.Answer = out[1].(*big.Int) + outstruct.StartedAt = out[2].(*big.Int) + outstruct.UpdatedAt = out[3].(*big.Int) + outstruct.AnsweredInRound = out[4].(*big.Int) + + return *outstruct, err + } // LatestRoundData is a free data retrieval call binding the contract method 0xfeaf968c. @@ -548,12 +616,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) LatestRoundData() (struct { // // Solidity: function latestTimestamp() view returns(uint256) func (_FluxAggregator *FluxAggregatorCaller) LatestTimestamp(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "latestTimestamp") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "latestTimestamp") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // LatestTimestamp is a free data retrieval call binding the contract method 0x8205bf6a. @@ -574,12 +647,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) LatestTimestamp() (*big.Int, // // Solidity: function linkToken() view returns(address) func (_FluxAggregator *FluxAggregatorCaller) LinkToken(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "linkToken") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "linkToken") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // LinkToken is a free data retrieval call binding the contract method 0x57970e93. @@ -600,12 +678,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) LinkToken() (common.Address, // // Solidity: function maxSubmissionCount() view returns(uint32) func (_FluxAggregator *FluxAggregatorCaller) MaxSubmissionCount(opts *bind.CallOpts) (uint32, error) { - var ( - ret0 = new(uint32) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "maxSubmissionCount") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "maxSubmissionCount") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + } // MaxSubmissionCount is a free data retrieval call binding the contract method 0x58609e44. @@ -626,12 +709,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) MaxSubmissionCount() (uint32 // // Solidity: function maxSubmissionValue() view returns(int256) func (_FluxAggregator *FluxAggregatorCaller) MaxSubmissionValue(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "maxSubmissionValue") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "maxSubmissionValue") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // MaxSubmissionValue is a free data retrieval call binding the contract method 0x23ca2903. @@ -652,12 +740,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) MaxSubmissionValue() (*big.I // // Solidity: function minSubmissionCount() view returns(uint32) func (_FluxAggregator *FluxAggregatorCaller) MinSubmissionCount(opts *bind.CallOpts) (uint32, error) { - var ( - ret0 = new(uint32) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "minSubmissionCount") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "minSubmissionCount") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + } // MinSubmissionCount is a free data retrieval call binding the contract method 0xc9374500. @@ -678,12 +771,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) MinSubmissionCount() (uint32 // // Solidity: function minSubmissionValue() view returns(int256) func (_FluxAggregator *FluxAggregatorCaller) MinSubmissionValue(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "minSubmissionValue") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "minSubmissionValue") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // MinSubmissionValue is a free data retrieval call binding the contract method 0x7c2b0b21. @@ -704,12 +802,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) MinSubmissionValue() (*big.I // // Solidity: function oracleCount() view returns(uint8) func (_FluxAggregator *FluxAggregatorCaller) OracleCount(opts *bind.CallOpts) (uint8, error) { - var ( - ret0 = new(uint8) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "oracleCount") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "oracleCount") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + } // OracleCount is a free data retrieval call binding the contract method 0x613d8fcc. @@ -739,7 +842,10 @@ func (_FluxAggregator *FluxAggregatorCaller) OracleRoundState(opts *bind.CallOpt OracleCount uint8 PaymentAmount *big.Int }, error) { - ret := new(struct { + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "oracleRoundState", _oracle, _queriedRoundId) + + outstruct := new(struct { EligibleToSubmit bool RoundId uint32 LatestSubmission *big.Int @@ -749,9 +855,18 @@ func (_FluxAggregator *FluxAggregatorCaller) OracleRoundState(opts *bind.CallOpt OracleCount uint8 PaymentAmount *big.Int }) - out := ret - err := _FluxAggregator.contract.Call(opts, out, "oracleRoundState", _oracle, _queriedRoundId) - return *ret, err + + outstruct.EligibleToSubmit = out[0].(bool) + outstruct.RoundId = out[1].(uint32) + outstruct.LatestSubmission = out[2].(*big.Int) + outstruct.StartedAt = out[3].(uint64) + outstruct.Timeout = out[4].(uint64) + outstruct.AvailableFunds = out[5].(*big.Int) + outstruct.OracleCount = out[6].(uint8) + outstruct.PaymentAmount = out[7].(*big.Int) + + return *outstruct, err + } // OracleRoundState is a free data retrieval call binding the contract method 0x88aa80e7. @@ -790,12 +905,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) OracleRoundState(_oracle com // // Solidity: function owner() view returns(address) func (_FluxAggregator *FluxAggregatorCaller) Owner(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "owner") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // Owner is a free data retrieval call binding the contract method 0x8da5cb5b. @@ -816,12 +936,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) Owner() (common.Address, err // // Solidity: function paymentAmount() view returns(uint128) func (_FluxAggregator *FluxAggregatorCaller) PaymentAmount(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "paymentAmount") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "paymentAmount") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // PaymentAmount is a free data retrieval call binding the contract method 0xc35905c6. @@ -842,12 +967,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) PaymentAmount() (*big.Int, e // // Solidity: function restartDelay() view returns(uint32) func (_FluxAggregator *FluxAggregatorCaller) RestartDelay(opts *bind.CallOpts) (uint32, error) { - var ( - ret0 = new(uint32) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "restartDelay") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "restartDelay") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + } // RestartDelay is a free data retrieval call binding the contract method 0x357ebb02. @@ -868,12 +998,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) RestartDelay() (uint32, erro // // Solidity: function timeout() view returns(uint32) func (_FluxAggregator *FluxAggregatorCaller) Timeout(opts *bind.CallOpts) (uint32, error) { - var ( - ret0 = new(uint32) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "timeout") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "timeout") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + } // Timeout is a free data retrieval call binding the contract method 0x70dea79a. @@ -894,12 +1029,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) Timeout() (uint32, error) { // // Solidity: function validator() view returns(address) func (_FluxAggregator *FluxAggregatorCaller) Validator(opts *bind.CallOpts) (common.Address, error) { - var ( - ret0 = new(common.Address) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "validator") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "validator") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + } // Validator is a free data retrieval call binding the contract method 0x3a5381b5. @@ -920,12 +1060,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) Validator() (common.Address, // // Solidity: function version() view returns(uint256) func (_FluxAggregator *FluxAggregatorCaller) Version(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "version") - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "version") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // Version is a free data retrieval call binding the contract method 0x54fd4d50. @@ -946,12 +1091,17 @@ func (_FluxAggregator *FluxAggregatorCallerSession) Version() (*big.Int, error) // // Solidity: function withdrawablePayment(address _oracle) view returns(uint256) func (_FluxAggregator *FluxAggregatorCaller) WithdrawablePayment(opts *bind.CallOpts, _oracle common.Address) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _FluxAggregator.contract.Call(opts, out, "withdrawablePayment", _oracle) - return *ret0, err + var out []interface{} + err := _FluxAggregator.contract.Call(opts, &out, "withdrawablePayment", _oracle) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // WithdrawablePayment is a free data retrieval call binding the contract method 0xe2e40317. diff --git a/core/internal/gethwrappers/generated/link_token_interface/link_token_interface.go b/core/internal/gethwrappers/generated/link_token_interface/link_token_interface.go index 8741a87509b..c1e2c2ed8cd 100644 --- a/core/internal/gethwrappers/generated/link_token_interface/link_token_interface.go +++ b/core/internal/gethwrappers/generated/link_token_interface/link_token_interface.go @@ -154,7 +154,7 @@ func bindLinkToken(address common.Address, caller bind.ContractCaller, transacto // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_LinkToken *LinkTokenRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_LinkToken *LinkTokenRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _LinkToken.Contract.LinkTokenCaller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_LinkToken *LinkTokenRaw) Transact(opts *bind.TransactOpts, method string, // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_LinkToken *LinkTokenCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_LinkToken *LinkTokenCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _LinkToken.Contract.contract.Call(opts, result, method, params...) } @@ -192,12 +192,17 @@ func (_LinkToken *LinkTokenTransactorRaw) Transact(opts *bind.TransactOpts, meth // // Solidity: function allowance(address _owner, address _spender) view returns(uint256 remaining) func (_LinkToken *LinkTokenCaller) Allowance(opts *bind.CallOpts, _owner common.Address, _spender common.Address) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _LinkToken.contract.Call(opts, out, "allowance", _owner, _spender) - return *ret0, err + var out []interface{} + err := _LinkToken.contract.Call(opts, &out, "allowance", _owner, _spender) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. @@ -218,12 +223,17 @@ func (_LinkToken *LinkTokenCallerSession) Allowance(_owner common.Address, _spen // // Solidity: function balanceOf(address _owner) view returns(uint256 balance) func (_LinkToken *LinkTokenCaller) BalanceOf(opts *bind.CallOpts, _owner common.Address) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _LinkToken.contract.Call(opts, out, "balanceOf", _owner) - return *ret0, err + var out []interface{} + err := _LinkToken.contract.Call(opts, &out, "balanceOf", _owner) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // BalanceOf is a free data retrieval call binding the contract method 0x70a08231. @@ -244,12 +254,17 @@ func (_LinkToken *LinkTokenCallerSession) BalanceOf(_owner common.Address) (*big // // Solidity: function decimals() view returns(uint8) func (_LinkToken *LinkTokenCaller) Decimals(opts *bind.CallOpts) (uint8, error) { - var ( - ret0 = new(uint8) - ) - out := ret0 - err := _LinkToken.contract.Call(opts, out, "decimals") - return *ret0, err + var out []interface{} + err := _LinkToken.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + } // Decimals is a free data retrieval call binding the contract method 0x313ce567. @@ -270,12 +285,17 @@ func (_LinkToken *LinkTokenCallerSession) Decimals() (uint8, error) { // // Solidity: function name() view returns(string) func (_LinkToken *LinkTokenCaller) Name(opts *bind.CallOpts) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _LinkToken.contract.Call(opts, out, "name") - return *ret0, err + var out []interface{} + err := _LinkToken.contract.Call(opts, &out, "name") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // Name is a free data retrieval call binding the contract method 0x06fdde03. @@ -296,12 +316,17 @@ func (_LinkToken *LinkTokenCallerSession) Name() (string, error) { // // Solidity: function symbol() view returns(string) func (_LinkToken *LinkTokenCaller) Symbol(opts *bind.CallOpts) (string, error) { - var ( - ret0 = new(string) - ) - out := ret0 - err := _LinkToken.contract.Call(opts, out, "symbol") - return *ret0, err + var out []interface{} + err := _LinkToken.contract.Call(opts, &out, "symbol") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + } // Symbol is a free data retrieval call binding the contract method 0x95d89b41. @@ -322,12 +347,17 @@ func (_LinkToken *LinkTokenCallerSession) Symbol() (string, error) { // // Solidity: function totalSupply() view returns(uint256) func (_LinkToken *LinkTokenCaller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _LinkToken.contract.Call(opts, out, "totalSupply") - return *ret0, err + var out []interface{} + err := _LinkToken.contract.Call(opts, &out, "totalSupply") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. diff --git a/core/internal/gethwrappers/generated/multiwordconsumer_wrapper/multiwordconsumer_wrapper.go b/core/internal/gethwrappers/generated/multiwordconsumer_wrapper/multiwordconsumer_wrapper.go new file mode 100644 index 00000000000..124c48f9283 --- /dev/null +++ b/core/internal/gethwrappers/generated/multiwordconsumer_wrapper/multiwordconsumer_wrapper.go @@ -0,0 +1,1245 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package multiwordconsumer_wrapper + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// MultiWordConsumerABI is the input ABI used to generate the binding from. +const MultiWordConsumerABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_link\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_oracle\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"_specId\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"ChainlinkCancelled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"ChainlinkFulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"ChainlinkRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes\",\"name\":\"price\",\"type\":\"bytes\"}],\"name\":\"RequestFulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"usd\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"eur\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"jpy\",\"type\":\"bytes32\"}],\"name\":\"RequestMultipleFulfilled\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_oracle\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"}],\"name\":\"addExternalRequest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_oracle\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_payment\",\"type\":\"uint256\"},{\"internalType\":\"bytes4\",\"name\":\"_callbackFunctionId\",\"type\":\"bytes4\"},{\"internalType\":\"uint256\",\"name\":\"_expiration\",\"type\":\"uint256\"}],\"name\":\"cancelRequest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentPrice\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eur\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_price\",\"type\":\"bytes\"}],\"name\":\"fulfillBytes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_usd\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_eur\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_jpy\",\"type\":\"bytes32\"}],\"name\":\"fulfillMultipleParameters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"jpy\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_currency\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_payment\",\"type\":\"uint256\"}],\"name\":\"requestEthereumPrice\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_currency\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_payment\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_callback\",\"type\":\"address\"}],\"name\":\"requestEthereumPriceByCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_currency\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_payment\",\"type\":\"uint256\"}],\"name\":\"requestMultipleParameters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_specId\",\"type\":\"bytes32\"}],\"name\":\"setSpecID\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"usd\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawLink\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + +// MultiWordConsumerBin is the compiled bytecode used for deploying new contracts. +var MultiWordConsumerBin = "0x6080604052600160045534801561001557600080fd5b506040516113ec3803806113ec8339818101604052606081101561003857600080fd5b508051602082015160409092015190919061005283610066565b61005b82610088565b600655506100aa9050565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b600380546001600160a01b0319166001600160a01b0392909216919091179055565b611333806100b96000396000f3fe608060405234801561001057600080fd5b50600436106100df5760003560e01c80639d1b464a1161008c578063d63a6ccd11610066578063d63a6ccd14610454578063e89855ba1461045c578063e8d5359d14610504578063faa367611461053d576100df565b80639d1b464a146102fb578063a856ff6b14610378578063c2fb8523146103a7576100df565b806374961d4d116100bd57806374961d4d1461018a57806383db5cbc1461024b5780638dc654a2146102f3576100df565b8063501fdd5d146100e45780635591a608146101035780637439ae5914610170575b600080fd5b610101600480360360208110156100fa57600080fd5b5035610545565b005b610101600480360360a081101561011957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813516906020810135906040810135907fffffffff00000000000000000000000000000000000000000000000000000000606082013516906080013561054a565b610178610611565b60408051918252519081900360200190f35b610101600480360360608110156101a057600080fd5b8101906020810181356401000000008111156101bb57600080fd5b8201836020820111156101cd57600080fd5b803590602001918460018302840111640100000000831117156101ef57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550508235935050506020013573ffffffffffffffffffffffffffffffffffffffff16610617565b6101016004803603604081101561026157600080fd5b81019060208101813564010000000081111561027c57600080fd5b82018360208201111561028e57600080fd5b803590602001918460018302840111640100000000831117156102b057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250610660915050565b61010161066f565b610303610839565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561033d578181015183820152602001610325565b50505050905090810190601f16801561036a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101016004803603608081101561038e57600080fd5b50803590602081013590604081013590606001356108e5565b610101600480360360408110156103bd57600080fd5b813591908101906040810160208201356401000000008111156103df57600080fd5b8201836020820111156103f157600080fd5b8035906020019184600183028401116401000000008311171561041357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610a08945050505050565b610178610bba565b6101016004803603604081101561047257600080fd5b81019060208101813564010000000081111561048d57600080fd5b82018360208201111561049f57600080fd5b803590602001918460018302840111640100000000831117156104c157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505091359250610bc0915050565b6101016004803603604081101561051a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610c02565b610178610c0c565b600655565b604080517f6ee4d55300000000000000000000000000000000000000000000000000000000815260048101869052602481018590527fffffffff0000000000000000000000000000000000000000000000000000000084166044820152606481018390529051869173ffffffffffffffffffffffffffffffffffffffff831691636ee4d5539160848082019260009290919082900301818387803b1580156105f157600080fd5b505af1158015610605573d6000803e3d6000fd5b50505050505050505050565b60095481565b61061f6111d0565b60065461064d90837fc2fb852300000000000000000000000000000000000000000000000000000000610c12565b90506106598184610c37565b5050505050565b61066b828230610617565b5050565b6000610679610c65565b90508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb338373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b1580156106ff57600080fd5b505afa158015610713573d6000803e3d6000fd5b505050506040513d602081101561072957600080fd5b5051604080517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815273ffffffffffffffffffffffffffffffffffffffff909316600484015260248301919091525160448083019260209291908290030181600087803b15801561079f57600080fd5b505af11580156107b3573d6000803e3d6000fd5b505050506040513d60208110156107c957600080fd5b505161083657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e61626c6520746f207472616e736665720000000000000000000000000000604482015290519081900360640190fd5b50565b6007805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156108dd5780601f106108b2576101008083540402835291602001916108dd565b820191906000526020600020905b8154815290600101906020018083116108c057829003601f168201915b505050505081565b600084815260056020526040902054849073ffffffffffffffffffffffffffffffffffffffff163314610963576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806112d66028913960400191505060405180910390fd5b60008181526005602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555182917f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a91a28284867f0ec0c13e44aa04198947078cb990660252870dd3363f4c4bb3cc780f808dabbe856040518082815260200191505060405180910390a450600892909255600955600a5550565b600082815260056020526040902054829073ffffffffffffffffffffffffffffffffffffffff163314610a86576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806112d66028913960400191505060405180910390fd5b60008181526005602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555182917f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a91a2816040518082805190602001908083835b60208310610b2f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610af2565b5181516020939093036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990911692169190911790526040519201829003822093508692507f1a111c5dcf9a71088bd5e1797fdfaf399fec2afbb24aca247e4e3e9f4b61df919160009150a38151610bb4906007906020850190611205565b50505050565b60085481565b610bc86111d0565b600654610bf690307fa856ff6b00000000000000000000000000000000000000000000000000000000610c12565b9050610bb48183610c37565b61066b8282610c81565b600a5481565b610c1a6111d0565b610c226111d0565b610c2e81868686610d68565b95945050505050565b600354600090610c5e9073ffffffffffffffffffffffffffffffffffffffff168484610dca565b9392505050565b60025473ffffffffffffffffffffffffffffffffffffffff1690565b600081815260056020526040902054819073ffffffffffffffffffffffffffffffffffffffff1615610d1457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5265717565737420697320616c72656164792070656e64696e67000000000000604482015290519081900360640190fd5b50600090815260056020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b610d706111d0565b610d808560800151610100610ff8565b505091835273ffffffffffffffffffffffffffffffffffffffff1660208301527fffffffff0000000000000000000000000000000000000000000000000000000016604082015290565b6004546040805130606090811b60208084019190915260348084018690528451808503909101815260549093018452825192810192909220908601939093526000838152600590915281812080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8816179055905182917fb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af991a26002805473ffffffffffffffffffffffffffffffffffffffff1690634000aea09086908590610eab908890611032565b6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610f19578181015183820152602001610f01565b50505050905090810190601f168015610f465780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b158015610f6757600080fd5b505af1158015610f7b573d6000803e3d6000fd5b505050506040513d6020811015610f9157600080fd5b5051610fe8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806112b36023913960400191505060405180910390fd5b6004805460010190559392505050565b611000611283565b60208206156110155760208206602003820191505b506020828101829052604080518085526000815290920101905290565b6060634042994660e01b6000808560000151866020015187604001518860600151888a6080015160000151604051602401808973ffffffffffffffffffffffffffffffffffffffff1681526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff168152602001857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156111125781810151838201526020016110fa565b50505050905090810190601f16801561113f5780820380516001836020036101000a031916815260200191505b50604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909d169c909c17909b525098995050505050505050505092915050565b6040805160a081018252600080825260208201819052918101829052606081019190915260808101611200611283565b905290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061124657805160ff1916838001178555611273565b82800160010185558215611273579182015b82811115611273578251825591602001919060010190611258565b5061127f92915061129d565b5090565b604051806040016040528060608152602001600081525090565b5b8082111561127f576000815560010161129e56fe756e61626c6520746f207472616e73666572416e6443616c6c20746f206f7261636c65536f75726365206d75737420626520746865206f7261636c65206f66207468652072657175657374a264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033" + +// DeployMultiWordConsumer deploys a new Ethereum contract, binding an instance of MultiWordConsumer to it. +func DeployMultiWordConsumer(auth *bind.TransactOpts, backend bind.ContractBackend, _link common.Address, _oracle common.Address, _specId [32]byte) (common.Address, *types.Transaction, *MultiWordConsumer, error) { + parsed, err := abi.JSON(strings.NewReader(MultiWordConsumerABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(MultiWordConsumerBin), backend, _link, _oracle, _specId) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &MultiWordConsumer{MultiWordConsumerCaller: MultiWordConsumerCaller{contract: contract}, MultiWordConsumerTransactor: MultiWordConsumerTransactor{contract: contract}, MultiWordConsumerFilterer: MultiWordConsumerFilterer{contract: contract}}, nil +} + +// MultiWordConsumer is an auto generated Go binding around an Ethereum contract. +type MultiWordConsumer struct { + MultiWordConsumerCaller // Read-only binding to the contract + MultiWordConsumerTransactor // Write-only binding to the contract + MultiWordConsumerFilterer // Log filterer for contract events +} + +// MultiWordConsumerCaller is an auto generated read-only Go binding around an Ethereum contract. +type MultiWordConsumerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MultiWordConsumerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type MultiWordConsumerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MultiWordConsumerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type MultiWordConsumerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MultiWordConsumerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type MultiWordConsumerSession struct { + Contract *MultiWordConsumer // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MultiWordConsumerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type MultiWordConsumerCallerSession struct { + Contract *MultiWordConsumerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// MultiWordConsumerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type MultiWordConsumerTransactorSession struct { + Contract *MultiWordConsumerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MultiWordConsumerRaw is an auto generated low-level Go binding around an Ethereum contract. +type MultiWordConsumerRaw struct { + Contract *MultiWordConsumer // Generic contract binding to access the raw methods on +} + +// MultiWordConsumerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type MultiWordConsumerCallerRaw struct { + Contract *MultiWordConsumerCaller // Generic read-only contract binding to access the raw methods on +} + +// MultiWordConsumerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type MultiWordConsumerTransactorRaw struct { + Contract *MultiWordConsumerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewMultiWordConsumer creates a new instance of MultiWordConsumer, bound to a specific deployed contract. +func NewMultiWordConsumer(address common.Address, backend bind.ContractBackend) (*MultiWordConsumer, error) { + contract, err := bindMultiWordConsumer(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &MultiWordConsumer{MultiWordConsumerCaller: MultiWordConsumerCaller{contract: contract}, MultiWordConsumerTransactor: MultiWordConsumerTransactor{contract: contract}, MultiWordConsumerFilterer: MultiWordConsumerFilterer{contract: contract}}, nil +} + +// NewMultiWordConsumerCaller creates a new read-only instance of MultiWordConsumer, bound to a specific deployed contract. +func NewMultiWordConsumerCaller(address common.Address, caller bind.ContractCaller) (*MultiWordConsumerCaller, error) { + contract, err := bindMultiWordConsumer(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &MultiWordConsumerCaller{contract: contract}, nil +} + +// NewMultiWordConsumerTransactor creates a new write-only instance of MultiWordConsumer, bound to a specific deployed contract. +func NewMultiWordConsumerTransactor(address common.Address, transactor bind.ContractTransactor) (*MultiWordConsumerTransactor, error) { + contract, err := bindMultiWordConsumer(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &MultiWordConsumerTransactor{contract: contract}, nil +} + +// NewMultiWordConsumerFilterer creates a new log filterer instance of MultiWordConsumer, bound to a specific deployed contract. +func NewMultiWordConsumerFilterer(address common.Address, filterer bind.ContractFilterer) (*MultiWordConsumerFilterer, error) { + contract, err := bindMultiWordConsumer(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &MultiWordConsumerFilterer{contract: contract}, nil +} + +// bindMultiWordConsumer binds a generic wrapper to an already deployed contract. +func bindMultiWordConsumer(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(MultiWordConsumerABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MultiWordConsumer *MultiWordConsumerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MultiWordConsumer.Contract.MultiWordConsumerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MultiWordConsumer *MultiWordConsumerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.MultiWordConsumerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MultiWordConsumer *MultiWordConsumerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.MultiWordConsumerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MultiWordConsumer *MultiWordConsumerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MultiWordConsumer.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MultiWordConsumer *MultiWordConsumerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MultiWordConsumer *MultiWordConsumerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.contract.Transact(opts, method, params...) +} + +// CurrentPrice is a free data retrieval call binding the contract method 0x9d1b464a. +// +// Solidity: function currentPrice() view returns(bytes) +func (_MultiWordConsumer *MultiWordConsumerCaller) CurrentPrice(opts *bind.CallOpts) ([]byte, error) { + var out []interface{} + err := _MultiWordConsumer.contract.Call(opts, &out, "currentPrice") + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +// CurrentPrice is a free data retrieval call binding the contract method 0x9d1b464a. +// +// Solidity: function currentPrice() view returns(bytes) +func (_MultiWordConsumer *MultiWordConsumerSession) CurrentPrice() ([]byte, error) { + return _MultiWordConsumer.Contract.CurrentPrice(&_MultiWordConsumer.CallOpts) +} + +// CurrentPrice is a free data retrieval call binding the contract method 0x9d1b464a. +// +// Solidity: function currentPrice() view returns(bytes) +func (_MultiWordConsumer *MultiWordConsumerCallerSession) CurrentPrice() ([]byte, error) { + return _MultiWordConsumer.Contract.CurrentPrice(&_MultiWordConsumer.CallOpts) +} + +// Eur is a free data retrieval call binding the contract method 0x7439ae59. +// +// Solidity: function eur() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerCaller) Eur(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _MultiWordConsumer.contract.Call(opts, &out, "eur") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Eur is a free data retrieval call binding the contract method 0x7439ae59. +// +// Solidity: function eur() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerSession) Eur() ([32]byte, error) { + return _MultiWordConsumer.Contract.Eur(&_MultiWordConsumer.CallOpts) +} + +// Eur is a free data retrieval call binding the contract method 0x7439ae59. +// +// Solidity: function eur() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerCallerSession) Eur() ([32]byte, error) { + return _MultiWordConsumer.Contract.Eur(&_MultiWordConsumer.CallOpts) +} + +// Jpy is a free data retrieval call binding the contract method 0xfaa36761. +// +// Solidity: function jpy() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerCaller) Jpy(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _MultiWordConsumer.contract.Call(opts, &out, "jpy") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Jpy is a free data retrieval call binding the contract method 0xfaa36761. +// +// Solidity: function jpy() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerSession) Jpy() ([32]byte, error) { + return _MultiWordConsumer.Contract.Jpy(&_MultiWordConsumer.CallOpts) +} + +// Jpy is a free data retrieval call binding the contract method 0xfaa36761. +// +// Solidity: function jpy() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerCallerSession) Jpy() ([32]byte, error) { + return _MultiWordConsumer.Contract.Jpy(&_MultiWordConsumer.CallOpts) +} + +// Usd is a free data retrieval call binding the contract method 0xd63a6ccd. +// +// Solidity: function usd() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerCaller) Usd(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _MultiWordConsumer.contract.Call(opts, &out, "usd") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Usd is a free data retrieval call binding the contract method 0xd63a6ccd. +// +// Solidity: function usd() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerSession) Usd() ([32]byte, error) { + return _MultiWordConsumer.Contract.Usd(&_MultiWordConsumer.CallOpts) +} + +// Usd is a free data retrieval call binding the contract method 0xd63a6ccd. +// +// Solidity: function usd() view returns(bytes32) +func (_MultiWordConsumer *MultiWordConsumerCallerSession) Usd() ([32]byte, error) { + return _MultiWordConsumer.Contract.Usd(&_MultiWordConsumer.CallOpts) +} + +// AddExternalRequest is a paid mutator transaction binding the contract method 0xe8d5359d. +// +// Solidity: function addExternalRequest(address _oracle, bytes32 _requestId) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) AddExternalRequest(opts *bind.TransactOpts, _oracle common.Address, _requestId [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "addExternalRequest", _oracle, _requestId) +} + +// AddExternalRequest is a paid mutator transaction binding the contract method 0xe8d5359d. +// +// Solidity: function addExternalRequest(address _oracle, bytes32 _requestId) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) AddExternalRequest(_oracle common.Address, _requestId [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.AddExternalRequest(&_MultiWordConsumer.TransactOpts, _oracle, _requestId) +} + +// AddExternalRequest is a paid mutator transaction binding the contract method 0xe8d5359d. +// +// Solidity: function addExternalRequest(address _oracle, bytes32 _requestId) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) AddExternalRequest(_oracle common.Address, _requestId [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.AddExternalRequest(&_MultiWordConsumer.TransactOpts, _oracle, _requestId) +} + +// CancelRequest is a paid mutator transaction binding the contract method 0x5591a608. +// +// Solidity: function cancelRequest(address _oracle, bytes32 _requestId, uint256 _payment, bytes4 _callbackFunctionId, uint256 _expiration) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) CancelRequest(opts *bind.TransactOpts, _oracle common.Address, _requestId [32]byte, _payment *big.Int, _callbackFunctionId [4]byte, _expiration *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "cancelRequest", _oracle, _requestId, _payment, _callbackFunctionId, _expiration) +} + +// CancelRequest is a paid mutator transaction binding the contract method 0x5591a608. +// +// Solidity: function cancelRequest(address _oracle, bytes32 _requestId, uint256 _payment, bytes4 _callbackFunctionId, uint256 _expiration) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) CancelRequest(_oracle common.Address, _requestId [32]byte, _payment *big.Int, _callbackFunctionId [4]byte, _expiration *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.CancelRequest(&_MultiWordConsumer.TransactOpts, _oracle, _requestId, _payment, _callbackFunctionId, _expiration) +} + +// CancelRequest is a paid mutator transaction binding the contract method 0x5591a608. +// +// Solidity: function cancelRequest(address _oracle, bytes32 _requestId, uint256 _payment, bytes4 _callbackFunctionId, uint256 _expiration) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) CancelRequest(_oracle common.Address, _requestId [32]byte, _payment *big.Int, _callbackFunctionId [4]byte, _expiration *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.CancelRequest(&_MultiWordConsumer.TransactOpts, _oracle, _requestId, _payment, _callbackFunctionId, _expiration) +} + +// FulfillBytes is a paid mutator transaction binding the contract method 0xc2fb8523. +// +// Solidity: function fulfillBytes(bytes32 _requestId, bytes _price) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) FulfillBytes(opts *bind.TransactOpts, _requestId [32]byte, _price []byte) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "fulfillBytes", _requestId, _price) +} + +// FulfillBytes is a paid mutator transaction binding the contract method 0xc2fb8523. +// +// Solidity: function fulfillBytes(bytes32 _requestId, bytes _price) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) FulfillBytes(_requestId [32]byte, _price []byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.FulfillBytes(&_MultiWordConsumer.TransactOpts, _requestId, _price) +} + +// FulfillBytes is a paid mutator transaction binding the contract method 0xc2fb8523. +// +// Solidity: function fulfillBytes(bytes32 _requestId, bytes _price) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) FulfillBytes(_requestId [32]byte, _price []byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.FulfillBytes(&_MultiWordConsumer.TransactOpts, _requestId, _price) +} + +// FulfillMultipleParameters is a paid mutator transaction binding the contract method 0xa856ff6b. +// +// Solidity: function fulfillMultipleParameters(bytes32 _requestId, bytes32 _usd, bytes32 _eur, bytes32 _jpy) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) FulfillMultipleParameters(opts *bind.TransactOpts, _requestId [32]byte, _usd [32]byte, _eur [32]byte, _jpy [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "fulfillMultipleParameters", _requestId, _usd, _eur, _jpy) +} + +// FulfillMultipleParameters is a paid mutator transaction binding the contract method 0xa856ff6b. +// +// Solidity: function fulfillMultipleParameters(bytes32 _requestId, bytes32 _usd, bytes32 _eur, bytes32 _jpy) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) FulfillMultipleParameters(_requestId [32]byte, _usd [32]byte, _eur [32]byte, _jpy [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.FulfillMultipleParameters(&_MultiWordConsumer.TransactOpts, _requestId, _usd, _eur, _jpy) +} + +// FulfillMultipleParameters is a paid mutator transaction binding the contract method 0xa856ff6b. +// +// Solidity: function fulfillMultipleParameters(bytes32 _requestId, bytes32 _usd, bytes32 _eur, bytes32 _jpy) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) FulfillMultipleParameters(_requestId [32]byte, _usd [32]byte, _eur [32]byte, _jpy [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.FulfillMultipleParameters(&_MultiWordConsumer.TransactOpts, _requestId, _usd, _eur, _jpy) +} + +// RequestEthereumPrice is a paid mutator transaction binding the contract method 0x83db5cbc. +// +// Solidity: function requestEthereumPrice(string _currency, uint256 _payment) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) RequestEthereumPrice(opts *bind.TransactOpts, _currency string, _payment *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "requestEthereumPrice", _currency, _payment) +} + +// RequestEthereumPrice is a paid mutator transaction binding the contract method 0x83db5cbc. +// +// Solidity: function requestEthereumPrice(string _currency, uint256 _payment) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) RequestEthereumPrice(_currency string, _payment *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.RequestEthereumPrice(&_MultiWordConsumer.TransactOpts, _currency, _payment) +} + +// RequestEthereumPrice is a paid mutator transaction binding the contract method 0x83db5cbc. +// +// Solidity: function requestEthereumPrice(string _currency, uint256 _payment) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) RequestEthereumPrice(_currency string, _payment *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.RequestEthereumPrice(&_MultiWordConsumer.TransactOpts, _currency, _payment) +} + +// RequestEthereumPriceByCallback is a paid mutator transaction binding the contract method 0x74961d4d. +// +// Solidity: function requestEthereumPriceByCallback(string _currency, uint256 _payment, address _callback) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) RequestEthereumPriceByCallback(opts *bind.TransactOpts, _currency string, _payment *big.Int, _callback common.Address) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "requestEthereumPriceByCallback", _currency, _payment, _callback) +} + +// RequestEthereumPriceByCallback is a paid mutator transaction binding the contract method 0x74961d4d. +// +// Solidity: function requestEthereumPriceByCallback(string _currency, uint256 _payment, address _callback) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) RequestEthereumPriceByCallback(_currency string, _payment *big.Int, _callback common.Address) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.RequestEthereumPriceByCallback(&_MultiWordConsumer.TransactOpts, _currency, _payment, _callback) +} + +// RequestEthereumPriceByCallback is a paid mutator transaction binding the contract method 0x74961d4d. +// +// Solidity: function requestEthereumPriceByCallback(string _currency, uint256 _payment, address _callback) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) RequestEthereumPriceByCallback(_currency string, _payment *big.Int, _callback common.Address) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.RequestEthereumPriceByCallback(&_MultiWordConsumer.TransactOpts, _currency, _payment, _callback) +} + +// RequestMultipleParameters is a paid mutator transaction binding the contract method 0xe89855ba. +// +// Solidity: function requestMultipleParameters(string _currency, uint256 _payment) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) RequestMultipleParameters(opts *bind.TransactOpts, _currency string, _payment *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "requestMultipleParameters", _currency, _payment) +} + +// RequestMultipleParameters is a paid mutator transaction binding the contract method 0xe89855ba. +// +// Solidity: function requestMultipleParameters(string _currency, uint256 _payment) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) RequestMultipleParameters(_currency string, _payment *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.RequestMultipleParameters(&_MultiWordConsumer.TransactOpts, _currency, _payment) +} + +// RequestMultipleParameters is a paid mutator transaction binding the contract method 0xe89855ba. +// +// Solidity: function requestMultipleParameters(string _currency, uint256 _payment) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) RequestMultipleParameters(_currency string, _payment *big.Int) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.RequestMultipleParameters(&_MultiWordConsumer.TransactOpts, _currency, _payment) +} + +// SetSpecID is a paid mutator transaction binding the contract method 0x501fdd5d. +// +// Solidity: function setSpecID(bytes32 _specId) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) SetSpecID(opts *bind.TransactOpts, _specId [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "setSpecID", _specId) +} + +// SetSpecID is a paid mutator transaction binding the contract method 0x501fdd5d. +// +// Solidity: function setSpecID(bytes32 _specId) returns() +func (_MultiWordConsumer *MultiWordConsumerSession) SetSpecID(_specId [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.SetSpecID(&_MultiWordConsumer.TransactOpts, _specId) +} + +// SetSpecID is a paid mutator transaction binding the contract method 0x501fdd5d. +// +// Solidity: function setSpecID(bytes32 _specId) returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) SetSpecID(_specId [32]byte) (*types.Transaction, error) { + return _MultiWordConsumer.Contract.SetSpecID(&_MultiWordConsumer.TransactOpts, _specId) +} + +// WithdrawLink is a paid mutator transaction binding the contract method 0x8dc654a2. +// +// Solidity: function withdrawLink() returns() +func (_MultiWordConsumer *MultiWordConsumerTransactor) WithdrawLink(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MultiWordConsumer.contract.Transact(opts, "withdrawLink") +} + +// WithdrawLink is a paid mutator transaction binding the contract method 0x8dc654a2. +// +// Solidity: function withdrawLink() returns() +func (_MultiWordConsumer *MultiWordConsumerSession) WithdrawLink() (*types.Transaction, error) { + return _MultiWordConsumer.Contract.WithdrawLink(&_MultiWordConsumer.TransactOpts) +} + +// WithdrawLink is a paid mutator transaction binding the contract method 0x8dc654a2. +// +// Solidity: function withdrawLink() returns() +func (_MultiWordConsumer *MultiWordConsumerTransactorSession) WithdrawLink() (*types.Transaction, error) { + return _MultiWordConsumer.Contract.WithdrawLink(&_MultiWordConsumer.TransactOpts) +} + +// MultiWordConsumerChainlinkCancelledIterator is returned from FilterChainlinkCancelled and is used to iterate over the raw logs and unpacked data for ChainlinkCancelled events raised by the MultiWordConsumer contract. +type MultiWordConsumerChainlinkCancelledIterator struct { + Event *MultiWordConsumerChainlinkCancelled // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *MultiWordConsumerChainlinkCancelledIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerChainlinkCancelled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerChainlinkCancelled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *MultiWordConsumerChainlinkCancelledIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MultiWordConsumerChainlinkCancelledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MultiWordConsumerChainlinkCancelled represents a ChainlinkCancelled event raised by the MultiWordConsumer contract. +type MultiWordConsumerChainlinkCancelled struct { + Id [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterChainlinkCancelled is a free log retrieval operation binding the contract event 0xe1fe3afa0f7f761ff0a8b89086790efd5140d2907ebd5b7ff6bfcb5e075fd4c5. +// +// Solidity: event ChainlinkCancelled(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) FilterChainlinkCancelled(opts *bind.FilterOpts, id [][32]byte) (*MultiWordConsumerChainlinkCancelledIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _MultiWordConsumer.contract.FilterLogs(opts, "ChainlinkCancelled", idRule) + if err != nil { + return nil, err + } + return &MultiWordConsumerChainlinkCancelledIterator{contract: _MultiWordConsumer.contract, event: "ChainlinkCancelled", logs: logs, sub: sub}, nil +} + +// WatchChainlinkCancelled is a free log subscription operation binding the contract event 0xe1fe3afa0f7f761ff0a8b89086790efd5140d2907ebd5b7ff6bfcb5e075fd4c5. +// +// Solidity: event ChainlinkCancelled(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) WatchChainlinkCancelled(opts *bind.WatchOpts, sink chan<- *MultiWordConsumerChainlinkCancelled, id [][32]byte) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _MultiWordConsumer.contract.WatchLogs(opts, "ChainlinkCancelled", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(MultiWordConsumerChainlinkCancelled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "ChainlinkCancelled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseChainlinkCancelled is a log parse operation binding the contract event 0xe1fe3afa0f7f761ff0a8b89086790efd5140d2907ebd5b7ff6bfcb5e075fd4c5. +// +// Solidity: event ChainlinkCancelled(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) ParseChainlinkCancelled(log types.Log) (*MultiWordConsumerChainlinkCancelled, error) { + event := new(MultiWordConsumerChainlinkCancelled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "ChainlinkCancelled", log); err != nil { + return nil, err + } + return event, nil +} + +// MultiWordConsumerChainlinkFulfilledIterator is returned from FilterChainlinkFulfilled and is used to iterate over the raw logs and unpacked data for ChainlinkFulfilled events raised by the MultiWordConsumer contract. +type MultiWordConsumerChainlinkFulfilledIterator struct { + Event *MultiWordConsumerChainlinkFulfilled // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *MultiWordConsumerChainlinkFulfilledIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerChainlinkFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerChainlinkFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *MultiWordConsumerChainlinkFulfilledIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MultiWordConsumerChainlinkFulfilledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MultiWordConsumerChainlinkFulfilled represents a ChainlinkFulfilled event raised by the MultiWordConsumer contract. +type MultiWordConsumerChainlinkFulfilled struct { + Id [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterChainlinkFulfilled is a free log retrieval operation binding the contract event 0x7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a. +// +// Solidity: event ChainlinkFulfilled(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) FilterChainlinkFulfilled(opts *bind.FilterOpts, id [][32]byte) (*MultiWordConsumerChainlinkFulfilledIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _MultiWordConsumer.contract.FilterLogs(opts, "ChainlinkFulfilled", idRule) + if err != nil { + return nil, err + } + return &MultiWordConsumerChainlinkFulfilledIterator{contract: _MultiWordConsumer.contract, event: "ChainlinkFulfilled", logs: logs, sub: sub}, nil +} + +// WatchChainlinkFulfilled is a free log subscription operation binding the contract event 0x7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a. +// +// Solidity: event ChainlinkFulfilled(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) WatchChainlinkFulfilled(opts *bind.WatchOpts, sink chan<- *MultiWordConsumerChainlinkFulfilled, id [][32]byte) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _MultiWordConsumer.contract.WatchLogs(opts, "ChainlinkFulfilled", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(MultiWordConsumerChainlinkFulfilled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "ChainlinkFulfilled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseChainlinkFulfilled is a log parse operation binding the contract event 0x7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a. +// +// Solidity: event ChainlinkFulfilled(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) ParseChainlinkFulfilled(log types.Log) (*MultiWordConsumerChainlinkFulfilled, error) { + event := new(MultiWordConsumerChainlinkFulfilled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "ChainlinkFulfilled", log); err != nil { + return nil, err + } + return event, nil +} + +// MultiWordConsumerChainlinkRequestedIterator is returned from FilterChainlinkRequested and is used to iterate over the raw logs and unpacked data for ChainlinkRequested events raised by the MultiWordConsumer contract. +type MultiWordConsumerChainlinkRequestedIterator struct { + Event *MultiWordConsumerChainlinkRequested // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *MultiWordConsumerChainlinkRequestedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerChainlinkRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerChainlinkRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *MultiWordConsumerChainlinkRequestedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MultiWordConsumerChainlinkRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MultiWordConsumerChainlinkRequested represents a ChainlinkRequested event raised by the MultiWordConsumer contract. +type MultiWordConsumerChainlinkRequested struct { + Id [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterChainlinkRequested is a free log retrieval operation binding the contract event 0xb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af9. +// +// Solidity: event ChainlinkRequested(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) FilterChainlinkRequested(opts *bind.FilterOpts, id [][32]byte) (*MultiWordConsumerChainlinkRequestedIterator, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _MultiWordConsumer.contract.FilterLogs(opts, "ChainlinkRequested", idRule) + if err != nil { + return nil, err + } + return &MultiWordConsumerChainlinkRequestedIterator{contract: _MultiWordConsumer.contract, event: "ChainlinkRequested", logs: logs, sub: sub}, nil +} + +// WatchChainlinkRequested is a free log subscription operation binding the contract event 0xb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af9. +// +// Solidity: event ChainlinkRequested(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) WatchChainlinkRequested(opts *bind.WatchOpts, sink chan<- *MultiWordConsumerChainlinkRequested, id [][32]byte) (event.Subscription, error) { + + var idRule []interface{} + for _, idItem := range id { + idRule = append(idRule, idItem) + } + + logs, sub, err := _MultiWordConsumer.contract.WatchLogs(opts, "ChainlinkRequested", idRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(MultiWordConsumerChainlinkRequested) + if err := _MultiWordConsumer.contract.UnpackLog(event, "ChainlinkRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseChainlinkRequested is a log parse operation binding the contract event 0xb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af9. +// +// Solidity: event ChainlinkRequested(bytes32 indexed id) +func (_MultiWordConsumer *MultiWordConsumerFilterer) ParseChainlinkRequested(log types.Log) (*MultiWordConsumerChainlinkRequested, error) { + event := new(MultiWordConsumerChainlinkRequested) + if err := _MultiWordConsumer.contract.UnpackLog(event, "ChainlinkRequested", log); err != nil { + return nil, err + } + return event, nil +} + +// MultiWordConsumerRequestFulfilledIterator is returned from FilterRequestFulfilled and is used to iterate over the raw logs and unpacked data for RequestFulfilled events raised by the MultiWordConsumer contract. +type MultiWordConsumerRequestFulfilledIterator struct { + Event *MultiWordConsumerRequestFulfilled // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *MultiWordConsumerRequestFulfilledIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerRequestFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerRequestFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *MultiWordConsumerRequestFulfilledIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MultiWordConsumerRequestFulfilledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MultiWordConsumerRequestFulfilled represents a RequestFulfilled event raised by the MultiWordConsumer contract. +type MultiWordConsumerRequestFulfilled struct { + RequestId [32]byte + Price common.Hash + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRequestFulfilled is a free log retrieval operation binding the contract event 0x1a111c5dcf9a71088bd5e1797fdfaf399fec2afbb24aca247e4e3e9f4b61df91. +// +// Solidity: event RequestFulfilled(bytes32 indexed requestId, bytes indexed price) +func (_MultiWordConsumer *MultiWordConsumerFilterer) FilterRequestFulfilled(opts *bind.FilterOpts, requestId [][32]byte, price [][]byte) (*MultiWordConsumerRequestFulfilledIterator, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + var priceRule []interface{} + for _, priceItem := range price { + priceRule = append(priceRule, priceItem) + } + + logs, sub, err := _MultiWordConsumer.contract.FilterLogs(opts, "RequestFulfilled", requestIdRule, priceRule) + if err != nil { + return nil, err + } + return &MultiWordConsumerRequestFulfilledIterator{contract: _MultiWordConsumer.contract, event: "RequestFulfilled", logs: logs, sub: sub}, nil +} + +// WatchRequestFulfilled is a free log subscription operation binding the contract event 0x1a111c5dcf9a71088bd5e1797fdfaf399fec2afbb24aca247e4e3e9f4b61df91. +// +// Solidity: event RequestFulfilled(bytes32 indexed requestId, bytes indexed price) +func (_MultiWordConsumer *MultiWordConsumerFilterer) WatchRequestFulfilled(opts *bind.WatchOpts, sink chan<- *MultiWordConsumerRequestFulfilled, requestId [][32]byte, price [][]byte) (event.Subscription, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + var priceRule []interface{} + for _, priceItem := range price { + priceRule = append(priceRule, priceItem) + } + + logs, sub, err := _MultiWordConsumer.contract.WatchLogs(opts, "RequestFulfilled", requestIdRule, priceRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(MultiWordConsumerRequestFulfilled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "RequestFulfilled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRequestFulfilled is a log parse operation binding the contract event 0x1a111c5dcf9a71088bd5e1797fdfaf399fec2afbb24aca247e4e3e9f4b61df91. +// +// Solidity: event RequestFulfilled(bytes32 indexed requestId, bytes indexed price) +func (_MultiWordConsumer *MultiWordConsumerFilterer) ParseRequestFulfilled(log types.Log) (*MultiWordConsumerRequestFulfilled, error) { + event := new(MultiWordConsumerRequestFulfilled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "RequestFulfilled", log); err != nil { + return nil, err + } + return event, nil +} + +// MultiWordConsumerRequestMultipleFulfilledIterator is returned from FilterRequestMultipleFulfilled and is used to iterate over the raw logs and unpacked data for RequestMultipleFulfilled events raised by the MultiWordConsumer contract. +type MultiWordConsumerRequestMultipleFulfilledIterator struct { + Event *MultiWordConsumerRequestMultipleFulfilled // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *MultiWordConsumerRequestMultipleFulfilledIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerRequestMultipleFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(MultiWordConsumerRequestMultipleFulfilled) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *MultiWordConsumerRequestMultipleFulfilledIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MultiWordConsumerRequestMultipleFulfilledIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MultiWordConsumerRequestMultipleFulfilled represents a RequestMultipleFulfilled event raised by the MultiWordConsumer contract. +type MultiWordConsumerRequestMultipleFulfilled struct { + RequestId [32]byte + Usd [32]byte + Eur [32]byte + Jpy [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRequestMultipleFulfilled is a free log retrieval operation binding the contract event 0x0ec0c13e44aa04198947078cb990660252870dd3363f4c4bb3cc780f808dabbe. +// +// Solidity: event RequestMultipleFulfilled(bytes32 indexed requestId, bytes32 indexed usd, bytes32 indexed eur, bytes32 jpy) +func (_MultiWordConsumer *MultiWordConsumerFilterer) FilterRequestMultipleFulfilled(opts *bind.FilterOpts, requestId [][32]byte, usd [][32]byte, eur [][32]byte) (*MultiWordConsumerRequestMultipleFulfilledIterator, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + var usdRule []interface{} + for _, usdItem := range usd { + usdRule = append(usdRule, usdItem) + } + var eurRule []interface{} + for _, eurItem := range eur { + eurRule = append(eurRule, eurItem) + } + + logs, sub, err := _MultiWordConsumer.contract.FilterLogs(opts, "RequestMultipleFulfilled", requestIdRule, usdRule, eurRule) + if err != nil { + return nil, err + } + return &MultiWordConsumerRequestMultipleFulfilledIterator{contract: _MultiWordConsumer.contract, event: "RequestMultipleFulfilled", logs: logs, sub: sub}, nil +} + +// WatchRequestMultipleFulfilled is a free log subscription operation binding the contract event 0x0ec0c13e44aa04198947078cb990660252870dd3363f4c4bb3cc780f808dabbe. +// +// Solidity: event RequestMultipleFulfilled(bytes32 indexed requestId, bytes32 indexed usd, bytes32 indexed eur, bytes32 jpy) +func (_MultiWordConsumer *MultiWordConsumerFilterer) WatchRequestMultipleFulfilled(opts *bind.WatchOpts, sink chan<- *MultiWordConsumerRequestMultipleFulfilled, requestId [][32]byte, usd [][32]byte, eur [][32]byte) (event.Subscription, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + var usdRule []interface{} + for _, usdItem := range usd { + usdRule = append(usdRule, usdItem) + } + var eurRule []interface{} + for _, eurItem := range eur { + eurRule = append(eurRule, eurItem) + } + + logs, sub, err := _MultiWordConsumer.contract.WatchLogs(opts, "RequestMultipleFulfilled", requestIdRule, usdRule, eurRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(MultiWordConsumerRequestMultipleFulfilled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "RequestMultipleFulfilled", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRequestMultipleFulfilled is a log parse operation binding the contract event 0x0ec0c13e44aa04198947078cb990660252870dd3363f4c4bb3cc780f808dabbe. +// +// Solidity: event RequestMultipleFulfilled(bytes32 indexed requestId, bytes32 indexed usd, bytes32 indexed eur, bytes32 jpy) +func (_MultiWordConsumer *MultiWordConsumerFilterer) ParseRequestMultipleFulfilled(log types.Log) (*MultiWordConsumerRequestMultipleFulfilled, error) { + event := new(MultiWordConsumerRequestMultipleFulfilled) + if err := _MultiWordConsumer.contract.UnpackLog(event, "RequestMultipleFulfilled", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/core/internal/gethwrappers/generated/operator_wrapper/operator_wrapper.go b/core/internal/gethwrappers/generated/operator_wrapper/operator_wrapper.go new file mode 100644 index 00000000000..f6307d830af --- /dev/null +++ b/core/internal/gethwrappers/generated/operator_wrapper/operator_wrapper.go @@ -0,0 +1,1295 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package operator_wrapper + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// OperatorABI is the input ABI used to generate the binding from. +const OperatorABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"link\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"CancelOracleRequest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"specId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requester\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"payment\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"callbackAddr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes4\",\"name\":\"callbackFunctionId\",\"type\":\"bytes4\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"cancelExpiration\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"dataVersion\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"OracleRequest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"OracleResponse\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EXPIRY_TIME\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"payment\",\"type\":\"uint256\"},{\"internalType\":\"bytes4\",\"name\":\"callbackFunc\",\"type\":\"bytes4\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"cancelOracleRequest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"forward\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"payment\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"callbackAddress\",\"type\":\"address\"},{\"internalType\":\"bytes4\",\"name\":\"callbackFunctionId\",\"type\":\"bytes4\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"data\",\"type\":\"bytes32\"}],\"name\":\"fulfillOracleRequest\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"payment\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"callbackAddress\",\"type\":\"address\"},{\"internalType\":\"bytes4\",\"name\":\"callbackFunctionId\",\"type\":\"bytes4\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"fulfillOracleRequest2\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getChainlinkToken\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"node\",\"type\":\"address\"}],\"name\":\"isAuthorizedSender\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"onTokenTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"payment\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"specId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"callbackAddress\",\"type\":\"address\"},{\"internalType\":\"bytes4\",\"name\":\"callbackFunctionId\",\"type\":\"bytes4\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"dataVersion\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"oracleRequest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"node\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"setAuthorizedSender\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// OperatorBin is the compiled bytecode used for deploying new contracts. +var OperatorBin = "0x60a0604052600160045534801561001557600080fd5b506040516122b33803806122b38339818101604052604081101561003857600080fd5b508051602090910151600080546001600160a01b0319166001600160a01b03928316178155606083901b6001600160601b0319166080529116906122169061009d90398061060552806106cb52806110fe52806111e95280611b0a52506122166000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80636fadcf7211610097578063f2fde38b11610066578063f2fde38b14610529578063f3dfc2a91461055c578063f3fef3a314610597578063fa00763a146105d0576100f5565b80636fadcf72146103c457806379ba5097146104515780638da5cb5b14610459578063a4c0ed3614610461576100f5565b80634b602282116100d35780634b6022821461028a57806350188301146102a45780636ae0bc76146102ac5780636ee4d55314610373576100f5565b8063165d35e1146100fa578063404299461461012b5780634ab0d19014610203575b600080fd5b610102610603565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b610201600480360361010081101561014257600080fd5b73ffffffffffffffffffffffffffffffffffffffff8235811692602081013592604082013592606083013516917fffffffff000000000000000000000000000000000000000000000000000000006080820135169160a08201359160c081013591810190610100810160e08201356401000000008111156101c257600080fd5b8201836020820111156101d457600080fd5b803590602001918460018302840111640100000000831117156101f657600080fd5b509092509050610627565b005b610276600480360360c081101561021957600080fd5b5080359060208101359073ffffffffffffffffffffffffffffffffffffffff604082013516907fffffffff000000000000000000000000000000000000000000000000000000006060820135169060808101359060a001356108a9565b604080519115158252519081900360200190f35b610292610ba8565b60408051918252519081900360200190f35b610292610bae565b610276600480360360c08110156102c257600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516917fffffffff00000000000000000000000000000000000000000000000000000000606082013516916080820135919081019060c0810160a082013564010000000081111561033457600080fd5b82018360208201111561034657600080fd5b8035906020019184600183028401116401000000008311171561036857600080fd5b509092509050610bc4565b6102016004803603608081101561038957600080fd5b508035906020810135907fffffffff000000000000000000000000000000000000000000000000000000006040820135169060600135610f54565b610201600480360360408110156103da57600080fd5b73ffffffffffffffffffffffffffffffffffffffff823516919081019060408101602082013564010000000081111561041257600080fd5b82018360208201111561042457600080fd5b8035906020019184600183028401116401000000008311171561044657600080fd5b50909250905061117f565b610201611372565b610102611474565b6102016004803603606081101561047757600080fd5b73ffffffffffffffffffffffffffffffffffffffff823516916020810135918101906060810160408201356401000000008111156104b457600080fd5b8201836020820111156104c657600080fd5b803590602001918460018302840111640100000000831117156104e857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611490945050505050565b6102016004803603602081101561053f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166117ac565b6102016004803603604081101561057257600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135151561192d565b610201600480360360408110156105ad57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611a09565b610276600480360360208110156105e657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611bd0565b7f000000000000000000000000000000000000000000000000000000000000000090565b61062f610603565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106c857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4d75737420757365204c494e4b20746f6b656e00000000000000000000000000604482015290519081900360640190fd5b857f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141561078457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f742063616c6c6261636b20746f204c494e4b000000000000000000604482015290519081900360640190fd5b6000806107958c8c8b8b8b8b611bfb565b91509150897fd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c658d848e8d8d878d8d8d604051808a73ffffffffffffffffffffffffffffffffffffffff1681526020018981526020018881526020018773ffffffffffffffffffffffffffffffffffffffff168152602001867bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001858152602001848152602001806020018281038252848482818152602001925080828437600083820152604051601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018290039c50909a5050505050505050505050a2505050505050505050505050565b3360009081526003602052604081205460ff16610911576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806121b7602a913960400191505060405180910390fd5b600087815260026020526040902054879060081b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166109b257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f4d757374206861766520612076616c6964207265717565737449640000000000604482015290519081900360640190fd5b6109c188888888886001611dd4565b60405188907f9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a6490600090a262061a805a1015610a5e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d7573742070726f7669646520636f6e73756d657220656e6f75676820676173604482015290519081900360640190fd5b60408051602481018a9052604480820186905282518083039091018152606490910182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000089161781529151815160009373ffffffffffffffffffffffffffffffffffffffff8b169392918291908083835b60208310610b3157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610af4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610b93576040519150601f19603f3d011682016040523d82523d6000602084013e610b98565b606091505b50909a9950505050505050505050565b61012c81565b600454600090610bbf906001611f62565b905090565b3360009081526003602052604081205460ff16610c2c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806121b7602a913960400191505060405180910390fd5b600088815260026020526040902054889060081b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016610ccd57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f4d757374206861766520612076616c6964207265717565737449640000000000604482015290519081900360640190fd5b8884848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050506020810151828114610d7757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f466972737420776f7264206d7573742062652072657175657374496400000000604482015290519081900360640190fd5b610d868c8c8c8c8c6002611dd4565b6040518c907f9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a6490600090a262061a805a1015610e2357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4d7573742070726f7669646520636f6e73756d657220656e6f75676820676173604482015290519081900360640190fd5b60008a73ffffffffffffffffffffffffffffffffffffffff168a898960405160200180847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526004018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610ed957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610e9c565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610f3b576040519150601f19603f3d011682016040523d82523d6000602084013e610f40565b606091505b50909e9d5050505050505050505050505050565b6000610f6284338585611fd9565b60008681526002602052604090205490915060081b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009081169082161461100a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f506172616d7320646f206e6f74206d6174636820726571756573742049440000604482015290519081900360640190fd5b4282111561107957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f52657175657374206973206e6f74206578706972656400000000000000000000604482015290519081900360640190fd5b6000858152600260205260408082208290555186917fa7842b9ec549398102c0d91b1b9919b2f20558aefdadf57528a95c6cd3292e9391a2604080517fa9059cbb00000000000000000000000000000000000000000000000000000000815233600482015260248101869052905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169163a9059cbb9160448083019260209291908290030181600087803b15801561114657600080fd5b505af115801561115a573d6000803e3d6000fd5b505050506040513d602081101561117057600080fd5b505161117857fe5b5050505050565b3360009081526003602052604090205460ff166111e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806121b7602a913960400191505060405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561128c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001806121506032913960400191505060405180910390fd5b60008373ffffffffffffffffffffffffffffffffffffffff168383604051808383808284376040519201945060009350909150508083038183865af19150503d80600081146112f7576040519150601f19603f3d011682016040523d82523d6000602084013e6112fc565b606091505b505090508061136c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f466f727761726465642063616c6c206661696c65642e00000000000000000000604482015290519081900360640190fd5b50505050565b60015473ffffffffffffffffffffffffffffffffffffffff1633146113f857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e657200000000000000000000604482015290519081900360640190fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b611498610603565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461153157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4d75737420757365204c494e4b20746f6b656e00000000000000000000000000604482015290519081900360640190fd5b80518190604411156115a457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f496e76616c69642072657175657374206c656e67746800000000000000000000604482015290519081900360640190fd5b602082015182907fffffffff0000000000000000000000000000000000000000000000000000000081167f40429946000000000000000000000000000000000000000000000000000000001461165b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f4d757374207573652077686974656c69737465642066756e6374696f6e730000604482015290519081900360640190fd5b85602485015284604485015260003073ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b602083106116ce57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611691565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855af49150503d806000811461172e576040519150601f19603f3d011682016040523d82523d6000602084013e611733565b606091505b50509050806117a357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e61626c6520746f2063726561746520726571756573740000000000000000604482015290519081900360640190fd5b50505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461183257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81163314156118b757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015290519081900360640190fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60005473ffffffffffffffffffffffffffffffffffffffff1633146119b357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff91909116600090815260036020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055565b60005473ffffffffffffffffffffffffffffffffffffffff163314611a8f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015290519081900360640190fd5b80611a9b81600161205e565b6004541015611af5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260358152602001806121826035913960400191505060405180910390fd5b600454611b029083611f62565b6004819055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015611b9957600080fd5b505af1158015611bad573d6000803e3d6000fd5b505050506040513d6020811015611bc357600080fd5b5051611bcb57fe5b505050565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205460ff1690565b60408051606088901b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660208083019190915260348083018690528351808403909101815260549092018352815191810191909120600081815260029092529181205460081b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001615611cf057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4d75737420757365206120756e69717565204944000000000000000000000000604482015290519081900360640190fd5b611cfc4261012c61205e565b90506000611d0c88888885611fd9565b905060405180604001604052808260ff19168152602001611d2c866120d9565b60ff90811690915260008581526002602090815260409091208351815494909201519092167f01000000000000000000000000000000000000000000000000000000000000000260089190911c7fff00000000000000000000000000000000000000000000000000000000000000909316929092177effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1691909117905550965096945050505050565b6000611de286868686611fd9565b60008881526002602052604090205490915060081b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0090811690821614611e8a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f506172616d7320646f206e6f74206d6174636820726571756573742049440000604482015290519081900360640190fd5b611e93826120d9565b60008881526002602052604090205460ff9182167f01000000000000000000000000000000000000000000000000000000000000009091049091161115611f3b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f446174612076657273696f6e73206d757374206d617463680000000000000000604482015290519081900360640190fd5b600454611f48908761205e565b600455505050600093845250506002602052506040812055565b600082821115611fd357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b6040805160208082019690965260609490941b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016848201527fffffffff000000000000000000000000000000000000000000000000000000009290921660548401526058808401919091528151808403909101815260789092019052805191012090565b6000828201838110156120d257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b6000610100821061214b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e756d62657220746f6f2062696720746f206361737400000000000000000000604482015290519081900360640190fd5b509056fe43616e6e6f74207573652023666f727761726420746f2073656e64206d6573736167657320746f204c696e6b20746f6b656e416d6f756e74207265717565737465642069732067726561746572207468616e20776974686472617761626c652062616c616e63654e6f7420616e20617574686f72697a6564206e6f646520746f2066756c66696c6c207265717565737473a264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033" + +// DeployOperator deploys a new Ethereum contract, binding an instance of Operator to it. +func DeployOperator(auth *bind.TransactOpts, backend bind.ContractBackend, link common.Address, owner common.Address) (common.Address, *types.Transaction, *Operator, error) { + parsed, err := abi.JSON(strings.NewReader(OperatorABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(OperatorBin), backend, link, owner) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Operator{OperatorCaller: OperatorCaller{contract: contract}, OperatorTransactor: OperatorTransactor{contract: contract}, OperatorFilterer: OperatorFilterer{contract: contract}}, nil +} + +// Operator is an auto generated Go binding around an Ethereum contract. +type Operator struct { + OperatorCaller // Read-only binding to the contract + OperatorTransactor // Write-only binding to the contract + OperatorFilterer // Log filterer for contract events +} + +// OperatorCaller is an auto generated read-only Go binding around an Ethereum contract. +type OperatorCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OperatorTransactor is an auto generated write-only Go binding around an Ethereum contract. +type OperatorTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OperatorFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type OperatorFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OperatorSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type OperatorSession struct { + Contract *Operator // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OperatorCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type OperatorCallerSession struct { + Contract *OperatorCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// OperatorTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type OperatorTransactorSession struct { + Contract *OperatorTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OperatorRaw is an auto generated low-level Go binding around an Ethereum contract. +type OperatorRaw struct { + Contract *Operator // Generic contract binding to access the raw methods on +} + +// OperatorCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type OperatorCallerRaw struct { + Contract *OperatorCaller // Generic read-only contract binding to access the raw methods on +} + +// OperatorTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type OperatorTransactorRaw struct { + Contract *OperatorTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewOperator creates a new instance of Operator, bound to a specific deployed contract. +func NewOperator(address common.Address, backend bind.ContractBackend) (*Operator, error) { + contract, err := bindOperator(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Operator{OperatorCaller: OperatorCaller{contract: contract}, OperatorTransactor: OperatorTransactor{contract: contract}, OperatorFilterer: OperatorFilterer{contract: contract}}, nil +} + +// NewOperatorCaller creates a new read-only instance of Operator, bound to a specific deployed contract. +func NewOperatorCaller(address common.Address, caller bind.ContractCaller) (*OperatorCaller, error) { + contract, err := bindOperator(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &OperatorCaller{contract: contract}, nil +} + +// NewOperatorTransactor creates a new write-only instance of Operator, bound to a specific deployed contract. +func NewOperatorTransactor(address common.Address, transactor bind.ContractTransactor) (*OperatorTransactor, error) { + contract, err := bindOperator(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &OperatorTransactor{contract: contract}, nil +} + +// NewOperatorFilterer creates a new log filterer instance of Operator, bound to a specific deployed contract. +func NewOperatorFilterer(address common.Address, filterer bind.ContractFilterer) (*OperatorFilterer, error) { + contract, err := bindOperator(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &OperatorFilterer{contract: contract}, nil +} + +// bindOperator binds a generic wrapper to an already deployed contract. +func bindOperator(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(OperatorABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Operator *OperatorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Operator.Contract.OperatorCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Operator *OperatorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Operator.Contract.OperatorTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Operator *OperatorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Operator.Contract.OperatorTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Operator *OperatorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Operator.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Operator *OperatorTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Operator.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Operator *OperatorTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Operator.Contract.contract.Transact(opts, method, params...) +} + +// EXPIRYTIME is a free data retrieval call binding the contract method 0x4b602282. +// +// Solidity: function EXPIRY_TIME() view returns(uint256) +func (_Operator *OperatorCaller) EXPIRYTIME(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Operator.contract.Call(opts, &out, "EXPIRY_TIME") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// EXPIRYTIME is a free data retrieval call binding the contract method 0x4b602282. +// +// Solidity: function EXPIRY_TIME() view returns(uint256) +func (_Operator *OperatorSession) EXPIRYTIME() (*big.Int, error) { + return _Operator.Contract.EXPIRYTIME(&_Operator.CallOpts) +} + +// EXPIRYTIME is a free data retrieval call binding the contract method 0x4b602282. +// +// Solidity: function EXPIRY_TIME() view returns(uint256) +func (_Operator *OperatorCallerSession) EXPIRYTIME() (*big.Int, error) { + return _Operator.Contract.EXPIRYTIME(&_Operator.CallOpts) +} + +// GetChainlinkToken is a free data retrieval call binding the contract method 0x165d35e1. +// +// Solidity: function getChainlinkToken() view returns(address) +func (_Operator *OperatorCaller) GetChainlinkToken(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Operator.contract.Call(opts, &out, "getChainlinkToken") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetChainlinkToken is a free data retrieval call binding the contract method 0x165d35e1. +// +// Solidity: function getChainlinkToken() view returns(address) +func (_Operator *OperatorSession) GetChainlinkToken() (common.Address, error) { + return _Operator.Contract.GetChainlinkToken(&_Operator.CallOpts) +} + +// GetChainlinkToken is a free data retrieval call binding the contract method 0x165d35e1. +// +// Solidity: function getChainlinkToken() view returns(address) +func (_Operator *OperatorCallerSession) GetChainlinkToken() (common.Address, error) { + return _Operator.Contract.GetChainlinkToken(&_Operator.CallOpts) +} + +// IsAuthorizedSender is a free data retrieval call binding the contract method 0xfa00763a. +// +// Solidity: function isAuthorizedSender(address node) view returns(bool) +func (_Operator *OperatorCaller) IsAuthorizedSender(opts *bind.CallOpts, node common.Address) (bool, error) { + var out []interface{} + err := _Operator.contract.Call(opts, &out, "isAuthorizedSender", node) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsAuthorizedSender is a free data retrieval call binding the contract method 0xfa00763a. +// +// Solidity: function isAuthorizedSender(address node) view returns(bool) +func (_Operator *OperatorSession) IsAuthorizedSender(node common.Address) (bool, error) { + return _Operator.Contract.IsAuthorizedSender(&_Operator.CallOpts, node) +} + +// IsAuthorizedSender is a free data retrieval call binding the contract method 0xfa00763a. +// +// Solidity: function isAuthorizedSender(address node) view returns(bool) +func (_Operator *OperatorCallerSession) IsAuthorizedSender(node common.Address) (bool, error) { + return _Operator.Contract.IsAuthorizedSender(&_Operator.CallOpts, node) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_Operator *OperatorCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Operator.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_Operator *OperatorSession) Owner() (common.Address, error) { + return _Operator.Contract.Owner(&_Operator.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_Operator *OperatorCallerSession) Owner() (common.Address, error) { + return _Operator.Contract.Owner(&_Operator.CallOpts) +} + +// Withdrawable is a free data retrieval call binding the contract method 0x50188301. +// +// Solidity: function withdrawable() view returns(uint256) +func (_Operator *OperatorCaller) Withdrawable(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Operator.contract.Call(opts, &out, "withdrawable") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Withdrawable is a free data retrieval call binding the contract method 0x50188301. +// +// Solidity: function withdrawable() view returns(uint256) +func (_Operator *OperatorSession) Withdrawable() (*big.Int, error) { + return _Operator.Contract.Withdrawable(&_Operator.CallOpts) +} + +// Withdrawable is a free data retrieval call binding the contract method 0x50188301. +// +// Solidity: function withdrawable() view returns(uint256) +func (_Operator *OperatorCallerSession) Withdrawable() (*big.Int, error) { + return _Operator.Contract.Withdrawable(&_Operator.CallOpts) +} + +// AcceptOwnership is a paid mutator transaction binding the contract method 0x79ba5097. +// +// Solidity: function acceptOwnership() returns() +func (_Operator *OperatorTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "acceptOwnership") +} + +// AcceptOwnership is a paid mutator transaction binding the contract method 0x79ba5097. +// +// Solidity: function acceptOwnership() returns() +func (_Operator *OperatorSession) AcceptOwnership() (*types.Transaction, error) { + return _Operator.Contract.AcceptOwnership(&_Operator.TransactOpts) +} + +// AcceptOwnership is a paid mutator transaction binding the contract method 0x79ba5097. +// +// Solidity: function acceptOwnership() returns() +func (_Operator *OperatorTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _Operator.Contract.AcceptOwnership(&_Operator.TransactOpts) +} + +// CancelOracleRequest is a paid mutator transaction binding the contract method 0x6ee4d553. +// +// Solidity: function cancelOracleRequest(bytes32 requestId, uint256 payment, bytes4 callbackFunc, uint256 expiration) returns() +func (_Operator *OperatorTransactor) CancelOracleRequest(opts *bind.TransactOpts, requestId [32]byte, payment *big.Int, callbackFunc [4]byte, expiration *big.Int) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "cancelOracleRequest", requestId, payment, callbackFunc, expiration) +} + +// CancelOracleRequest is a paid mutator transaction binding the contract method 0x6ee4d553. +// +// Solidity: function cancelOracleRequest(bytes32 requestId, uint256 payment, bytes4 callbackFunc, uint256 expiration) returns() +func (_Operator *OperatorSession) CancelOracleRequest(requestId [32]byte, payment *big.Int, callbackFunc [4]byte, expiration *big.Int) (*types.Transaction, error) { + return _Operator.Contract.CancelOracleRequest(&_Operator.TransactOpts, requestId, payment, callbackFunc, expiration) +} + +// CancelOracleRequest is a paid mutator transaction binding the contract method 0x6ee4d553. +// +// Solidity: function cancelOracleRequest(bytes32 requestId, uint256 payment, bytes4 callbackFunc, uint256 expiration) returns() +func (_Operator *OperatorTransactorSession) CancelOracleRequest(requestId [32]byte, payment *big.Int, callbackFunc [4]byte, expiration *big.Int) (*types.Transaction, error) { + return _Operator.Contract.CancelOracleRequest(&_Operator.TransactOpts, requestId, payment, callbackFunc, expiration) +} + +// Forward is a paid mutator transaction binding the contract method 0x6fadcf72. +// +// Solidity: function forward(address to, bytes data) returns() +func (_Operator *OperatorTransactor) Forward(opts *bind.TransactOpts, to common.Address, data []byte) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "forward", to, data) +} + +// Forward is a paid mutator transaction binding the contract method 0x6fadcf72. +// +// Solidity: function forward(address to, bytes data) returns() +func (_Operator *OperatorSession) Forward(to common.Address, data []byte) (*types.Transaction, error) { + return _Operator.Contract.Forward(&_Operator.TransactOpts, to, data) +} + +// Forward is a paid mutator transaction binding the contract method 0x6fadcf72. +// +// Solidity: function forward(address to, bytes data) returns() +func (_Operator *OperatorTransactorSession) Forward(to common.Address, data []byte) (*types.Transaction, error) { + return _Operator.Contract.Forward(&_Operator.TransactOpts, to, data) +} + +// FulfillOracleRequest is a paid mutator transaction binding the contract method 0x4ab0d190. +// +// Solidity: function fulfillOracleRequest(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes32 data) returns(bool) +func (_Operator *OperatorTransactor) FulfillOracleRequest(opts *bind.TransactOpts, requestId [32]byte, payment *big.Int, callbackAddress common.Address, callbackFunctionId [4]byte, expiration *big.Int, data [32]byte) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "fulfillOracleRequest", requestId, payment, callbackAddress, callbackFunctionId, expiration, data) +} + +// FulfillOracleRequest is a paid mutator transaction binding the contract method 0x4ab0d190. +// +// Solidity: function fulfillOracleRequest(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes32 data) returns(bool) +func (_Operator *OperatorSession) FulfillOracleRequest(requestId [32]byte, payment *big.Int, callbackAddress common.Address, callbackFunctionId [4]byte, expiration *big.Int, data [32]byte) (*types.Transaction, error) { + return _Operator.Contract.FulfillOracleRequest(&_Operator.TransactOpts, requestId, payment, callbackAddress, callbackFunctionId, expiration, data) +} + +// FulfillOracleRequest is a paid mutator transaction binding the contract method 0x4ab0d190. +// +// Solidity: function fulfillOracleRequest(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes32 data) returns(bool) +func (_Operator *OperatorTransactorSession) FulfillOracleRequest(requestId [32]byte, payment *big.Int, callbackAddress common.Address, callbackFunctionId [4]byte, expiration *big.Int, data [32]byte) (*types.Transaction, error) { + return _Operator.Contract.FulfillOracleRequest(&_Operator.TransactOpts, requestId, payment, callbackAddress, callbackFunctionId, expiration, data) +} + +// FulfillOracleRequest2 is a paid mutator transaction binding the contract method 0x6ae0bc76. +// +// Solidity: function fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes data) returns(bool) +func (_Operator *OperatorTransactor) FulfillOracleRequest2(opts *bind.TransactOpts, requestId [32]byte, payment *big.Int, callbackAddress common.Address, callbackFunctionId [4]byte, expiration *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "fulfillOracleRequest2", requestId, payment, callbackAddress, callbackFunctionId, expiration, data) +} + +// FulfillOracleRequest2 is a paid mutator transaction binding the contract method 0x6ae0bc76. +// +// Solidity: function fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes data) returns(bool) +func (_Operator *OperatorSession) FulfillOracleRequest2(requestId [32]byte, payment *big.Int, callbackAddress common.Address, callbackFunctionId [4]byte, expiration *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.Contract.FulfillOracleRequest2(&_Operator.TransactOpts, requestId, payment, callbackAddress, callbackFunctionId, expiration, data) +} + +// FulfillOracleRequest2 is a paid mutator transaction binding the contract method 0x6ae0bc76. +// +// Solidity: function fulfillOracleRequest2(bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes data) returns(bool) +func (_Operator *OperatorTransactorSession) FulfillOracleRequest2(requestId [32]byte, payment *big.Int, callbackAddress common.Address, callbackFunctionId [4]byte, expiration *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.Contract.FulfillOracleRequest2(&_Operator.TransactOpts, requestId, payment, callbackAddress, callbackFunctionId, expiration, data) +} + +// OnTokenTransfer is a paid mutator transaction binding the contract method 0xa4c0ed36. +// +// Solidity: function onTokenTransfer(address sender, uint256 amount, bytes data) returns() +func (_Operator *OperatorTransactor) OnTokenTransfer(opts *bind.TransactOpts, sender common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "onTokenTransfer", sender, amount, data) +} + +// OnTokenTransfer is a paid mutator transaction binding the contract method 0xa4c0ed36. +// +// Solidity: function onTokenTransfer(address sender, uint256 amount, bytes data) returns() +func (_Operator *OperatorSession) OnTokenTransfer(sender common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.Contract.OnTokenTransfer(&_Operator.TransactOpts, sender, amount, data) +} + +// OnTokenTransfer is a paid mutator transaction binding the contract method 0xa4c0ed36. +// +// Solidity: function onTokenTransfer(address sender, uint256 amount, bytes data) returns() +func (_Operator *OperatorTransactorSession) OnTokenTransfer(sender common.Address, amount *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.Contract.OnTokenTransfer(&_Operator.TransactOpts, sender, amount, data) +} + +// OracleRequest is a paid mutator transaction binding the contract method 0x40429946. +// +// Solidity: function oracleRequest(address sender, uint256 payment, bytes32 specId, address callbackAddress, bytes4 callbackFunctionId, uint256 nonce, uint256 dataVersion, bytes data) returns() +func (_Operator *OperatorTransactor) OracleRequest(opts *bind.TransactOpts, sender common.Address, payment *big.Int, specId [32]byte, callbackAddress common.Address, callbackFunctionId [4]byte, nonce *big.Int, dataVersion *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "oracleRequest", sender, payment, specId, callbackAddress, callbackFunctionId, nonce, dataVersion, data) +} + +// OracleRequest is a paid mutator transaction binding the contract method 0x40429946. +// +// Solidity: function oracleRequest(address sender, uint256 payment, bytes32 specId, address callbackAddress, bytes4 callbackFunctionId, uint256 nonce, uint256 dataVersion, bytes data) returns() +func (_Operator *OperatorSession) OracleRequest(sender common.Address, payment *big.Int, specId [32]byte, callbackAddress common.Address, callbackFunctionId [4]byte, nonce *big.Int, dataVersion *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.Contract.OracleRequest(&_Operator.TransactOpts, sender, payment, specId, callbackAddress, callbackFunctionId, nonce, dataVersion, data) +} + +// OracleRequest is a paid mutator transaction binding the contract method 0x40429946. +// +// Solidity: function oracleRequest(address sender, uint256 payment, bytes32 specId, address callbackAddress, bytes4 callbackFunctionId, uint256 nonce, uint256 dataVersion, bytes data) returns() +func (_Operator *OperatorTransactorSession) OracleRequest(sender common.Address, payment *big.Int, specId [32]byte, callbackAddress common.Address, callbackFunctionId [4]byte, nonce *big.Int, dataVersion *big.Int, data []byte) (*types.Transaction, error) { + return _Operator.Contract.OracleRequest(&_Operator.TransactOpts, sender, payment, specId, callbackAddress, callbackFunctionId, nonce, dataVersion, data) +} + +// SetAuthorizedSender is a paid mutator transaction binding the contract method 0xf3dfc2a9. +// +// Solidity: function setAuthorizedSender(address node, bool allowed) returns() +func (_Operator *OperatorTransactor) SetAuthorizedSender(opts *bind.TransactOpts, node common.Address, allowed bool) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "setAuthorizedSender", node, allowed) +} + +// SetAuthorizedSender is a paid mutator transaction binding the contract method 0xf3dfc2a9. +// +// Solidity: function setAuthorizedSender(address node, bool allowed) returns() +func (_Operator *OperatorSession) SetAuthorizedSender(node common.Address, allowed bool) (*types.Transaction, error) { + return _Operator.Contract.SetAuthorizedSender(&_Operator.TransactOpts, node, allowed) +} + +// SetAuthorizedSender is a paid mutator transaction binding the contract method 0xf3dfc2a9. +// +// Solidity: function setAuthorizedSender(address node, bool allowed) returns() +func (_Operator *OperatorTransactorSession) SetAuthorizedSender(node common.Address, allowed bool) (*types.Transaction, error) { + return _Operator.Contract.SetAuthorizedSender(&_Operator.TransactOpts, node, allowed) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address to) returns() +func (_Operator *OperatorTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "transferOwnership", to) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address to) returns() +func (_Operator *OperatorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _Operator.Contract.TransferOwnership(&_Operator.TransactOpts, to) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address to) returns() +func (_Operator *OperatorTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _Operator.Contract.TransferOwnership(&_Operator.TransactOpts, to) +} + +// Withdraw is a paid mutator transaction binding the contract method 0xf3fef3a3. +// +// Solidity: function withdraw(address recipient, uint256 amount) returns() +func (_Operator *OperatorTransactor) Withdraw(opts *bind.TransactOpts, recipient common.Address, amount *big.Int) (*types.Transaction, error) { + return _Operator.contract.Transact(opts, "withdraw", recipient, amount) +} + +// Withdraw is a paid mutator transaction binding the contract method 0xf3fef3a3. +// +// Solidity: function withdraw(address recipient, uint256 amount) returns() +func (_Operator *OperatorSession) Withdraw(recipient common.Address, amount *big.Int) (*types.Transaction, error) { + return _Operator.Contract.Withdraw(&_Operator.TransactOpts, recipient, amount) +} + +// Withdraw is a paid mutator transaction binding the contract method 0xf3fef3a3. +// +// Solidity: function withdraw(address recipient, uint256 amount) returns() +func (_Operator *OperatorTransactorSession) Withdraw(recipient common.Address, amount *big.Int) (*types.Transaction, error) { + return _Operator.Contract.Withdraw(&_Operator.TransactOpts, recipient, amount) +} + +// OperatorCancelOracleRequestIterator is returned from FilterCancelOracleRequest and is used to iterate over the raw logs and unpacked data for CancelOracleRequest events raised by the Operator contract. +type OperatorCancelOracleRequestIterator struct { + Event *OperatorCancelOracleRequest // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OperatorCancelOracleRequestIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OperatorCancelOracleRequest) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OperatorCancelOracleRequest) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OperatorCancelOracleRequestIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OperatorCancelOracleRequestIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OperatorCancelOracleRequest represents a CancelOracleRequest event raised by the Operator contract. +type OperatorCancelOracleRequest struct { + RequestId [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterCancelOracleRequest is a free log retrieval operation binding the contract event 0xa7842b9ec549398102c0d91b1b9919b2f20558aefdadf57528a95c6cd3292e93. +// +// Solidity: event CancelOracleRequest(bytes32 indexed requestId) +func (_Operator *OperatorFilterer) FilterCancelOracleRequest(opts *bind.FilterOpts, requestId [][32]byte) (*OperatorCancelOracleRequestIterator, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _Operator.contract.FilterLogs(opts, "CancelOracleRequest", requestIdRule) + if err != nil { + return nil, err + } + return &OperatorCancelOracleRequestIterator{contract: _Operator.contract, event: "CancelOracleRequest", logs: logs, sub: sub}, nil +} + +// WatchCancelOracleRequest is a free log subscription operation binding the contract event 0xa7842b9ec549398102c0d91b1b9919b2f20558aefdadf57528a95c6cd3292e93. +// +// Solidity: event CancelOracleRequest(bytes32 indexed requestId) +func (_Operator *OperatorFilterer) WatchCancelOracleRequest(opts *bind.WatchOpts, sink chan<- *OperatorCancelOracleRequest, requestId [][32]byte) (event.Subscription, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _Operator.contract.WatchLogs(opts, "CancelOracleRequest", requestIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OperatorCancelOracleRequest) + if err := _Operator.contract.UnpackLog(event, "CancelOracleRequest", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseCancelOracleRequest is a log parse operation binding the contract event 0xa7842b9ec549398102c0d91b1b9919b2f20558aefdadf57528a95c6cd3292e93. +// +// Solidity: event CancelOracleRequest(bytes32 indexed requestId) +func (_Operator *OperatorFilterer) ParseCancelOracleRequest(log types.Log) (*OperatorCancelOracleRequest, error) { + event := new(OperatorCancelOracleRequest) + if err := _Operator.contract.UnpackLog(event, "CancelOracleRequest", log); err != nil { + return nil, err + } + return event, nil +} + +// OperatorOracleRequestIterator is returned from FilterOracleRequest and is used to iterate over the raw logs and unpacked data for OracleRequest events raised by the Operator contract. +type OperatorOracleRequestIterator struct { + Event *OperatorOracleRequest // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OperatorOracleRequestIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OperatorOracleRequest) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OperatorOracleRequest) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OperatorOracleRequestIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OperatorOracleRequestIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OperatorOracleRequest represents a OracleRequest event raised by the Operator contract. +type OperatorOracleRequest struct { + SpecId [32]byte + Requester common.Address + RequestId [32]byte + Payment *big.Int + CallbackAddr common.Address + CallbackFunctionId [4]byte + CancelExpiration *big.Int + DataVersion *big.Int + Data []byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOracleRequest is a free log retrieval operation binding the contract event 0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65. +// +// Solidity: event OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data) +func (_Operator *OperatorFilterer) FilterOracleRequest(opts *bind.FilterOpts, specId [][32]byte) (*OperatorOracleRequestIterator, error) { + + var specIdRule []interface{} + for _, specIdItem := range specId { + specIdRule = append(specIdRule, specIdItem) + } + + logs, sub, err := _Operator.contract.FilterLogs(opts, "OracleRequest", specIdRule) + if err != nil { + return nil, err + } + return &OperatorOracleRequestIterator{contract: _Operator.contract, event: "OracleRequest", logs: logs, sub: sub}, nil +} + +// WatchOracleRequest is a free log subscription operation binding the contract event 0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65. +// +// Solidity: event OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data) +func (_Operator *OperatorFilterer) WatchOracleRequest(opts *bind.WatchOpts, sink chan<- *OperatorOracleRequest, specId [][32]byte) (event.Subscription, error) { + + var specIdRule []interface{} + for _, specIdItem := range specId { + specIdRule = append(specIdRule, specIdItem) + } + + logs, sub, err := _Operator.contract.WatchLogs(opts, "OracleRequest", specIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OperatorOracleRequest) + if err := _Operator.contract.UnpackLog(event, "OracleRequest", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOracleRequest is a log parse operation binding the contract event 0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65. +// +// Solidity: event OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data) +func (_Operator *OperatorFilterer) ParseOracleRequest(log types.Log) (*OperatorOracleRequest, error) { + event := new(OperatorOracleRequest) + if err := _Operator.contract.UnpackLog(event, "OracleRequest", log); err != nil { + return nil, err + } + return event, nil +} + +// OperatorOracleResponseIterator is returned from FilterOracleResponse and is used to iterate over the raw logs and unpacked data for OracleResponse events raised by the Operator contract. +type OperatorOracleResponseIterator struct { + Event *OperatorOracleResponse // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OperatorOracleResponseIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OperatorOracleResponse) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OperatorOracleResponse) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OperatorOracleResponseIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OperatorOracleResponseIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OperatorOracleResponse represents a OracleResponse event raised by the Operator contract. +type OperatorOracleResponse struct { + RequestId [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOracleResponse is a free log retrieval operation binding the contract event 0x9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a64. +// +// Solidity: event OracleResponse(bytes32 indexed requestId) +func (_Operator *OperatorFilterer) FilterOracleResponse(opts *bind.FilterOpts, requestId [][32]byte) (*OperatorOracleResponseIterator, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _Operator.contract.FilterLogs(opts, "OracleResponse", requestIdRule) + if err != nil { + return nil, err + } + return &OperatorOracleResponseIterator{contract: _Operator.contract, event: "OracleResponse", logs: logs, sub: sub}, nil +} + +// WatchOracleResponse is a free log subscription operation binding the contract event 0x9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a64. +// +// Solidity: event OracleResponse(bytes32 indexed requestId) +func (_Operator *OperatorFilterer) WatchOracleResponse(opts *bind.WatchOpts, sink chan<- *OperatorOracleResponse, requestId [][32]byte) (event.Subscription, error) { + + var requestIdRule []interface{} + for _, requestIdItem := range requestId { + requestIdRule = append(requestIdRule, requestIdItem) + } + + logs, sub, err := _Operator.contract.WatchLogs(opts, "OracleResponse", requestIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OperatorOracleResponse) + if err := _Operator.contract.UnpackLog(event, "OracleResponse", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOracleResponse is a log parse operation binding the contract event 0x9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a64. +// +// Solidity: event OracleResponse(bytes32 indexed requestId) +func (_Operator *OperatorFilterer) ParseOracleResponse(log types.Log) (*OperatorOracleResponse, error) { + event := new(OperatorOracleResponse) + if err := _Operator.contract.UnpackLog(event, "OracleResponse", log); err != nil { + return nil, err + } + return event, nil +} + +// OperatorOwnershipTransferRequestedIterator is returned from FilterOwnershipTransferRequested and is used to iterate over the raw logs and unpacked data for OwnershipTransferRequested events raised by the Operator contract. +type OperatorOwnershipTransferRequestedIterator struct { + Event *OperatorOwnershipTransferRequested // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OperatorOwnershipTransferRequestedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OperatorOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OperatorOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OperatorOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OperatorOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OperatorOwnershipTransferRequested represents a OwnershipTransferRequested event raised by the Operator contract. +type OperatorOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferRequested is a free log retrieval operation binding the contract event 0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278. +// +// Solidity: event OwnershipTransferRequested(address indexed from, address indexed to) +func (_Operator *OperatorFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*OperatorOwnershipTransferRequestedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Operator.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &OperatorOwnershipTransferRequestedIterator{contract: _Operator.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferRequested is a free log subscription operation binding the contract event 0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278. +// +// Solidity: event OwnershipTransferRequested(address indexed from, address indexed to) +func (_Operator *OperatorFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *OperatorOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Operator.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OperatorOwnershipTransferRequested) + if err := _Operator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferRequested is a log parse operation binding the contract event 0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278. +// +// Solidity: event OwnershipTransferRequested(address indexed from, address indexed to) +func (_Operator *OperatorFilterer) ParseOwnershipTransferRequested(log types.Log) (*OperatorOwnershipTransferRequested, error) { + event := new(OperatorOwnershipTransferRequested) + if err := _Operator.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + return event, nil +} + +// OperatorOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the Operator contract. +type OperatorOwnershipTransferredIterator struct { + Event *OperatorOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OperatorOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OperatorOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OperatorOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OperatorOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OperatorOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OperatorOwnershipTransferred represents a OwnershipTransferred event raised by the Operator contract. +type OperatorOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed from, address indexed to) +func (_Operator *OperatorFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*OperatorOwnershipTransferredIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Operator.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &OperatorOwnershipTransferredIterator{contract: _Operator.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed from, address indexed to) +func (_Operator *OperatorFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *OperatorOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Operator.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OperatorOwnershipTransferred) + if err := _Operator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed from, address indexed to) +func (_Operator *OperatorFilterer) ParseOwnershipTransferred(log types.Log) (*OperatorOwnershipTransferred, error) { + event := new(OperatorOwnershipTransferred) + if err := _Operator.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + return event, nil +} diff --git a/core/internal/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go b/core/internal/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go index 30a2b8d3b2f..68c1f4ed49a 100644 --- a/core/internal/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go +++ b/core/internal/gethwrappers/generated/solidity_vrf_consumer_interface/solidity_vrf_consumer_interface.go @@ -27,10 +27,10 @@ var ( ) // VRFConsumerABI is the input ABI used to generate the binding from. -const VRFConsumerABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"randomnessOutput\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"randomness\",\"type\":\"uint256\"}],\"name\":\"rawFulfillRandomness\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"requestId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_seed\",\"type\":\"uint256\"}],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" +const VRFConsumerABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_vrfCoordinator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_link\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"randomnessOutput\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"randomness\",\"type\":\"uint256\"}],\"name\":\"rawFulfillRandomness\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"requestId\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_keyHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_seed\",\"type\":\"uint256\"}],\"name\":\"testRequestRandomness\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" // VRFConsumerBin is the compiled bytecode used for deploying new contracts. -var VRFConsumerBin = "0x60c060405234801561001057600080fd5b506040516105823803806105828339818101604052604081101561003357600080fd5b5080516020909101516001600160601b0319606092831b811660a052911b1660805260805160601c60a05160601c6104ff61008360003980610131528061021d5250806101e152506104ff6000f3fe608060405234801561001057600080fd5b50600436106100665760003560e01c80638a5f002b116100505780638a5f002b1461008d57806394985ddd146100b65780639e317f12146100db57610066565b80626d6cae1461006b5780632f47fd8614610085575b600080fd5b6100736100f8565b60408051918252519081900360200190f35b6100736100fe565b610073600480360360608110156100a357600080fd5b5080359060208101359060400135610104565b6100d9600480360360408110156100cc57600080fd5b5080359060200135610119565b005b610073600480360360208110156100f157600080fd5b50356101cb565b60025481565b60015481565b60006101118484846101dd565b949350505050565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146101bd57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4f6e6c7920565246436f6f7264696e61746f722063616e2066756c66696c6c00604482015290519081900360640190fd5b6101c782826103c6565b5050565b60006020819052908152604090205481565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16634000aea07f000000000000000000000000000000000000000000000000000000000000000085878660405160200180838152602001828152602001925050506040516020818303038152906040526040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156102e95781810151838201526020016102d1565b50505050905090810190601f1680156103165780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b15801561033757600080fd5b505af115801561034b573d6000803e3d6000fd5b505050506040513d602081101561036157600080fd5b5050600084815260208190526040812054610381908690859030906103ce565b6000868152602081905260409020549091506103a490600163ffffffff61042216565b6000868152602081905260409020556103bd858261049d565b95945050505050565b600155600255565b604080516020808201969096528082019490945273ffffffffffffffffffffffffffffffffffffffff9290921660608401526080808401919091528151808403909101815260a09092019052805191012090565b60008282018381101561049657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b60408051602080820194909452808201929092528051808303820181526060909201905280519101209056fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033" +var VRFConsumerBin = "0x60c060405234801561001057600080fd5b506040516105373803806105378339818101604052604081101561003357600080fd5b5080516020909101516001600160601b0319606092831b811660a052911b1660805260805160601c60a05160601c6104b56100826000398060f952806101d352508061019752506104b56000f3fe608060405234801561001057600080fd5b506004361061004b5760003560e01c80626d6cae146100505780632f47fd861461006a5780638a5f002b1461007257806394985ddd1461009b575b600080fd5b6100586100c0565b60408051918252519081900360200190f35b6100586100c6565b6100586004803603606081101561008857600080fd5b50803590602081013590604001356100cc565b6100be600480360360408110156100b157600080fd5b50803590602001356100e1565b005b60025481565b60015481565b60006100d9848484610193565b949350505050565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461018557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4f6e6c7920565246436f6f7264696e61746f722063616e2066756c66696c6c00604482015290519081900360640190fd5b61018f828261037c565b5050565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16634000aea07f000000000000000000000000000000000000000000000000000000000000000085878660405160200180838152602001828152602001925050506040516020818303038152906040526040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561029f578181015183820152602001610287565b50505050905090810190601f1680156102cc5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b1580156102ed57600080fd5b505af1158015610301573d6000803e3d6000fd5b505050506040513d602081101561031757600080fd5b505060008481526020819052604081205461033790869085903090610384565b60008681526020819052604090205490915061035a90600163ffffffff6103d816565b6000868152602081905260409020556103738582610453565b95945050505050565b600155600255565b604080516020808201969096528082019490945273ffffffffffffffffffffffffffffffffffffffff9290921660608401526080808401919091528151808403909101815260a09092019052805191012090565b60008282018381101561044c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b60408051602080820194909452808201929092528051808303820181526060909201905280519101209056fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033" // DeployVRFConsumer deploys a new Ethereum contract, binding an instance of VRFConsumer to it. func DeployVRFConsumer(auth *bind.TransactOpts, backend bind.ContractBackend, _vrfCoordinator common.Address, _link common.Address) (common.Address, *types.Transaction, *VRFConsumer, error) { @@ -154,7 +154,7 @@ func bindVRFConsumer(address common.Address, caller bind.ContractCaller, transac // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFConsumer *VRFConsumerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFConsumer *VRFConsumerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFConsumer.Contract.VRFConsumerCaller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_VRFConsumer *VRFConsumerRaw) Transact(opts *bind.TransactOpts, method str // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFConsumer *VRFConsumerCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFConsumer *VRFConsumerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFConsumer.Contract.contract.Call(opts, result, method, params...) } @@ -188,42 +188,21 @@ func (_VRFConsumer *VRFConsumerTransactorRaw) Transact(opts *bind.TransactOpts, return _VRFConsumer.Contract.contract.Transact(opts, method, params...) } -// Nonces is a free data retrieval call binding the contract method 0x9e317f12. -// -// Solidity: function nonces(bytes32 ) view returns(uint256) -func (_VRFConsumer *VRFConsumerCaller) Nonces(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFConsumer.contract.Call(opts, out, "nonces", arg0) - return *ret0, err -} - -// Nonces is a free data retrieval call binding the contract method 0x9e317f12. -// -// Solidity: function nonces(bytes32 ) view returns(uint256) -func (_VRFConsumer *VRFConsumerSession) Nonces(arg0 [32]byte) (*big.Int, error) { - return _VRFConsumer.Contract.Nonces(&_VRFConsumer.CallOpts, arg0) -} - -// Nonces is a free data retrieval call binding the contract method 0x9e317f12. -// -// Solidity: function nonces(bytes32 ) view returns(uint256) -func (_VRFConsumer *VRFConsumerCallerSession) Nonces(arg0 [32]byte) (*big.Int, error) { - return _VRFConsumer.Contract.Nonces(&_VRFConsumer.CallOpts, arg0) -} - // RandomnessOutput is a free data retrieval call binding the contract method 0x2f47fd86. // // Solidity: function randomnessOutput() view returns(uint256) func (_VRFConsumer *VRFConsumerCaller) RandomnessOutput(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFConsumer.contract.Call(opts, out, "randomnessOutput") - return *ret0, err + var out []interface{} + err := _VRFConsumer.contract.Call(opts, &out, "randomnessOutput") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // RandomnessOutput is a free data retrieval call binding the contract method 0x2f47fd86. @@ -244,12 +223,17 @@ func (_VRFConsumer *VRFConsumerCallerSession) RandomnessOutput() (*big.Int, erro // // Solidity: function requestId() view returns(bytes32) func (_VRFConsumer *VRFConsumerCaller) RequestId(opts *bind.CallOpts) ([32]byte, error) { - var ( - ret0 = new([32]byte) - ) - out := ret0 - err := _VRFConsumer.contract.Call(opts, out, "requestId") - return *ret0, err + var out []interface{} + err := _VRFConsumer.contract.Call(opts, &out, "requestId") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + } // RequestId is a free data retrieval call binding the contract method 0x006d6cae. diff --git a/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go b/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go index f3c1ea098e5..1f739adf297 100644 --- a/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go +++ b/core/internal/gethwrappers/generated/solidity_vrf_coordinator_interface/solidity_vrf_coordinator_interface.go @@ -154,7 +154,7 @@ func bindVRFCoordinator(address common.Address, caller bind.ContractCaller, tran // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFCoordinator *VRFCoordinatorRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFCoordinator *VRFCoordinatorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFCoordinator.Contract.VRFCoordinatorCaller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_VRFCoordinator *VRFCoordinatorRaw) Transact(opts *bind.TransactOpts, meth // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFCoordinator *VRFCoordinatorCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFCoordinator *VRFCoordinatorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFCoordinator.Contract.contract.Call(opts, result, method, params...) } @@ -192,12 +192,17 @@ func (_VRFCoordinator *VRFCoordinatorTransactorRaw) Transact(opts *bind.Transact // // Solidity: function PRESEED_OFFSET() view returns(uint256) func (_VRFCoordinator *VRFCoordinatorCaller) PRESEEDOFFSET(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFCoordinator.contract.Call(opts, out, "PRESEED_OFFSET") - return *ret0, err + var out []interface{} + err := _VRFCoordinator.contract.Call(opts, &out, "PRESEED_OFFSET") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // PRESEEDOFFSET is a free data retrieval call binding the contract method 0xb415f4f5. @@ -218,12 +223,17 @@ func (_VRFCoordinator *VRFCoordinatorCallerSession) PRESEEDOFFSET() (*big.Int, e // // Solidity: function PROOF_LENGTH() view returns(uint256) func (_VRFCoordinator *VRFCoordinatorCaller) PROOFLENGTH(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFCoordinator.contract.Call(opts, out, "PROOF_LENGTH") - return *ret0, err + var out []interface{} + err := _VRFCoordinator.contract.Call(opts, &out, "PROOF_LENGTH") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // PROOFLENGTH is a free data retrieval call binding the contract method 0xe911439c. @@ -244,12 +254,17 @@ func (_VRFCoordinator *VRFCoordinatorCallerSession) PROOFLENGTH() (*big.Int, err // // Solidity: function PUBLIC_KEY_OFFSET() view returns(uint256) func (_VRFCoordinator *VRFCoordinatorCaller) PUBLICKEYOFFSET(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFCoordinator.contract.Call(opts, out, "PUBLIC_KEY_OFFSET") - return *ret0, err + var out []interface{} + err := _VRFCoordinator.contract.Call(opts, &out, "PUBLIC_KEY_OFFSET") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // PUBLICKEYOFFSET is a free data retrieval call binding the contract method 0x8aa7927b. @@ -274,14 +289,21 @@ func (_VRFCoordinator *VRFCoordinatorCaller) Callbacks(opts *bind.CallOpts, arg0 RandomnessFee *big.Int SeedAndBlockNum [32]byte }, error) { - ret := new(struct { + var out []interface{} + err := _VRFCoordinator.contract.Call(opts, &out, "callbacks", arg0) + + outstruct := new(struct { CallbackContract common.Address RandomnessFee *big.Int SeedAndBlockNum [32]byte }) - out := ret - err := _VRFCoordinator.contract.Call(opts, out, "callbacks", arg0) - return *ret, err + + outstruct.CallbackContract = out[0].(common.Address) + outstruct.RandomnessFee = out[1].(*big.Int) + outstruct.SeedAndBlockNum = out[2].([32]byte) + + return *outstruct, err + } // Callbacks is a free data retrieval call binding the contract method 0x21f36509. @@ -310,12 +332,17 @@ func (_VRFCoordinator *VRFCoordinatorCallerSession) Callbacks(arg0 [32]byte) (st // // Solidity: function hashOfKey(uint256[2] _publicKey) pure returns(bytes32) func (_VRFCoordinator *VRFCoordinatorCaller) HashOfKey(opts *bind.CallOpts, _publicKey [2]*big.Int) ([32]byte, error) { - var ( - ret0 = new([32]byte) - ) - out := ret0 - err := _VRFCoordinator.contract.Call(opts, out, "hashOfKey", _publicKey) - return *ret0, err + var out []interface{} + err := _VRFCoordinator.contract.Call(opts, &out, "hashOfKey", _publicKey) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + } // HashOfKey is a free data retrieval call binding the contract method 0xcaf70c4a. @@ -340,14 +367,21 @@ func (_VRFCoordinator *VRFCoordinatorCaller) ServiceAgreements(opts *bind.CallOp Fee *big.Int JobID [32]byte }, error) { - ret := new(struct { + var out []interface{} + err := _VRFCoordinator.contract.Call(opts, &out, "serviceAgreements", arg0) + + outstruct := new(struct { VRFOracle common.Address Fee *big.Int JobID [32]byte }) - out := ret - err := _VRFCoordinator.contract.Call(opts, out, "serviceAgreements", arg0) - return *ret, err + + outstruct.VRFOracle = out[0].(common.Address) + outstruct.Fee = out[1].(*big.Int) + outstruct.JobID = out[2].([32]byte) + + return *outstruct, err + } // ServiceAgreements is a free data retrieval call binding the contract method 0x75d35070. @@ -376,12 +410,17 @@ func (_VRFCoordinator *VRFCoordinatorCallerSession) ServiceAgreements(arg0 [32]b // // Solidity: function withdrawableTokens(address ) view returns(uint256) func (_VRFCoordinator *VRFCoordinatorCaller) WithdrawableTokens(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFCoordinator.contract.Call(opts, out, "withdrawableTokens", arg0) - return *ret0, err + var out []interface{} + err := _VRFCoordinator.contract.Call(opts, &out, "withdrawableTokens", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // WithdrawableTokens is a free data retrieval call binding the contract method 0x006f6ad0. diff --git a/core/internal/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go b/core/internal/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go index b1177731a16..5f17392a4d3 100644 --- a/core/internal/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go +++ b/core/internal/gethwrappers/generated/solidity_vrf_request_id/solidity_vrf_request_id.go @@ -154,7 +154,7 @@ func bindVRFRequestIDBaseTestHelper(address common.Address, caller bind.Contract // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFRequestIDBaseTestHelper.Contract.VRFRequestIDBaseTestHelperCaller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperRaw) Transact(opts // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFRequestIDBaseTestHelper.Contract.contract.Call(opts, result, method, params...) } @@ -192,12 +192,17 @@ func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperTransactorRaw) Tran // // Solidity: function makeRequestId_(bytes32 _keyHash, uint256 _vRFInputSeed) pure returns(bytes32) func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperCaller) MakeRequestId(opts *bind.CallOpts, _keyHash [32]byte, _vRFInputSeed *big.Int) ([32]byte, error) { - var ( - ret0 = new([32]byte) - ) - out := ret0 - err := _VRFRequestIDBaseTestHelper.contract.Call(opts, out, "makeRequestId_", _keyHash, _vRFInputSeed) - return *ret0, err + var out []interface{} + err := _VRFRequestIDBaseTestHelper.contract.Call(opts, &out, "makeRequestId_", _keyHash, _vRFInputSeed) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + } // MakeRequestId is a free data retrieval call binding the contract method 0xbda087ae. @@ -218,12 +223,17 @@ func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperCallerSession) Make // // Solidity: function makeVRFInputSeed_(bytes32 _keyHash, uint256 _userSeed, address _requester, uint256 _nonce) pure returns(uint256) func (_VRFRequestIDBaseTestHelper *VRFRequestIDBaseTestHelperCaller) MakeVRFInputSeed(opts *bind.CallOpts, _keyHash [32]byte, _userSeed *big.Int, _requester common.Address, _nonce *big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFRequestIDBaseTestHelper.contract.Call(opts, out, "makeVRFInputSeed_", _keyHash, _userSeed, _requester, _nonce) - return *ret0, err + var out []interface{} + err := _VRFRequestIDBaseTestHelper.contract.Call(opts, &out, "makeVRFInputSeed_", _keyHash, _userSeed, _requester, _nonce) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // MakeVRFInputSeed is a free data retrieval call binding the contract method 0x37ab429a. diff --git a/core/internal/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go b/core/internal/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go index c0aea3e48ca..3fab61b9df5 100644 --- a/core/internal/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go +++ b/core/internal/gethwrappers/generated/solidity_vrf_verifier_wrapper/solidity_vrf_verifier_wrapper.go @@ -154,7 +154,7 @@ func bindVRFTestHelper(address common.Address, caller bind.ContractCaller, trans // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFTestHelper *VRFTestHelperRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFTestHelper *VRFTestHelperRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFTestHelper.Contract.VRFTestHelperCaller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_VRFTestHelper *VRFTestHelperRaw) Transact(opts *bind.TransactOpts, method // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFTestHelper *VRFTestHelperCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFTestHelper *VRFTestHelperCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFTestHelper.Contract.contract.Call(opts, result, method, params...) } @@ -192,12 +192,17 @@ func (_VRFTestHelper *VRFTestHelperTransactorRaw) Transact(opts *bind.TransactOp // // Solidity: function PROOF_LENGTH() view returns(uint256) func (_VRFTestHelper *VRFTestHelperCaller) PROOFLENGTH(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "PROOF_LENGTH") - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "PROOF_LENGTH") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // PROOFLENGTH is a free data retrieval call binding the contract method 0xe911439c. @@ -218,12 +223,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) PROOFLENGTH() (*big.Int, error // // Solidity: function affineECAdd_(uint256[2] p1, uint256[2] p2, uint256 invZ) pure returns(uint256[2]) func (_VRFTestHelper *VRFTestHelperCaller) AffineECAdd(opts *bind.CallOpts, p1 [2]*big.Int, p2 [2]*big.Int, invZ *big.Int) ([2]*big.Int, error) { - var ( - ret0 = new([2]*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "affineECAdd_", p1, p2, invZ) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "affineECAdd_", p1, p2, invZ) + + if err != nil { + return *new([2]*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new([2]*big.Int)).(*[2]*big.Int) + + return out0, err + } // AffineECAdd is a free data retrieval call binding the contract method 0x244f896d. @@ -244,12 +254,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) AffineECAdd(p1 [2]*big.Int, p2 // // Solidity: function bigModExp_(uint256 base, uint256 exponent) view returns(uint256) func (_VRFTestHelper *VRFTestHelperCaller) BigModExp(opts *bind.CallOpts, base *big.Int, exponent *big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "bigModExp_", base, exponent) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "bigModExp_", base, exponent) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // BigModExp is a free data retrieval call binding the contract method 0x5de60042. @@ -270,12 +285,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) BigModExp(base *big.Int, expon // // Solidity: function ecmulVerify_(uint256[2] x, uint256 scalar, uint256[2] q) pure returns(bool) func (_VRFTestHelper *VRFTestHelperCaller) EcmulVerify(opts *bind.CallOpts, x [2]*big.Int, scalar *big.Int, q [2]*big.Int) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "ecmulVerify_", x, scalar, q) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "ecmulVerify_", x, scalar, q) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // EcmulVerify is a free data retrieval call binding the contract method 0xaa7b2fbb. @@ -296,12 +316,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) EcmulVerify(x [2]*big.Int, sca // // Solidity: function fieldHash_(bytes b) pure returns(uint256) func (_VRFTestHelper *VRFTestHelperCaller) FieldHash(opts *bind.CallOpts, b []byte) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "fieldHash_", b) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "fieldHash_", b) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // FieldHash is a free data retrieval call binding the contract method 0xb481e260. @@ -322,12 +347,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) FieldHash(b []byte) (*big.Int, // // Solidity: function hashToCurve_(uint256[2] pk, uint256 x) view returns(uint256[2]) func (_VRFTestHelper *VRFTestHelperCaller) HashToCurve(opts *bind.CallOpts, pk [2]*big.Int, x *big.Int) ([2]*big.Int, error) { - var ( - ret0 = new([2]*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "hashToCurve_", pk, x) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "hashToCurve_", pk, x) + + if err != nil { + return *new([2]*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new([2]*big.Int)).(*[2]*big.Int) + + return out0, err + } // HashToCurve is a free data retrieval call binding the contract method 0x35452450. @@ -348,12 +378,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) HashToCurve(pk [2]*big.Int, x // // Solidity: function linearCombination_(uint256 c, uint256[2] p1, uint256[2] cp1Witness, uint256 s, uint256[2] p2, uint256[2] sp2Witness, uint256 zInv) pure returns(uint256[2]) func (_VRFTestHelper *VRFTestHelperCaller) LinearCombination(opts *bind.CallOpts, c *big.Int, p1 [2]*big.Int, cp1Witness [2]*big.Int, s *big.Int, p2 [2]*big.Int, sp2Witness [2]*big.Int, zInv *big.Int) ([2]*big.Int, error) { - var ( - ret0 = new([2]*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "linearCombination_", c, p1, cp1Witness, s, p2, sp2Witness, zInv) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "linearCombination_", c, p1, cp1Witness, s, p2, sp2Witness, zInv) + + if err != nil { + return *new([2]*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new([2]*big.Int)).(*[2]*big.Int) + + return out0, err + } // LinearCombination is a free data retrieval call binding the contract method 0xfe54f2a2. @@ -374,18 +409,19 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) LinearCombination(c *big.Int, // // Solidity: function projectiveECAdd_(uint256 px, uint256 py, uint256 qx, uint256 qy) pure returns(uint256, uint256, uint256) func (_VRFTestHelper *VRFTestHelperCaller) ProjectiveECAdd(opts *bind.CallOpts, px *big.Int, py *big.Int, qx *big.Int, qy *big.Int) (*big.Int, *big.Int, *big.Int, error) { - var ( - ret0 = new(*big.Int) - ret1 = new(*big.Int) - ret2 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "projectiveECAdd_", px, py, qx, qy) + + if err != nil { + return *new(*big.Int), *new(*big.Int), *new(*big.Int), err } - err := _VRFTestHelper.contract.Call(opts, out, "projectiveECAdd_", px, py, qx, qy) - return *ret0, *ret1, *ret2, err + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + out1 := *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + + return out0, out1, out2, err + } // ProjectiveECAdd is a free data retrieval call binding the contract method 0x95e6ee92. @@ -406,12 +442,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) ProjectiveECAdd(px *big.Int, p // // Solidity: function randomValueFromVRFProof_(bytes proof) view returns(uint256 output) func (_VRFTestHelper *VRFTestHelperCaller) RandomValueFromVRFProof(opts *bind.CallOpts, proof []byte) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "randomValueFromVRFProof_", proof) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "randomValueFromVRFProof_", proof) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // RandomValueFromVRFProof is a free data retrieval call binding the contract method 0xcefda0c5. @@ -432,12 +473,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) RandomValueFromVRFProof(proof // // Solidity: function scalarFromCurvePoints_(uint256[2] hash, uint256[2] pk, uint256[2] gamma, address uWitness, uint256[2] v) pure returns(uint256) func (_VRFTestHelper *VRFTestHelperCaller) ScalarFromCurvePoints(opts *bind.CallOpts, hash [2]*big.Int, pk [2]*big.Int, gamma [2]*big.Int, uWitness common.Address, v [2]*big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "scalarFromCurvePoints_", hash, pk, gamma, uWitness, v) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "scalarFromCurvePoints_", hash, pk, gamma, uWitness, v) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // ScalarFromCurvePoints is a free data retrieval call binding the contract method 0x7f8f50a8. @@ -458,12 +504,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) ScalarFromCurvePoints(hash [2] // // Solidity: function squareRoot_(uint256 x) view returns(uint256) func (_VRFTestHelper *VRFTestHelperCaller) SquareRoot(opts *bind.CallOpts, x *big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "squareRoot_", x) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "squareRoot_", x) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // SquareRoot is a free data retrieval call binding the contract method 0x8af046ea. @@ -484,12 +535,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) SquareRoot(x *big.Int) (*big.I // // Solidity: function verifyLinearCombinationWithGenerator_(uint256 c, uint256[2] p, uint256 s, address lcWitness) pure returns(bool) func (_VRFTestHelper *VRFTestHelperCaller) VerifyLinearCombinationWithGenerator(opts *bind.CallOpts, c *big.Int, p [2]*big.Int, s *big.Int, lcWitness common.Address) (bool, error) { - var ( - ret0 = new(bool) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "verifyLinearCombinationWithGenerator_", c, p, s, lcWitness) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "verifyLinearCombinationWithGenerator_", c, p, s, lcWitness) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + } // VerifyLinearCombinationWithGenerator is a free data retrieval call binding the contract method 0x91d5f691. @@ -510,10 +566,15 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) VerifyLinearCombinationWithGen // // Solidity: function verifyVRFProof_(uint256[2] pk, uint256[2] gamma, uint256 c, uint256 s, uint256 seed, address uWitness, uint256[2] cGammaWitness, uint256[2] sHashWitness, uint256 zInv) view returns() func (_VRFTestHelper *VRFTestHelperCaller) VerifyVRFProof(opts *bind.CallOpts, pk [2]*big.Int, gamma [2]*big.Int, c *big.Int, s *big.Int, seed *big.Int, uWitness common.Address, cGammaWitness [2]*big.Int, sHashWitness [2]*big.Int, zInv *big.Int) error { - var () - out := &[]interface{}{} - err := _VRFTestHelper.contract.Call(opts, out, "verifyVRFProof_", pk, gamma, c, s, seed, uWitness, cGammaWitness, sHashWitness, zInv) + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "verifyVRFProof_", pk, gamma, c, s, seed, uWitness, cGammaWitness, sHashWitness, zInv) + + if err != nil { + return err + } + return err + } // VerifyVRFProof is a free data retrieval call binding the contract method 0xef3b10ec. @@ -534,12 +595,17 @@ func (_VRFTestHelper *VRFTestHelperCallerSession) VerifyVRFProof(pk [2]*big.Int, // // Solidity: function ySquared_(uint256 x) pure returns(uint256) func (_VRFTestHelper *VRFTestHelperCaller) YSquared(opts *bind.CallOpts, x *big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestHelper.contract.Call(opts, out, "ySquared_", x) - return *ret0, err + var out []interface{} + err := _VRFTestHelper.contract.Call(opts, &out, "ySquared_", x) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // YSquared is a free data retrieval call binding the contract method 0x9d6f0337. diff --git a/core/internal/gethwrappers/generated/vrf_testnet_d20/vrf_testnet_d20.go b/core/internal/gethwrappers/generated/vrf_testnet_d20/vrf_testnet_d20.go index 4d979f2dece..9b2f1279101 100644 --- a/core/internal/gethwrappers/generated/vrf_testnet_d20/vrf_testnet_d20.go +++ b/core/internal/gethwrappers/generated/vrf_testnet_d20/vrf_testnet_d20.go @@ -154,7 +154,7 @@ func bindVRFTestnetD20(address common.Address, caller bind.ContractCaller, trans // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFTestnetD20 *VRFTestnetD20Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFTestnetD20 *VRFTestnetD20Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFTestnetD20.Contract.VRFTestnetD20Caller.contract.Call(opts, result, method, params...) } @@ -173,7 +173,7 @@ func (_VRFTestnetD20 *VRFTestnetD20Raw) Transact(opts *bind.TransactOpts, method // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_VRFTestnetD20 *VRFTestnetD20CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_VRFTestnetD20 *VRFTestnetD20CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _VRFTestnetD20.Contract.contract.Call(opts, result, method, params...) } @@ -192,12 +192,17 @@ func (_VRFTestnetD20 *VRFTestnetD20TransactorRaw) Transact(opts *bind.TransactOp // // Solidity: function d20Results(uint256 ) view returns(uint256) func (_VRFTestnetD20 *VRFTestnetD20Caller) D20Results(opts *bind.CallOpts, arg0 *big.Int) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestnetD20.contract.Call(opts, out, "d20Results", arg0) - return *ret0, err + var out []interface{} + err := _VRFTestnetD20.contract.Call(opts, &out, "d20Results", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // D20Results is a free data retrieval call binding the contract method 0x4ab5fc50. @@ -218,12 +223,17 @@ func (_VRFTestnetD20 *VRFTestnetD20CallerSession) D20Results(arg0 *big.Int) (*bi // // Solidity: function latestRoll() view returns(uint256 d20result) func (_VRFTestnetD20 *VRFTestnetD20Caller) LatestRoll(opts *bind.CallOpts) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestnetD20.contract.Call(opts, out, "latestRoll") - return *ret0, err + var out []interface{} + err := _VRFTestnetD20.contract.Call(opts, &out, "latestRoll") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // LatestRoll is a free data retrieval call binding the contract method 0xae383a4d. @@ -244,12 +254,17 @@ func (_VRFTestnetD20 *VRFTestnetD20CallerSession) LatestRoll() (*big.Int, error) // // Solidity: function nonces(bytes32 ) view returns(uint256) func (_VRFTestnetD20 *VRFTestnetD20Caller) Nonces(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _VRFTestnetD20.contract.Call(opts, out, "nonces", arg0) - return *ret0, err + var out []interface{} + err := _VRFTestnetD20.contract.Call(opts, &out, "nonces", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + } // Nonces is a free data retrieval call binding the contract method 0x9e317f12. diff --git a/core/internal/gethwrappers/generation/common.sh b/core/internal/gethwrappers/generation/common.sh deleted file mode 100644 index 5264da35091..00000000000 --- a/core/internal/gethwrappers/generation/common.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# GETH_VERSION is the version of go-ethereum chainlink is using -GETH_VERSION=$(go list -json -m github.com/ethereum/go-ethereum | jq -r .Version) -export GETH_VERSION diff --git a/core/internal/gethwrappers/generation/compile_command.txt b/core/internal/gethwrappers/generation/compile_command.txt new file mode 100644 index 00000000000..92b076b8209 --- /dev/null +++ b/core/internal/gethwrappers/generation/compile_command.txt @@ -0,0 +1 @@ +yarn workspace @chainlink/contracts belt compile solc diff --git a/core/internal/gethwrappers/generation/compile_contracts.sh b/core/internal/gethwrappers/generation/compile_contracts.sh index 21795bf442a..f339c20fae9 100755 --- a/core/internal/gethwrappers/generation/compile_contracts.sh +++ b/core/internal/gethwrappers/generation/compile_contracts.sh @@ -2,11 +2,11 @@ echo "compiling contracts" -CDIR=$(dirname "$0") -COMPILE_COMMAND=$(<"$CDIR/compile_command.txt") +CDIR="$(dirname "$0")" +COMPILE_COMMAND="$(<"$CDIR/compile_command.txt")" # Only print compilation output on failure. -OUT="$(bash -c $COMPILE_COMMAND 2>&1)" +OUT="$(bash -c "${COMPILE_COMMAND}" 2>&1)" ERR="$?" # shellcheck disable=SC2181 diff --git a/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 7195ad23604..0ca091798de 100644 --- a/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/internal/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,9 +1,13 @@ -GETH_VERSION: 1.9.22 +GETH_VERSION: 1.9.24 flags_wrapper: ../../../evm-contracts/abi/v0.6/Flags.json 279350a8fa9c3288650affd7b18c053832943a7082eaf7dcf906484d044e000b flux_aggregator_wrapper: ../../../evm-contracts/abi/v0.6/FluxAggregator.json 21f633c47d1e49b9e49ed5789a7efaa5a9afdc500f3602277e2ade8a2f58a419 +multiwordconsumer: ../../../evm-contracts/abi/v0.7/MultiWordConsumer.json 62487d717de008a196bf273f282111771873ecfcc31f7cd6b56294356ce105a2 +multiwordconsumer_wrapper: ../../../evm-contracts/abi/v0.7/MultiWordConsumer.json 62487d717de008a196bf273f282111771873ecfcc31f7cd6b56294356ce105a2 +operator: ../../../evm-contracts/abi/v0.7/Operator.json f427fded692f7fe1c1f8b1ca8134d12f2457809336dbecfde5b5f59ddcd76787 +operator_consumer: ../../../evm-contracts/abi/v0.7/Consumer.json 90d0d0d904411ddf2d6530eb5c3d57da2d6a5151e16a7a726ddd56d95fa0a844 +operator_wrapper: ../../../evm-contracts/abi/v0.7/Operator.json f427fded692f7fe1c1f8b1ca8134d12f2457809336dbecfde5b5f59ddcd76787 solidity_request_id: ../../../evm-contracts/abi/v0.6/VRFRequestIDBaseTestHelper.json 4cd458d90afeb98d83b11fb1a2e9bf9d3665fe2b95661d2244e94697a6b09d32 -solidity_vrf_consumer_interface: ../../../evm-contracts/abi/v0.6/VRFConsumer.json e2d9cd3645d39a3d30e25b868f8bd59d6ea36e4f7960422efd37c22b1316cb82 +solidity_vrf_consumer_interface: ../../../evm-contracts/abi/v0.6/VRFConsumer.json 6f8cccd81d5397f83dc66d871f196e95fbc2f7c5e3227f8fdfd92b13dc094a3d solidity_vrf_coordinator_interface: ../../../evm-contracts/abi/v0.6/VRFCoordinator.json d089593178c41de40ef86f63879582d6b22a5d47fb10eea671d2b0cd54cb45c8 solidity_vrf_request_id: ../../../evm-contracts/abi/v0.6/VRFRequestIDBaseTestHelper.json 4cd458d90afeb98d83b11fb1a2e9bf9d3665fe2b95661d2244e94697a6b09d32 solidity_vrf_verifier_wrapper: ../../../evm-contracts/abi/v0.6/VRFTestHelper.json 7f91b75e89d021f038cc44ffc531ef60436c7b9e23f632ec57238ec61a2df517 -vrf_testnet_d20: ../../../evm-contracts/abi/v0.6/VRFTestnetD20.json c002b0245f58e1c03014338df0ccfbc0a99812b12b9411b5b5f5cbcf54ed05c9 diff --git a/core/internal/gethwrappers/go_generate.go b/core/internal/gethwrappers/go_generate.go index 4a2e29532dc..a7d2d3d58cc 100644 --- a/core/internal/gethwrappers/go_generate.go +++ b/core/internal/gethwrappers/go_generate.go @@ -10,8 +10,9 @@ package gethwrappers //go:generate go run ./generation/generate/wrap.go ../../../evm-contracts/abi/v0.6/VRFCoordinator.json solidity_vrf_coordinator_interface //go:generate go run ./generation/generate/wrap.go ../../../evm-contracts/abi/v0.6/VRFConsumer.json solidity_vrf_consumer_interface //go:generate go run ./generation/generate/wrap.go ../../../evm-contracts/abi/v0.6/VRFRequestIDBaseTestHelper.json solidity_vrf_request_id -//go:generate go run ./generation/generate/wrap.go ../../../evm-contracts/abi/v0.6/VRFTestnetD20.json vrf_testnet_d20 //go:generate go run ./generation/generate/wrap.go ../../../evm-contracts/abi/v0.6/Flags.json flags_wrapper +//go:generate go run ./generation/generate/wrap.go ../../../evm-contracts/abi/v0.7/Operator.json operator_wrapper +//go:generate go run ./generation/generate/wrap.go ../../../evm-contracts/abi/v0.7/MultiWordConsumer.json multiwordconsumer_wrapper //go:generate go run ./generation/generate_link/wrap_link.go diff --git a/core/internal/gethwrappers/go_generate_test.go b/core/internal/gethwrappers/go_generate_test.go index b44a44ab433..5b43017f420 100644 --- a/core/internal/gethwrappers/go_generate_test.go +++ b/core/internal/gethwrappers/go_generate_test.go @@ -91,6 +91,21 @@ func TestArtifactCompilerVersionMatchesConfig(t *testing.T) { require.NoError(t, scanner.Err()) } +// rootDir is the local chainlink root working directory +var rootDir string + +func init() { // compute rootDir + var err error + thisDir, err := os.Getwd() + if err != nil { + panic(err) + } + rootDir, err = filepath.Abs(filepath.Join(thisDir, "../../..")) + if err != nil { + panic(err) + } +} + // compareCurrentCompilerAritfactAgainstRecordsAndSoliditySources checks that // the file at each ContractVersion.CompilerArtifactPath hashes to its // ContractVersion.Hash, and that the solidity source code recorded in the @@ -111,11 +126,6 @@ func compareCurrentCompilerAritfactAgainstRecordsAndSoliditySources( contract, err := ExtractContractDetails(apath) require.NoError(t, err, "could not get details for contract %s", versionInfo) hash := contract.VersionHash() - thisDir, _ := os.Getwd() - rootDir, err := filepath.Abs(filepath.Join(thisDir, "../../..")) - if err != nil { - rootDir = "" - } recompileCommand := fmt.Sprintf("(cd %s; make go-solidity-wrappers)", rootDir) assert.Equal(t, versionInfo.Hash, hash, boxOutput(`compiler artifact %s has changed; please rerun @@ -124,7 +134,7 @@ and commit the changes`, apath, recompileCommand)) // Check that each of the contract source codes hasn't changed soliditySourceRoot := filepath.Dir(filepath.Dir(filepath.Dir(apath))) - contractPath := filepath.Join(soliditySourceRoot, "src", "v0.6") + contractPath := filepath.Join(soliditySourceRoot, "src", filepath.Base(filepath.Dir(versionInfo.CompilerArtifactPath))) for sourcePath, sourceCode := range contract.Sources { // compare to current source sourcePath = filepath.Join(contractPath, sourcePath) actualSource, err := ioutil.ReadFile(sourcePath) @@ -164,6 +174,8 @@ func init() { } fmt.Printf("some solidity artifacts missing (%s); rebuilding...", solidityArtifactsMissing) + // Don't want to run "make go-solidity-wrappers" here, because that would + // result in an infinite loop cmd := exec.Command("bash", "-c", compileCommand(nil)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/core/internal/mocks/advisory_locker.go b/core/internal/mocks/advisory_locker.go index 9508d6ee351..06eb53c25c5 100644 --- a/core/internal/mocks/advisory_locker.go +++ b/core/internal/mocks/advisory_locker.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/after_nower.go b/core/internal/mocks/after_nower.go index 5d9029b54aa..6018cea1b53 100644 --- a/core/internal/mocks/after_nower.go +++ b/core/internal/mocks/after_nower.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/application.go b/core/internal/mocks/application.go index 779d0ae62c2..5fa3f1cd4c7 100644 --- a/core/internal/mocks/application.go +++ b/core/internal/mocks/application.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks @@ -13,6 +13,8 @@ import ( models "github.com/smartcontractkit/chainlink/core/store/models" + null "gopkg.in/guregu/null.v4" + packr "github.com/gobuffalo/packr" store "github.com/smartcontractkit/chainlink/core/store" @@ -39,20 +41,20 @@ func (_m *Application) AddJob(_a0 models.JobSpec) error { return r0 } -// AddJobV2 provides a mock function with given fields: ctx, _a1 -func (_m *Application) AddJobV2(ctx context.Context, _a1 job.Spec) (int32, error) { - ret := _m.Called(ctx, _a1) +// AddJobV2 provides a mock function with given fields: ctx, _a1, name +func (_m *Application) AddJobV2(ctx context.Context, _a1 job.Spec, name null.String) (int32, error) { + ret := _m.Called(ctx, _a1, name) var r0 int32 - if rf, ok := ret.Get(0).(func(context.Context, job.Spec) int32); ok { - r0 = rf(ctx, _a1) + if rf, ok := ret.Get(0).(func(context.Context, job.Spec, null.String) int32); ok { + r0 = rf(ctx, _a1, name) } else { r0 = ret.Get(0).(int32) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, job.Spec) error); ok { - r1 = rf(ctx, _a1) + if rf, ok := ret.Get(1).(func(context.Context, job.Spec, null.String) error); ok { + r1 = rf(ctx, _a1, name) } else { r1 = ret.Error(1) } diff --git a/core/internal/mocks/client.go b/core/internal/mocks/client.go index 7f3e442ebe7..2888eb2c5c5 100644 --- a/core/internal/mocks/client.go +++ b/core/internal/mocks/client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/deviation_checker.go b/core/internal/mocks/deviation_checker.go index 4184351bdad..cc8a2fea6eb 100644 --- a/core/internal/mocks/deviation_checker.go +++ b/core/internal/mocks/deviation_checker.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/deviation_checker_factory.go b/core/internal/mocks/deviation_checker_factory.go index 159b09f49a6..49e24b3e75e 100644 --- a/core/internal/mocks/deviation_checker_factory.go +++ b/core/internal/mocks/deviation_checker_factory.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/fetcher.go b/core/internal/mocks/fetcher.go index a7ceb25dce2..ce97bfe1192 100644 --- a/core/internal/mocks/fetcher.go +++ b/core/internal/mocks/fetcher.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/flux_aggregator.go b/core/internal/mocks/flux_aggregator.go index 1bbe918dc4c..68ff38df2d9 100644 --- a/core/internal/mocks/flux_aggregator.go +++ b/core/internal/mocks/flux_aggregator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks @@ -125,6 +125,27 @@ func (_m *FluxAggregator) GetOracles() ([]common.Address, error) { return r0, r1 } +// LatestRoundData provides a mock function with given fields: +func (_m *FluxAggregator) LatestRoundData() (contracts.FluxAggregatorRoundData, error) { + ret := _m.Called() + + var r0 contracts.FluxAggregatorRoundData + if rf, ok := ret.Get(0).(func() contracts.FluxAggregatorRoundData); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(contracts.FluxAggregatorRoundData) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // RoundState provides a mock function with given fields: oracle, roundID func (_m *FluxAggregator) RoundState(oracle common.Address, roundID uint32) (contracts.FluxAggregatorRoundState, error) { ret := _m.Called(oracle, roundID) diff --git a/core/internal/mocks/geth_client.go b/core/internal/mocks/geth_client.go index 1b963c25392..73b21756e94 100644 --- a/core/internal/mocks/geth_client.go +++ b/core/internal/mocks/geth_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/head_trackable.go b/core/internal/mocks/head_trackable.go index a2ca679423a..83d949bdf40 100644 --- a/core/internal/mocks/head_trackable.go +++ b/core/internal/mocks/head_trackable.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/job_subscriber.go b/core/internal/mocks/job_subscriber.go index 86bc8aff3b2..991b40943a5 100644 --- a/core/internal/mocks/job_subscriber.go +++ b/core/internal/mocks/job_subscriber.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/key_store_interface.go b/core/internal/mocks/key_store_interface.go index 58ec651cc5b..3a56e35137a 100644 --- a/core/internal/mocks/key_store_interface.go +++ b/core/internal/mocks/key_store_interface.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/log_broadcast.go b/core/internal/mocks/log_broadcast.go index 581d230a8fb..aa6224a6b93 100644 --- a/core/internal/mocks/log_broadcast.go +++ b/core/internal/mocks/log_broadcast.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/log_broadcaster.go b/core/internal/mocks/log_broadcaster.go index d8be806420c..cf8c877b7d6 100644 --- a/core/internal/mocks/log_broadcaster.go +++ b/core/internal/mocks/log_broadcaster.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/log_listener.go b/core/internal/mocks/log_listener.go index b3734cd7033..7bef77e33a0 100644 --- a/core/internal/mocks/log_listener.go +++ b/core/internal/mocks/log_listener.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/notify_new_eth_tx.go b/core/internal/mocks/notify_new_eth_tx.go index 363598a0443..faf7f0b69cc 100644 --- a/core/internal/mocks/notify_new_eth_tx.go +++ b/core/internal/mocks/notify_new_eth_tx.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/prometheus_backend.go b/core/internal/mocks/prometheus_backend.go index 129d6b699d2..9776e50675f 100644 --- a/core/internal/mocks/prometheus_backend.go +++ b/core/internal/mocks/prometheus_backend.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks @@ -14,6 +14,16 @@ func (_m *PrometheusBackend) SetMaxUnconfirmedBlocks(_a0 int64) { _m.Called(_a0) } +// SetPipelineRunsQueued provides a mock function with given fields: n +func (_m *PrometheusBackend) SetPipelineRunsQueued(n int) { + _m.Called(n) +} + +// SetPipelineTaskRunsQueued provides a mock function with given fields: n +func (_m *PrometheusBackend) SetPipelineTaskRunsQueued(n int) { + _m.Called(n) +} + // SetUnconfirmedTransactions provides a mock function with given fields: _a0 func (_m *PrometheusBackend) SetUnconfirmedTransactions(_a0 int64) { _m.Called(_a0) diff --git a/core/internal/mocks/rpc_client.go b/core/internal/mocks/rpc_client.go index a3674905b06..992a635662f 100644 --- a/core/internal/mocks/rpc_client.go +++ b/core/internal/mocks/rpc_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/run_executor.go b/core/internal/mocks/run_executor.go index f794691fde2..c33e2ad48a5 100644 --- a/core/internal/mocks/run_executor.go +++ b/core/internal/mocks/run_executor.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/run_manager.go b/core/internal/mocks/run_manager.go index e21a5b6251a..27d0e73a93b 100644 --- a/core/internal/mocks/run_manager.go +++ b/core/internal/mocks/run_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/run_queue.go b/core/internal/mocks/run_queue.go index c8d908849e8..8db071c95ce 100644 --- a/core/internal/mocks/run_queue.go +++ b/core/internal/mocks/run_queue.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/service.go b/core/internal/mocks/service.go index fb5f5e41483..3cd960139e4 100644 --- a/core/internal/mocks/service.go +++ b/core/internal/mocks/service.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/mocks/stats_pusher.go b/core/internal/mocks/stats_pusher.go index 3d3e0ec4b64..3245bde54b2 100644 --- a/core/internal/mocks/stats_pusher.go +++ b/core/internal/mocks/stats_pusher.go @@ -1,12 +1,13 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks import ( - url "net/url" - synchronization "github.com/smartcontractkit/chainlink/core/services/synchronization" + models "github.com/smartcontractkit/chainlink/core/store/models" mock "github.com/stretchr/testify/mock" + + url "net/url" ) // StatsPusher is an autogenerated mock type for the StatsPusher type @@ -14,6 +15,20 @@ type StatsPusher struct { mock.Mock } +// AllSyncEvents provides a mock function with given fields: cb +func (_m *StatsPusher) AllSyncEvents(cb func(models.SyncEvent) error) error { + ret := _m.Called(cb) + + var r0 error + if rf, ok := ret.Get(0).(func(func(models.SyncEvent) error) error); ok { + r0 = rf(cb) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Close provides a mock function with given fields: func (_m *StatsPusher) Close() error { ret := _m.Called() diff --git a/core/internal/mocks/subscription.go b/core/internal/mocks/subscription.go index df34ead6d3a..5db29616efa 100644 --- a/core/internal/mocks/subscription.go +++ b/core/internal/mocks/subscription.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/internal/testdata/multiword_v1_runlog.json b/core/internal/testdata/multiword_v1_runlog.json new file mode 100644 index 00000000000..533e38d0d30 --- /dev/null +++ b/core/internal/testdata/multiword_v1_runlog.json @@ -0,0 +1,28 @@ +{ + "initiators": [ + { + "type": "runlog" + } + ], + "tasks": [ + {"type": "httpgetwithunrestrictednetworkaccess", "params": {"get": "{url}"}}, + {"type": "jsonparse", "params": {"path": ["USD"]}}, + {"type": "ethbytes32"}, + {"type": "resultcollect"}, + {"type": "httpgetwithunrestrictednetworkaccess", "params": {"get": "{url}"}}, + {"type": "jsonparse", "params": {"path": ["EUR"]}}, + {"type": "ethbytes32"}, + {"type": "resultcollect"}, + {"type": "httpgetwithunrestrictednetworkaccess", "params": {"get": "{url}"}}, + {"type": "jsonparse", "params": {"path": ["JPY"]}}, + {"type": "ethbytes32"}, + {"type": "resultcollect"}, + { + "type": "ethtx", + "confirmations": 1, + "params": { + "abiEncoding": ["bytes32", "bytes32", "bytes32", "bytes32"] + } + } + ] +} diff --git a/core/internal/testdata/multiword_v1_web.json b/core/internal/testdata/multiword_v1_web.json new file mode 100644 index 00000000000..f74c1b7e40d --- /dev/null +++ b/core/internal/testdata/multiword_v1_web.json @@ -0,0 +1,21 @@ +{ + "initiators": [{"type": "web"}], + "tasks": [ + {"type": "httpgetwithunrestrictednetworkaccess", "params": {"get": "https://bitstamp.net/api/ticker/"}}, + {"type": "jsonparse", "params": {"path": ["bid"]}}, + {"type": "ethbytes32"}, + {"type": "resultcollect"}, + {"type": "httpgetwithunrestrictednetworkaccess", "params": {"get": "https://bitstamp.net/api/ticker/"}}, + {"type": "jsonparse", "params": {"path": ["ask"]}}, + {"type": "ethbytes32"}, + {"type": "resultcollect"}, + { + "type": "ethtx", + "confirmations": 0, + "params": { + "dataPrefix": "0x0000000000000000000000000000000000000000000000000000000000000001", + "abiEncoding": ["bytes32", "bytes32", "bytes32"] + } + } + ] +} diff --git a/core/main_test.go b/core/main_test.go index 033ffd90d2f..7601fadf90a 100644 --- a/core/main_test.go +++ b/core/main_test.go @@ -31,24 +31,25 @@ func ExampleRun() { // Output: // NAME: // core.test - CLI for Chainlink - // + // USAGE: // core.test [global options] command [command options] [arguments...] - // + // VERSION: // unset@unset - // + // COMMANDS: - // admin Commands for remotely taking admin related actions - // bridges Commands for Bridges communicating with External Adapters - // config Commands for the node's configuration - // jobs Commands for managing Jobs - // keys Commands for managing various types of keys used by the Chainlink node - // node, local Commands for admin actions that must be run locally - // runs Commands for managing Runs - // txs Commands for handling Ethereum transactions - // help, h Shows a list of commands or help for one command - // + // admin Commands for remotely taking admin related actions + // attempts, txas Commands for managing Ethereum Transaction Attempts + // bridges Commands for Bridges communicating with External Adapters + // config Commands for the node's configuration + // jobs Commands for managing Jobs + // keys Commands for managing various types of keys used by the Chainlink node + // node, local Commands for admin actions that must be run locally + // runs Commands for managing Runs + // txs Commands for handling Ethereum transactions + // help, h Shows a list of commands or help for one command + // GLOBAL OPTIONS: // --json, -j json output as opposed to table // --help, -h show help diff --git a/core/services/bulletprooftxmanager/eth_broadcaster.go b/core/services/bulletprooftxmanager/eth_broadcaster.go index 8a586f890b9..8e00ec7d6a6 100644 --- a/core/services/bulletprooftxmanager/eth_broadcaster.go +++ b/core/services/bulletprooftxmanager/eth_broadcaster.go @@ -195,7 +195,6 @@ func (eb *ethBroadcaster) processUnstartedEthTxs(fromAddress gethCommon.Address) if err := eb.handleAnyInProgressEthTx(fromAddress); err != nil { return errors.Wrap(err, "processUnstartedEthTxs failed") } - for { etx, err := eb.nextUnstartedTransactionWithNonce(fromAddress) if err != nil { @@ -209,6 +208,7 @@ func (eb *ethBroadcaster) processUnstartedEthTxs(fromAddress gethCommon.Address) if err != nil { return errors.Wrap(err, "processUnstartedEthTxs failed") } + if err := eb.saveInProgressTransaction(etx, &a); err != nil { return errors.Wrap(err, "processUnstartedEthTxs failed") } diff --git a/core/services/bulletprooftxmanager/eth_confirmer.go b/core/services/bulletprooftxmanager/eth_confirmer.go index 53cbdeeb4cb..a3a39a6ead7 100644 --- a/core/services/bulletprooftxmanager/eth_confirmer.go +++ b/core/services/bulletprooftxmanager/eth_confirmer.go @@ -473,7 +473,7 @@ func (ec *ethConfirmer) handleInProgressAttempt(ctx context.Context, etx models. // This should really not ever happen in normal operation since we // already bumped above the required minimum in ethBroadcaster. // - // It could conceivably happen if the remote eth node changed it's configuration. + // It could conceivably happen if the remote eth node changed its configuration. bumpedGasPrice, err := BumpGas(ec.config, attempt.GasPrice.ToInt()) if err != nil { return errors.Wrap(err, "could not bump gas for terminally underpriced transaction") diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 05179b41523..a0c723763a6 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -20,12 +20,12 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/postgres" "github.com/smartcontractkit/chainlink/core/services/synchronization" - "github.com/smartcontractkit/chainlink/core/services/telemetry" strpkg "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/smartcontractkit/chainlink/core/utils" "go.uber.org/multierr" + "gopkg.in/guregu/null.v4" ) // headTrackableCallback is a simple wrapper around an On Connect callback @@ -51,7 +51,7 @@ type Application interface { GetStatsPusher() synchronization.StatsPusher WakeSessionReaper() AddJob(job models.JobSpec) error - AddJobV2(ctx context.Context, job job.Spec) (int32, error) + AddJobV2(ctx context.Context, job job.Spec, name null.String) (int32, error) ArchiveJob(*models.ID) error DeleteJobV2(ctx context.Context, jobID int32) error RunJobV2(ctx context.Context, jobID int32, meta map[string]interface{}) (int64, error) @@ -85,7 +85,6 @@ type ChainlinkApplication struct { shutdownOnce sync.Once shutdownSignal gracefulpanic.Signal balanceMonitor services.BalanceMonitor - monitoringEndpoint telemetry.MonitoringEndpoint explorerClient synchronization.ExplorerClient } @@ -100,12 +99,10 @@ func NewApplication(config *orm.Config, ethClient eth.Client, advisoryLocker pos explorerClient := synchronization.ExplorerClient(&synchronization.NoopExplorerClient{}) statsPusher := synchronization.StatsPusher(&synchronization.NoopStatsPusher{}) - telemetryAgent := telemetry.MonitoringEndpoint(&telemetry.NoopAgent{}) if config.ExplorerURL() != nil { explorerClient = synchronization.NewExplorerClient(config.ExplorerURL(), config.ExplorerAccessKey(), config.ExplorerSecret()) - statsPusher = synchronization.NewStatsPusher(store.ORM, explorerClient) - telemetryAgent = telemetry.NewAgent(explorerClient) + statsPusher = synchronization.NewStatsPusher(store.DB, explorerClient) } runExecutor := services.NewRunExecutor(store, statsPusher) @@ -136,6 +133,7 @@ func NewApplication(config *orm.Config, ethClient eth.Client, advisoryLocker pos if config.Dev() || config.FeatureOffchainReporting() { offchainreporting.RegisterJobType(store.ORM.DB, jobORM, store.Config, store.OCRKeyStore, jobSpawner, pipelineRunner, ethClient, logBroadcaster) } + services.RegisterEthRequestEventDelegate(jobSpawner) store.NotifyNewEthTx = ethBroadcaster @@ -160,7 +158,6 @@ func NewApplication(config *orm.Config, ethClient eth.Client, advisoryLocker pos pendingConnectionResumer: pendingConnectionResumer, shutdownSignal: shutdownSignal, balanceMonitor: balanceMonitor, - monitoringEndpoint: telemetryAgent, explorerClient: explorerClient, } @@ -254,21 +251,38 @@ func (app *ChainlinkApplication) Stop() error { }() logger.Info("Gracefully exiting...") + logger.Debug("Stopping LogBroadcaster...") merr = multierr.Append(merr, app.LogBroadcaster.Stop()) + logger.Debug("Stopping EventBroadcaster...") merr = multierr.Append(merr, app.EventBroadcaster.Stop()) + logger.Debug("Stopping Scheduler...") app.Scheduler.Stop() + logger.Debug("Stopping HeadTracker...") merr = multierr.Append(merr, app.HeadTracker.Stop()) + logger.Debug("Stopping balanceMonitor...") merr = multierr.Append(merr, app.balanceMonitor.Stop()) + logger.Debug("Stopping JobSubscriber...") merr = multierr.Append(merr, app.JobSubscriber.Stop()) + logger.Debug("Stopping FluxMonitor...") app.FluxMonitor.Stop() + logger.Debug("Stopping EthBroadcaster...") merr = multierr.Append(merr, app.EthBroadcaster.Stop()) + logger.Debug("Stopping RunQueue...") app.RunQueue.Stop() + logger.Debug("Stopping StatsPusher...") merr = multierr.Append(merr, app.StatsPusher.Close()) + logger.Debug("Stopping explorerClient...") merr = multierr.Append(merr, app.explorerClient.Close()) + logger.Debug("Stopping SessionReaper...") merr = multierr.Append(merr, app.SessionReaper.Stop()) + logger.Debug("Stopping pipelineRunner...") app.pipelineRunner.Stop() + logger.Debug("Stopping jobSpawner...") app.jobSpawner.Stop() + logger.Debug("Closing Store...") merr = multierr.Append(merr, app.Store.Close()) + + logger.Info("Exited all services") }) return merr } @@ -302,8 +316,8 @@ func (app *ChainlinkApplication) AddJob(job models.JobSpec) error { return nil } -func (app *ChainlinkApplication) AddJobV2(ctx context.Context, job job.Spec) (int32, error) { - return app.jobSpawner.CreateJob(ctx, job) +func (app *ChainlinkApplication) AddJobV2(ctx context.Context, job job.Spec, name null.String) (int32, error) { + return app.jobSpawner.CreateJob(ctx, job, name) } func (app *ChainlinkApplication) RunJobV2(ctx context.Context, jobID int32, meta map[string]interface{}) (int64, error) { diff --git a/core/services/eth/contract.go b/core/services/eth/contract.go index be6c0501de7..f68b2760ef5 100644 --- a/core/services/eth/contract.go +++ b/core/services/eth/contract.go @@ -30,7 +30,7 @@ func NewConnectedContract( return &connectedContract{codec, address, ethClient, logBroadcaster} } -func (contract *connectedContract) Call(result interface{}, methodName string, args ...interface{}) error { +func (contract *connectedContract) Call(result interface{}, methodName string, args ...interface{}) (err error) { data, err := contract.EncodeMessageCall(methodName, args...) if err != nil { return errors.Wrap(err, "unable to encode message call") @@ -42,8 +42,7 @@ func (contract *connectedContract) Call(result interface{}, methodName string, a if err != nil { return errors.Wrap(err, "unable to call client") } - - err = contract.ABI().Unpack(result, methodName, rawResult) + err = contract.ABI().UnpackIntoInterface(result, methodName, rawResult) return errors.Wrap(err, "unable to unpack values") } diff --git a/core/services/eth/contracts/FluxAggregator.go b/core/services/eth/contracts/FluxAggregator.go index 953b35d8674..51c2ae4ea0f 100644 --- a/core/services/eth/contracts/FluxAggregator.go +++ b/core/services/eth/contracts/FluxAggregator.go @@ -16,6 +16,7 @@ type FluxAggregator interface { eth.ConnectedContract RoundState(oracle common.Address, roundID uint32) (FluxAggregatorRoundState, error) GetOracles() ([]common.Address, error) + LatestRoundData() (FluxAggregatorRoundData, error) } const ( @@ -85,6 +86,14 @@ type FluxAggregatorRoundState struct { OracleCount uint8 `abi:"_oracleCount" json:"oracleCount"` } +type FluxAggregatorRoundData struct { + RoundID uint32 `abi:"roundId" json:"reportableRoundID"` + Answer *big.Int `abi:"answer" json:"latestAnswer,omitempty"` + StartedAt uint64 `abi:"startedAt" json:"startedAt"` + UpdatedAt uint64 `abi:"updatedAt" json:"updatedAt"` + AnsweredInRound uint32 `abi:"answeredInRound" json:"availableFunds,omitempty"` +} + func (rs FluxAggregatorRoundState) TimesOutAt() uint64 { return rs.StartedAt + rs.Timeout } @@ -106,3 +115,13 @@ func (fa *fluxAggregator) GetOracles() (oracles []common.Address, err error) { } return oracles, nil } + +func (fa *fluxAggregator) LatestRoundData() (FluxAggregatorRoundData, error) { + var result FluxAggregatorRoundData + err := fa.Call(&result, "latestRoundData") + if err != nil { + return FluxAggregatorRoundData{}, + errors.Wrap(err, "error calling fluxaggregator#latestRoundData - contract may have 0 rounds") + } + return result, nil +} diff --git a/core/services/eth/geth_copied.go b/core/services/eth/geth_copied.go index 185a83f5c22..7d165b52ca9 100644 --- a/core/services/eth/geth_copied.go +++ b/core/services/eth/geth_copied.go @@ -17,7 +17,7 @@ import ( // https://github.com/ethereum/go-ethereum/blob/v1.9.11/accounts/abi/bind/base.go#L328 func gethUnpackLog(codec *contractCodec, out interface{}, event string, log types.Log) error { if len(log.Data) > 0 { - if err := codec.abi.Unpack(out, event, log.Data); err != nil { + if err := codec.abi.UnpackIntoInterface(out, event, log.Data); err != nil { return err } } diff --git a/core/services/eth_request_events.go b/core/services/eth_request_events.go new file mode 100644 index 00000000000..b4a24f89a13 --- /dev/null +++ b/core/services/eth_request_events.go @@ -0,0 +1,93 @@ +package services + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/pipeline" + "github.com/smartcontractkit/chainlink/core/store/models" + "gopkg.in/guregu/null.v4" +) + +// EthRequestEvent is a wrapper for `models.EthRequestEvent`, the DB +// representation of the job spec. It fulfills the job.Spec interface +// and has facilities for unmarshaling the pipeline DAG from the job spec text. +type EthRequestEventSpec struct { + Type string `toml:"type"` + SchemaVersion uint32 `toml:"schemaVersion"` + Name null.String `toml:"name"` + MaxTaskDuration models.Interval `toml:"maxTaskDuration"` + + models.EthRequestEventSpec + + // The `jobID` field exists to cache the ID from the jobs table that joins + // to the eth_request_events table. + jobID int32 + + // The `Pipeline` field is only used during unmarshaling. A pipeline.TaskDAG + // is a type that implements gonum.org/v1/gonum/graph#Graph, which means that + // you can dot.Unmarshal(...) raw DOT source directly into it, and it will + // be a fully-instantiated DAG containing information about all of the nodes + // and edges described by the DOT. Our pipeline.TaskDAG type has a method + // called `.TasksInDependencyOrder()` which converts this node/edge data + // structure into task specs which can then be saved to the database. + Pipeline pipeline.TaskDAG `toml:"observationSource"` +} + +// EthRequestEventSpec conforms to the job.Spec interface +var _ job.Spec = EthRequestEventSpec{} + +func (spec EthRequestEventSpec) JobID() int32 { + return spec.jobID +} + +func (spec EthRequestEventSpec) JobType() job.Type { + return models.EthRequestEventJobType +} + +func (spec EthRequestEventSpec) TaskDAG() pipeline.TaskDAG { + return spec.Pipeline +} + +type ethRequestEventSpecDelegate struct{} + +func (d *ethRequestEventSpecDelegate) JobType() job.Type { + return models.EthRequestEventJobType +} + +func (d *ethRequestEventSpecDelegate) ToDBRow(spec job.Spec) models.JobSpecV2 { + concreteSpec, ok := spec.(EthRequestEventSpec) + if !ok { + panic(fmt.Sprintf("expected a services.EthRequestEventSpec, got %T", spec)) + } + return models.JobSpecV2{ + EthRequestEventSpec: &concreteSpec.EthRequestEventSpec, + Type: string(models.EthRequestEventJobType), + SchemaVersion: concreteSpec.SchemaVersion, + MaxTaskDuration: concreteSpec.MaxTaskDuration, + } +} + +func (d *ethRequestEventSpecDelegate) FromDBRow(spec models.JobSpecV2) job.Spec { + if spec.EthRequestEventSpec == nil { + return nil + } + return &EthRequestEventSpec{ + EthRequestEventSpec: *spec.EthRequestEventSpec, + jobID: spec.ID, + } +} + +func (d *ethRequestEventSpecDelegate) ServicesForSpec(job.Spec) (services []job.Service, err error) { + return +} + +func RegisterEthRequestEventDelegate(jobSpawner job.Spawner) { + jobSpawner.RegisterDelegate( + NewEthRequestEventDelegate(jobSpawner), + ) +} + +func NewEthRequestEventDelegate(jobSpawner job.Spawner) *ethRequestEventSpecDelegate { + return ðRequestEventSpecDelegate{} +} diff --git a/core/services/fluxmonitor/fetchers.go b/core/services/fluxmonitor/fetchers.go index 77a80df0ec2..1c47ec20577 100644 --- a/core/services/fluxmonitor/fetchers.go +++ b/core/services/fluxmonitor/fetchers.go @@ -13,11 +13,11 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/guregu/null" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/shopspring/decimal" "go.uber.org/multierr" + "gopkg.in/guregu/null.v4" ) //go:generate mockery --name Fetcher --output ../../internal/mocks/ --case=underscore diff --git a/core/services/fluxmonitor/fetchers_test.go b/core/services/fluxmonitor/fetchers_test.go index 1276c8d0c91..caebe4eb2c7 100644 --- a/core/services/fluxmonitor/fetchers_test.go +++ b/core/services/fluxmonitor/fetchers_test.go @@ -9,10 +9,10 @@ import ( "testing" "time" - "github.com/guregu/null" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/services/eth/contracts" "github.com/smartcontractkit/chainlink/core/store/models" diff --git a/core/services/fluxmonitor/flux_monitor.go b/core/services/fluxmonitor/flux_monitor.go index aa28019fc42..63f0682dfb8 100644 --- a/core/services/fluxmonitor/flux_monitor.go +++ b/core/services/fluxmonitor/flux_monitor.go @@ -562,9 +562,7 @@ func (p *PollingDeviationChecker) consume() { p.readyForLogs() p.setIsHibernatingStatus() - p.resetTickers(contracts.FluxAggregatorRoundState{ - StartedAt: uint64(time.Now().Unix()), - }) + p.setInitialTickers() p.performInitialPoll() for { @@ -637,7 +635,7 @@ func (p *PollingDeviationChecker) SetOracleAddress() error { } func (p *PollingDeviationChecker) performInitialPoll() { - if !p.initr.PollTimer.Disabled && !p.isHibernating { + if p.shouldPerformInitialPoll() { p.pollIfEligible(DeviationThresholds{ Rel: float64(p.initr.Threshold), Abs: float64(p.initr.AbsoluteThreshold), @@ -645,6 +643,10 @@ func (p *PollingDeviationChecker) performInitialPoll() { } } +func (p *PollingDeviationChecker) shouldPerformInitialPoll() bool { + return !(p.initr.PollTimer.Disabled && p.initr.IdleTimer.Disabled || p.isHibernating) +} + // hibernate restarts the PollingDeviationChecker in hibernation mode func (p *PollingDeviationChecker) hibernate() { logger.Infof("entering hibernation mode for contract: %s", p.initr.Address.Hex()) @@ -656,9 +658,7 @@ func (p *PollingDeviationChecker) hibernate() { func (p *PollingDeviationChecker) reactivate() { logger.Infof("exiting hibernation mode, reactivating contract: %s", p.initr.Address.Hex()) p.isHibernating = false - p.resetTickers(contracts.FluxAggregatorRoundState{ - StartedAt: uint64(time.Now().Unix()), - }) + p.setInitialTickers() p.pollIfEligible(DeviationThresholds{Rel: 0, Abs: 0}) } @@ -725,10 +725,12 @@ func (p *PollingDeviationChecker) processLogs() { func (p *PollingDeviationChecker) respondToAnswerUpdatedLog(log contracts.LogAnswerUpdated) { logger.Debugw("AnswerUpdated log", p.loggerFieldsForAnswerUpdated(log)...) - _, err := p.roundState(0) + roundState, err := p.roundState(0) if err != nil { logger.Errorw(fmt.Sprintf("could not fetch oracleRoundState: %v", err), p.loggerFieldsForAnswerUpdated(log)...) + return } + p.resetTickers(roundState) } // The NewRound log tells us that an oracle has initiated a new round. This tells us that we @@ -800,29 +802,19 @@ func (p *PollingDeviationChecker) respondToNewRoundLog(log contracts.LogNewRound } } - roundStats, err := p.store.FindOrCreateFluxMonitorRoundStats(p.initr.Address, logRoundID) + roundStats, jobRunStatus, err := p.statsAndStatusForRound(logRoundID) if err != nil { - logger.Errorw(fmt.Sprintf("error fetching Flux Monitor round stats from DB: %v", err), p.loggerFieldsForNewRound(log)...) + logger.Errorw(fmt.Sprintf("error determining round stats / run status for round: %v", err), p.loggerFieldsForNewRound(log)...) return } - // JobRun will not exist if this is the first time responding to this round - var jobRun models.JobRun - if roundStats.JobRunID != nil { - jobRun, err = p.store.FindJobRun(roundStats.JobRunID) - if err != nil { - logger.Errorw(fmt.Sprintf("error finding JobRun associated with FMRoundStat: %v", err), p.loggerFieldsForNewRound(log)...) - return - } - } - if roundStats.NumSubmissions > 0 { // This indicates either that: // - We tried to start a round at the same time as another node, and their transaction was mined first, or // - The chain experienced a shallow reorg that unstarted the current round. // If our previous attempt is still pending, return early and don't re-submit // If our previous attempt is already over (completed or errored), we should retry - if !jobRun.Status.Finished() { + if !jobRunStatus.Finished() { logger.Debugw("Ignoring new round request: started round simultaneously with another node", p.loggerFieldsForNewRound(log)...) return } @@ -840,6 +832,7 @@ func (p *PollingDeviationChecker) respondToNewRoundLog(log contracts.LogNewRound logger.Errorw(fmt.Sprintf("Ignoring new round request: error fetching eligibility from contract: %v", err), p.loggerFieldsForNewRound(log)...) return } + p.resetTickers(roundState) err = p.checkEligibilityAndAggregatorFunding(roundState) if err != nil { logger.Infow(fmt.Sprintf("Ignoring new round request: %v", err), p.loggerFieldsForNewRound(log)...) @@ -950,17 +943,18 @@ func (p *PollingDeviationChecker) pollIfEligible(thresholds DeviationThresholds) p.store.UpsertErrorFor(p.JobID(), "Unable to call roundState method on provided contract. Check contract address.") return } + p.resetTickers(roundState) loggerFields = append(loggerFields, "reportableRound", roundState.ReportableRoundID) - // If we've just submitted to this round (as the result of a NewRound log, for example) don't submit again - roundStats, err := p.store.FindOrCreateFluxMonitorRoundStats(p.initr.Address, roundState.ReportableRoundID) + roundStats, jobRunStatus, err := p.statsAndStatusForRound(roundState.ReportableRoundID) if err != nil { - logger.Errorw(fmt.Sprintf("error fetching Flux Monitor round stats from DB: %v", err), loggerFields...) - p.store.UpsertErrorFor(p.JobID(), "Error fetching Flux Monitor round stats from DB") + logger.Errorw(fmt.Sprintf("error determining round stats / run status for round: %v", err), loggerFields...) return } - if roundStats.NumSubmissions > 0 { + // If we've already successfully submitted to this round (ie through a NewRound log) + // and the associated JobRun hasn't errored, skip polling + if roundStats.NumSubmissions > 0 && !jobRunStatus.Errored() { logger.Infow("skipping poll: round already answered, tx unconfirmed", loggerFields...) return } @@ -1026,13 +1020,38 @@ func (p *PollingDeviationChecker) roundState(roundID uint32) (contracts.FluxAggr if err != nil { return contracts.FluxAggregatorRoundState{}, err } - - // Update our tickers to reflect the current on-chain round - p.resetTickers(roundState) - return roundState, nil } +// initialRoundState fetches the round information that the fluxmonitor should use when starting +// new jobs. Choosing the correct round on startup is key to setting timers correctly. +func (p *PollingDeviationChecker) initialRoundState() contracts.FluxAggregatorRoundState { + defaultRoundState := contracts.FluxAggregatorRoundState{ + StartedAt: uint64(time.Now().Unix()), + } + latestRoundData, err := p.fluxAggregator.LatestRoundData() + if err != nil { + logger.Warnf( + "unable to retrieve latestRoundData for FluxAggregator contract %s - defaulting "+ + "to current time for tickers: %v", + p.initr.Address.Hex(), + err, + ) + return defaultRoundState + } + latestRoundState, err := p.fluxAggregator.RoundState(p.oracleAddress, latestRoundData.RoundID) + if err != nil { + logger.Warnf( + "unable to call roundState for latest round, contract: %s, round: %d, err: %v", + p.initr.Address.Hex(), + latestRoundData.RoundID, + err, + ) + return defaultRoundState + } + return latestRoundState +} + func (p *PollingDeviationChecker) resetTickers(roundState contracts.FluxAggregatorRoundState) { p.resetPollTicker() p.resetHibernationTimer() @@ -1040,6 +1059,10 @@ func (p *PollingDeviationChecker) resetTickers(roundState contracts.FluxAggregat p.resetRoundTimer(roundState.TimesOutAt()) } +func (p *PollingDeviationChecker) setInitialTickers() { + p.resetTickers(p.initialRoundState()) +} + func (p *PollingDeviationChecker) resetPollTicker() { if !p.initr.PollTimer.Disabled && !p.isHibernating { p.pollTicker.Resume() @@ -1271,3 +1294,23 @@ func MakeIdleTimer(log contracts.LogNewRound, idleThreshold models.Duration, clo func defaultIdleTimer(idleThreshold models.Duration, clock utils.AfterNower) <-chan time.Time { return clock.After(idleThreshold.Duration()) } + +func (p *PollingDeviationChecker) statsAndStatusForRound(roundID uint32) ( + models.FluxMonitorRoundStats, + models.RunStatus, + error, +) { + roundStats, err := p.store.FindOrCreateFluxMonitorRoundStats(p.initr.Address, roundID) + if err != nil { + return models.FluxMonitorRoundStats{}, "", err + } + // JobRun will not exist if this is the first time responding to this round + var jobRun models.JobRun + if roundStats.JobRunID != nil { + jobRun, err = p.store.FindJobRun(roundStats.JobRunID) + if err != nil { + return models.FluxMonitorRoundStats{}, "", err + } + } + return roundStats, jobRun.Status, nil +} diff --git a/core/services/fluxmonitor/flux_monitor_simulated_blockchain_test.go b/core/services/fluxmonitor/flux_monitor_simulated_blockchain_test.go index 8edb11f0497..16e36f82409 100644 --- a/core/services/fluxmonitor/flux_monitor_simulated_blockchain_test.go +++ b/core/services/fluxmonitor/flux_monitor_simulated_blockchain_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/shopspring/decimal" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/flags_wrapper" faw "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/flux_aggregator_wrapper" @@ -159,289 +160,305 @@ func checkOraclesAdded(t *testing.T, f fluxAggregatorUniverse, oracleList []comm } } -// type answerParams struct { -// fa *fluxAggregatorUniverse -// roundId, answer int64 -// from *bind.TransactOpts -// isNewRound, completesAnswer bool -// } - -// // checkSubmission verifies all the logs emitted by fa's FluxAggregator -// // contract after an updateAnswer with the given values. -// func checkSubmission(t *testing.T, p answerParams, -// currentBalance int64, receiptBlock uint64) { -// t.Helper() -// if receiptBlock == 0 { -// receiptBlock = p.fa.backend.Blockchain().CurrentBlock().Number().Uint64() -// } -// fromBlock := &bind.FilterOpts{Start: receiptBlock, End: &receiptBlock} - -// // Could filter for the known values here, but while that would be more -// // succinct it leads to less informative error messages... Did the log not -// // appear at all, or did it just have a wrong value? -// ilogs, err := p.fa.aggregatorContract.FilterSubmissionReceived(fromBlock, []*big.Int{}, []uint32{}, []common.Address{}) -// require.NoError(t, err, "failed to get SubmissionReceived logs") - -// var srlogs []*faw.FluxAggregatorSubmissionReceived -// _ = cltest.GetLogs(t, &srlogs, ilogs) -// require.Len(t, srlogs, 1, "FluxAggregator did not emit correct SubmissionReceived log") -// assert.Equal(t, uint32(p.roundId), srlogs[0].Round, "SubmissionReceived log has wrong round") -// assert.Equal(t, p.from.From, srlogs[0].Oracle, "SubmissionReceived log has wrong oracle") - -// inrlogs, err := p.fa.aggregatorContract.FilterNewRound(fromBlock, []*big.Int{}, []common.Address{}) -// require.NoError(t, err, "failed to get NewRound logs") - -// if p.isNewRound { -// var nrlogs []*faw.FluxAggregatorNewRound -// cltest.GetLogs(t, &nrlogs, inrlogs) -// require.Len(t, nrlogs, 1, "FluxAggregator did not emit correct NewRound log") -// assert.Equal(t, p.roundId, nrlogs[0].RoundId.Int64(), "NewRound log has wrong roundId") -// assert.Equal(t, p.from.From, nrlogs[0].StartedBy, "NewRound log started by wrong oracle") -// } else { -// assert.Len(t, cltest.GetLogs(t, nil, inrlogs), 0, "FluxAggregator emitted unexpected NewRound log") -// } - -// iaflogs, err := p.fa.aggregatorContract.FilterAvailableFundsUpdated(fromBlock, []*big.Int{}) -// require.NoError(t, err, "failed to get AvailableFundsUpdated logs") - -// var aflogs []*faw.FluxAggregatorAvailableFundsUpdated -// _ = cltest.GetLogs(t, &aflogs, iaflogs) -// assert.Len(t, aflogs, 1, "FluxAggregator did not emit correct AvailableFundsUpdated log") -// assert.Equal(t, currentBalance-fee, aflogs[0].Amount.Int64(), "AvailableFundsUpdated log has wrong amount") - -// iaulogs, err := p.fa.aggregatorContract.FilterAnswerUpdated(fromBlock, -// []*big.Int{big.NewInt(p.answer)}, []*big.Int{big.NewInt(p.roundId)}) -// require.NoError(t, err, "failed to get AnswerUpdated logs") -// if p.completesAnswer { -// var aulogs []*faw.FluxAggregatorAnswerUpdated -// _ = cltest.GetLogs(t, &aulogs, iaulogs) -// assert.Len(t, aulogs, 1, "FluxAggregator did not emit correct AnswerUpdated log") -// assert.Equal(t, p.roundId, aulogs[0].RoundId.Int64(), "AnswerUpdated log has wrong roundId") -// assert.Equal(t, p.answer, aulogs[0].Current.Int64(), "AnswerUpdated log has wrong current value") -// } -// } - -// // currentbalance returns the current balance of fa's FluxAggregator -// func currentBalance(t *testing.T, fa *fluxAggregatorUniverse) *big.Int { -// currentBalance, err := fa.aggregatorContract.AvailableFunds(nil) -// require.NoError(t, err, "failed to get current FA balance") -// return currentBalance -// } - -// // submitAnswer simulates a call to fa's FluxAggregator contract from from, with -// // the given roundId and answer, and checks that all the logs emitted by the -// // contract are correct -// func submitAnswer(t *testing.T, p answerParams) { -// cb := currentBalance(t, p.fa) -// _, err := p.fa.aggregatorContract.Submit(p.from, big.NewInt(p.roundId), big.NewInt(p.answer)) -// require.NoError(t, err, "failed to initialize first flux aggregation round:") - -// p.fa.backend.Commit() -// checkSubmission(t, p, cb.Int64(), 0) -// } - -// type maliciousFluxMonitor interface { -// CreateJob(t *testing.T, jobSpecId *models.ID, polledAnswer decimal.Decimal, nextRound *big.Int) error -// } - -func waitForRunsAndEthTxCount( - t *testing.T, - job models.JobSpec, - runCount int, - app *cltest.TestApplication, - backend *backends.SimulatedBackend, -) []models.JobRun { +type answerParams struct { + fa *fluxAggregatorUniverse + roundId, answer int64 + from *bind.TransactOpts + isNewRound, completesAnswer bool +} + +// checkSubmission verifies all the logs emitted by fa's FluxAggregator +// contract after an updateAnswer with the given values. +func checkSubmission(t *testing.T, p answerParams, + currentBalance int64, receiptBlock uint64) { t.Helper() - store := app.Store - jrs := cltest.WaitForRuns(t, job, store, runCount) // Submit answer from - app.EthBroadcaster.Trigger() - txes := cltest.WaitForEthTxCount(t, store, runCount) - txas := cltest.WaitForEthTxAttemptsForEthTx(t, store, txes[0]) - cltest.WaitForTxInMempool(t, backend, txas[0].Hash) - - backend.Commit() - return jrs + if receiptBlock == 0 { + receiptBlock = p.fa.backend.Blockchain().CurrentBlock().Number().Uint64() + } + blockRange := &bind.FilterOpts{Start: 0, End: &receiptBlock} + + // Could filter for the known values here, but while that would be more + // succinct it leads to less informative error messages... Did the log not + // appear at all, or did it just have a wrong value? + ilogs, err := p.fa.aggregatorContract.FilterSubmissionReceived( + blockRange, + []*big.Int{big.NewInt(p.answer)}, + []uint32{uint32(p.roundId)}, + []common.Address{p.from.From}, + ) + require.NoError(t, err, "failed to get SubmissionReceived logs") + + var srlogs []*faw.FluxAggregatorSubmissionReceived + _ = cltest.GetLogs(t, &srlogs, ilogs) + require.Len(t, srlogs, 1, "FluxAggregator did not emit correct "+ + "SubmissionReceived log") + + inrlogs, err := p.fa.aggregatorContract.FilterNewRound( + blockRange, []*big.Int{big.NewInt(p.roundId)}, []common.Address{p.from.From}, + ) + require.NoError(t, err, "failed to get NewRound logs") + + if p.isNewRound { + var nrlogs []*faw.FluxAggregatorNewRound + cltest.GetLogs(t, &nrlogs, inrlogs) + require.Len(t, nrlogs, 1, "FluxAggregator did not emit correct NewRound "+ + "log") + } else { + assert.Len(t, cltest.GetLogs(t, nil, inrlogs), 0, "FluxAggregator emitted "+ + "unexpected NewRound log") + } + + iaflogs, err := p.fa.aggregatorContract.FilterAvailableFundsUpdated( + blockRange, []*big.Int{big.NewInt(currentBalance - fee)}, + ) + require.NoError(t, err, "failed to get AvailableFundsUpdated logs") + var aflogs []*faw.FluxAggregatorAvailableFundsUpdated + _ = cltest.GetLogs(t, &aflogs, iaflogs) + assert.Len(t, aflogs, 1, "FluxAggregator did not emit correct "+ + "AvailableFundsUpdated log") + + iaulogs, err := p.fa.aggregatorContract.FilterAnswerUpdated(blockRange, + []*big.Int{big.NewInt(p.answer)}, []*big.Int{big.NewInt(p.roundId)}, + ) + require.NoError(t, err, "failed to get AnswerUpdated logs") + if p.completesAnswer { + var aulogs []*faw.FluxAggregatorAnswerUpdated + _ = cltest.GetLogs(t, &aulogs, iaulogs) + // XXX: sometimes this log is repeated; don't know why... + assert.NotEmpty(t, aulogs, "FluxAggregator did not emit correct "+ + "AnswerUpdated log") + } } -// TODO: This test is non-deterministic and needs to be rewritten or rethought -// See: https://www.pivotaltracker.com/story/show/175757546 -// func TestFluxMonitorAntiSpamLogic(t *testing.T) { -// t.Skip() -// // Comments starting with "-" describe the steps this test executes. - -// // - deploy a brand new FM contract -// fa := setupFluxAggregatorUniverse(t) - -// // - add oracles -// oracleList := []common.Address{fa.neil.From, fa.ned.From, fa.nallory.From} -// _, err := fa.aggregatorContract.ChangeOracles(fa.sergey, emptyList, oracleList, oracleList, 2, 3, 2) -// assert.NoError(t, err, "failed to add oracles to aggregator") -// fa.backend.Commit() -// checkOraclesAdded(t, fa, oracleList) - -// // Set up chainlink app -// config, cfgCleanup := cltest.NewConfig(t) -// config.Config.Set("DEFAULT_HTTP_TIMEOUT", "100ms") -// config.Config.Set("TRIGGER_FALLBACK_DB_POLL_INTERVAL", "1s") -// defer cfgCleanup() -// app, cleanup := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, fa.backend) -// defer cleanup() -// require.NoError(t, app.StartAndConnect()) -// minFee := app.Store.Config.MinimumContractPayment().ToInt().Int64() -// require.Equal(t, fee, minFee, "fee paid by FluxAggregator (%d) must at "+ -// "least match MinimumContractPayment (%s). (Which is currently set in "+ -// "cltest.go.)", fee, minFee) - -// answer := int64(1) // Answer the nodes give on the first round - -// //- have one of the fake nodes start a round. -// roundId := int64(1) -// processedAnswer := answer * 100 // [> job has multiply times 100 <] -// submitAnswer(t, answerParams{ -// fa: &fa, -// roundId: roundId, -// answer: processedAnswer, -// from: fa.neil, -// isNewRound: true, -// completesAnswer: false, -// }) - -// // - successfully close the round through the submissions of the other nodes -// // Response by malicious chainlink node, nallory -// initialBalance := currentBalance(t, &fa).Int64() -// reportPrice := answer -// priceResponse := func() string { -// return fmt.Sprintf(`{"data":{"result": %d}}`, atomic.LoadInt64(&reportPrice)) -// } -// mockServer := cltest.NewHTTPMockServerWithAlterableResponse(t, priceResponse) -// defer mockServer.Close() - -// // When event appears on submissionReceived, flux monitor job run is complete -// submissionReceived := make(chan *faw.FluxAggregatorSubmissionReceived) -// subscription, err := fa.aggregatorContract.WatchSubmissionReceived( -// nil, -// submissionReceived, -// []*big.Int{}, -// []uint32{}, -// []common.Address{fa.nallory.From}, -// ) -// require.NoError(t, err, "failed to subscribe to SubmissionReceived events") -// defer subscription.Unsubscribe() - -// // Create FM Job, and wait for job run to start (the above UpdateAnswer call -// // to FluxAggregator contract initiates a run.) -// buffer := cltest.MustReadFile(t, "../../internal/testdata/flux_monitor_job.json") -// var job models.JobSpec -// require.NoError(t, json.Unmarshal(buffer, &job)) -// initr := &job.Initiators[0] -// initr.InitiatorParams.Feeds = cltest.JSONFromString(t, fmt.Sprintf(`["%s"]`, mockServer.URL)) -// initr.InitiatorParams.PollTimer.Period = models.MustMakeDuration(pollTimerPeriod) -// initr.InitiatorParams.Address = fa.aggregatorContractAddress - -// j := cltest.CreateJobSpecViaWeb(t, app, job) -// jrs := waitForRunsAndEthTxCount(t, j, 1, app, fa.backend) - -// reportedPrice := jrs[0].RunRequest.RequestParams.Get("result").String() -// assert.Equal(t, reportedPrice, fmt.Sprintf("%d", atomic.LoadInt64(&reportPrice)), "failed to report correct price to contract") -// var receiptBlock uint64 -// select { // block until FluxAggregator contract acknowledges chainlink message -// case log := <-submissionReceived: -// receiptBlock = log.Raw.BlockNumber -// case <-time.After(pollTimerPeriod): -// t.Fatalf("chainlink failed to submit answer to FluxAggregator contract") -// } -// checkSubmission(t, -// answerParams{ -// fa: &fa, -// roundId: roundId, -// answer: processedAnswer, -// from: fa.nallory, -// isNewRound: false, -// completesAnswer: true}, -// initialBalance, -// receiptBlock, -// ) - -// //- have the malicious node start the next round. -// nextRoundBalance := initialBalance - fee -// // Triggers a new round, since price deviation exceeds threshold -// atomic.StoreInt64(&reportPrice, answer+1) - -// waitForRunsAndEthTxCount(t, j, 2, app, fa.backend) - -// select { -// case log := <-submissionReceived: -// receiptBlock = log.Raw.BlockNumber -// case <-time.After(2 * pollTimerPeriod): -// t.Fatalf("chainlink failed to submit answer to FluxAggregator contract") -// } -// newRound := roundId + 1 -// processedAnswer = 100 * atomic.LoadInt64(&reportPrice) -// checkSubmission(t, -// answerParams{ -// fa: &fa, -// roundId: newRound, -// answer: processedAnswer, -// from: fa.nallory, -// isNewRound: true, -// completesAnswer: false}, -// nextRoundBalance, -// receiptBlock, -// ) - -// // Successfully close the round through the submissions of the other nodes -// submitAnswer(t, -// answerParams{ -// fa: &fa, -// roundId: newRound, -// answer: processedAnswer, -// from: fa.neil, -// isNewRound: false, -// completesAnswer: true}, -// ) - -// // Have the malicious node try to start another round repeatedly until the -// // roundDelay is reached, making sure that it isn't successful -// newRound = newRound + 1 -// processedAnswer = 100 * atomic.LoadInt64(&reportPrice) -// precision := job.Initiators[0].InitiatorParams.Precision -// // FORCE node to try to start a new round -// err = app.FluxMonitor.(maliciousFluxMonitor).CreateJob(t, j.ID, decimal.New(processedAnswer, precision), big.NewInt(newRound)) -// require.NoError(t, err) - -// waitForRunsAndEthTxCount(t, j, 3, app, fa.backend) - -// select { -// case <-submissionReceived: -// t.Fatalf("FA allowed chainlink node to start a new round early") -// case <-time.After(5 * pollTimerPeriod): -// } -// // Remove the record of the submitted round, or else FM's reorg protection will cause the test to fail -// err = app.Store.DeleteFluxMonitorRoundsBackThrough(fa.aggregatorContractAddress, uint32(newRound)) -// require.NoError(t, err) - -// // Try to start a new round directly, should fail -// _, err = fa.aggregatorContract.RequestNewRound(fa.nallory) -// assert.Error(t, err, "FA allowed chainlink node to start a new round early") - -// //- finally, ensure it can start a legitimate round after roundDelay is reached -// // start an intervening round -// submitAnswer(t, answerParams{fa: &fa, roundId: newRound, -// answer: processedAnswer, from: fa.ned, isNewRound: true, -// completesAnswer: false}) -// submitAnswer(t, answerParams{fa: &fa, roundId: newRound, -// answer: processedAnswer, from: fa.neil, isNewRound: false, -// completesAnswer: true}) -// // start a legitimate new round -// atomic.StoreInt64(&reportPrice, reportPrice+3) -// waitForRunsAndEthTxCount(t, j, 4, app, fa.backend) - -// select { -// case <-submissionReceived: -// case <-time.After(5 * pollTimerPeriod): -// t.Fatalf("could not start a new round, even though delay has passed") -// } -// } +// currentbalance returns the current balance of fa's FluxAggregator +func currentBalance(t *testing.T, fa *fluxAggregatorUniverse) *big.Int { + currentBalance, err := fa.aggregatorContract.AvailableFunds(nil) + require.NoError(t, err, "failed to get current FA balance") + return currentBalance +} + +// submitAnswer simulates a call to fa's FluxAggregator contract from from, with +// the given roundId and answer, and checks that all the logs emitted by the +// contract are correct +func submitAnswer(t *testing.T, p answerParams) { + cb := currentBalance(t, p.fa) + + // used to ensure that the simulated backend has processed the submission, + // before we search for the log and checek it. + srCh := make(chan *faw.FluxAggregatorSubmissionReceived) + fromBlock := uint64(0) + srSubscription, err := p.fa.aggregatorContract.WatchSubmissionReceived( + &bind.WatchOpts{Start: &fromBlock}, + srCh, + []*big.Int{big.NewInt(p.answer)}, + []uint32{uint32(p.roundId)}, + []common.Address{p.from.From}, + ) + defer func() { + srSubscription.Unsubscribe() + err = <-srSubscription.Err() + require.NoError(t, err, "failed to unsubscribe from AvailableFundsUpdated logs") + }() + + _, err = p.fa.aggregatorContract.Submit( + p.from, big.NewInt(p.roundId), big.NewInt(p.answer), + ) + require.NoError(t, err, "failed to submit answer to flux aggregator") + + p.fa.backend.Commit() + + select { + case <-srCh: + case <-time.After(5 * time.Second): + t.Fatalf("failed to complete submission to flux aggregator") + } + checkSubmission(t, p, cb.Int64(), 0) +} + +func awaitSubmission(t *testing.T, submissionReceived chan *faw.FluxAggregatorSubmissionReceived) ( + receiptBlock uint64, answer int64, +) { + select { // block until FluxAggregator contract acknowledges chainlink message + case log := <-submissionReceived: + return log.Raw.BlockNumber, log.Submission.Int64() + case <-time.After(10 * pollTimerPeriod): + t.Fatalf("chainlink failed to submit answer to FluxAggregator contract") + return 0, 0 // unreachable + } +} + +type maliciousFluxMonitor interface { + CreateJob(t *testing.T, jobSpecId *models.ID, polledAnswer decimal.Decimal, nextRound *big.Int) error +} + +func TestFluxMonitorAntiSpamLogic(t *testing.T) { + // Comments starting with "-" describe the steps this test executes. + + // - deploy a brand new FM contract + fa := setupFluxAggregatorUniverse(t) + + // - add oracles + oracleList := []common.Address{fa.neil.From, fa.ned.From, fa.nallory.From} + _, err := fa.aggregatorContract.ChangeOracles(fa.sergey, emptyList, oracleList, oracleList, 2, 3, 2) + assert.NoError(t, err, "failed to add oracles to aggregator") + fa.backend.Commit() + checkOraclesAdded(t, fa, oracleList) + + // Set up chainlink app + config, cfgCleanup := cltest.NewConfig(t) + config.Config.Set("DEFAULT_HTTP_TIMEOUT", "100ms") + config.Config.Set("TRIGGER_FALLBACK_DB_POLL_INTERVAL", "1s") + defer cfgCleanup() + app, cleanup := cltest.NewApplicationWithConfigAndKeyOnSimulatedBlockchain(t, config, fa.backend) + defer cleanup() + require.NoError(t, app.StartAndConnect()) + minFee := app.Store.Config.MinimumContractPayment().ToInt().Int64() + require.Equal(t, fee, minFee, "fee paid by FluxAggregator (%d) must at "+ + "least match MinimumContractPayment (%s). (Which is currently set in "+ + "cltest.go.)", fee, minFee) + + answer := int64(1) // Answer the nodes give on the first round + + //- have one of the fake nodes start a round. + roundId := int64(1) + processedAnswer := answer * 100 /* job has multiply times 100 */ + submitAnswer(t, answerParams{ + fa: &fa, + roundId: roundId, + answer: processedAnswer, + from: fa.neil, + isNewRound: true, + completesAnswer: false, + }) + + // - successfully close the round through the submissions of the other nodes + // Response by malicious chainlink node, nallory + initialBalance := currentBalance(t, &fa).Int64() + reportPrice := answer + priceResponse := func() string { + return fmt.Sprintf(`{"data":{"result": %d}}`, atomic.LoadInt64(&reportPrice)) + } + mockServer := cltest.NewHTTPMockServerWithAlterableResponse(t, priceResponse) + defer mockServer.Close() + + // When event appears on submissionReceived, flux monitor job run is complete + submissionReceived := make(chan *faw.FluxAggregatorSubmissionReceived) + subscription, err := fa.aggregatorContract.WatchSubmissionReceived( + nil, + submissionReceived, + []*big.Int{}, + []uint32{}, + []common.Address{fa.nallory.From}, + ) + require.NoError(t, err, "failed to subscribe to SubmissionReceived events") + defer subscription.Unsubscribe() + + // Create FM Job, and wait for job run to start (the above UpdateAnswer call + // to FluxAggregator contract initiates a run.) + buffer := cltest.MustReadFile(t, "../../internal/testdata/flux_monitor_job.json") + var job models.JobSpec + require.NoError(t, json.Unmarshal(buffer, &job)) + initr := &job.Initiators[0] + initr.InitiatorParams.Feeds = cltest.JSONFromString(t, fmt.Sprintf(`["%s"]`, mockServer.URL)) + initr.InitiatorParams.PollTimer.Period = models.MustMakeDuration(pollTimerPeriod) + initr.InitiatorParams.Address = fa.aggregatorContractAddress + + j := cltest.CreateJobSpecViaWeb(t, app, job) + + receiptBlock, answer := awaitSubmission(t, submissionReceived) + + assert.Equal(t, 100*atomic.LoadInt64(&reportPrice), answer, + "failed to report correct price to contract") + checkSubmission(t, + answerParams{ + fa: &fa, + roundId: roundId, + answer: processedAnswer, + from: fa.nallory, + isNewRound: false, + completesAnswer: true}, + initialBalance, + receiptBlock, + ) + + //- have the malicious node start the next round. + nextRoundBalance := initialBalance - fee + // Triggers a new round, since price deviation exceeds threshold + atomic.StoreInt64(&reportPrice, answer+1) + + receiptBlock, _ = awaitSubmission(t, submissionReceived) + newRound := roundId + 1 + processedAnswer = 100 * atomic.LoadInt64(&reportPrice) + checkSubmission(t, + answerParams{ + fa: &fa, + roundId: newRound, + answer: processedAnswer, + from: fa.nallory, + isNewRound: true, + completesAnswer: false}, + nextRoundBalance, + receiptBlock, + ) + + // Successfully close the round through the submissions of the other nodes + submitAnswer(t, + answerParams{ + fa: &fa, + roundId: newRound, + answer: processedAnswer, + from: fa.neil, + isNewRound: false, + completesAnswer: true}, + ) + + // Have the malicious node try to start another round repeatedly until the + // restartDelay is reached, making sure that it isn't successful + newRound = newRound + 1 + processedAnswer = 100 * atomic.LoadInt64(&reportPrice) + precision := job.Initiators[0].InitiatorParams.Precision + // FORCE node to try to start a new round + err = app.FluxMonitor.(maliciousFluxMonitor).CreateJob(t, j.ID, decimal.New(processedAnswer, precision), big.NewInt(newRound)) + require.NoError(t, err) + + select { + case <-submissionReceived: + t.Fatalf("FA allowed chainlink node to start a new round early") + case <-time.After(5 * pollTimerPeriod): + } + // Remove the record of the submitted round, or else FM's reorg protection will cause the test to fail + err = app.Store.DeleteFluxMonitorRoundsBackThrough(fa.aggregatorContractAddress, uint32(newRound)) + require.NoError(t, err) + + // Try to start a new round directly, should fail + _, err = fa.aggregatorContract.RequestNewRound(fa.nallory) + assert.Error(t, err, "FA allowed chainlink node to start a new round early") + + //- finally, ensure it can start a legitimate round after restartDelay is + //reached start an intervening round + submitAnswer(t, answerParams{fa: &fa, roundId: newRound, + answer: processedAnswer, from: fa.ned, isNewRound: true, + completesAnswer: false}) + submitAnswer(t, answerParams{fa: &fa, roundId: newRound, + answer: processedAnswer, from: fa.neil, isNewRound: false, + completesAnswer: true}) + // start a legitimate new round + atomic.StoreInt64(&reportPrice, reportPrice+3) + + // Wait for the expected SubmissionReceived log. For some reason, the above + // waitForRunsAndAttemptsCount's backend.Commit() sometimes does not catch the + // resulting job. + waitCount := 20 + select { + case <-submissionReceived: + case <-time.After(1 * time.Second): + waitCount-- + if waitCount == 0 { + t.Fatalf("could not start a new round, even though delay has passed") + } + fa.backend.Commit() + } +} func TestFluxMonitor_HibernationMode(t *testing.T) { fa := setupFluxAggregatorUniverse(t) @@ -483,8 +500,8 @@ func TestFluxMonitor_HibernationMode(t *testing.T) { require.NoError(t, err, "failed to subscribe to SubmissionReceived events") defer subscription.Unsubscribe() - // // Create FM Job, and wait for job run to start (the above UpdateAnswer call - // // to FluxAggregator contract initiates a run.) + // Create FM Job, and wait for job run to start (the above UpdateAnswer call + // to FluxAggregator contract initiates a run.) buffer := cltest.MustReadFile(t, "../../internal/testdata/flux_monitor_job.json") var job models.JobSpec require.NoError(t, json.Unmarshal(buffer, &job)) @@ -506,21 +523,20 @@ func TestFluxMonitor_HibernationMode(t *testing.T) { // lower global kill switch flag - should trigger job run fa.flagsContract.LowerFlags(fa.sergey, []common.Address{utils.ZeroAddress}) fa.backend.Commit() - waitForRunsAndEthTxCount(t, job, 1, app, fa.backend) + _, _ = awaitSubmission(t, submissionReceived) - // change in price should trigger run - reportPrice = int64(2) - waitForRunsAndEthTxCount(t, job, 2, app, fa.backend) + reportPrice = int64(2) // change in price should trigger run + _, _ = awaitSubmission(t, submissionReceived) // lower contract's flag - should have no effect (but currently does) // TODO - https://www.pivotaltracker.com/story/show/175419789 fa.flagsContract.LowerFlags(fa.sergey, []common.Address{initr.Address}) fa.backend.Commit() - waitForRunsAndEthTxCount(t, job, 3, app, fa.backend) + _, _ = awaitSubmission(t, submissionReceived) // change in price should trigger run reportPrice = int64(4) - waitForRunsAndEthTxCount(t, job, 4, app, fa.backend) + _, _ = awaitSubmission(t, submissionReceived) // raise both flags fa.flagsContract.RaiseFlag(fa.sergey, initr.Address) @@ -537,5 +553,9 @@ func TestFluxMonitor_HibernationMode(t *testing.T) { // change in price should not trigger run reportPrice = int64(8) - cltest.AssertRunsStays(t, job, app.Store, 4) + select { + case <-submissionReceived: + t.Fatalf("should not trigger a new run, while flag is raised") + case <-time.After(5 * time.Second): + } } diff --git a/core/services/fluxmonitor/flux_monitor_test.go b/core/services/fluxmonitor/flux_monitor_test.go index 0eb937bfe34..252bfd4551c 100644 --- a/core/services/fluxmonitor/flux_monitor_test.go +++ b/core/services/fluxmonitor/flux_monitor_test.go @@ -34,10 +34,22 @@ import ( const oracleCount uint8 = 17 +type answerSet struct{ latestAnswer, polledAnswer int64 } + var ( submitHash = utils.MustHash("submit(uint256,int256)") submitSelector = submitHash[:4] oracles = []common.Address{cltest.DefaultKeyAddress, cltest.NewAddress()} + now = func() uint64 { return uint64(time.Now().UTC().Unix()) } + + makeRoundDataForRoundID = func(roundID uint32) contracts.FluxAggregatorRoundData { + return contracts.FluxAggregatorRoundData{ + RoundID: roundID, + } + } + freshContractRoundDataResponse = func() (contracts.FluxAggregatorRoundData, error) { + return contracts.FluxAggregatorRoundData{}, errors.New("unstarted") + } ) func ensureAccount(t *testing.T, store *store.Store) common.Address { @@ -117,89 +129,59 @@ func TestConcreteFluxMonitor_AddJobRemoveJob(t *testing.T) { func TestPollingDeviationChecker_PollIfEligible(t *testing.T) { t.Parallel() + tests := []struct { name string eligible bool connected bool funded bool - threshold float64 - absoluteThreshold float64 - latestAnswer int64 - polledAnswer int64 + answersDeviate bool + hasPreviousRun bool + previousRunStatus models.RunStatus expectedToPoll bool expectedToSubmit bool }{ - {name: "eligible, connected, funded, threshold > 0, answers deviate", - eligible: true, connected: true, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 1, polledAnswer: 100, - expectedToPoll: true, expectedToSubmit: true}, - {name: "eligible, connected, funded, threshold > 0, answers do not deviate", - eligible: true, connected: true, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: true, expectedToSubmit: false}, - - {name: "eligible, disconnected, funded, threshold > 0, answers deviate", - eligible: true, connected: false, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 1, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - {name: "eligible, disconnected, funded, threshold > 0, answers do not deviate", - eligible: true, connected: false, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - - {name: "ineligible, connected, funded, threshold > 0, answers deviate", - eligible: false, connected: true, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 1, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - {name: "ineligible, connected, funded, threshold > 0, answers do not deviate", - eligible: false, connected: true, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - - {name: "ineligible, disconnected, funded, threshold > 0, answers deviate", - eligible: false, connected: false, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 1, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - {name: "ineligible, disconnected, funded, threshold > 0, answers do not deviate", - eligible: false, connected: false, funded: true, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - - {name: "eligible, connected, underfunded, threshold > 0, answers deviate", - eligible: true, connected: true, funded: false, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 1, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - {name: "eligible, connected, underfunded, threshold > 0, answers do not deviate", - eligible: true, connected: true, funded: false, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - - {name: "eligible, disconnected, underfunded, threshold > 0, answers deviate", - eligible: true, connected: false, funded: false, threshold: 0.1, - absoluteThreshold: 1, latestAnswer: 200, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - {name: "eligible, disconnected, underfunded, threshold > 0, answers do not deviate", - eligible: true, connected: false, funded: false, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - - {name: "ineligible, connected, underfunded, threshold > 0, answers deviate", - eligible: false, connected: true, funded: false, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 1, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - {name: "ineligible, connected, underfunded, threshold > 0, answers do not deviate", - eligible: false, connected: true, funded: false, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - - {name: "ineligible, disconnected, underfunded, threshold > 0, answers deviate", - eligible: false, connected: false, funded: false, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 1, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, - {name: "ineligible, disconnected, underfunded, threshold > 0, answers do not deviate", - eligible: false, connected: false, funded: false, threshold: 0.1, - absoluteThreshold: 200, latestAnswer: 100, polledAnswer: 100, - expectedToPoll: false, expectedToSubmit: false}, + { + name: "elligible", + eligible: true, connected: true, funded: true, answersDeviate: true, + expectedToPoll: true, expectedToSubmit: true, + }, { + name: "ineligible", + eligible: false, connected: true, funded: true, answersDeviate: true, + expectedToPoll: false, expectedToSubmit: false, + }, { + name: "disconnected", + eligible: true, connected: false, funded: true, answersDeviate: true, + expectedToPoll: false, expectedToSubmit: false, + }, { + name: "under funded", + eligible: true, connected: true, funded: false, answersDeviate: true, + expectedToPoll: false, expectedToSubmit: false, + }, { + name: "answer undeviated", + eligible: true, connected: true, funded: true, answersDeviate: false, + expectedToPoll: true, expectedToSubmit: false, + }, { + name: "previous job run completed", + eligible: true, connected: true, funded: true, answersDeviate: true, + hasPreviousRun: true, previousRunStatus: models.RunStatusCompleted, + expectedToPoll: false, expectedToSubmit: false, + }, { + name: "previous job run in progress", + eligible: true, connected: true, funded: true, answersDeviate: true, + hasPreviousRun: true, previousRunStatus: models.RunStatusInProgress, + expectedToPoll: false, expectedToSubmit: false, + }, { + name: "previous job run cancelled", + eligible: true, connected: true, funded: true, answersDeviate: true, + hasPreviousRun: true, previousRunStatus: models.RunStatusCancelled, + expectedToPoll: false, expectedToSubmit: false, + }, { + name: "previous job run errored", + eligible: true, connected: true, funded: true, answersDeviate: true, + hasPreviousRun: true, previousRunStatus: models.RunStatusErrored, + expectedToPoll: true, expectedToSubmit: true, + }, } store, cleanup := cltest.NewStore(t) @@ -207,97 +189,110 @@ func TestPollingDeviationChecker_PollIfEligible(t *testing.T) { nodeAddr := ensureAccount(t, store) + const reportableRoundID = 2 + thresholds := struct{ abs, rel float64 }{0.1, 200} + deviatedAnswers := answerSet{1, 100} + undeviatedAnswers := answerSet{100, 101} + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { - // Run one test for relative thresholds, one for absolute thresholds - for _, thresholds := range []struct{ abs, rel float64 }{{0.1, 200}, {1, 10}} { - test := test // Copy test so that for loop can overwrite test during asynchronous operation (t.Parallel()) - test.threshold = thresholds.rel - test.absoluteThreshold = thresholds.abs - t.Run(test.name, func(t *testing.T) { - rm := new(mocks.RunManager) - fetcher := new(mocks.Fetcher) - fluxAggregator := new(mocks.FluxAggregator) - logBroadcaster := new(mocks.LogBroadcaster) - - job := cltest.NewJobWithFluxMonitorInitiator() - initr := job.Initiators[0] - initr.ID = 1 - require.NoError(t, store.CreateJob(&job)) - - const reportableRoundID = 2 - latestAnswerNoPrecision := test.latestAnswer * int64(math.Pow10(int(initr.InitiatorParams.Precision))) - - var availableFunds *big.Int - var paymentAmount *big.Int - minPayment := store.Config.MinimumContractPayment().ToInt() - if test.funded { - availableFunds = big.NewInt(1).Mul(big.NewInt(10000), minPayment) - paymentAmount = minPayment - } else { - availableFunds = big.NewInt(1) - paymentAmount = minPayment - } + answers := undeviatedAnswers + if test.answersDeviate { + answers = deviatedAnswers + } - roundState := contracts.FluxAggregatorRoundState{ - ReportableRoundID: reportableRoundID, - EligibleToSubmit: test.eligible, - LatestAnswer: big.NewInt(latestAnswerNoPrecision), - AvailableFunds: availableFunds, - PaymentAmount: paymentAmount, - OracleCount: oracleCount, - } - fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(roundState, nil).Maybe() + rm := new(mocks.RunManager) + fetcher := new(mocks.Fetcher) + fluxAggregator := new(mocks.FluxAggregator) + logBroadcaster := new(mocks.LogBroadcaster) - if test.expectedToPoll { - fetcher.On("Fetch", mock.Anything).Return(decimal.NewFromInt(test.polledAnswer), nil) - } + job := cltest.NewJobWithFluxMonitorInitiator() + initr := job.Initiators[0] + initr.ID = 1 + require.NoError(t, store.CreateJob(&job)) + + if test.hasPreviousRun { + run := cltest.NewJobRun(job) + run.Status = test.previousRunStatus + require.NoError(t, store.CreateJobRun(&run)) + _, err := store.FindOrCreateFluxMonitorRoundStats(initr.Address, reportableRoundID) + require.NoError(t, err) + store.UpdateFluxMonitorRoundStats(initr.Address, reportableRoundID, run.ID) + } - if test.expectedToSubmit { - run := cltest.NewJobRun(job) - require.NoError(t, store.CreateJobRun(&run)) + latestAnswerNoPrecision := answers.latestAnswer * int64(math.Pow10(int(initr.InitiatorParams.Precision))) + + var availableFunds *big.Int + var paymentAmount *big.Int + minPayment := store.Config.MinimumContractPayment().ToInt() + if test.funded { + availableFunds = big.NewInt(1).Mul(big.NewInt(10000), minPayment) + paymentAmount = minPayment + } else { + availableFunds = big.NewInt(1) + paymentAmount = minPayment + } - data, err := models.ParseJSON([]byte(fmt.Sprintf(`{ + roundState := contracts.FluxAggregatorRoundState{ + ReportableRoundID: reportableRoundID, + EligibleToSubmit: test.eligible, + LatestAnswer: big.NewInt(latestAnswerNoPrecision), + AvailableFunds: availableFunds, + PaymentAmount: paymentAmount, + OracleCount: oracleCount, + } + fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(roundState, nil).Maybe() + + if test.expectedToPoll { + fetcher.On("Fetch", mock.Anything).Return(decimal.NewFromInt(answers.polledAnswer), nil) + } + + if test.expectedToSubmit { + run := cltest.NewJobRun(job) + require.NoError(t, store.CreateJobRun(&run)) + + data, err := models.ParseJSON([]byte(fmt.Sprintf(`{ "result": "%d", "address": "%s", "functionSelector": "0x%x", "dataPrefix": "0x000000000000000000000000000000000000000000000000000000000000000%d" - }`, test.polledAnswer, initr.InitiatorParams.Address.Hex(), submitSelector, reportableRoundID))) - require.NoError(t, err) + }`, answers.polledAnswer, initr.InitiatorParams.Address.Hex(), submitSelector, reportableRoundID))) + require.NoError(t, err) - rm.On("Create", job.ID, &initr, mock.Anything, mock.MatchedBy(func(runRequest *models.RunRequest) bool { - return reflect.DeepEqual(runRequest.RequestParams.Result.Value(), data.Result.Value()) - })).Return(&run, nil) + rm.On("Create", job.ID, &initr, mock.Anything, mock.MatchedBy(func(runRequest *models.RunRequest) bool { + return reflect.DeepEqual(runRequest.RequestParams.Result.Value(), data.Result.Value()) + })).Return(&run, nil) - fluxAggregator.On("GetMethodID", "submit").Return(submitSelector, nil) - } + fluxAggregator.On("GetMethodID", "submit").Return(submitSelector, nil) + } - checker, err := fluxmonitor.NewPollingDeviationChecker( - store, - fluxAggregator, - logBroadcaster, - initr, - nil, - rm, - fetcher, - nil, - func() {}, - ) - require.NoError(t, err) + checker, err := fluxmonitor.NewPollingDeviationChecker( + store, + fluxAggregator, + logBroadcaster, + initr, + nil, + rm, + fetcher, + nil, + func() {}, + ) + require.NoError(t, err) - if test.connected { - checker.OnConnect() - } - fluxAggregator.On("GetOracles").Return(oracles, nil) - checker.SetOracleAddress() + if test.connected { + checker.OnConnect() + } + fluxAggregator.On("GetOracles").Return(oracles, nil) + checker.SetOracleAddress() - checker.ExportedPollIfEligible(test.threshold, test.absoluteThreshold) + checker.ExportedPollIfEligible(thresholds.rel, thresholds.abs) - fluxAggregator.AssertExpectations(t) - fetcher.AssertExpectations(t) - rm.AssertExpectations(t) - }) - } + fluxAggregator.AssertExpectations(t) + fetcher.AssertExpectations(t) + rm.AssertExpectations(t) + }) } } @@ -400,6 +395,7 @@ func TestPollingDeviationChecker_BuffersLogs(t *testing.T) { fluxAggregator := new(mocks.FluxAggregator) fluxAggregator.On("SubscribeToLogs", mock.Anything).Return(true, eth.UnsubscribeFunc(func() {}), nil) fluxAggregator.On("GetMethodID", "submit").Return(submitSelector, nil) + fluxAggregator.On("LatestRoundData").Return(freshContractRoundDataResponse()).Once() fluxAggregator.On("RoundState", nodeAddr, uint32(1)). Return(makeRoundStateForRoundID(1), nil). Run(func(mock.Arguments) { @@ -505,12 +501,14 @@ func TestPollingDeviationChecker_TriggerIdleTimeThreshold(t *testing.T) { idleDurationOccured := make(chan struct{}, 3) - now := func() uint64 { return uint64(time.Now().UTC().Unix()) } - + fluxAggregator.On("LatestRoundData").Return(freshContractRoundDataResponse()).Once() if test.expectedToSubmit { - // idleDuration 1 + // performInitialPoll() roundState1 := contracts.FluxAggregatorRoundState{ReportableRoundID: 1, EligibleToSubmit: false, LatestAnswer: answerBigInt, StartedAt: now()} - fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(roundState1, nil).Once().Run(func(args mock.Arguments) { + fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(roundState1, nil).Once() + // idleDuration 1 + roundState2 := contracts.FluxAggregatorRoundState{ReportableRoundID: 1, EligibleToSubmit: false, LatestAnswer: answerBigInt, StartedAt: now()} + fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(roundState2, nil).Once().Run(func(args mock.Arguments) { idleDurationOccured <- struct{}{} }) } @@ -592,6 +590,9 @@ func TestPollingDeviationChecker_RoundTimeoutCausesPoll_timesOutAtZero(t *testin const fetchedAnswer = 100 answerBigInt := big.NewInt(fetchedAnswer * int64(math.Pow10(int(initr.InitiatorParams.Precision)))) fluxAggregator.On("SubscribeToLogs", mock.Anything).Return(true, eth.UnsubscribeFunc(func() {}), nil) + fluxAggregator.On("LatestRoundData").Return(makeRoundDataForRoundID(1), nil).Once() + roundState0 := contracts.FluxAggregatorRoundState{ReportableRoundID: 1, EligibleToSubmit: false, LatestAnswer: answerBigInt, StartedAt: now()} + fluxAggregator.On("RoundState", nodeAddr, uint32(1)).Return(roundState0, nil).Once() // initialRoundState() fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(contracts.FluxAggregatorRoundState{ ReportableRoundID: 1, EligibleToSubmit: false, @@ -631,6 +632,167 @@ func TestPollingDeviationChecker_RoundTimeoutCausesPoll_timesOutAtZero(t *testin fluxAggregator.AssertExpectations(t) } +func TestPollingDeviationChecker_UsesPreviousRoundStateOnStartup_RoundTimeout(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + nodeAddr := ensureAccount(t, store) + fetcher := new(mocks.Fetcher) + runManager := new(mocks.RunManager) + logBroadcaster := new(mocks.LogBroadcaster) + + job := cltest.NewJobWithFluxMonitorInitiator() + initr := job.Initiators[0] + initr.PollTimer.Disabled = true + initr.IdleTimer.Disabled = true + + tests := []struct { + name string + timeout uint64 + expectedToSubmit bool + }{ + {"active round exists - round will time out", 2, true}, + {"active round exists - round will not time out", 100, false}, + {"no active round", 0, false}, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + fluxAggregator := new(mocks.FluxAggregator) + + fluxAggregator.On("SubscribeToLogs", mock.Anything).Return(true, eth.UnsubscribeFunc(func() {}), nil) + fluxAggregator.On("GetOracles").Return(oracles, nil) + + fluxAggregator.On("LatestRoundData").Return(makeRoundDataForRoundID(1), nil).Once() + fluxAggregator.On("RoundState", nodeAddr, uint32(1)).Return(contracts.FluxAggregatorRoundState{ + ReportableRoundID: 1, + EligibleToSubmit: false, + StartedAt: now(), + Timeout: test.timeout, + }, nil).Once() + + // 2nd roundstate call means round timer triggered + chRoundState := make(chan struct{}) + fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(contracts.FluxAggregatorRoundState{ + ReportableRoundID: 1, + EligibleToSubmit: false, + }, nil). + Run(func(mock.Arguments) { close(chRoundState) }). + Maybe() + + deviationChecker, err := fluxmonitor.NewPollingDeviationChecker( + store, + fluxAggregator, + logBroadcaster, + initr, + nil, + runManager, + fetcher, + nil, + func() {}, + ) + require.NoError(t, err) + + deviationChecker.Start() + deviationChecker.OnConnect() + + if test.expectedToSubmit { + gomega.NewGomegaWithT(t).Eventually(chRoundState).Should(gomega.BeClosed()) + } else { + gomega.NewGomegaWithT(t).Consistently(chRoundState).ShouldNot(gomega.BeClosed()) + } + + deviationChecker.Stop() + fluxAggregator.AssertExpectations(t) + }) + } +} + +func TestPollingDeviationChecker_UsesPreviousRoundStateOnStartup_IdleTimer(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + nodeAddr := ensureAccount(t, store) + fetcher := new(mocks.Fetcher) + runManager := new(mocks.RunManager) + logBroadcaster := new(mocks.LogBroadcaster) + + job := cltest.NewJobWithFluxMonitorInitiator() + initr := job.Initiators[0] + initr.PollTimer.Disabled = true + initr.IdleTimer.Disabled = false + + almostExpired := time.Now(). + Add(initr.IdleTimer.Duration.Duration() * -1). + Add(2 * time.Second). + Unix() + + tests := []struct { + name string + startedAt uint64 + expectedToSubmit bool + }{ + {"active round exists - idleTimer about to expired", uint64(almostExpired), true}, + {"active round exists - idleTimer will not expire", 100, false}, + {"no active round", 0, false}, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + fluxAggregator := new(mocks.FluxAggregator) + + fluxAggregator.On("SubscribeToLogs", mock.Anything).Return(true, eth.UnsubscribeFunc(func() {}), nil) + fluxAggregator.On("GetOracles").Return(oracles, nil) + + fluxAggregator.On("LatestRoundData").Return(makeRoundDataForRoundID(1), nil).Once() + // first roundstate in setInitialTickers() + fluxAggregator.On("RoundState", nodeAddr, uint32(1)).Return(contracts.FluxAggregatorRoundState{ + ReportableRoundID: 1, + EligibleToSubmit: false, + StartedAt: test.startedAt, + Timeout: 10000, // round won't time out + }, nil).Once() + + // 2nd roundstate in performInitialPoll() + roundState := contracts.FluxAggregatorRoundState{ReportableRoundID: 1, EligibleToSubmit: false} + fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(roundState, nil).Once() + + // 3rd roundState call means idleTimer triggered + chRoundState := make(chan struct{}) + fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(roundState, nil). + Run(func(mock.Arguments) { close(chRoundState) }). + Maybe() + + deviationChecker, err := fluxmonitor.NewPollingDeviationChecker( + store, + fluxAggregator, + logBroadcaster, + initr, + nil, + runManager, + fetcher, + nil, + func() {}, + ) + require.NoError(t, err) + + deviationChecker.Start() + deviationChecker.OnConnect() + + if test.expectedToSubmit { + gomega.NewGomegaWithT(t).Eventually(chRoundState).Should(gomega.BeClosed()) + } else { + gomega.NewGomegaWithT(t).Consistently(chRoundState).ShouldNot(gomega.BeClosed()) + } + + deviationChecker.Stop() + fluxAggregator.AssertExpectations(t) + }) + } +} + func TestPollingDeviationChecker_RoundTimeoutCausesPoll_timesOutNotZero(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() @@ -656,6 +818,15 @@ func TestPollingDeviationChecker_RoundTimeoutCausesPoll_timesOutNotZero(t *testi fluxAggregator.On("SubscribeToLogs", mock.Anything).Return(true, eth.UnsubscribeFunc(func() {}), nil) + fluxAggregator.On("LatestRoundData").Return(makeRoundDataForRoundID(1), nil).Once() + fluxAggregator.On("RoundState", nodeAddr, uint32(1)).Return(contracts.FluxAggregatorRoundState{ + ReportableRoundID: 1, + EligibleToSubmit: false, + LatestAnswer: answerBigInt, + StartedAt: now(), + Timeout: uint64(1000000), + }, nil).Once() + startedAt := uint64(time.Now().Unix()) timeout := uint64(3) fluxAggregator.On("RoundState", nodeAddr, uint32(0)).Return(contracts.FluxAggregatorRoundState{ diff --git a/core/services/fluxmonitor/helpers_test.go b/core/services/fluxmonitor/helpers_test.go index 9657db22ef6..e12eb9a19c9 100644 --- a/core/services/fluxmonitor/helpers_test.go +++ b/core/services/fluxmonitor/helpers_test.go @@ -131,7 +131,7 @@ func dataWithResult(t *testing.T, result decimal.Decimal) adapterResponseData { // job with a specific answer and round, for testing nodes with malicious // behavior func (fm *concreteFluxMonitor) CreateJob(t *testing.T, jobSpecId *models.ID, polledAnswer decimal.Decimal, nextRound *big.Int) error { - jobSpec, err := fm.store.ORM.FindJob(jobSpecId) + jobSpec, err := fm.store.ORM.FindJobSpec(jobSpecId) require.NoError(t, err, "could not find job spec with that ID") checker, err := fm.checkerFactory.New(jobSpec.Initiators[0], nil, fm.runManager, fm.store.ORM, models.MustMakeDuration(100*time.Second)) diff --git a/core/services/head_tracker.go b/core/services/head_tracker.go index b08e186f257..7a65632d077 100644 --- a/core/services/head_tracker.go +++ b/core/services/head_tracker.go @@ -185,10 +185,6 @@ func (ht *HeadTracker) Stop() error { return nil } - if ht.connected { - ht.connected = false - ht.disconnect() - } logger.Info(fmt.Sprintf("Head tracker disconnecting from %v", ht.store.Config.EthereumURL())) close(ht.done) close(ht.subscriptionSucceeded) diff --git a/core/services/job/helpers_factories_test.go b/core/services/job/helpers_factories_test.go index 52cf089a08b..2f8b5a2c4bf 100644 --- a/core/services/job/helpers_factories_test.go +++ b/core/services/job/helpers_factories_test.go @@ -60,7 +60,11 @@ func makeOCRJobSpec(t *testing.T, db *gorm.DB) (*offchainreporting.OracleSpec, * err := toml.Unmarshal([]byte(jobSpecText), &ocrspec) require.NoError(t, err) - dbSpec := models.JobSpecV2{OffchainreportingOracleSpec: &ocrspec.OffchainReportingOracleSpec} + dbSpec := models.JobSpecV2{ + OffchainreportingOracleSpec: &ocrspec.OffchainReportingOracleSpec, + Type: string(offchainreporting.JobType), + SchemaVersion: ocrspec.SchemaVersion, + } return &ocrspec, &dbSpec } diff --git a/core/services/job/mocks/delegate.go b/core/services/job/mocks/delegate.go index e8b665f4705..3ac93a4bd48 100644 --- a/core/services/job/mocks/delegate.go +++ b/core/services/job/mocks/delegate.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/services/job/mocks/orm.go b/core/services/job/mocks/orm.go index 6d77592f6f1..d7626dcc1a5 100644 --- a/core/services/job/mocks/orm.go +++ b/core/services/job/mocks/orm.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/services/job/mocks/service.go b/core/services/job/mocks/service.go index 795f9267abe..62229248106 100644 --- a/core/services/job/mocks/service.go +++ b/core/services/job/mocks/service.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/services/job/mocks/spawner.go b/core/services/job/mocks/spawner.go index da490708177..685d5e27f92 100644 --- a/core/services/job/mocks/spawner.go +++ b/core/services/job/mocks/spawner.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks @@ -7,6 +7,8 @@ import ( job "github.com/smartcontractkit/chainlink/core/services/job" mock "github.com/stretchr/testify/mock" + + null "gopkg.in/guregu/null.v4" ) // Spawner is an autogenerated mock type for the Spawner type @@ -14,20 +16,20 @@ type Spawner struct { mock.Mock } -// CreateJob provides a mock function with given fields: ctx, spec -func (_m *Spawner) CreateJob(ctx context.Context, spec job.Spec) (int32, error) { - ret := _m.Called(ctx, spec) +// CreateJob provides a mock function with given fields: ctx, spec, name +func (_m *Spawner) CreateJob(ctx context.Context, spec job.Spec, name null.String) (int32, error) { + ret := _m.Called(ctx, spec, name) var r0 int32 - if rf, ok := ret.Get(0).(func(context.Context, job.Spec) int32); ok { - r0 = rf(ctx, spec) + if rf, ok := ret.Get(0).(func(context.Context, job.Spec, null.String) int32); ok { + r0 = rf(ctx, spec, name) } else { r0 = ret.Get(0).(int32) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, job.Spec) error); ok { - r1 = rf(ctx, spec) + if rf, ok := ret.Get(1).(func(context.Context, job.Spec, null.String) error); ok { + r1 = rf(ctx, spec, name) } else { r1 = ret.Error(1) } diff --git a/core/services/job/mocks/spec.go b/core/services/job/mocks/spec.go index bece8f6d9a5..c9de1a27819 100644 --- a/core/services/job/mocks/spec.go +++ b/core/services/job/mocks/spec.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/services/job/orm_test.go b/core/services/job/orm_test.go index ce46a528327..c4557c71fdf 100644 --- a/core/services/job/orm_test.go +++ b/core/services/job/orm_test.go @@ -62,7 +62,7 @@ func TestORM(t *testing.T) { require.Len(t, unclaimed, 1) compareOCRJobSpecs(t, *dbSpec, unclaimed[0]) require.Equal(t, int32(1), unclaimed[0].ID) - require.Equal(t, int32(1), unclaimed[0].OffchainreportingOracleSpecID) + require.Equal(t, int32(1), *unclaimed[0].OffchainreportingOracleSpecID) require.Equal(t, int32(1), unclaimed[0].PipelineSpecID) require.Equal(t, int32(1), unclaimed[0].OffchainreportingOracleSpec.ID) @@ -78,7 +78,7 @@ func TestORM(t *testing.T) { require.Len(t, unclaimed, 1) compareOCRJobSpecs(t, *dbSpec2, unclaimed[0]) require.Equal(t, int32(2), unclaimed[0].ID) - require.Equal(t, int32(2), unclaimed[0].OffchainreportingOracleSpecID) + require.Equal(t, int32(2), *unclaimed[0].OffchainreportingOracleSpecID) require.Equal(t, int32(2), unclaimed[0].PipelineSpecID) require.Equal(t, int32(2), unclaimed[0].OffchainreportingOracleSpec.ID) }) diff --git a/core/services/job/spawner.go b/core/services/job/spawner.go index 76f8ca1fea1..3a7df999606 100644 --- a/core/services/job/spawner.go +++ b/core/services/job/spawner.go @@ -7,6 +7,7 @@ import ( "time" "github.com/pkg/errors" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/postgres" @@ -29,7 +30,7 @@ type ( Spawner interface { Start() Stop() - CreateJob(ctx context.Context, spec Spec) (int32, error) + CreateJob(ctx context.Context, spec Spec, name null.String) (int32, error) DeleteJob(ctx context.Context, jobID int32) error RegisterDelegate(delegate Delegate) } @@ -204,6 +205,7 @@ func (js *spawner) startUnclaimedServices() { moreServices, err := delegate.ServicesForSpec(spec) if err != nil { logger.Errorw("Error creating services for job", "jobID", specDBRow.ID, "error", err) + js.orm.RecordError(ctx, specDBRow.ID, err.Error()) continue } services = append(services, moreServices...) @@ -271,7 +273,7 @@ func (js *spawner) handlePGDeleteEvent(ctx context.Context, ev postgres.Event) { js.unloadDeletedJob(ctx, jobID) } -func (js *spawner) CreateJob(ctx context.Context, spec Spec) (int32, error) { +func (js *spawner) CreateJob(ctx context.Context, spec Spec, name null.String) (int32, error) { js.jobTypeDelegatesMu.Lock() defer js.jobTypeDelegatesMu.Unlock() @@ -285,6 +287,7 @@ func (js *spawner) CreateJob(ctx context.Context, spec Spec) (int32, error) { defer cancel() specDBRow := delegate.ToDBRow(spec) + specDBRow.Name = name err := js.orm.CreateJob(ctx, &specDBRow, spec.TaskDAG()) if err != nil { logger.Errorw("Error creating job", "type", spec.JobType(), "error", err) diff --git a/core/services/job/spawner_test.go b/core/services/job/spawner_test.go index 1312016c1ba..b2d2b9adc3a 100644 --- a/core/services/job/spawner_test.go +++ b/core/services/job/spawner_test.go @@ -18,6 +18,7 @@ import ( "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/services/postgres" "github.com/smartcontractkit/chainlink/core/store/models" + "gopkg.in/guregu/null.v4" ) type delegate struct { @@ -104,7 +105,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { delegateA := &delegate{jobTypeA, []job.Service{serviceA1, serviceA2}, 0, make(chan struct{}), offchainreporting.NewJobSpawnerDelegate(nil, orm, nil, nil, nil, nil, nil)} spawner.RegisterDelegate(delegateA) - jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA) + jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA, null.String{}) require.NoError(t, err) delegateA.jobID = jobSpecIDA close(delegateA.chContinueCreatingServices) @@ -121,7 +122,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { delegateB := &delegate{jobTypeB, []job.Service{serviceB1, serviceB2}, 0, make(chan struct{}), offchainreporting.NewJobSpawnerDelegate(nil, orm, nil, nil, nil, nil, nil)} spawner.RegisterDelegate(delegateB) - jobSpecIDB, err := spawner.CreateJob(context.Background(), jobSpecB) + jobSpecIDB, err := spawner.CreateJob(context.Background(), jobSpecB, null.String{}) require.NoError(t, err) delegateB.jobID = jobSpecIDB close(delegateB.chContinueCreatingServices) @@ -164,7 +165,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { delegateA := &delegate{jobTypeA, []job.Service{serviceA1, serviceA2}, 0, nil, offchainreporting.NewJobSpawnerDelegate(nil, orm, nil, nil, nil, nil, nil)} spawner.RegisterDelegate(delegateA) - jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA) + jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA, null.String{}) require.NoError(t, err) delegateA.jobID = jobSpecIDA @@ -195,7 +196,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { delegateA := &delegate{jobTypeA, []job.Service{serviceA1, serviceA2}, 0, nil, offchainreporting.NewJobSpawnerDelegate(nil, orm, nil, nil, nil, nil, nil)} spawner.RegisterDelegate(delegateA) - jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA) + jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA, null.String{}) require.NoError(t, err) delegateA.jobID = jobSpecIDA @@ -231,7 +232,7 @@ func TestSpawner_CreateJobDeleteJob(t *testing.T) { delegateA := &delegate{jobTypeA, []job.Service{serviceA1, serviceA2}, 0, nil, offchainreporting.NewJobSpawnerDelegate(nil, nil, nil, nil, nil, nil, nil)} spawner.RegisterDelegate(delegateA) - jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA) + jobSpecIDA, err := spawner.CreateJob(context.Background(), jobSpecA, null.String{}) require.NoError(t, err) delegateA.jobID = jobSpecIDA diff --git a/core/services/offchainreporting/contract_config_tracker.go b/core/services/offchainreporting/contract_config_tracker.go index 92568ad4b9e..efbec72a5a2 100644 --- a/core/services/offchainreporting/contract_config_tracker.go +++ b/core/services/offchainreporting/contract_config_tracker.go @@ -80,7 +80,11 @@ func (oc *OCRContractConfigTracker) LatestConfigDetails(ctx context.Context) (ch if err != nil { return 0, configDigest, errors.Wrap(err, "error getting LatestConfigDetails") } - return uint64(result.BlockNumber), ocrtypes.BytesToConfigDigest(result.ConfigDigest[:]), err + configDigest, err = ocrtypes.BytesToConfigDigest(result.ConfigDigest[:]) + if err != nil { + return 0, configDigest, errors.Wrap(err, "error getting config digest") + } + return uint64(result.BlockNumber), configDigest, err } func (oc *OCRContractConfigTracker) ConfigFromLogs(ctx context.Context, changedInBlock uint64) (c ocrtypes.ContractConfig, err error) { diff --git a/core/services/offchainreporting/database_test.go b/core/services/offchainreporting/database_test.go index f62bc87b3e7..090015b1b51 100644 --- a/core/services/offchainreporting/database_test.go +++ b/core/services/offchainreporting/database_test.go @@ -21,7 +21,7 @@ func Test_DB_ReadWriteState(t *testing.T) { defer cleanup() sqldb := store.DB.DB() - configDigest := ocrtypes.BytesToConfigDigest([]byte{42}) + configDigest := cltest.MakeConfigDigest(t) spec := cltest.MustInsertOffchainreportingOracleSpec(t, store) t.Run("reads and writes state", func(t *testing.T) { @@ -89,7 +89,7 @@ func Test_DB_ReadWriteState(t *testing.T) { err := db.WriteState(ctx, configDigest, state) require.NoError(t, err) - readState, err := db.ReadState(ctx, ocrtypes.BytesToConfigDigest([]byte{43})) + readState, err := db.ReadState(ctx, cltest.MakeConfigDigest(t)) require.NoError(t, err) require.Nil(t, readState) @@ -102,7 +102,7 @@ func Test_DB_ReadWriteConfig(t *testing.T) { sqldb := store.DB.DB() config := ocrtypes.ContractConfig{ - ConfigDigest: ocrtypes.BytesToConfigDigest([]byte{42}), + ConfigDigest: cltest.MakeConfigDigest(t), Signers: []common.Address{cltest.NewAddress()}, Transmitters: []common.Address{cltest.NewAddress()}, Threshold: uint8(35), @@ -127,7 +127,7 @@ func Test_DB_ReadWriteConfig(t *testing.T) { db := offchainreporting.NewDB(sqldb, spec.ID) newConfig := ocrtypes.ContractConfig{ - ConfigDigest: ocrtypes.BytesToConfigDigest([]byte{43}), + ConfigDigest: cltest.MakeConfigDigest(t), Signers: []common.Address{utils.ZeroAddress, cltest.DefaultKeyAddress, cltest.NewAddress()}, Transmitters: []common.Address{utils.ZeroAddress, cltest.DefaultKeyAddress, cltest.NewAddress()}, Threshold: uint8(36), @@ -168,7 +168,7 @@ func Test_DB_PendingTransmissions(t *testing.T) { spec2 := cltest.MustInsertOffchainreportingOracleSpec(t, store) db := offchainreporting.NewDB(sqldb, spec.ID) db2 := offchainreporting.NewDB(sqldb, spec2.ID) - configDigest := ocrtypes.BytesToConfigDigest([]byte{42}) + configDigest := cltest.MakeConfigDigest(t) k := ocrtypes.PendingTransmissionKey{ ConfigDigest: configDigest, diff --git a/core/services/offchainreporting/logger.go b/core/services/offchainreporting/logger.go index 801a887f8a5..26b94db6bd8 100644 --- a/core/services/offchainreporting/logger.go +++ b/core/services/offchainreporting/logger.go @@ -1,8 +1,6 @@ package offchainreporting import ( - "fmt" - "github.com/smartcontractkit/chainlink/core/logger" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" ) @@ -43,20 +41,14 @@ func (ol *ocrLogger) Warn(msg string, fields ocrtypes.LogFields) { ol.internal.Warnw(msg, toKeysAndValues(fields)...) } +// Note that the structured fields may contain dynamic data (timestamps etc.) +// So when saving the error, we only save the top level string, details +// are included in the log. func (ol *ocrLogger) Error(msg string, fields ocrtypes.LogFields) { - ol.saveError(msg + toString(fields)) + ol.saveError(msg) ol.internal.Errorw(msg, toKeysAndValues(fields)...) } -// Helpers -func toString(fields ocrtypes.LogFields) string { - res := "" - for key, val := range fields { - res += fmt.Sprintf("%s: %v", key, val) - } - return res -} - func toKeysAndValues(fields ocrtypes.LogFields) []interface{} { out := []interface{}{} for key, val := range fields { diff --git a/core/services/offchainreporting/models.go b/core/services/offchainreporting/models.go index f17891933cf..d9c612ca57f 100644 --- a/core/services/offchainreporting/models.go +++ b/core/services/offchainreporting/models.go @@ -4,14 +4,17 @@ import ( "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/pipeline" "github.com/smartcontractkit/chainlink/core/store/models" + "gopkg.in/guregu/null.v4" ) // OracleSpec is a wrapper for `models.OffchainReportingOracleSpec`, the DB // representation of the OCR job spec. It fulfills the job.Spec interface // and has facilities for unmarshaling the pipeline DAG from the job spec text. type OracleSpec struct { - Type string `toml:"type"` - SchemaVersion uint32 `toml:"schemaVersion"` + Type string `toml:"type"` + SchemaVersion uint32 `toml:"schemaVersion"` + Name null.String `toml:"name"` + MaxTaskDuration models.Interval `toml:"maxTaskDuration"` models.OffchainReportingOracleSpec diff --git a/core/services/offchainreporting/oracle.go b/core/services/offchainreporting/oracle.go index 8513d01fc2f..a7a92587864 100644 --- a/core/services/offchainreporting/oracle.go +++ b/core/services/offchainreporting/oracle.go @@ -3,6 +3,7 @@ package offchainreporting import ( "context" "fmt" + "net/url" "strings" "time" @@ -17,6 +18,8 @@ import ( "github.com/smartcontractkit/chainlink/core/services/eth" "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/pipeline" + "github.com/smartcontractkit/chainlink/core/services/synchronization" + "github.com/smartcontractkit/chainlink/core/services/telemetry" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/smartcontractkit/chainlink/core/utils" @@ -73,7 +76,12 @@ func (d jobSpawnerDelegate) ToDBRow(spec job.Spec) models.JobSpecV2 { if !ok { panic(fmt.Sprintf("expected an offchainreporting.OracleSpec, got %T", spec)) } - return models.JobSpecV2{OffchainreportingOracleSpec: &concreteSpec.OffchainReportingOracleSpec} + return models.JobSpecV2{ + OffchainreportingOracleSpec: &concreteSpec.OffchainReportingOracleSpec, + Type: string(JobType), + SchemaVersion: concreteSpec.SchemaVersion, + MaxTaskDuration: concreteSpec.MaxTaskDuration, + } } func (d jobSpawnerDelegate) FromDBRow(spec models.JobSpecV2) job.Spec { @@ -86,7 +94,7 @@ func (d jobSpawnerDelegate) FromDBRow(spec models.JobSpecV2) job.Spec { } } -func (d jobSpawnerDelegate) ServicesForSpec(spec job.Spec) ([]job.Service, error) { +func (d jobSpawnerDelegate) ServicesForSpec(spec job.Spec) (services []job.Service, err error) { concreteSpec, is := spec.(*OracleSpec) if !is { return nil, errors.Errorf("offchainreporting.jobSpawnerDelegate expects an *offchainreporting.OracleSpec, got %T", spec) @@ -115,9 +123,17 @@ func (d jobSpawnerDelegate) ServicesForSpec(spec job.Spec) ([]job.Service, error return nil, errors.Wrap(err, "error calling NewOCRContract") } - p2pkey, exists := d.keyStore.DecryptedP2PKey(peer.ID(concreteSpec.P2PPeerID)) + peerID, err := d.config.P2PPeerID(concreteSpec.P2PPeerID) + if err != nil { + return nil, err + } + p2pkey, exists := d.keyStore.DecryptedP2PKey(peer.ID(peerID)) if !exists { - return nil, errors.Errorf("P2P key '%v' does not exist", concreteSpec.P2PPeerID) + return nil, errors.Errorf("P2P key '%v' does not exist", peerID) + } + bootstrapPeers, err := d.config.P2PBootstrapPeers(concreteSpec.P2PBootstrapPeers) + if err != nil { + return nil, err } pstorewrapper, err := NewPeerstoreWrapper(d.db, d.config.P2PPeerstoreWriteInterval(), concreteSpec.JobID()) @@ -125,6 +141,8 @@ func (d jobSpawnerDelegate) ServicesForSpec(spec job.Spec) ([]job.Service, error return nil, errors.Wrap(err, "could not make new pstorewrapper") } + services = append(services, pstorewrapper) + loggerWith := logger.CreateLogger(logger.Default.With( "contractAddress", concreteSpec.ContractAddress, "jobID", concreteSpec.jobID)) @@ -161,38 +179,67 @@ func (d jobSpawnerDelegate) ServicesForSpec(spec job.Spec) ([]job.Service, error DHTLookupInterval: d.config.OCRDHTLookupInterval(), BootstrapCheckInterval: d.config.OCRBootstrapCheckInterval(), }, + DHTAnnouncementCounterUserPrefix: d.config.P2PDHTAnnouncementCounterUserPrefix(), }) if err != nil { return nil, errors.Wrap(err, "error calling NewPeer") } - var service job.Service + var endpointURL *url.URL + if me := d.config.OCRMonitoringEndpoint(concreteSpec.MonitoringEndpoint); me != "" { + endpointURL, err = url.Parse(me) + if err != nil { + return nil, errors.Wrapf(err, "invalid monitoring url: %s", me) + } + } else { + endpointURL = d.config.ExplorerURL() + } + + var monitoringEndpoint ocrtypes.MonitoringEndpoint + if endpointURL != nil { + client := synchronization.NewExplorerClient(endpointURL, d.config.ExplorerAccessKey(), d.config.ExplorerSecret()) + monitoringEndpoint = telemetry.NewAgent(client) + services = append(services, client) + } else { + monitoringEndpoint = ocrtypes.MonitoringEndpoint(nil) + } + + lc := ocrtypes.LocalConfig{ + BlockchainTimeout: d.config.OCRBlockchainTimeout(time.Duration(concreteSpec.BlockchainTimeout)), + ContractConfigConfirmations: d.config.OCRContractConfirmations(concreteSpec.ContractConfigConfirmations), + ContractConfigTrackerPollInterval: d.config.OCRContractPollInterval(time.Duration(concreteSpec.ContractConfigTrackerPollInterval)), + ContractConfigTrackerSubscribeInterval: d.config.OCRContractSubscribeInterval(time.Duration(concreteSpec.ContractConfigTrackerSubscribeInterval)), + ContractTransmitterTransmitTimeout: d.config.OCRContractTransmitterTransmitTimeout(), + DatabaseTimeout: d.config.OCRDatabaseTimeout(), + DataSourceTimeout: d.config.OCRObservationTimeout(time.Duration(concreteSpec.ObservationTimeout)), + } + if err := ocr.SanityCheckLocalConfig(lc); err != nil { + return nil, err + } + if concreteSpec.IsBootstrapPeer { - service, err = ocr.NewBootstrapNode(ocr.BootstrapNodeArgs{ + bootstrapper, err := ocr.NewBootstrapNode(ocr.BootstrapNodeArgs{ BootstrapperFactory: peer, - Bootstrappers: concreteSpec.P2PBootstrapPeers, + Bootstrappers: bootstrapPeers, ContractConfigTracker: ocrContract, Database: NewDB(d.db.DB(), concreteSpec.ID), - LocalConfig: ocrtypes.LocalConfig{ - BlockchainTimeout: time.Duration(concreteSpec.BlockchainTimeout), - ContractConfigConfirmations: concreteSpec.ContractConfigConfirmations, - ContractConfigTrackerPollInterval: time.Duration(concreteSpec.ContractConfigTrackerPollInterval), - ContractConfigTrackerSubscribeInterval: time.Duration(concreteSpec.ContractConfigTrackerSubscribeInterval), - ContractTransmitterTransmitTimeout: d.config.OCRContractTransmitterTransmitTimeout(), - DatabaseTimeout: d.config.OCRDatabaseTimeout(), - DataSourceTimeout: time.Duration(concreteSpec.ObservationTimeout), - }, - Logger: ocrLogger, + LocalConfig: lc, + Logger: ocrLogger, + MonitoringEndpoint: monitoringEndpoint, }) if err != nil { return nil, errors.Wrap(err, "error calling NewBootstrapNode") } - + services = append(services, bootstrapper) } else { - if concreteSpec.EncryptedOCRKeyBundleID == nil { - return nil, errors.Errorf("OCR key must be specified") + if len(bootstrapPeers) < 1 { + return nil, errors.New("need at least one bootstrap peer") } - ocrkey, exists := d.keyStore.DecryptedOCRKey(*concreteSpec.EncryptedOCRKeyBundleID) + kb, err := d.config.OCRKeyBundleID(concreteSpec.EncryptedOCRKeyBundleID) + if err != nil { + return nil, err + } + ocrkey, exists := d.keyStore.DecryptedOCRKey(kb) if !exists { return nil, errors.Errorf("OCR key '%v' does not exist", concreteSpec.EncryptedOCRKeyBundleID) } @@ -200,35 +247,33 @@ func (d jobSpawnerDelegate) ServicesForSpec(spec job.Spec) ([]job.Service, error if err != nil { return nil, errors.Wrap(err, "could not get contract ABI JSON") } + + ta, err := d.config.OCRTransmitterAddress(concreteSpec.TransmitterAddress) + if err != nil { + return nil, err + } contractTransmitter := NewOCRContractTransmitter(concreteSpec.ContractAddress.Address(), contractCaller, contractABI, - NewTransmitter(d.db.DB(), concreteSpec.TransmitterAddress.Address(), d.config.EthGasLimitDefault())) - - service, err = ocr.NewOracle(ocr.OracleArgs{ - LocalConfig: ocrtypes.LocalConfig{ - BlockchainTimeout: time.Duration(concreteSpec.BlockchainTimeout), - ContractConfigConfirmations: concreteSpec.ContractConfigConfirmations, - ContractConfigTrackerPollInterval: time.Duration(concreteSpec.ContractConfigTrackerPollInterval), - ContractConfigTrackerSubscribeInterval: time.Duration(concreteSpec.ContractConfigTrackerSubscribeInterval), - ContractTransmitterTransmitTimeout: d.config.OCRContractTransmitterTransmitTimeout(), - DatabaseTimeout: d.config.OCRDatabaseTimeout(), - DataSourceTimeout: time.Duration(concreteSpec.ObservationTimeout), - }, + NewTransmitter(d.db.DB(), ta.Address(), d.config.EthGasLimitDefault())) + + oracle, err := ocr.NewOracle(ocr.OracleArgs{ Database: NewDB(d.db.DB(), concreteSpec.ID), Datasource: dataSource{jobID: concreteSpec.JobID(), pipelineRunner: d.pipelineRunner}, + LocalConfig: lc, ContractTransmitter: contractTransmitter, ContractConfigTracker: ocrContract, PrivateKeys: &ocrkey, BinaryNetworkEndpointFactory: peer, - MonitoringEndpoint: ocrtypes.MonitoringEndpoint(nil), + MonitoringEndpoint: monitoringEndpoint, Logger: ocrLogger, - Bootstrappers: concreteSpec.P2PBootstrapPeers, + Bootstrappers: bootstrapPeers, }) if err != nil { return nil, errors.Wrap(err, "error calling NewOracle") } + services = append(services, oracle) } - return []job.Service{pstorewrapper, service}, nil + return services, nil } // dataSource is an abstraction over the process of initiating a pipeline run diff --git a/core/services/pipeline/common.go b/core/services/pipeline/common.go index d2ddd306c07..6c3e1b01055 100644 --- a/core/services/pipeline/common.go +++ b/core/services/pipeline/common.go @@ -30,6 +30,7 @@ type ( OutputTask() Task SetOutputTask(task Task) OutputIndex() int32 + TaskTimeout() (time.Duration, bool) } Result struct { @@ -60,14 +61,21 @@ var ( type BaseTask struct { outputTask Task - dotID string `mapstructure:"-"` - Index int32 `mapstructure:"index" json:"-" ` + dotID string `mapstructure:"-"` + Index int32 `mapstructure:"index" json:"-" ` + Timeout time.Duration `mapstructure:"timeout"` } func (t BaseTask) DotID() string { return t.dotID } func (t BaseTask) OutputIndex() int32 { return t.Index } func (t BaseTask) OutputTask() Task { return t.outputTask } func (t *BaseTask) SetOutputTask(outputTask Task) { t.outputTask = outputTask } +func (t BaseTask) TaskTimeout() (time.Duration, bool) { + if t.Timeout == time.Duration(0) { + return time.Duration(0), false + } + return t.Timeout, true +} type JSONSerializable struct { Val interface{} @@ -150,6 +158,7 @@ func UnmarshalTaskFromMap(taskType TaskType, taskMap interface{}, dotID string, Result: task, DecodeHook: mapstructure.ComposeDecodeHookFunc( mapstructure.StringToSliceHookFunc(","), + mapstructure.StringToTimeDurationHookFunc(), func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { switch f { case reflect.TypeOf(""): @@ -175,6 +184,8 @@ func UnmarshalTaskFromMap(taskType TaskType, taskMap interface{}, dotID string, case reflect.TypeOf(true): b, err2 := strconv.ParseBool(data.(string)) return b, err2 + case reflect.TypeOf(MaybeBool("")): + return MaybeBoolFromString(data.(string)) } } return data, nil diff --git a/core/services/pipeline/common_test.go b/core/services/pipeline/common_test.go new file mode 100644 index 00000000000..38eeeaae6ff --- /dev/null +++ b/core/services/pipeline/common_test.go @@ -0,0 +1,45 @@ +package pipeline_test + +import ( + "testing" + + "github.com/bmizerany/assert" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/services/pipeline" + "github.com/stretchr/testify/require" +) + +func TestTimeoutAttribute(t *testing.T) { + g := pipeline.NewTaskDAG() + a := `ds1 [type=http method=GET url="https://chain.link/voter_turnout/USA-2020" requestData="{\"hi\": \"hello\"}" timeout="10s"];` + err := g.UnmarshalText([]byte(a)) + require.NoError(t, err) + tasks, err := g.TasksInDependencyOrder() + require.NoError(t, err) + timeout, set := tasks[0].TaskTimeout() + assert.Equal(t, cltest.MustParseDuration(t, "10s"), timeout) + assert.Equal(t, true, set) + + g = pipeline.NewTaskDAG() + a = `ds1 [type=http method=GET url="https://chain.link/voter_turnout/USA-2020" requestData="{\"hi\": \"hello\"}"];` + err = g.UnmarshalText([]byte(a)) + require.NoError(t, err) + tasks, err = g.TasksInDependencyOrder() + require.NoError(t, err) + timeout, set = tasks[0].TaskTimeout() + assert.Equal(t, cltest.MustParseDuration(t, "0s"), timeout) + assert.Equal(t, false, set) +} + +func Test_TaskHTTPUnmarshal(t *testing.T) { + g := pipeline.NewTaskDAG() + a := `ds1 [type=http allowunrestrictednetworkaccess=true method=GET url="https://chain.link/voter_turnout/USA-2020" requestData="{\"hi\": \"hello\"}" timeout="10s"];` + err := g.UnmarshalText([]byte(a)) + require.NoError(t, err) + tasks, err := g.TasksInDependencyOrder() + require.NoError(t, err) + require.Len(t, tasks, 1) + + task := tasks[0].(*pipeline.HTTPTask) + require.Equal(t, pipeline.MaybeBoolTrue, task.AllowUnrestrictedNetworkAccess) +} diff --git a/core/services/pipeline/fixtures_test.go b/core/services/pipeline/fixtures_test.go index e22bee9bca0..bba92364ff2 100644 --- a/core/services/pipeline/fixtures_test.go +++ b/core/services/pipeline/fixtures_test.go @@ -6,6 +6,12 @@ import ( "io/ioutil" "net/http" "testing" + "time" + + "github.com/smartcontractkit/chainlink/core/store/orm" + + "github.com/lib/pq" + "github.com/smartcontractkit/chainlink/core/services" "github.com/jinzhu/gorm" "github.com/pelletier/go-toml" @@ -19,7 +25,8 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" ) -const dotStr = ` +const ( + dotStr = ` // data source 1 ds1 [type=bridge name=voter_turnout]; ds1_parse [type=jsonparse path="one,two"]; @@ -36,8 +43,6 @@ const dotStr = ` answer1 [type=median index=0]; answer2 [type=bridge name=election_winner index=1]; ` - -const ( ocrJobSpecTemplate = ` type = "offchainreporting" schemaVersion = 1 @@ -59,7 +64,6 @@ observationSource = """ %s """ ` - voterTurnoutDataSourceTemplate = ` // data source 1 ds1 [type=bridge name=voter_turnout]; @@ -80,13 +84,58 @@ answer2 [type=bridge name=election_winner index=1]; simpleFetchDataSourceTemplate = ` // data source 1 -ds1 [type=http method=GET url="%s"]; +ds1 [type=http method=GET url="%s" allowunrestrictednetworkaccess="true"]; ds1_parse [type=jsonparse path="USD" lax=%t]; ds1_multiply [type=multiply times=100]; ds1 -> ds1_parse -> ds1_multiply; +` + minimalNonBootstrapTemplate = ` + type = "offchainreporting" + schemaVersion = 1 + contractAddress = "%s" + p2pPeerID = "%s" + p2pBootstrapPeers = ["/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju"] + isBootstrapPeer = false + transmitterAddress = "%s" + keyBundleID = "%s" + observationTimeout = "10s" + observationSource = """ +ds1 [type=http method=GET url="%s" allowunrestrictednetworkaccess="true" %s]; +ds1_parse [type=jsonparse path="USD" lax=true]; +ds1 -> ds1_parse; +""" +` + minimalBootstrapTemplate = ` + type = "offchainreporting" + schemaVersion = 1 + contractAddress = "%s" + p2pPeerID = "%s" + p2pBootstrapPeers = [] + isBootstrapPeer = true ` ) +func makeMinimalHTTPOracleSpec(t *testing.T, contractAddress, peerID, transmitterAddress, keyBundle, fetchUrl, timeout string) *offchainreporting.OracleSpec { + t.Helper() + var os = offchainreporting.OracleSpec{ + OffchainReportingOracleSpec: models.OffchainReportingOracleSpec{ + P2PBootstrapPeers: pq.StringArray{}, + ObservationTimeout: models.Interval(10 * time.Second), + BlockchainTimeout: models.Interval(20 * time.Second), + ContractConfigTrackerSubscribeInterval: models.Interval(2 * time.Minute), + ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), + ContractConfigConfirmations: uint16(3), + }, + Pipeline: *pipeline.NewTaskDAG(), + } + s := fmt.Sprintf(minimalNonBootstrapTemplate, contractAddress, peerID, transmitterAddress, keyBundle, fetchUrl, timeout) + _, err := services.ValidatedOracleSpecToml(orm.NewConfig(), s) + require.NoError(t, err) + err = toml.Unmarshal([]byte(s), &os) + require.NoError(t, err) + return &os +} + func makeVoterTurnoutOCRJobSpec(t *testing.T, db *gorm.DB) (*offchainreporting.OracleSpec, *models.JobSpecV2) { t.Helper() return makeVoterTurnoutOCRJobSpecWithHTTPURL(t, db, "https://example.com/foo/bar") @@ -117,7 +166,11 @@ func makeOCRJobSpecWithHTTPURL(t *testing.T, db *gorm.DB, jobSpecToml string) (* err := toml.Unmarshal([]byte(jobSpecToml), &ocrspec) require.NoError(t, err) - dbSpec := models.JobSpecV2{OffchainreportingOracleSpec: &ocrspec.OffchainReportingOracleSpec} + dbSpec := models.JobSpecV2{ + OffchainreportingOracleSpec: &ocrspec.OffchainReportingOracleSpec, + Type: string(offchainreporting.JobType), + SchemaVersion: ocrspec.SchemaVersion, + } return &ocrspec, &dbSpec } diff --git a/core/services/pipeline/mocks/config.go b/core/services/pipeline/mocks/config.go index 4bf779d0ba6..6e756cde2d4 100644 --- a/core/services/pipeline/mocks/config.go +++ b/core/services/pipeline/mocks/config.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/services/pipeline/mocks/orm.go b/core/services/pipeline/mocks/orm.go index ea85307bb1f..e7b57a72c53 100644 --- a/core/services/pipeline/mocks/orm.go +++ b/core/services/pipeline/mocks/orm.go @@ -1,13 +1,16 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks import ( context "context" - models "github.com/smartcontractkit/chainlink/core/store/models" + gorm "github.com/jinzhu/gorm" + mock "github.com/stretchr/testify/mock" + models "github.com/smartcontractkit/chainlink/core/store/models" + pipeline "github.com/smartcontractkit/chainlink/core/services/pipeline" postgres "github.com/smartcontractkit/chainlink/core/services/postgres" @@ -55,20 +58,20 @@ func (_m *ORM) CreateRun(ctx context.Context, jobID int32, meta map[string]inter return r0, r1 } -// CreateSpec provides a mock function with given fields: ctx, taskDAG -func (_m *ORM) CreateSpec(ctx context.Context, taskDAG pipeline.TaskDAG) (int32, error) { - ret := _m.Called(ctx, taskDAG) +// CreateSpec provides a mock function with given fields: ctx, db, taskDAG +func (_m *ORM) CreateSpec(ctx context.Context, db *gorm.DB, taskDAG pipeline.TaskDAG) (int32, error) { + ret := _m.Called(ctx, db, taskDAG) var r0 int32 - if rf, ok := ret.Get(0).(func(context.Context, pipeline.TaskDAG) int32); ok { - r0 = rf(ctx, taskDAG) + if rf, ok := ret.Get(0).(func(context.Context, *gorm.DB, pipeline.TaskDAG) int32); ok { + r0 = rf(ctx, db, taskDAG) } else { r0 = ret.Get(0).(int32) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, pipeline.TaskDAG) error); ok { - r1 = rf(ctx, taskDAG) + if rf, ok := ret.Get(1).(func(context.Context, *gorm.DB, pipeline.TaskDAG) error); ok { + r1 = rf(ctx, db, taskDAG) } else { r1 = ret.Error(1) } diff --git a/core/services/pipeline/mocks/task.go b/core/services/pipeline/mocks/task.go index 926e378c1ed..811adc67c0c 100644 --- a/core/services/pipeline/mocks/task.go +++ b/core/services/pipeline/mocks/task.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks @@ -7,6 +7,8 @@ import ( pipeline "github.com/smartcontractkit/chainlink/core/services/pipeline" mock "github.com/stretchr/testify/mock" + + time "time" ) // Task is an autogenerated mock type for the Task type @@ -77,6 +79,27 @@ func (_m *Task) SetOutputTask(task pipeline.Task) { _m.Called(task) } +// TaskTimeout provides a mock function with given fields: +func (_m *Task) TaskTimeout() (time.Duration, bool) { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + var r1 bool + if rf, ok := ret.Get(1).(func() bool); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + // Type provides a mock function with given fields: func (_m *Task) Type() pipeline.TaskType { ret := _m.Called() diff --git a/core/services/pipeline/orm.go b/core/services/pipeline/orm.go index 777cf9f8b69..56a4dc4aefa 100644 --- a/core/services/pipeline/orm.go +++ b/core/services/pipeline/orm.go @@ -8,6 +8,8 @@ import ( "github.com/jinzhu/gorm" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/postgres" "github.com/smartcontractkit/chainlink/core/store/models" @@ -40,6 +42,27 @@ type ( var _ ORM = (*orm)(nil) +var ( + promPipelineRunErrors = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pipeline_run_errors", + Help: "Number of errors for each pipeline spec", + }, + []string{"pipeline_spec_id"}, + ) + promPipelineRunTotalTimeToCompletion = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "pipeline_run_total_time_to_completion", + Help: "How long each pipeline run took to finish (from the moment it was created)", + }, + []string{"pipeline_spec_id"}, + ) + promPipelineTasksTotalFinished = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "pipeline_tasks_total_finished", + Help: "The total number of pipeline tasks which have finished", + }, + []string{"pipeline_spec_id", "task_type", "status"}, + ) +) + func NewORM(db *gorm.DB, config Config, eventBroadcaster postgres.EventBroadcaster) *orm { return &orm{db, config, eventBroadcaster} } @@ -268,6 +291,15 @@ func (o *orm) processNextUnclaimedTaskRun(ctx context.Context, fn ProcessTaskRun return errors.Wrap(err, "could not mark pipeline_task_run as finished") } + pipelineSpecIDStr := string(ptRun.PipelineTaskSpec.PipelineSpecID) + var status string + if result.Error != nil { + status = "error" + } else { + status = "completed" + } + promPipelineTasksTotalFinished.WithLabelValues(pipelineSpecIDStr, string(ptRun.PipelineTaskSpec.Type), status).Inc() + if ptRun.PipelineTaskSpec.IsFinalPipelineOutput() { err = tx.Exec(`UPDATE pipeline_runs SET finished_at = ?, outputs = ?, errors = ? WHERE id = ?`, time.Now(), out, errString, ptRun.PipelineRunID).Error if err != nil { @@ -278,6 +310,13 @@ func (o *orm) processNextUnclaimedTaskRun(ctx context.Context, fn ProcessTaskRun if err != nil { return errors.Wrap(err, "could not notify pipeline_run_completed") } + + elapsed := time.Since(ptRun.CreatedAt) + promPipelineRunTotalTimeToCompletion.WithLabelValues(pipelineSpecIDStr).Set(float64(elapsed)) + if finalErrors, is := result.Error.(FinalErrors); (is && finalErrors.HasErrors()) || result.Error != nil { + promPipelineRunErrors.WithLabelValues(pipelineSpecIDStr).Inc() + } + logger.Infow("Pipeline run completed", "runID", ptRun.PipelineRunID) } return nil diff --git a/core/services/pipeline/runner.go b/core/services/pipeline/runner.go index c74b59534cf..5bcb47e3129 100644 --- a/core/services/pipeline/runner.go +++ b/core/services/pipeline/runner.go @@ -6,7 +6,11 @@ import ( "sync" "time" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/jinzhu/gorm" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/services/postgres" "github.com/smartcontractkit/chainlink/core/utils" @@ -36,6 +40,15 @@ type ( } ) +var ( + promPipelineTaskExecutionTime = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "pipeline_task_execution_time", + Help: "How long each pipeline task took to execute", + }, + []string{"pipeline_spec_id", "task_type"}, + ) +) + func NewRunner(orm ORM, config Config) *runner { r := &runner{ orm: orm, @@ -180,6 +193,8 @@ func (r *runner) processTaskRun() (anyRemaining bool, err error) { "taskRunID", taskRun.ID, } + start := time.Now() + logger.Infow("Running pipeline task", loggerFields...) inputs := make([]Result, len(predecessors)) @@ -198,6 +213,25 @@ func (r *runner) processTaskRun() (anyRemaining bool, err error) { logger.Errorw("Pipeline task run could not be unmarshaled", append(loggerFields, "error", err)...) return Result{Error: err} } + var job models.JobSpecV2 + err = txdb.Find(&job, "id = ?", jobID).Error + if err != nil { + logger.Errorw("unexpected error could not find job by ID", append(loggerFields, "error", err)...) + return Result{Error: err} + } + + // Order of precedence for task timeout: + // - Specific task timeout (task.TaskTimeout) + // - Job level task timeout (job.MaxTaskDuration) + // - Node level task timeout (JobPipelineMaxTaskDuration) + taskTimeout, isSet := task.TaskTimeout() + if isSet { + ctx, cancel = utils.CombinedContext(r.chStop, taskTimeout) + defer cancel() + } else if job.MaxTaskDuration != models.Interval(time.Duration(0)) { + ctx, cancel = utils.CombinedContext(r.chStop, time.Duration(job.MaxTaskDuration)) + defer cancel() + } result := task.Run(ctx, taskRun, inputs) if _, is := result.Error.(FinalErrors); !is && result.Error != nil { @@ -212,6 +246,9 @@ func (r *runner) processTaskRun() (anyRemaining bool, err error) { logger.Infow("Pipeline task completed", f...) } + elapsed := time.Since(start) + promPipelineTaskExecutionTime.WithLabelValues(string(taskRun.PipelineTaskSpec.PipelineSpecID), string(taskRun.PipelineTaskSpec.Type)).Set(float64(elapsed)) + return result }) } diff --git a/core/services/pipeline/runner_test.go b/core/services/pipeline/runner_test.go index bcd21b912be..959e8ae7c87 100644 --- a/core/services/pipeline/runner_test.go +++ b/core/services/pipeline/runner_test.go @@ -4,12 +4,14 @@ import ( "context" "fmt" "net/http" + "net/http/httptest" "testing" "time" + "gopkg.in/guregu/null.v4" + "github.com/pelletier/go-toml" - "github.com/lib/pq" "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/services/offchainreporting" @@ -121,6 +123,8 @@ func TestRunner(t *testing.T) { } }) + config.Set("DEFAULT_HTTP_ALLOW_UNRESTRICTED_NETWORK_ACCESS", false) + t.Run("handles the case where the parsed value is literally null", func(t *testing.T) { var httpURL string resp := `{"USD": null}` @@ -164,6 +168,7 @@ func TestRunner(t *testing.T) { for _, run := range runs { if run.DotID() == "ds1" { assert.True(t, run.Error.IsZero()) + require.NotNil(t, resp, run.Output) assert.Equal(t, resp, run.Output.Val) } else if run.DotID() == "ds1_parse" { assert.True(t, run.Error.IsZero()) @@ -299,54 +304,183 @@ func TestRunner(t *testing.T) { } }) - t.Run("test min non-bootstrap", func(t *testing.T) { + t.Run("missing required env vars", func(t *testing.T) { + keyStore := offchainreporting.NewKeyStore(db, utils.GetScryptParams(config.Config)) + var os = offchainreporting.OracleSpec{ + Pipeline: *pipeline.NewTaskDAG(), + } + s := ` + type = "offchainreporting" + schemaVersion = 1 + contractAddress = "%s" + isBootstrapPeer = false + observationSource = """ +ds1 [type=http method=GET url="%s" allowunrestrictednetworkaccess="true" %s]; +ds1_parse [type=jsonparse path="USD" lax=true]; +ds1 -> ds1_parse; +""" +` + s = fmt.Sprintf(s, cltest.NewEIP55Address(), "http://blah.com", "") + _, err := services.ValidatedOracleSpecToml(config.Config, s) + require.NoError(t, err) + err = toml.Unmarshal([]byte(s), &os) + require.NoError(t, err) + js := models.JobSpecV2{ + MaxTaskDuration: models.Interval(cltest.MustParseDuration(t, "1s")), + OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Type: string(offchainreporting.JobType), + SchemaVersion: os.SchemaVersion, + } + err = jobORM.CreateJob(context.Background(), &js, os.TaskDAG()) + require.NoError(t, err) + var jb models.JobSpecV2 + err = db.Preload("OffchainreportingOracleSpec", "id = ?", js.ID). + Find(&jb).Error + require.NoError(t, err) + config.Config.Set("P2P_LISTEN_PORT", 2000) // Required to create job spawner delegate. + sd := offchainreporting.NewJobSpawnerDelegate( + db, + jobORM, + config.Config, + keyStore, + nil, + nil, + nil) + _, err = sd.ServicesForSpec(sd.FromDBRow(jb)) + // We expect this to fail as neither the required vars are not set either via the env nor the job itself. + require.Error(t, err) + }) + + t.Run("use env for minimal bootstrap", func(t *testing.T) { + keyStore := offchainreporting.NewKeyStore(db, utils.GetScryptParams(config.Config)) + _, ek, err := keyStore.GenerateEncryptedP2PKey() + require.NoError(t, err) + var os = offchainreporting.OracleSpec{ + Pipeline: *pipeline.NewTaskDAG(), + } + s := ` + type = "offchainreporting" + schemaVersion = 1 + contractAddress = "%s" + isBootstrapPeer = true +` + config.Set("P2P_PEER_ID", ek.PeerID) + s = fmt.Sprintf(s, cltest.NewEIP55Address()) + _, err = services.ValidatedOracleSpecToml(config.Config, s) + require.NoError(t, err) + err = toml.Unmarshal([]byte(s), &os) + require.NoError(t, err) + js := models.JobSpecV2{ + MaxTaskDuration: models.Interval(cltest.MustParseDuration(t, "1s")), + OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Type: string(offchainreporting.JobType), + SchemaVersion: os.SchemaVersion, + } + err = jobORM.CreateJob(context.Background(), &js, os.TaskDAG()) + require.NoError(t, err) + var jb models.JobSpecV2 + err = db.Preload("OffchainreportingOracleSpec", "id = ?", js.ID). + Find(&jb).Error + require.NoError(t, err) + config.Config.Set("P2P_LISTEN_PORT", 2000) // Required to create job spawner delegate. + sd := offchainreporting.NewJobSpawnerDelegate( + db, + jobORM, + config.Config, + keyStore, + nil, + nil, + nil) + _, err = sd.ServicesForSpec(sd.FromDBRow(jb)) + require.NoError(t, err) + }) + + t.Run("use env for minimal non-bootstrap", func(t *testing.T) { keyStore := offchainreporting.NewKeyStore(db, utils.GetScryptParams(config.Config)) _, ek, err := keyStore.GenerateEncryptedP2PKey() require.NoError(t, err) kb, _, err := keyStore.GenerateEncryptedOCRKeyBundle() require.NoError(t, err) - minimalNonBootstrapTemplate := ` + var os = offchainreporting.OracleSpec{ + Pipeline: *pipeline.NewTaskDAG(), + } + s := ` type = "offchainreporting" schemaVersion = 1 contractAddress = "%s" - p2pPeerID = "%s" - p2pBootstrapPeers = ["/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju"] isBootstrapPeer = false - transmitterAddress = "%s" - keyBundleID = "%s" observationTimeout = "10s" observationSource = """ -ds1 [type=http method=GET url="http://data.com"]; +ds1 [type=http method=GET url="%s" allowunrestrictednetworkaccess="true" %s]; ds1_parse [type=jsonparse path="USD" lax=true]; ds1 -> ds1_parse; """ ` + s = fmt.Sprintf(s, cltest.NewEIP55Address(), "http://blah.com", "") + config.Set("P2P_PEER_ID", ek.PeerID) + config.Set("P2P_BOOTSTRAP_PEERS", []string{"/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", + "/dns4/chain.link/tcp/1235/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju"}) + config.Set("OCR_KEY_BUNDLE_ID", kb.ID.String()) + config.Set("OCR_TRANSMITTER_ADDRESS", cltest.DefaultKey) + _, err = services.ValidatedOracleSpecToml(config.Config, s) + require.NoError(t, err) + err = toml.Unmarshal([]byte(s), &os) + require.NoError(t, err) + js := models.JobSpecV2{ + MaxTaskDuration: models.Interval(cltest.MustParseDuration(t, "1s")), + OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Type: string(offchainreporting.JobType), + SchemaVersion: os.SchemaVersion, + } + err = jobORM.CreateJob(context.Background(), &js, os.TaskDAG()) + require.NoError(t, err) + var jb models.JobSpecV2 + err = db.Preload("OffchainreportingOracleSpec", "id = ?", js.ID). + Find(&jb).Error + require.NoError(t, err) + assert.Equal(t, jb.MaxTaskDuration, models.Interval(cltest.MustParseDuration(t, "1s"))) + + config.Config.Set("P2P_LISTEN_PORT", 2000) // Required to create job spawner delegate. + sd := offchainreporting.NewJobSpawnerDelegate( + db, + jobORM, + config.Config, + keyStore, + nil, + nil, + nil) + _, err = sd.ServicesForSpec(sd.FromDBRow(jb)) + require.NoError(t, err) + }) + + t.Run("test min non-bootstrap", func(t *testing.T) { + keyStore := offchainreporting.NewKeyStore(db, utils.GetScryptParams(config.Config)) + _, ek, err := keyStore.GenerateEncryptedP2PKey() + require.NoError(t, err) + kb, _, err := keyStore.GenerateEncryptedOCRKeyBundle() + require.NoError(t, err) var os = offchainreporting.OracleSpec{ - OffchainReportingOracleSpec: models.OffchainReportingOracleSpec{ - P2PBootstrapPeers: pq.StringArray{}, - ObservationTimeout: models.Interval(10 * time.Second), - BlockchainTimeout: models.Interval(20 * time.Second), - ContractConfigTrackerSubscribeInterval: models.Interval(2 * time.Minute), - ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), - ContractConfigConfirmations: uint16(3), - }, Pipeline: *pipeline.NewTaskDAG(), } - s := fmt.Sprintf(minimalNonBootstrapTemplate, cltest.NewEIP55Address(), ek.PeerID, cltest.DefaultKey, kb.ID) - _, err = services.ValidatedOracleSpecToml(s) + s := fmt.Sprintf(minimalNonBootstrapTemplate, cltest.NewEIP55Address(), ek.PeerID, cltest.DefaultKey, kb.ID, "http://blah.com", "") + _, err = services.ValidatedOracleSpecToml(config.Config, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &os) require.NoError(t, err) err = jobORM.CreateJob(context.Background(), &models.JobSpecV2{ + MaxTaskDuration: models.Interval(cltest.MustParseDuration(t, "1s")), OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Type: string(offchainreporting.JobType), + SchemaVersion: os.SchemaVersion, }, os.TaskDAG()) require.NoError(t, err) var jb models.JobSpecV2 err = db.Preload("OffchainreportingOracleSpec", "p2p_peer_id = ?", ek.PeerID). Find(&jb).Error require.NoError(t, err) + assert.Equal(t, jb.MaxTaskDuration, models.Interval(cltest.MustParseDuration(t, "1s"))) config.Config.Set("P2P_LISTEN_PORT", 2000) // Required to create job spawner delegate. sd := offchainreporting.NewJobSpawnerDelegate( @@ -365,32 +499,18 @@ ds1 -> ds1_parse; keyStore := offchainreporting.NewKeyStore(db, utils.GetScryptParams(config.Config)) _, ek, err := keyStore.GenerateEncryptedP2PKey() require.NoError(t, err) - minimalBootstrapTemplate := ` - type = "offchainreporting" - schemaVersion = 1 - contractAddress = "%s" - p2pPeerID = "%s" - p2pBootstrapPeers = [] - isBootstrapPeer = true -` var os = offchainreporting.OracleSpec{ - OffchainReportingOracleSpec: models.OffchainReportingOracleSpec{ - P2PBootstrapPeers: pq.StringArray{}, - ObservationTimeout: models.Interval(10 * time.Second), - BlockchainTimeout: models.Interval(20 * time.Second), - ContractConfigTrackerSubscribeInterval: models.Interval(2 * time.Minute), - ContractConfigTrackerPollInterval: models.Interval(1 * time.Minute), - ContractConfigConfirmations: uint16(3), - }, Pipeline: *pipeline.NewTaskDAG(), } s := fmt.Sprintf(minimalBootstrapTemplate, cltest.NewEIP55Address(), ek.PeerID) - _, err = services.ValidatedOracleSpecToml(s) + _, err = services.ValidatedOracleSpecToml(config.Config, s) require.NoError(t, err) err = toml.Unmarshal([]byte(s), &os) require.NoError(t, err) err = jobORM.CreateJob(context.Background(), &models.JobSpecV2{ OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Type: string(offchainreporting.JobType), + SchemaVersion: os.SchemaVersion, }, os.TaskDAG()) require.NoError(t, err) var jb models.JobSpecV2 @@ -519,4 +639,73 @@ ds1 -> ds1_parse; err = runner.AwaitRun(ctx, runID) require.EqualError(t, err, fmt.Sprintf("could not determine if run is finished (run ID: %v): record not found", runID)) }) + + t.Run("timeouts", func(t *testing.T) { + // There are 4 timeouts: + // - ObservationTimeout = how long the whole OCR time needs to run, or it fails (default 10 seconds) + // - config.JobPipelineMaxTaskDuration() = node level maximum time for a pipeline task (default 10 minutes) + // - config.DefaultHTTPTimeout() * config.DefaultMaxHTTPAttempts() = global, http specific timeouts (default 15s * 5 retries = 75s) + // - "d1 [.... timeout="2s"]" = per task level timeout (should override the global config) + serv := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + time.Sleep(1 * time.Millisecond) + res.WriteHeader(http.StatusOK) + res.Write([]byte(`{"USD":10.1}`)) + })) + defer serv.Close() + + os := makeMinimalHTTPOracleSpec(t, cltest.NewEIP55Address().String(), cltest.DefaultPeerID, cltest.DefaultKey, cltest.DefaultOCRKeyBundleID, serv.URL, `timeout="1ns"`) + jb := &models.JobSpecV2{ + OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Name: null.NewString("a job", true), + Type: string(offchainreporting.JobType), + SchemaVersion: 1, + } + err := jobORM.CreateJob(context.Background(), jb, os.TaskDAG()) + require.NoError(t, err) + runID, err := runner.CreateRun(context.Background(), jb.ID, nil) + require.NoError(t, err) + err = runner.AwaitRun(context.Background(), runID) + require.NoError(t, err) + r, err := runner.ResultsForRun(context.Background(), runID) + require.NoError(t, err) + assert.Error(t, r[0].Error) + + // No task timeout should succeed. + os = makeMinimalHTTPOracleSpec(t, cltest.NewEIP55Address().String(), cltest.DefaultPeerID, cltest.DefaultKey, cltest.DefaultOCRKeyBundleID, serv.URL, "") + jb = &models.JobSpecV2{ + OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Name: null.NewString("a job 2", true), + Type: string(offchainreporting.JobType), + SchemaVersion: 1, + } + err = jobORM.CreateJob(context.Background(), jb, os.TaskDAG()) + require.NoError(t, err) + runID, err = runner.CreateRun(context.Background(), jb.ID, nil) + require.NoError(t, err) + err = runner.AwaitRun(context.Background(), runID) + require.NoError(t, err) + r, err = runner.ResultsForRun(context.Background(), runID) + require.NoError(t, err) + assert.Equal(t, 10.1, r[0].Value) + assert.NoError(t, r[0].Error) + + // Job specified task timeout should fail. + os = makeMinimalHTTPOracleSpec(t, cltest.NewEIP55Address().String(), cltest.DefaultPeerID, cltest.DefaultKey, cltest.DefaultOCRKeyBundleID, serv.URL, "") + jb = &models.JobSpecV2{ + MaxTaskDuration: models.Interval(time.Duration(1)), + OffchainreportingOracleSpec: &os.OffchainReportingOracleSpec, + Name: null.NewString("a job 3", true), + Type: string(offchainreporting.JobType), + SchemaVersion: 1, + } + err = jobORM.CreateJob(context.Background(), jb, os.TaskDAG()) + require.NoError(t, err) + runID, err = runner.CreateRun(context.Background(), jb.ID, nil) + require.NoError(t, err) + err = runner.AwaitRun(context.Background(), runID) + require.NoError(t, err) + r, err = runner.ResultsForRun(context.Background(), runID) + require.NoError(t, err) + assert.Error(t, r[0].Error) + }) } diff --git a/core/services/pipeline/task.bridge.go b/core/services/pipeline/task.bridge.go index 7a57116567e..e0bd95d67fd 100644 --- a/core/services/pipeline/task.bridge.go +++ b/core/services/pipeline/task.bridge.go @@ -56,7 +56,10 @@ func (t *BridgeTask) Run(ctx context.Context, taskRun TaskRun, inputs []Result) URL: models.WebURL(url), Method: "POST", RequestData: withIDAndMeta(t.RequestData, taskRun.PipelineRunID, meta), - config: t.config, + // URL is "safe" because it comes from the node's own database + // Some node operators may run external adapters on their own hardware + AllowUnrestrictedNetworkAccess: MaybeBoolTrue, + config: t.config, }).Run(ctx, taskRun, inputs) if result.Error != nil { return result diff --git a/core/services/pipeline/task.bridge_test.go b/core/services/pipeline/task.bridge_test.go index b52ea52ff4c..054fa20db42 100644 --- a/core/services/pipeline/task.bridge_test.go +++ b/core/services/pipeline/task.bridge_test.go @@ -260,17 +260,22 @@ func TestBridgeTask_AddsID(t *testing.T) { feedWebURL := (*models.WebURL)(feedURL) task := pipeline.BridgeTask{ + Name: "test", RequestData: pipeline.HttpRequestData(ethUSDPairing), } + // Bridge task should be true by default. + store.Config.Set("DEFAULT_HTTP_ALLOW_UNRESTRICTED_NETWORK_ACCESS", false) task.HelperSetConfigAndTxDB(store.Config, store.DB) _, bridge := cltest.NewBridgeType(t) bridge.URL = *feedWebURL + bridge.Name = "test" require.NoError(t, store.ORM.DB.Create(&bridge).Error) - task.Run(context.Background(), pipeline.TaskRun{ + r := task.Run(context.Background(), pipeline.TaskRun{ PipelineRun: pipeline.Run{ Meta: pipeline.JSONSerializable{emptyMeta}, }, }, nil) + require.NoError(t, r.Error) } diff --git a/core/services/pipeline/task.http.go b/core/services/pipeline/task.http.go index d70764f532e..776498a2943 100644 --- a/core/services/pipeline/task.http.go +++ b/core/services/pipeline/task.http.go @@ -6,19 +6,57 @@ import ( "encoding/json" "io" "net/http" + "time" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" ) +type ( + MaybeBool string +) + +const ( + MaybeBoolTrue = MaybeBool("true") + MaybeBoolFalse = MaybeBool("false") + MaybeBoolNull = MaybeBool("") +) + +func MaybeBoolFromString(s string) (MaybeBool, error) { + switch s { + case "true": + return MaybeBoolTrue, nil + case "false": + return MaybeBoolFalse, nil + case "": + return MaybeBoolNull, nil + default: + return "", errors.Errorf("unknown value for bool: %s", s) + } +} + +func (m MaybeBool) Bool() (b bool, isSet bool) { + switch m { + case MaybeBoolTrue: + return true, true + case MaybeBoolFalse: + return false, true + default: + return false, false + } +} + type HTTPTask struct { - BaseTask `mapstructure:",squash"` - Method string - URL models.WebURL - RequestData HttpRequestData `json:"requestData"` + BaseTask `mapstructure:",squash"` + Method string + URL models.WebURL + RequestData HttpRequestData `json:"requestData"` + AllowUnrestrictedNetworkAccess MaybeBool config Config } @@ -30,6 +68,21 @@ type PossibleErrorResponses struct { var _ Task = (*HTTPTask)(nil) +var ( + promHTTPFetchTime = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "pipeline_task_http_fetch_time", + Help: "Time taken to fully execute the HTTP request", + }, + []string{"pipeline_task_spec_id"}, + ) + promHTTPResponseBodySize = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "pipeline_task_http_response_body_size", + Help: "Size (in bytes) of the HTTP response body", + }, + []string{"pipeline_task_spec_id"}, + ) +) + func (t *HTTPTask) Type() TaskType { return TaskTypeHTTP } @@ -58,7 +111,7 @@ func (t *HTTPTask) Run(ctx context.Context, taskRun TaskRun, inputs []Result) Re Timeout: t.config.DefaultHTTPTimeout().Duration(), MaxAttempts: t.config.DefaultMaxHTTPAttempts(), SizeLimit: t.config.DefaultHTTPLimit(), - AllowUnrestrictedNetworkAccess: t.config.DefaultHTTPAllowUnrestrictedNetworkAccess(), + AllowUnrestrictedNetworkAccess: t.allowUnrestrictedNetworkAccess(), } httpRequest := utils.HTTPRequest{ @@ -66,10 +119,14 @@ func (t *HTTPTask) Run(ctx context.Context, taskRun TaskRun, inputs []Result) Re Config: config, } + start := time.Now() responseBytes, statusCode, err := httpRequest.SendRequest(ctx) if err != nil { return Result{Error: errors.Wrapf(err, "error making http request")} } + elapsed := time.Since(start) + promHTTPFetchTime.WithLabelValues(string(taskRun.PipelineTaskSpecID)).Set(float64(elapsed)) + promHTTPResponseBodySize.WithLabelValues(string(taskRun.PipelineTaskSpecID)).Set(float64(len(responseBytes))) if statusCode >= 400 { maybeErr := bestEffortExtractError(responseBytes) @@ -83,6 +140,14 @@ func (t *HTTPTask) Run(ctx context.Context, taskRun TaskRun, inputs []Result) Re return Result{Value: responseBytes} } +func (t *HTTPTask) allowUnrestrictedNetworkAccess() bool { + b, isSet := t.AllowUnrestrictedNetworkAccess.Bool() + if isSet { + return b + } + return t.config.DefaultHTTPAllowUnrestrictedNetworkAccess() +} + func bestEffortExtractError(responseBytes []byte) string { var resp PossibleErrorResponses err := json.Unmarshal(responseBytes, &resp) diff --git a/core/services/pipeline/task.result.go b/core/services/pipeline/task.result.go index dd4b1b87cd9..4c260f64591 100644 --- a/core/services/pipeline/task.result.go +++ b/core/services/pipeline/task.result.go @@ -41,6 +41,15 @@ func (t *ResultTask) Run(_ context.Context, taskRun TaskRun, inputs []Result) Re type FinalErrors []null.String +func (fe FinalErrors) HasErrors() bool { + for _, err := range fe { + if !err.IsZero() { + return true + } + } + return false +} + func (fe FinalErrors) Error() string { bs, err := json.Marshal(fe) if err != nil { diff --git a/core/services/postgres/mocks/event_broadcaster.go b/core/services/postgres/mocks/event_broadcaster.go index 8c3d68a3428..4349d20032f 100644 --- a/core/services/postgres/mocks/event_broadcaster.go +++ b/core/services/postgres/mocks/event_broadcaster.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/services/postgres/mocks/subscription.go b/core/services/postgres/mocks/subscription.go index de163b2bd37..6c5846496fb 100644 --- a/core/services/postgres/mocks/subscription.go +++ b/core/services/postgres/mocks/subscription.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.3.0. DO NOT EDIT. +// Code generated by mockery v2.4.0-beta. DO NOT EDIT. package mocks diff --git a/core/services/prom_reporter.go b/core/services/prom_reporter.go index 0c4fb9dd5b2..f7d7a8a6fa0 100644 --- a/core/services/prom_reporter.go +++ b/core/services/prom_reporter.go @@ -24,6 +24,8 @@ type ( PrometheusBackend interface { SetUnconfirmedTransactions(int64) SetMaxUnconfirmedBlocks(int64) + SetPipelineRunsQueued(n int) + SetPipelineTaskRunsQueued(n int) } defaultBackend struct{} @@ -38,6 +40,14 @@ var ( Name: "max_unconfirmed_blocks", Help: "The max number of blocks any currently unconfirmed transaction has been unconfirmed for", }) + promPipelineRunsQueued = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "pipeline_runs_queued", + Help: "The total number of pipeline runs that are awaiting execution", + }) + promPipelineTaskRunsQueued = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "pipeline_task_runs_queued", + Help: "The total number of pipeline task runs that are awaiting execution", + }) ) func (defaultBackend) SetUnconfirmedTransactions(n int64) { @@ -48,6 +58,14 @@ func (defaultBackend) SetMaxUnconfirmedBlocks(n int64) { promMaxUnconfirmedBlocks.Set(float64(n)) } +func (defaultBackend) SetPipelineRunsQueued(n int) { + promPipelineTaskRunsQueued.Set(float64(n)) +} + +func (defaultBackend) SetPipelineTaskRunsQueued(n int) { + promPipelineRunsQueued.Set(float64(n)) +} + func NewPromReporter(db *sql.DB, opts ...PrometheusBackend) store.HeadTrackable { var backend PrometheusBackend if len(opts) > 0 { @@ -72,6 +90,7 @@ func (pr *promReporter) OnNewLongestChain(ctx context.Context, head models.Head) err := multierr.Combine( errors.Wrap(pr.reportPendingEthTxes(ctx), "reportPendingEthTxes failed"), errors.Wrap(pr.reportMaxUnconfirmedBlocks(ctx, head), "reportMaxUnconfirmedBlocks failed"), + errors.Wrap(pr.reportPipelineRunStats(ctx), "reportPipelineRunStats failed"), ) if err != nil { @@ -124,3 +143,32 @@ AND eth_txes.state = 'unconfirmed'`) pr.backend.SetMaxUnconfirmedBlocks(blocksUnconfirmed) return nil } + +func (pr *promReporter) reportPipelineRunStats(ctx context.Context) (err error) { + rows, err := pr.db.QueryContext(ctx, ` +SELECT pipeline_run_id FROM pipeline_task_runs WHERE finished_at IS NULL +`) + if err != nil { + return errors.Wrap(err, "failed to query for pipeline_run_id") + } + defer func() { + err = multierr.Combine(err, rows.Close()) + }() + + pipelineTaskRunsQueued := 0 + pipelineRunsQueuedSet := make(map[int32]struct{}) + for rows.Next() { + var pipelineRunID int32 + if err := rows.Scan(&pipelineRunID); err != nil { + return errors.Wrap(err, "unexpected error scanning row") + } + pipelineTaskRunsQueued++ + pipelineRunsQueuedSet[pipelineRunID] = struct{}{} + } + pipelineRunsQueued := len(pipelineRunsQueuedSet) + + pr.backend.SetPipelineTaskRunsQueued(pipelineTaskRunsQueued) + pr.backend.SetPipelineRunsQueued(pipelineRunsQueued) + + return nil +} diff --git a/core/services/prom_reporter_test.go b/core/services/prom_reporter_test.go index bc0a40a8e1d..cbfa273f6ba 100644 --- a/core/services/prom_reporter_test.go +++ b/core/services/prom_reporter_test.go @@ -12,15 +12,17 @@ import ( ) func Test_PromReporter_OnNewLongestChain(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - t.Run("with nothing in the database", func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + backend := new(mocks.PrometheusBackend) reporter := services.NewPromReporter(store.DB.DB(), backend) backend.On("SetUnconfirmedTransactions", int64(0)).Return() backend.On("SetMaxUnconfirmedBlocks", int64(0)).Return() + backend.On("SetPipelineTaskRunsQueued", 0).Return() + backend.On("SetPipelineRunsQueued", 0).Return() head := models.Head{Number: 42} reporter.OnNewLongestChain(context.Background(), head) @@ -29,6 +31,9 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { }) t.Run("with unconfirmed eth_txes", func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + backend := new(mocks.PrometheusBackend) reporter := services.NewPromReporter(store.DB.DB(), backend) @@ -39,6 +44,33 @@ func Test_PromReporter_OnNewLongestChain(t *testing.T) { backend.On("SetUnconfirmedTransactions", int64(3)).Return() backend.On("SetMaxUnconfirmedBlocks", int64(35)).Return() + backend.On("SetPipelineTaskRunsQueued", 0).Return() + backend.On("SetPipelineRunsQueued", 0).Return() + + head := models.Head{Number: 42} + reporter.OnNewLongestChain(context.Background(), head) + + backend.AssertExpectations(t) + }) + + t.Run("with unfinished pipeline task runs", func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + require.NoError(t, store.DB.Exec(`SET CONSTRAINTS pipeline_task_runs_pipeline_run_id_fkey DEFERRED`).Error) + require.NoError(t, store.DB.Exec(`SET CONSTRAINTS pipeline_task_runs_pipeline_task_spec_id_fkey DEFERRED`).Error) + + backend := new(mocks.PrometheusBackend) + reporter := services.NewPromReporter(store.DB.DB(), backend) + + cltest.MustInsertUnfinishedPipelineTaskRun(t, store, 1) + cltest.MustInsertUnfinishedPipelineTaskRun(t, store, 1) + cltest.MustInsertUnfinishedPipelineTaskRun(t, store, 2) + + backend.On("SetUnconfirmedTransactions", int64(0)).Return() + backend.On("SetMaxUnconfirmedBlocks", int64(0)).Return() + backend.On("SetPipelineTaskRunsQueued", 3).Return() + backend.On("SetPipelineRunsQueued", 2).Return() head := models.Head{Number: 42} reporter.OnNewLongestChain(context.Background(), head) diff --git a/core/services/run_manager.go b/core/services/run_manager.go index 22e14759074..6d85de416e6 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -158,7 +158,7 @@ func (rm *runManager) CreateErrored( jobSpecID *models.ID, initiator models.Initiator, runErr error) (*models.JobRun, error) { - job, err := rm.orm.Unscoped().FindJob(jobSpecID) + job, err := rm.orm.Unscoped().FindJobSpec(jobSpecID) if err != nil { return nil, errors.Wrap(err, "failed to find job spec") } @@ -190,7 +190,7 @@ func (rm *runManager) Create( "creation_height", creationHeight.String(), ) - job, err := rm.orm.Unscoped().FindJob(jobSpecID) + job, err := rm.orm.Unscoped().FindJobSpec(jobSpecID) if err != nil { return nil, errors.Wrap(err, "failed to find job spec") } diff --git a/core/services/run_manager_test.go b/core/services/run_manager_test.go index b3a79c31d00..6a3cdb479d4 100644 --- a/core/services/run_manager_test.go +++ b/core/services/run_manager_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" ) func makeJobRunWithInitiator(t *testing.T, store *strpkg.Store, job models.JobSpec) models.JobRun { @@ -184,7 +184,7 @@ func TestRunManager_ResumeAllPendingConnection(t *testing.T) { run := makeJobRunWithInitiator(t, store, cltest.NewJob()) run.SetStatus(models.RunStatusPendingConnection) - job, err := store.FindJob(run.JobSpecID) + job, err := store.FindJobSpec(run.JobSpecID) require.NoError(t, err) run.TaskRuns = []models.TaskRun{models.TaskRun{ID: models.NewID(), TaskSpecID: job.Tasks[0].ID, Status: models.RunStatusUnstarted}} @@ -277,7 +277,7 @@ func TestRunManager_Create_DoesNotSaveToTaskSpec(t *testing.T) { require.NoError(t, err) cltest.WaitForJobRunToComplete(t, store, *jr) - retrievedJob, err := store.FindJob(job.ID) + retrievedJob, err := store.FindJobSpec(job.ID) require.NoError(t, err) require.Len(t, job.Tasks, 1) require.Len(t, retrievedJob.Tasks, 1) diff --git a/core/services/scheduler_test.go b/core/services/scheduler_test.go index cd1df3953d2..a1987883a85 100644 --- a/core/services/scheduler_test.go +++ b/core/services/scheduler_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" ) func TestScheduler_Start_LoadingRecurringJobs(t *testing.T) { diff --git a/core/services/synchronization/explorer_client.go b/core/services/synchronization/explorer_client.go index 6bcf492a72e..a6f792a9403 100644 --- a/core/services/synchronization/explorer_client.go +++ b/core/services/synchronization/explorer_client.go @@ -7,10 +7,11 @@ import ( "net/http" "net/url" "sync" + "sync/atomic" "time" "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" + "github.com/smartcontractkit/chainlink/core/static" "github.com/smartcontractkit/chainlink/core/utils" "github.com/gorilla/websocket" @@ -33,6 +34,9 @@ const ( ConnectionStatusError = ConnectionStatus("error") ) +// SendBufferSize is the number of messages to keep in the buffer before dropping additional ones +const SendBufferSize = 100 + // ExplorerClient encapsulates all the functionality needed to // push run information to explorer. type ExplorerClient interface { @@ -54,17 +58,18 @@ func (NoopExplorerClient) Send([]byte) {} func (NoopExplorerClient) Receive(...time.Duration) ([]byte, error) { return nil, nil } type explorerClient struct { - boot *sync.Mutex - conn *websocket.Conn - cancel context.CancelFunc - send chan []byte - receive chan []byte - sleeper utils.Sleeper - started bool - status ConnectionStatus - url *url.URL - accessKey string - secret string + boot *sync.RWMutex + conn *websocket.Conn + cancel context.CancelFunc + send chan []byte + dropMessageCount uint32 + receive chan []byte + sleeper utils.Sleeper + started bool + status ConnectionStatus + url *url.URL + accessKey string + secret string closeRequested chan struct{} closed chan struct{} @@ -77,9 +82,9 @@ type explorerClient struct { func NewExplorerClient(url *url.URL, accessKey, secret string) ExplorerClient { return &explorerClient{ url: url, - send: make(chan []byte), + send: make(chan []byte, SendBufferSize), receive: make(chan []byte), - boot: &sync.Mutex{}, + boot: new(sync.RWMutex), sleeper: utils.NewBackoffSleeper(), status: ConnectionStatusDisconnected, accessKey: accessKey, @@ -125,7 +130,36 @@ func (ec *explorerClient) Start() error { // holds it in a small buffer until connection, throwing away messages // once buffer is full. func (ec *explorerClient) Send(data []byte) { - ec.send <- data + ec.boot.RLock() + defer ec.boot.RUnlock() + if !ec.started { + panic("send on unstarted explorer client") + } + select { + case ec.send <- data: + atomic.StoreUint32(&ec.dropMessageCount, 0) + default: + ec.logBufferFullWithExpBackoff(data) + } +} + +// logBufferFullWithExpBackoff logs messages at +// 1 +// 2 +// 4 +// 8 +// 16 +// 32 +// 64 +// 100 +// 200 +// 300 +// etc... +func (ec *explorerClient) logBufferFullWithExpBackoff(data []byte) { + count := atomic.AddUint32(&ec.dropMessageCount, 1) + if count > 0 && (count%100 == 0 || count&(count-1) == 0) { + logger.Warnw("explorer client buffer full, dropping message", "data", data, "droppedCount", count) + } } // Receive blocks the caller while waiting for a response from the server, @@ -258,8 +292,8 @@ func (ec *explorerClient) connect(ctx context.Context) error { authHeader := http.Header{} authHeader.Add("X-Explore-Chainlink-Accesskey", ec.accessKey) authHeader.Add("X-Explore-Chainlink-Secret", ec.secret) - authHeader.Add("X-Explore-Chainlink-Core-Version", store.Version) - authHeader.Add("X-Explore-Chainlink-Core-Sha", store.Sha) + authHeader.Add("X-Explore-Chainlink-Core-Version", static.Version) + authHeader.Add("X-Explore-Chainlink-Core-Sha", static.Sha) conn, _, err := websocket.DefaultDialer.DialContext(ctx, ec.url.String(), authHeader) if err != nil { diff --git a/core/services/synchronization/explorer_client_test.go b/core/services/synchronization/explorer_client_test.go index 2a6a20e6e03..e8c8eb2ff9a 100644 --- a/core/services/synchronization/explorer_client_test.go +++ b/core/services/synchronization/explorer_client_test.go @@ -9,7 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/services/synchronization" - "github.com/smartcontractkit/chainlink/core/store" + "github.com/smartcontractkit/chainlink/core/static" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -85,8 +85,8 @@ func TestWebSocketClient_Authentication(t *testing.T) { headers := <-headerChannel assert.Equal(t, []string{"accessKey"}, headers["X-Explore-Chainlink-Accesskey"]) assert.Equal(t, []string{"secret"}, headers["X-Explore-Chainlink-Secret"]) - assert.Equal(t, []string{store.Version}, headers["X-Explore-Chainlink-Core-Version"]) - assert.Equal(t, []string{store.Sha}, headers["X-Explore-Chainlink-Core-Sha"]) + assert.Equal(t, []string{static.Version}, headers["X-Explore-Chainlink-Core-Version"]) + assert.Equal(t, []string{static.Sha}, headers["X-Explore-Chainlink-Core-Sha"]) }) } diff --git a/core/services/synchronization/presenters.go b/core/services/synchronization/presenters.go index c3d4041b9e4..97ca8ae8e3f 100644 --- a/core/services/synchronization/presenters.go +++ b/core/services/synchronization/presenters.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) // SyncJobRunPresenter presents a JobRun for synchronization purposes diff --git a/core/services/synchronization/presenters_test.go b/core/services/synchronization/presenters_test.go index 79d0ef6ec20..25cf201ac1f 100644 --- a/core/services/synchronization/presenters_test.go +++ b/core/services/synchronization/presenters_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) func TestSyncJobRunPresenter_HappyPath(t *testing.T) { diff --git a/core/services/synchronization/stats_pusher.go b/core/services/synchronization/stats_pusher.go index 8bce3f018e8..7f4ea3bcdb1 100644 --- a/core/services/synchronization/stats_pusher.go +++ b/core/services/synchronization/stats_pusher.go @@ -9,7 +9,6 @@ import ( "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/smartcontractkit/chainlink/core/utils" "github.com/jinzhu/gorm" @@ -43,18 +42,20 @@ type StatsPusher interface { PushNow() GetURL() url.URL GetStatus() ConnectionStatus + AllSyncEvents(cb func(models.SyncEvent) error) error } type NoopStatsPusher struct{} -func (NoopStatsPusher) Start() error { return nil } -func (NoopStatsPusher) Close() error { return nil } -func (NoopStatsPusher) PushNow() {} -func (NoopStatsPusher) GetURL() url.URL { return url.URL{} } -func (NoopStatsPusher) GetStatus() ConnectionStatus { return ConnectionStatusDisconnected } +func (NoopStatsPusher) Start() error { return nil } +func (NoopStatsPusher) Close() error { return nil } +func (NoopStatsPusher) PushNow() {} +func (NoopStatsPusher) GetURL() url.URL { return url.URL{} } +func (NoopStatsPusher) GetStatus() ConnectionStatus { return ConnectionStatusDisconnected } +func (NoopStatsPusher) AllSyncEvents(cb func(models.SyncEvent) error) error { return nil } type statsPusher struct { - ORM *orm.ORM + DB *gorm.DB ExplorerClient ExplorerClient Period time.Duration cancel context.CancelFunc @@ -69,7 +70,7 @@ const ( ) // NewStatsPusher returns a new StatsPusher service -func NewStatsPusher(orm *orm.ORM, explorerClient ExplorerClient, afters ...utils.Afterer) StatsPusher { +func NewStatsPusher(db *gorm.DB, explorerClient ExplorerClient, afters ...utils.Afterer) StatsPusher { var clock utils.Afterer if len(afters) == 0 { clock = utils.Clock{} @@ -78,7 +79,7 @@ func NewStatsPusher(orm *orm.ORM, explorerClient ExplorerClient, afters ...utils } return &statsPusher{ - ORM: orm, + DB: db, ExplorerClient: explorerClient, Period: 30 * time.Minute, clock: clock, @@ -103,15 +104,8 @@ func (sp *statsPusher) GetStatus() ConnectionStatus { // Start starts the stats pusher func (sp *statsPusher) Start() error { gormCallbacksMutex.Lock() - err := sp.ORM.RawDB(func(db *gorm.DB) error { - db.Callback().Create().Register(createCallbackName, createSyncEventWithStatsPusher(sp, sp.ORM)) - db.Callback().Update().Register(updateCallbackName, createSyncEventWithStatsPusher(sp, sp.ORM)) - return nil - }) - if err != nil { - gormCallbacksMutex.Unlock() - return errors.Wrap(err, "error creating statsPusher orm callbacks") - } + sp.DB.Callback().Create().Register(createCallbackName, createSyncEventWithStatsPusher(sp)) + sp.DB.Callback().Update().Register(updateCallbackName, createSyncEventWithStatsPusher(sp)) gormCallbacksMutex.Unlock() ctx, cancel := context.WithCancel(context.Background()) @@ -127,14 +121,8 @@ func (sp *statsPusher) Close() error { } gormCallbacksMutex.Lock() - err := sp.ORM.RawDB(func(db *gorm.DB) error { - db.Callback().Create().Remove(createCallbackName) - db.Callback().Update().Remove(updateCallbackName) - return nil - }) - if err != nil { - logger.Errorw("error removing gorm statsPusher callbacks on shutdown", "error", err) - } + sp.DB.Callback().Create().Remove(createCallbackName) + sp.DB.Callback().Update().Remove(updateCallbackName) gormCallbacksMutex.Unlock() return nil @@ -194,7 +182,7 @@ func (sp *statsPusher) pusherLoop(parentCtx context.Context) error { func (sp *statsPusher) pushEvents() error { gormCallbacksMutex.RLock() defer gormCallbacksMutex.RUnlock() - err := sp.ORM.AllSyncEvents(func(event models.SyncEvent) error { + err := sp.AllSyncEvents(func(event models.SyncEvent) error { return sp.syncEvent(event) }) @@ -206,6 +194,25 @@ func (sp *statsPusher) pushEvents() error { return nil } +func (sp *statsPusher) AllSyncEvents(cb func(models.SyncEvent) error) error { + var events []models.SyncEvent + err := sp.DB. + Order("id, created_at asc"). + Find(&events).Error + if err != nil { + return err + } + + for _, event := range events { + err = cb(event) + if err != nil { + return err + } + } + + return nil +} + func (sp *statsPusher) syncEvent(event models.SyncEvent) error { sp.ExplorerClient.Send([]byte(event.Body)) numberEventsSent.Inc() @@ -225,9 +232,7 @@ func (sp *statsPusher) syncEvent(event models.SyncEvent) error { return errors.New("event not created") } - err = sp.ORM.RawDB(func(db *gorm.DB) error { - return db.Delete(event).Error - }) + err = sp.DB.Delete(event).Error if err != nil { return errors.Wrap(err, "syncEvent#DB.Delete failed") } @@ -235,7 +240,7 @@ func (sp *statsPusher) syncEvent(event models.SyncEvent) error { return nil } -func createSyncEventWithStatsPusher(sp StatsPusher, orm *orm.ORM) func(*gorm.Scope) { +func createSyncEventWithStatsPusher(sp StatsPusher) func(*gorm.Scope) { return func(scope *gorm.Scope) { if scope.HasError() { return @@ -251,8 +256,6 @@ func createSyncEventWithStatsPusher(sp StatsPusher, orm *orm.ORM) func(*gorm.Sco return } - orm.MustEnsureAdvisoryLock() - presenter := SyncJobRunPresenter{run} bodyBytes, err := json.Marshal(presenter) if err != nil { diff --git a/core/services/synchronization/stats_pusher_test.go b/core/services/synchronization/stats_pusher_test.go index e3cac21be71..eb7f03f9044 100644 --- a/core/services/synchronization/stats_pusher_test.go +++ b/core/services/synchronization/stats_pusher_test.go @@ -24,7 +24,7 @@ func TestStatsPusher(t *testing.T) { err := explorerClient.Start() require.NoError(t, err) - pusher := synchronization.NewStatsPusher(store.ORM, explorerClient) + pusher := synchronization.NewStatsPusher(store.DB, explorerClient) pusher.Start() defer pusher.Close() @@ -52,7 +52,7 @@ func TestStatsPusher_ClockTrigger(t *testing.T) { err := explorerClient.Start() require.NoError(t, err) - pusher := synchronization.NewStatsPusher(store.ORM, explorerClient, clock) + pusher := synchronization.NewStatsPusher(store.DB, explorerClient, clock) pusher.Start() defer pusher.Close() @@ -81,7 +81,7 @@ func TestStatsPusher_NoAckLeavesEvent(t *testing.T) { err := explorerClient.Start() require.NoError(t, err) - pusher := synchronization.NewStatsPusher(store.ORM, explorerClient) + pusher := synchronization.NewStatsPusher(store.DB, explorerClient) pusher.Start() defer pusher.Close() @@ -107,7 +107,7 @@ func TestStatsPusher_BadSyncLeavesEvent(t *testing.T) { err := explorerClient.Start() require.NoError(t, err) - pusher := synchronization.NewStatsPusher(store.ORM, explorerClient, clock) + pusher := synchronization.NewStatsPusher(store.DB, explorerClient, clock) pusher.Start() defer pusher.Close() diff --git a/core/services/telemetry/types.go b/core/services/telemetry/types.go deleted file mode 100644 index c5335d6d263..00000000000 --- a/core/services/telemetry/types.go +++ /dev/null @@ -1,8 +0,0 @@ -package telemetry - -// FIXME: remove this - -// MonitoringEndpoint is where the OCR protocol sends monitoring output -type MonitoringEndpoint interface { - SendLog(log []byte) -} diff --git a/core/services/validators.go b/core/services/validators.go index 563c10c897e..45d09c1b135 100644 --- a/core/services/validators.go +++ b/core/services/validators.go @@ -4,11 +4,12 @@ import ( "encoding/json" "fmt" "net/url" - "reflect" "regexp" "strings" "time" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/multiformats/go-multiaddr" "github.com/smartcontractkit/chainlink/core/services/pipeline" "go.uber.org/multierr" @@ -24,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/smartcontractkit/chainlink/core/utils" + ocr "github.com/smartcontractkit/libocr/offchainreporting" "github.com/tidwall/gjson" ) @@ -376,7 +378,7 @@ func ValidateServiceAgreement(sa models.ServiceAgreement, store *store.Store) er } // ValidatedOracleSpecToml validates an oracle spec that came from TOML -func ValidatedOracleSpecToml(tomlString string) (spec offchainreporting.OracleSpec, err error) { +func ValidatedOracleSpecToml(config *orm.Config, tomlString string) (spec offchainreporting.OracleSpec, err error) { defer func() { if r := recover(); r != nil { err = errors.Errorf("panicked with err %v", r) @@ -391,6 +393,8 @@ func ValidatedOracleSpecToml(tomlString string) (spec offchainreporting.OracleSp if err != nil { return spec, err } + // Note this validates all the fields which implement an UnmarshalText + // i.e. TransmitterAddress, PeerID... err = tree.Unmarshal(&oros) if err != nil { return spec, err @@ -403,22 +407,6 @@ func ValidatedOracleSpecToml(tomlString string) (spec offchainreporting.OracleSp // TODO(#175801426): upstream a way to check for undecoded keys in go-toml // TODO(#175801038): upstream support for time.Duration defaults in go-toml - var defaults = map[string]interface{}{ - "observationTimeout": models.Interval(10 * time.Second), - "blockchainTimeout": models.Interval(20 * time.Second), - "contractConfigTrackerSubscribeInterval": models.Interval(2 * time.Minute), - "contractConfigTrackerPollInterval": models.Interval(1 * time.Minute), - } - for k, v := range defaults { - cv := v - if !tree.Has(k) { - reflect.ValueOf(&spec.OffchainReportingOracleSpec). - Elem(). - FieldByName(strings.Title(k)). - Set(reflect.ValueOf(cv)) - } - } - if spec.Type != "offchainreporting" { return spec, errors.Errorf("the only supported type is currently 'offchainreporting', got %s", spec.Type) } @@ -428,14 +416,22 @@ func ValidatedOracleSpecToml(tomlString string) (spec offchainreporting.OracleSp if !tree.Has("isBootstrapPeer") { return spec, errors.New("isBootstrapPeer is not defined") } + for i := range spec.P2PBootstrapPeers { + if _, err := multiaddr.NewMultiaddr(spec.P2PBootstrapPeers[i]); err != nil { + return spec, errors.Wrapf(err, "p2p bootstrap peer %v is invalid", spec.P2PBootstrapPeers[i]) + } + } if spec.IsBootstrapPeer { if err := validateBootstrapSpec(tree, spec); err != nil { return spec, err } - } else if err := validateNonBootstrapSpec(tree, spec); err != nil { + } else if err := validateNonBootstrapSpec(tree, config, spec); err != nil { + return spec, err + } + if err := validateTimingParameters(config, spec); err != nil { return spec, err } - if err := validateTimingParameters(spec); err != nil { + if err := validateMonitoringURL(spec); err != nil { return spec, err } return spec, nil @@ -445,21 +441,16 @@ func ValidatedOracleSpecToml(tomlString string) (spec offchainreporting.OracleSp var ( // Common to both bootstrap and non-boostrap params = map[string]struct{}{ - "type": {}, - "schemaVersion": {}, - "contractAddress": {}, - "isBootstrapPeer": {}, - "p2pPeerID": {}, - "p2pBootstrapPeers": {}, + "type": {}, + "schemaVersion": {}, + "contractAddress": {}, + "isBootstrapPeer": {}, } // Boostrap and non-bootstrap parameters // are mutually exclusive. bootstrapParams = map[string]struct{}{} nonBootstrapParams = map[string]struct{}{ - "observationSource": {}, - "observationTimeout": {}, - "keyBundleID": {}, - "transmitterAddress": {}, + "observationSource": {}, } ) @@ -471,24 +462,16 @@ func cloneSet(in map[string]struct{}) map[string]struct{} { return out } -func validateTimingParameters(spec offchainreporting.OracleSpec) error { - // TODO: expose these various constants from libocr so they are defined in one place. - if time.Duration(spec.ObservationTimeout) < 1*time.Millisecond || time.Duration(spec.ObservationTimeout) > 20*time.Second { - return errors.Errorf("require 1ms <= observation timeout <= 20s") - } - if time.Duration(spec.BlockchainTimeout) < 1*time.Millisecond || time.Duration(spec.ObservationTimeout) > 20*time.Second { - return errors.Errorf("require 1ms <= blockchain timeout <= 20s ") - } - if time.Duration(spec.ContractConfigTrackerPollInterval) < 15*time.Second || time.Duration(spec.ContractConfigTrackerPollInterval) > 120*time.Second { - return errors.Errorf("require 15s <= contract config tracker poll interval <= 120s ") - } - if time.Duration(spec.ContractConfigTrackerSubscribeInterval) < 2*time.Minute || time.Duration(spec.ContractConfigTrackerSubscribeInterval) > 5*time.Minute { - return errors.Errorf("require 2m <= contract config subscribe interval <= 5m ") - } - if spec.ContractConfigConfirmations < 2 || spec.ContractConfigConfirmations > 10 { - return errors.Errorf("require 2 <= contract config confirmations <= 10 ") - } - return nil +func validateTimingParameters(config *orm.Config, spec offchainreporting.OracleSpec) error { + return ocr.SanityCheckLocalConfig(ocrtypes.LocalConfig{ + BlockchainTimeout: config.OCRBlockchainTimeout(time.Duration(spec.BlockchainTimeout)), + ContractConfigConfirmations: config.OCRContractConfirmations(spec.ContractConfigConfirmations), + ContractConfigTrackerPollInterval: config.OCRContractPollInterval(time.Duration(spec.ContractConfigTrackerPollInterval)), + ContractConfigTrackerSubscribeInterval: config.OCRContractSubscribeInterval(time.Duration(spec.ContractConfigTrackerSubscribeInterval)), + ContractTransmitterTransmitTimeout: config.OCRContractTransmitterTransmitTimeout(), + DatabaseTimeout: config.OCRDatabaseTimeout(), + DataSourceTimeout: config.OCRObservationTimeout(time.Duration(spec.ObservationTimeout)), + }) } func validateBootstrapSpec(tree *toml.Tree, spec offchainreporting.OracleSpec) error { @@ -499,15 +482,10 @@ func validateBootstrapSpec(tree *toml.Tree, spec offchainreporting.OracleSpec) e if err := validateExplicitlySetKeys(tree, expected, notExpected, "bootstrap"); err != nil { return err } - for i := range spec.P2PBootstrapPeers { - if _, err := multiaddr.NewMultiaddr(spec.P2PBootstrapPeers[i]); err != nil { - return errors.Errorf("p2p bootstrap peer %d is invalid: err %v", i, err) - } - } return nil } -func validateNonBootstrapSpec(tree *toml.Tree, spec offchainreporting.OracleSpec) error { +func validateNonBootstrapSpec(tree *toml.Tree, config *orm.Config, spec offchainreporting.OracleSpec) error { expected, notExpected := cloneSet(params), cloneSet(bootstrapParams) for k := range nonBootstrapParams { expected[k] = struct{}{} @@ -518,12 +496,18 @@ func validateNonBootstrapSpec(tree *toml.Tree, spec offchainreporting.OracleSpec if spec.Pipeline.DOTSource == "" { return errors.New("no pipeline specified") } - if len(spec.P2PBootstrapPeers) < 1 { - return errors.New("must specify at least one bootstrap peer") + observationTimeout := config.OCRObservationTimeout(time.Duration(spec.ObservationTimeout)) + if time.Duration(spec.MaxTaskDuration) > observationTimeout { + return errors.Errorf("max task duration must be < observation timeout") } - for i := range spec.P2PBootstrapPeers { - if _, err := multiaddr.NewMultiaddr(spec.P2PBootstrapPeers[i]); err != nil { - return errors.Errorf("p2p bootstrap peer %d is invalid: err %v", i, err) + tasks, err := spec.Pipeline.TasksInDependencyOrder() + if err != nil { + return errors.Wrap(err, "invalid observation source") + } + for _, task := range tasks { + timeout, set := task.TaskTimeout() + if set && timeout > observationTimeout { + return errors.Errorf("individual max task duration must be < observation timeout") } } return nil @@ -544,3 +528,45 @@ func validateExplicitlySetKeys(tree *toml.Tree, expected map[string]struct{}, no } return err } + +func validateMonitoringURL(spec offchainreporting.OracleSpec) error { + if spec.MonitoringEndpoint == "" { + return nil + } + _, err := url.Parse(spec.MonitoringEndpoint) + return err +} + +func ValidatedEthRequestEventSpec(tomlString string) (spec EthRequestEventSpec, err error) { + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("panicked with err %v", r) + } + }() + + var eres models.EthRequestEventSpec + spec = EthRequestEventSpec{ + Pipeline: *pipeline.NewTaskDAG(), + } + tree, err := toml.Load(tomlString) + if err != nil { + return spec, err + } + err = tree.Unmarshal(&eres) + if err != nil { + return spec, err + } + err = tree.Unmarshal(&spec) + if err != nil { + return spec, err + } + spec.EthRequestEventSpec = eres + + if spec.Type != "ethrequestevent" { + return spec, errors.Errorf("unsupported type %s", spec.Type) + } + if spec.SchemaVersion != uint32(1) { + return spec, errors.Errorf("the only supported schema version is currently 1, got %v", spec.SchemaVersion) + } + return spec, nil +} diff --git a/core/services/validators_test.go b/core/services/validators_test.go index e15740ea4db..f8ad4f31a85 100644 --- a/core/services/validators_test.go +++ b/core/services/validators_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/smartcontractkit/chainlink/core/store/orm" + "github.com/smartcontractkit/chainlink/core/services/offchainreporting" "github.com/smartcontractkit/chainlink/core/adapters" @@ -486,9 +488,10 @@ func TestValidateInitiator_FeedsErrors(t *testing.T) { func TestValidateOracleSpec(t *testing.T) { var tt = []struct { - name string - toml string - assertion func(t *testing.T, os offchainreporting.OracleSpec, err error) + name string + toml string + setGlobals func(t *testing.T, c *orm.Config) + assertion func(t *testing.T, os offchainreporting.OracleSpec, err error) }{ { name: "decodes valid oracle spec toml", @@ -560,9 +563,6 @@ answer1 [type=median index=0]; `, assertion: func(t *testing.T, os offchainreporting.OracleSpec, err error) { require.Error(t, err) - assert.Contains(t, err.Error(), "unrecognised key for bootstrap peer: keyBundleID") - assert.Contains(t, err.Error(), "unrecognised key for bootstrap peer: transmitterAddress") - assert.Contains(t, err.Error(), "unrecognised key for bootstrap peer: observationTimeout") assert.Contains(t, err.Error(), "unrecognised key for bootstrap peer: observationSource") }, }, @@ -651,7 +651,7 @@ blah }, }, { - name: "sane defaults", + name: "broken monitoring endpoint", toml: ` type = "offchainreporting" schemaVersion = 1 @@ -659,15 +659,73 @@ contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" p2pBootstrapPeers = [] isBootstrapPeer = true +monitoringEndpoint = "\t/fd\2ff )(*&^%$#@" `, assertion: func(t *testing.T, os offchainreporting.OracleSpec, err error) { - require.NoError(t, err) - assert.Equal(t, os.ContractConfigConfirmations, uint16(3)) - assert.Equal(t, os.ObservationTimeout, models.Interval(10*time.Second)) - assert.Equal(t, os.BlockchainTimeout, models.Interval(20*time.Second)) - assert.Equal(t, os.ContractConfigTrackerSubscribeInterval, models.Interval(2*time.Minute)) - assert.Equal(t, os.ContractConfigTrackerPollInterval, models.Interval(1*time.Minute)) - assert.Len(t, os.P2PBootstrapPeers, 0) + require.EqualError(t, err, "(8, 23): invalid escape sequence: \\2") + }, + }, + { + name: "max task duration > observation timeout should error", + toml: ` +type = "offchainreporting" +maxTaskDuration = "30s" +schemaVersion = 1 +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" +p2pBootstrapPeers = [ +"/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", +] +isBootstrapPeer = false +keyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" +monitoringEndpoint = "chain.link:4321" +transmitterAddress = "0xaA07d525B4006a2f927D79CA78a23A8ee680A32A" +observationTimeout = "10s" +observationSource = """ +ds1 [type=bridge name=voter_turnout]; +""" +`, + assertion: func(t *testing.T, os offchainreporting.OracleSpec, err error) { + require.Error(t, err) + require.Contains(t, err.Error(), "max task duration must be < observation timeout") + }, + }, + { + name: "invalid peer ID", + toml: ` +type = "offchainreporting" +schemaVersion = 1 +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +p2pPeerID = "blah" +isBootstrapPeer = true +`, + assertion: func(t *testing.T, os offchainreporting.OracleSpec, err error) { + require.Error(t, err) + require.Contains(t, err.Error(), "failed to parse peer ID") + }, + }, + { + name: "individual max task duration > observation timeout should error", + toml: ` +type = "offchainreporting" +schemaVersion = 1 +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" +p2pBootstrapPeers = [ +"/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", +] +isBootstrapPeer = false +keyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" +monitoringEndpoint = "chain.link:4321" +transmitterAddress = "0xaA07d525B4006a2f927D79CA78a23A8ee680A32A" +observationTimeout = "10s" +observationSource = """ +ds1 [type=bridge name=voter_turnout timeout="30s"]; +""" +`, + assertion: func(t *testing.T, os offchainreporting.OracleSpec, err error) { + require.Error(t, err) + require.Contains(t, err.Error(), "individual max task duration must be < observation timeout") }, }, { @@ -677,11 +735,44 @@ isBootstrapPeer = true require.Error(t, err) }, }, + { + name: "invalid global default", + toml: ` +type = "offchainreporting" +schemaVersion = 1 +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +p2pPeerID = "12D3KooWHfYFQ8hGttAYbMCevQVESEQhzJAqFZokMVtom8bNxwGq" +p2pBootstrapPeers = [ +"/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju", +] +isBootstrapPeer = false +keyBundleID = "73e8966a78ca09bb912e9565cfb79fbe8a6048fab1f0cf49b18047c3895e0447" +monitoringEndpoint = "chain.link:4321" +transmitterAddress = "0xaA07d525B4006a2f927D79CA78a23A8ee680A32A" +observationSource = """ +ds1 [type=bridge name=voter_turnout]; +ds1_parse [type=jsonparse path="one,two"]; +ds1_multiply [type=multiply times=1.23]; +ds1 -> ds1_parse -> ds1_multiply -> answer1; +answer1 [type=median index=0]; +""" +`, + assertion: func(t *testing.T, os offchainreporting.OracleSpec, err error) { + require.Error(t, err) + }, + setGlobals: func(t *testing.T, c *orm.Config) { + c.Set("OCR_OBSERVATION_TIMEOUT", "20m") + }, + }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - s, err := services.ValidatedOracleSpecToml(tc.toml) + c := orm.NewConfig() + if tc.setGlobals != nil { + tc.setGlobals(t, c) + } + s, err := services.ValidatedOracleSpecToml(c, tc.toml) tc.assertion(t, s, err) }) } diff --git a/core/services/vrf/vrf_testnet_d20_test.go b/core/services/vrf/vrf_testnet_d20_test.go deleted file mode 100644 index e4eb924fc2f..00000000000 --- a/core/services/vrf/vrf_testnet_d20_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package vrf_test - -import ( - "math/big" - "testing" - - "github.com/smartcontractkit/chainlink/core/internal/gethwrappers/generated/vrf_testnet_d20" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/stretchr/testify/require" -) - -func TestD20ExampleContract(t *testing.T) { - coordinator := newVRFCoordinatorUniverse(t) - keyHash, _, _ := registerProvingKey(t, coordinator) - d20Address, _, d20, err := vrf_testnet_d20.DeployVRFTestnetD20(coordinator.sergey, - coordinator.backend, coordinator.rootContractAddress, - coordinator.linkContractAddress, keyHash) - require.NoError(t, err, "failed to deploy example contract") - coordinator.backend.Commit() - _, err = coordinator.linkContract.Transfer(coordinator.sergey, d20Address, - big.NewInt(1).Lsh(oneEth, 4)) - require.NoError(t, err, "failed to func example contract with LINK") - coordinator.backend.Commit() - // negative control: make sure there are no results prior to rolling - _, err = d20.D20Results(nil, big.NewInt(0)) - require.Error(t, err, "should be no results in D20 contract") - _, err = d20.RollDice(coordinator.sergey, seed) - require.NoError(t, err, "failed to initiate VRF randomness request") - coordinator.backend.Commit() - log, err := coordinator.rootContract.FilterRandomnessRequest(nil, nil) - require.NoError(t, err, "failed to subscribe to RandomnessRequest logs") - logCount := 0 - for log.Next() { - logCount += 1 - } - require.Equal(t, 1, logCount, - "unexpected log generated by randomness request to VRFCoordinator") - cLog := models.RawRandomnessRequestLogToRandomnessRequestLog( - (*models.RawRandomnessRequestLog)(log.Event)) - _ = fulfillRandomnessRequest(t, coordinator, *cLog) - result, err := d20.D20Results(nil, big.NewInt(0)) - require.NoError(t, err, "failed to retrieve result from D20 contract") - require.LessOrEqual(t, result.Cmp(big.NewInt(20)), 0) - require.GreaterOrEqual(t, result.Cmp(big.NewInt(0)), 0) -} diff --git a/core/store/version.go b/core/static/static.go similarity index 87% rename from core/store/version.go rename to core/static/static.go index eefbc56ddf7..cc464fea94e 100644 --- a/core/store/version.go +++ b/core/static/static.go @@ -1,4 +1,4 @@ -package store +package static // Version the version of application var Version = "unset" diff --git a/core/store/migrations/migrate.go b/core/store/migrations/migrate.go index 20ed1a356a2..4c2f54efdc0 100644 --- a/core/store/migrations/migrate.go +++ b/core/store/migrations/migrate.go @@ -3,14 +3,11 @@ package migrations import ( "regexp" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605816413" - - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605630295" - - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605218542" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1607113528" "github.com/jinzhu/gorm" "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration0" "github.com/smartcontractkit/chainlink/core/store/migrations/migration1559081901" "github.com/smartcontractkit/chainlink/core/store/migrations/migration1559767166" @@ -92,6 +89,14 @@ import ( "github.com/smartcontractkit/chainlink/core/store/migrations/migration1604707007" "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605186531" "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605213161" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605218542" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605630295" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1605816413" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1606141477" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1606303568" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1606320711" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1606749860" + "github.com/smartcontractkit/chainlink/core/store/migrations/migration1606910307" gormigrate "gopkg.in/gormigrate.v1" ) @@ -438,6 +443,30 @@ func init() { ID: "migration1605816413", Migrate: migration1605816413.Migrate, }, + { + ID: "migration1606303568", + Migrate: migration1606303568.Migrate, + }, + { + ID: "migration1606320711", + Migrate: migration1606320711.Migrate, + }, + { + ID: "migration1606910307", + Migrate: migration1606910307.Migrate, + }, + { + ID: "migration1606141477", + Migrate: migration1606141477.Migrate, + }, + { + ID: "1606749860", + Migrate: migration1606749860.Migrate, + }, + { + ID: "1607113528", + Migrate: migration1607113528.Migrate, + }, } } diff --git a/core/store/migrations/migrate_test.go b/core/store/migrations/migrate_test.go index 9c1be81e0f7..4e3ee00bd15 100644 --- a/core/store/migrations/migrate_test.go +++ b/core/store/migrations/migrate_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/internal/cltest" diff --git a/core/store/migrations/migration0/migrate.go b/core/store/migrations/migration0/migrate.go index 43f6b650a8e..cf4e696bd82 100644 --- a/core/store/migrations/migration0/migrate.go +++ b/core/store/migrations/migration0/migrate.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/jinzhu/gorm" "github.com/pkg/errors" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" ) // Migrate runs the initial migration diff --git a/core/store/migrations/migration1559081901/migrate.go b/core/store/migrations/migration1559081901/migrate.go index 4180ff35b37..5abc438a1e4 100644 --- a/core/store/migrations/migration1559081901/migrate.go +++ b/core/store/migrations/migration1559081901/migrate.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/jinzhu/gorm" "github.com/pkg/errors" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) func Migrate(tx *gorm.DB) error { diff --git a/core/store/migrations/migration1568833756/migrate.go b/core/store/migrations/migration1568833756/migrate.go index 126db143cf6..4393fd4f815 100644 --- a/core/store/migrations/migration1568833756/migrate.go +++ b/core/store/migrations/migration1568833756/migrate.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/jinzhu/gorm" "github.com/pkg/errors" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" ) // Initiator could be thought of as a trigger, defines how a Job can be diff --git a/core/store/migrations/migration1606141477/migrate.go b/core/store/migrations/migration1606141477/migrate.go new file mode 100644 index 00000000000..5781c3d06a9 --- /dev/null +++ b/core/store/migrations/migration1606141477/migrate.go @@ -0,0 +1,15 @@ +package migration1606141477 + +import "github.com/jinzhu/gorm" + +// Migrate makes a bundle of foreign keys deferrable +// This does not change any existing behaviour, but it does make certain types of test fixture much easier to work with +func Migrate(tx *gorm.DB) error { + return tx.Exec(` + ALTER TABLE pipeline_task_runs ALTER CONSTRAINT pipeline_task_runs_pipeline_run_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + ALTER TABLE pipeline_task_runs ALTER CONSTRAINT pipeline_task_runs_pipeline_task_spec_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + ALTER TABLE pipeline_task_specs ALTER CONSTRAINT pipeline_task_specs_pipeline_spec_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + ALTER TABLE pipeline_task_specs ALTER CONSTRAINT pipeline_task_specs_successor_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + ALTER TABLE pipeline_runs ALTER CONSTRAINT pipeline_runs_pipeline_spec_id_fkey DEFERRABLE INITIALLY IMMEDIATE; + `).Error +} diff --git a/core/store/migrations/migration1606303568/migrate.go b/core/store/migrations/migration1606303568/migrate.go new file mode 100644 index 00000000000..907b4fb33e4 --- /dev/null +++ b/core/store/migrations/migration1606303568/migrate.go @@ -0,0 +1,16 @@ +package migration1606303568 + +import "github.com/jinzhu/gorm" + +// Migrate adds name to v2 job specs +func Migrate(tx *gorm.DB) error { + return tx.Exec(` + ALTER TABLE jobs ADD COLUMN name VARCHAR(255), ADD COLUMN schema_version INT, ADD COLUMN type VARCHAR(255); + + UPDATE jobs SET schema_version = 1, type = 'offchainreporting'; + + ALTER TABLE jobs ALTER COLUMN schema_version SET NOT NULL, ALTER COLUMN type SET NOT NULL, + ADD CONSTRAINT chk_schema_version CHECK (schema_version > 0), + ADD CONSTRAINT chk_type CHECK (type != ''); + `).Error +} diff --git a/core/store/migrations/migration1606320711/migrate.go b/core/store/migrations/migration1606320711/migrate.go new file mode 100644 index 00000000000..28bdfadd445 --- /dev/null +++ b/core/store/migrations/migration1606320711/migrate.go @@ -0,0 +1,9 @@ +package migration1606320711 + +import "github.com/jinzhu/gorm" + +func Migrate(tx *gorm.DB) error { + return tx.Exec(` + ALTER TABLE jobs ADD COLUMN max_task_duration bigint; + `).Error +} diff --git a/core/store/migrations/migration1606749860/migrate.go b/core/store/migrations/migration1606749860/migrate.go new file mode 100644 index 00000000000..28cd676daa0 --- /dev/null +++ b/core/store/migrations/migration1606749860/migrate.go @@ -0,0 +1,26 @@ +package migration1606749860 + +import "github.com/jinzhu/gorm" + +// Migrate adds the eth_request_event_spec_id to jobs table +func Migrate(tx *gorm.DB) error { + return tx.Exec(` + CREATE TABLE eth_request_event_specs ( + id SERIAL PRIMARY KEY, + contract_address bytea NOT NULL CHECK (octet_length(contract_address) = 20), + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL + ); + + ALTER TABLE jobs ADD COLUMN eth_request_event_spec_id INT REFERENCES eth_request_event_specs (id), + ALTER COLUMN offchainreporting_oracle_spec_id SET DEFAULT NULL, + DROP CONSTRAINT chk_valid, + ADD CONSTRAINT chk_only_one_spec CHECK ( + (offchainreporting_oracle_spec_id IS NOT NULL AND eth_request_event_spec_id IS NULL) + OR + (offchainreporting_oracle_spec_id IS NULL AND eth_request_event_spec_id IS NOT NULL) + ); + + CREATE UNIQUE INDEX idx_jobs_unique_eth_request_event_spec_id ON jobs (eth_request_event_spec_id); + `).Error +} diff --git a/core/store/migrations/migration1606910307/migrate.go b/core/store/migrations/migration1606910307/migrate.go new file mode 100644 index 00000000000..c3e2686e9f7 --- /dev/null +++ b/core/store/migrations/migration1606910307/migrate.go @@ -0,0 +1,9 @@ +package migration1606910307 + +import "github.com/jinzhu/gorm" + +func Migrate(tx *gorm.DB) error { + return tx.Exec(` + DROP INDEX job_specs_name_index_active; + `).Error +} diff --git a/core/store/migrations/migration1607113528/migrate.go b/core/store/migrations/migration1607113528/migrate.go new file mode 100644 index 00000000000..48e71895ff5 --- /dev/null +++ b/core/store/migrations/migration1607113528/migrate.go @@ -0,0 +1,16 @@ +package migration1607113528 + +import "github.com/jinzhu/gorm" + +func Migrate(tx *gorm.DB) error { + return tx.Exec(` + ALTER TABLE offchainreporting_oracle_specs ALTER COLUMN p2p_peer_id DROP NOT NULL; + ALTER TABLE offchainreporting_oracle_specs ALTER COLUMN p2p_bootstrap_peers DROP NOT NULL; + ALTER TABLE offchainreporting_oracle_specs ALTER COLUMN observation_timeout DROP NOT NULL; + ALTER TABLE offchainreporting_oracle_specs ALTER COLUMN blockchain_timeout DROP NOT NULL; + ALTER TABLE offchainreporting_oracle_specs ALTER COLUMN contract_config_tracker_poll_interval DROP NOT NULL; + ALTER TABLE offchainreporting_oracle_specs ALTER COLUMN contract_config_tracker_subscribe_interval DROP NOT NULL; + ALTER TABLE offchainreporting_oracle_specs ALTER COLUMN contract_config_confirmations DROP NOT NULL; + ALTER TABLE offchainreporting_oracle_specs DROP CONSTRAINT encrypted_ocr_key_bundle_id_not_null; + `).Error +} diff --git a/core/store/models/bridge_run_result.go b/core/store/models/bridge_run_result.go index 4ff548a1724..9fd154478da 100644 --- a/core/store/models/bridge_run_result.go +++ b/core/store/models/bridge_run_result.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) // BridgeRunResult handles the parsing of RunResults from external adapters. diff --git a/core/store/models/cbor.go b/core/store/models/cbor.go index 9ba28635234..3ec45fce0b1 100644 --- a/core/store/models/cbor.go +++ b/core/store/models/cbor.go @@ -3,8 +3,8 @@ package models import ( "bytes" "encoding/json" - - "github.com/smartcontractkit/chainlink/core/utils" + "fmt" + "math/big" "github.com/fxamacker/cbor/v2" ) @@ -22,7 +22,7 @@ func ParseCBOR(b []byte) (JSON, error) { return JSON{}, err } - coerced, err := utils.CoerceInterfaceMapToStringMap(m) + coerced, err := CoerceInterfaceMapToStringMap(m) if err != nil { return JSON{}, err } @@ -52,3 +52,57 @@ func autoAddMapDelimiters(b []byte) []byte { return b } + +// CoerceInterfaceMapToStringMap converts map[interface{}]interface{} (interface maps) to +// map[string]interface{} (string maps) and []interface{} with interface maps to string maps. +// Relevant when serializing between CBOR and JSON. +// +// It also handles the CBOR 'bignum' type as documented here: https://tools.ietf.org/html/rfc7049#section-2.4.2 +func CoerceInterfaceMapToStringMap(in interface{}) (interface{}, error) { + switch typed := in.(type) { + case map[string]interface{}: + for k, v := range typed { + coerced, err := CoerceInterfaceMapToStringMap(v) + if err != nil { + return nil, err + } + typed[k] = coerced + } + return typed, nil + case map[interface{}]interface{}: + m := map[string]interface{}{} + for k, v := range typed { + coercedKey, ok := k.(string) + if !ok { + return nil, fmt.Errorf("unable to coerce key %T %v to a string", k, k) + } + coerced, err := CoerceInterfaceMapToStringMap(v) + if err != nil { + return nil, err + } + m[coercedKey] = coerced + } + return m, nil + case []interface{}: + r := make([]interface{}, len(typed)) + for i, v := range typed { + coerced, err := CoerceInterfaceMapToStringMap(v) + if err != nil { + return nil, err + } + r[i] = coerced + } + return r, nil + case cbor.Tag: + if value, ok := typed.Content.([]byte); ok { + if typed.Number == 2 { + return big.NewInt(0).SetBytes(value), nil + } else if typed.Number == 3 { + return big.NewInt(0).Sub(big.NewInt(-1), big.NewInt(0).SetBytes(value)), nil + } + } + return in, nil + default: + return in, nil + } +} diff --git a/core/store/models/cbor_test.go b/core/store/models/cbor_test.go index fa74576b0fa..b0cfa8c17b9 100644 --- a/core/store/models/cbor_test.go +++ b/core/store/models/cbor_test.go @@ -2,11 +2,13 @@ package models import ( "encoding/json" - "log" + "reflect" "testing" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/fxamacker/cbor/v2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_ParseCBOR(t *testing.T) { @@ -21,28 +23,34 @@ func Test_ParseCBOR(t *testing.T) { { "hello world", `0xbf6375726c781a68747470733a2f2f657468657270726963652e636f6d2f61706964706174689f66726563656e7463757364ffff`, - jsonMustUnmarshal(`{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), + jsonMustUnmarshal(t, `{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), false, }, { "trailing empty bytes", `0xbf6375726c781a68747470733a2f2f657468657270726963652e636f6d2f61706964706174689f66726563656e7463757364ffff000000`, - jsonMustUnmarshal(`{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), + jsonMustUnmarshal(t, `{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), false, }, { "nested maps", `0xbf657461736b739f6868747470706f7374ff66706172616d73bf636d73676f68656c6c6f5f636861696e6c696e6b6375726c75687474703a2f2f6c6f63616c686f73743a36363930ffff`, - jsonMustUnmarshal(`{"params":{"msg":"hello_chainlink","url":"http://localhost:6690"},"tasks":["httppost"]}`), + jsonMustUnmarshal(t, `{"params":{"msg":"hello_chainlink","url":"http://localhost:6690"},"tasks":["httppost"]}`), false, }, { "missing initial start map marker", `0x636B65796576616C7565ff`, - jsonMustUnmarshal(`{"key":"value"}`), + jsonMustUnmarshal(t, `{"key":"value"}`), false, }, - {"empty object", `0xa0`, jsonMustUnmarshal(`{}`), false}, + { + "bignums", + `0xbf676269676e756d739fc249010000000000000000c258204000000000000000000000000000000000000000000000000000000000000000c348ffffffffffffffffc358203fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`, + jsonMustUnmarshal(t, `{"bignums":[18446744073709551616,28948022309329048855892746252171976963317496166410141009864396001978282409984,-18446744073709551616,-28948022309329048855892746252171976963317496166410141009864396001978282409984]}`), + false, + }, + {"empty object", `0xa0`, jsonMustUnmarshal(t, `{}`), false}, {"empty string", `0x`, JSON{}, false}, {"invalid CBOR", `0xff`, JSON{}, true}, } @@ -120,11 +128,108 @@ func Test_autoAddMapDelimiters(t *testing.T) { } } -func jsonMustUnmarshal(in string) JSON { +func jsonMustUnmarshal(t *testing.T, in string) JSON { var j JSON err := json.Unmarshal([]byte(in), &j) - if err != nil { - log.Panicf("Failed to unmarshal '%s'", in) - } + require.NoError(t, err) return j } + +func TestCoerceInterfaceMapToStringMap(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input interface{} + want interface{} + }{ + {"empty map", map[interface{}]interface{}{}, map[string]interface{}{}}, + {"simple map", map[interface{}]interface{}{"key": "value"}, map[string]interface{}{"key": "value"}}, + {"int map", map[int]interface{}{1: "value"}, map[int]interface{}{1: "value"}}, + { + "nested string map map", + map[string]interface{}{"key": map[interface{}]interface{}{"nk": "nv"}}, + map[string]interface{}{"key": map[string]interface{}{"nk": "nv"}}, + }, + { + "nested map map", + map[interface{}]interface{}{"key": map[interface{}]interface{}{"nk": "nv"}}, + map[string]interface{}{"key": map[string]interface{}{"nk": "nv"}}, + }, + { + "nested map array", + map[interface{}]interface{}{"key": []interface{}{1, "value"}}, + map[string]interface{}{"key": []interface{}{1, "value"}}, + }, + {"empty array", []interface{}{}, []interface{}{}}, + {"simple array", []interface{}{1, "value"}, []interface{}{1, "value"}}, + { + "nested array map", + []interface{}{map[interface{}]interface{}{"key": map[interface{}]interface{}{"nk": "nv"}}}, + []interface{}{map[string]interface{}{"key": map[string]interface{}{"nk": "nv"}}}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + decoded, err := CoerceInterfaceMapToStringMap(test.input) + require.NoError(t, err) + assert.True(t, reflect.DeepEqual(test.want, decoded)) + }) + } +} + +func TestCoerceInterfaceMapToStringMap_BadInputs(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input interface{} + }{ + {"error map", map[interface{}]interface{}{1: "value"}}, + {"error array", []interface{}{map[interface{}]interface{}{1: "value"}}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := CoerceInterfaceMapToStringMap(test.input) + assert.Error(t, err) + }) + } +} + +func TestJSON_CBOR(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in JSON + }{ + {"empty object", JSON{}}, + {"array", jsonMustUnmarshal(t, `[1,2,3,4]`)}, + { + "basic object", + jsonMustUnmarshal(t, `{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), + }, + { + "complex object", + jsonMustUnmarshal(t, `{"a":{"1":[{"b":"free"},{"c":"more"},{"d":["less", {"nesting":{"4":"life"}}]}]}}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + encoded, err := test.in.CBOR() + assert.NoError(t, err) + + var decoded interface{} + err = cbor.Unmarshal(encoded, &decoded) + + assert.NoError(t, err) + + decoded, err = CoerceInterfaceMapToStringMap(decoded) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(test.in.Result.Value(), decoded)) + }) + } +} diff --git a/core/store/models/common.go b/core/store/models/common.go index c5f70a3f0bc..e2157ff93e0 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -138,6 +138,11 @@ func (s *RunStatus) Scan(value interface{}) error { return nil } +const ( + ResultKey = "result" + ResultCollectionKey = "__chainlink_result_collection__" +) + // JSON stores the json types string, number, bool, and null. // Arrays and Objects are returned as their raw json types. type JSON struct { @@ -251,6 +256,16 @@ func (j JSON) Add(insertKey string, insertValue interface{}) (JSON, error) { return j.MultiAdd(KV{insertKey: insertValue}) } +func (j JSON) PrependAtArrayKey(insertKey string, insertValue interface{}) (JSON, error) { + curr := j.Get(insertKey).Array() + updated := make([]interface{}, 0) + updated = append(updated, insertValue) + for _, c := range curr { + updated = append(updated, c.Value()) + } + return j.Add(insertKey, updated) +} + // KV represents a key/value pair to be added to a JSON object type KV map[string]interface{} @@ -589,6 +604,10 @@ func (i Interval) Value() (driver.Value, error) { return time.Duration(i).Nanoseconds(), nil } +func (i Interval) IsZero() bool { + return time.Duration(i) == time.Duration(0) +} + // WithdrawalRequest request to withdraw LINK. type WithdrawalRequest struct { DestinationAddress common.Address `json:"address"` @@ -608,8 +627,8 @@ type CreateKeyRequest struct { CurrentPassword string `json:"current_password"` } -// CreateOCRJobSpecRequest represents a request to create and start and OCR job spec. -type CreateOCRJobSpecRequest struct { +// CreateJobSpecRequest represents a request to create and start a job spec. +type CreateJobSpecRequest struct { TOML string `json:"toml"` } diff --git a/core/store/models/common_test.go b/core/store/models/common_test.go index bf14c72103f..896e0e81623 100644 --- a/core/store/models/common_test.go +++ b/core/store/models/common_test.go @@ -3,15 +3,12 @@ package models_test import ( "encoding/json" "net/url" - "reflect" "testing" "time" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/fxamacker/cbor/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -195,42 +192,6 @@ func TestJSON_Delete(t *testing.T) { } } -func TestJSON_CBOR(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - in models.JSON - }{ - {"empty object", models.JSON{}}, - {"array", cltest.JSONFromString(t, `[1,2,3,4]`)}, - { - "hello world", - cltest.JSONFromString(t, `{"path":["recent","usd"],"url":"https://etherprice.com/api"}`), - }, - { - "complex object", - cltest.JSONFromString(t, `{"a":{"1":[{"b":"free"},{"c":"more"},{"d":["less", {"nesting":{"4":"life"}}]}]}}`), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - encoded, err := test.in.CBOR() - assert.NoError(t, err) - - var decoded interface{} - err = cbor.Unmarshal(encoded, &decoded) - - assert.NoError(t, err) - - decoded, err = utils.CoerceInterfaceMapToStringMap(decoded) - assert.NoError(t, err) - assert.True(t, reflect.DeepEqual(test.in.Result.Value(), decoded)) - }) - } -} - func TestWebURL_UnmarshalJSON_Error(t *testing.T) { t.Parallel() j := []byte(`"NotAUrl"`) diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index a9ad266e71f..e8dcf27619e 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) var ( diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index 0eecc0258ed..3d56be54694 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) func TestJobRun_RetrievingFromDBWithError(t *testing.T) { @@ -63,7 +63,7 @@ func TestJobRun_SavesASyncEvent(t *testing.T) { defer cleanup() explorerClient := synchronization.NoopExplorerClient{} - pusher := synchronization.NewStatsPusher(store.ORM, explorerClient) + pusher := synchronization.NewStatsPusher(store.DB, explorerClient) require.NoError(t, pusher.Start()) defer pusher.Close() @@ -76,7 +76,7 @@ func TestJobRun_SavesASyncEvent(t *testing.T) { assert.NoError(t, err) var events []models.SyncEvent - err = store.AllSyncEvents(func(event models.SyncEvent) error { + err = pusher.AllSyncEvents(func(event models.SyncEvent) error { events = append(events, event) return nil }) @@ -116,11 +116,7 @@ func TestJobRun_SkipsEventSaveIfURLBlank(t *testing.T) { assert.NoError(t, err) var events []models.SyncEvent - err = store.AllSyncEvents(func(event models.SyncEvent) error { - events = append(events, event) - return nil - }) - require.NoError(t, err) + require.NoError(t, store.DB.Find(&events).Error) require.Len(t, events, 0) } diff --git a/core/store/models/job_spec.go b/core/store/models/job_spec.go index d69606a0bdb..cc19dbf13d9 100644 --- a/core/store/models/job_spec.go +++ b/core/store/models/job_spec.go @@ -8,13 +8,12 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" "github.com/smartcontractkit/chainlink/core/assets" clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/utils" - - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) // JobSpecRequest represents a schema for the incoming job spec request as used by the API. @@ -45,7 +44,7 @@ type TaskSpecRequest struct { // individual steps to be carried out), StartAt, EndAt, and CreatedAt fields. type JobSpec struct { ID *ID `json:"id,omitempty" gorm:"primary_key;not null"` - Name string `json:"name" gorm:"index;not null"` + Name string `json:"name"` CreatedAt time.Time `json:"createdAt" gorm:"index"` Initiators []Initiator `json:"initiators"` MinPayment *assets.Link `json:"minPayment,omitempty" gorm:"type:varchar(255)"` @@ -78,7 +77,6 @@ func NewJob() JobSpec { id := NewID() return JobSpec{ ID: id, - Name: fmt.Sprintf("Job%s", id), CreatedAt: time.Now(), } } diff --git a/core/store/models/job_spec_test.go b/core/store/models/job_spec_test.go index 20a4f188448..c1b94fd2c0c 100644 --- a/core/store/models/job_spec_test.go +++ b/core/store/models/job_spec_test.go @@ -14,7 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) func TestNewInitiatorFromRequest(t *testing.T) { @@ -175,13 +175,13 @@ func TestNewJobFromRequest(t *testing.T) { j2 := models.NewJobFromRequest(jsr) require.NoError(t, store.CreateJob(&j2)) - fetched1, err := store.FindJob(j1.ID) + fetched1, err := store.FindJobSpec(j1.ID) assert.NoError(t, err) assert.Len(t, fetched1.Initiators, 1) assert.Len(t, fetched1.Tasks, 1) assert.Nil(t, fetched1.MinPayment) - fetched2, err := store.FindJob(j2.ID) + fetched2, err := store.FindJobSpec(j2.ID) assert.NoError(t, err) assert.Len(t, fetched2.Initiators, 1) assert.Len(t, fetched2.Tasks, 1) @@ -206,7 +206,7 @@ func TestJobSpec_Save(t *testing.T) { initr := j1.Initiators[0] - j2, err := store.FindJob(j1.ID) + j2, err := store.FindJobSpec(j1.ID) require.NoError(t, err) require.Len(t, j2.Initiators, 1) assert.Equal(t, initr.Schedule, j2.Initiators[0].Schedule) @@ -245,7 +245,7 @@ func TestJobEnded(t *testing.T) { current time.Time want bool }{ - {"no end at", null.Time{Valid: false}, endAt.Time, false}, + {"no end at", null.TimeFromPtr(nil), endAt.Time, false}, {"before end at", endAt, endAt.Time.Add(-time.Nanosecond), false}, {"at end at", endAt, endAt.Time, false}, {"after end at", endAt, endAt.Time.Add(time.Nanosecond), true}, @@ -272,7 +272,7 @@ func TestJobSpec_Started(t *testing.T) { current time.Time want bool }{ - {"no start at", null.Time{Valid: false}, startAt.Time, true}, + {"no start at", null.TimeFromPtr(nil), startAt.Time, true}, {"before start at", startAt, startAt.Time.Add(-time.Nanosecond), false}, {"at start at", startAt, startAt.Time, true}, {"after start at", startAt, startAt.Time.Add(time.Nanosecond), true}, diff --git a/core/store/models/job_spec_v2.go b/core/store/models/job_spec_v2.go index e83dd618300..5a78a8f9abb 100644 --- a/core/store/models/job_spec_v2.go +++ b/core/store/models/job_spec_v2.go @@ -10,16 +10,27 @@ import ( "github.com/lib/pq" "github.com/libp2p/go-libp2p-core/peer" "github.com/pkg/errors" + null "gopkg.in/guregu/null.v4" ) type ( + IDEmbed struct { + ID int32 `json:"-" toml:"-" gorm:"primary_key"` + } + JobSpecV2 struct { - ID int32 `json:"-" gorm:"primary_key"` - OffchainreportingOracleSpecID int32 `json:"-"` + IDEmbed + OffchainreportingOracleSpecID *int32 `json:"-"` OffchainreportingOracleSpec *OffchainReportingOracleSpec `json:"offChainReportingOracleSpec" gorm:"save_association:true;association_autoupdate:true;association_autocreate:true"` + EthRequestEventSpecID *int32 `json:"-"` + EthRequestEventSpec *EthRequestEventSpec `json:"ethRequestEventSpec" gorm:"save_association:true;association_autoupdate:true;association_autocreate:true"` PipelineSpecID int32 `json:"-"` PipelineSpec *PipelineSpec `json:"pipelineSpec"` JobSpecErrors []JobSpecErrorV2 `json:"errors" gorm:"foreignKey:JobID"` + Type string `json:"type"` + SchemaVersion uint32 `json:"schemaVersion"` + Name null.String `json:"name"` + MaxTaskDuration Interval `json:"maxTaskDuration"` } JobSpecErrorV2 struct { @@ -31,12 +42,12 @@ type ( UpdatedAt time.Time `json:"updatedAt"` } - OCRJobRun struct { + PipelineRun struct { ID int64 `json:"-" gorm:"primary_key"` } PipelineSpec struct { - ID int32 `json:"-" gorm:"primary_key"` + IDEmbed DotDagSource string `json:"dotDagSource"` CreatedAt time.Time `json:"-"` } @@ -44,9 +55,9 @@ type ( // TODO: remove pointers when upgrading to gormv2 // which has https://github.com/go-gorm/gorm/issues/2748 fixed. OffchainReportingOracleSpec struct { - ID int32 `json:"-" toml:"-" gorm:"primary_key"` + IDEmbed ContractAddress EIP55Address `json:"contractAddress" toml:"contractAddress"` - P2PPeerID PeerID `json:"p2pPeerID" toml:"p2pPeerID" gorm:"column:p2p_peer_id"` + P2PPeerID PeerID `json:"p2pPeerID" toml:"p2pPeerID" gorm:"column:p2p_peer_id;default:null"` P2PBootstrapPeers pq.StringArray `json:"p2pBootstrapPeers" toml:"p2pBootstrapPeers" gorm:"column:p2p_bootstrap_peers;type:text[]"` IsBootstrapPeer bool `json:"isBootstrapPeer" toml:"isBootstrapPeer"` EncryptedOCRKeyBundleID *Sha256Hash `json:"keyBundleID" toml:"keyBundleID" gorm:"type:bytea"` @@ -61,19 +72,30 @@ type ( UpdatedAt time.Time `json:"updatedAt" toml:"-"` } + EthRequestEventSpec struct { + IDEmbed + ContractAddress EIP55Address `json:"contractAddress" toml:"contractAddress"` + CreatedAt time.Time `json:"createdAt" toml:"-"` + UpdatedAt time.Time `json:"updatedAt" toml:"-"` + } + PeerID peer.ID ) -func (js JobSpecV2) GetID() string { - return fmt.Sprintf("%v", js.ID) +const ( + EthRequestEventJobType = "ethrequestevent" +) + +func (id IDEmbed) GetID() string { + return fmt.Sprintf("%v", id.ID) } -func (js *JobSpecV2) SetID(value string) error { +func (id *IDEmbed) SetID(value string) error { ID, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } - js.ID = int32(ID) + id.ID = int32(ID) return nil } @@ -94,16 +116,16 @@ func (p PeerID) String() string { return peer.ID(p).String() } -func (jr OCRJobRun) GetID() string { - return fmt.Sprintf("%v", jr.ID) +func (pr PipelineRun) GetID() string { + return fmt.Sprintf("%v", pr.ID) } -func (jr *OCRJobRun) SetID(value string) error { +func (pr *PipelineRun) SetID(value string) error { ID, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } - jr.ID = int64(ID) + pr.ID = int64(ID) return nil } @@ -162,3 +184,4 @@ func (s *OffchainReportingOracleSpec) BeforeSave() error { func (JobSpecV2) TableName() string { return "jobs" } func (JobSpecErrorV2) TableName() string { return "job_spec_errors_v2" } func (OffchainReportingOracleSpec) TableName() string { return "offchainreporting_oracle_specs" } +func (EthRequestEventSpec) TableName() string { return "eth_request_event_specs" } diff --git a/core/store/models/key.go b/core/store/models/key.go index 83e97bd0a10..13bf5aa14a3 100644 --- a/core/store/models/key.go +++ b/core/store/models/key.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/tidwall/gjson" "go.uber.org/multierr" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/utils" ) diff --git a/core/store/models/log_events.go b/core/store/models/log_events.go index 744b1cb9a89..e93f9a502a9 100644 --- a/core/store/models/log_events.go +++ b/core/store/models/log_events.go @@ -1,6 +1,8 @@ package models import ( + "bytes" + "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -46,6 +48,7 @@ var ( // OracleFulfillmentFunctionID20190128withoutCast is the function selector for fulfilling Ethereum requests, // as updated on 2019-01-28, removing the cast to uint256 for the requestId. OracleFulfillmentFunctionID20190128withoutCast = utils.MustHash("fulfillOracleRequest(bytes32,uint256,address,bytes4,uint256,bytes32)").Hex()[:10] + OracleFulfillmentFunctionID2020 = utils.MustHash("fulfillOracleRequest2(bytes32,uint256,address,bytes4,uint256,bytes)").Hex()[:10] ) type logRequestParser interface { @@ -423,10 +426,31 @@ func (parseRunLog20190207withoutIndexes) parseJSON(log Log) (JSON, error) { return JSON{}, err } + // The operator contract restricts us to 256 versions, so + // 8 bytes worth of data versions is plenty. + dataVersionBytes, err := UntrustedBytes(data).SafeByteSlice(expirationEnd+versionSize-8, expirationEnd+versionSize) + if err != nil { + return JSON{}, err + } + b := bytes.NewBuffer(dataVersionBytes) + var dataVersion uint64 + err = binary.Read(b, binary.BigEndian, &dataVersion) + if err != nil { + return JSON{}, err + } + var fnSelector string + switch dataVersion { + case 1: + fnSelector = OracleFulfillmentFunctionID20190128withoutCast + case 2: + fnSelector = OracleFulfillmentFunctionID2020 + default: + return JSON{}, errors.Errorf("unsupported data version %d", dataVersion) + } return js.MultiAdd(KV{ "address": log.Address.String(), "dataPrefix": bytesToHex(dataPrefixBytes), - "functionSelector": OracleFulfillmentFunctionID20190128withoutCast, + "functionSelector": fnSelector, }) } diff --git a/core/store/models/ocrkey/key_bundle.go b/core/store/models/ocrkey/key_bundle.go index 55497e77a7c..ef80a653cfd 100644 --- a/core/store/models/ocrkey/key_bundle.go +++ b/core/store/models/ocrkey/key_bundle.go @@ -21,7 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" "golang.org/x/crypto/curve25519" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" ) type ( diff --git a/core/store/models/p2pkey/p2p_key.go b/core/store/models/p2pkey/p2p_key.go index 92bfb2b339c..2d1fcc1ba22 100644 --- a/core/store/models/p2pkey/p2p_key.go +++ b/core/store/models/p2pkey/p2p_key.go @@ -12,7 +12,7 @@ import ( cryptop2p "github.com/libp2p/go-libp2p-core/crypto" peer "github.com/libp2p/go-libp2p-core/peer" "github.com/pkg/errors" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" diff --git a/core/store/models/run_input.go b/core/store/models/run_input.go index c331a10a7df..6e29d334775 100644 --- a/core/store/models/run_input.go +++ b/core/store/models/run_input.go @@ -26,7 +26,7 @@ func NewRunInput(jobRunID *ID, taskRunID ID, data JSON, status RunStatus) *RunIn // NewRunInputWithResult creates a new RunInput with a value in the "result" field func NewRunInputWithResult(jobRunID *ID, taskRunID ID, value interface{}, status RunStatus) *RunInput { - data, err := JSON{}.Add("result", value) + data, err := JSON{}.Add(ResultKey, value) if err != nil { panic(fmt.Sprintf("invariant violated, add should not fail on empty JSON %v", err)) } @@ -38,9 +38,13 @@ func NewRunInputWithResult(jobRunID *ID, taskRunID ID, value interface{}, status } } +func (ri RunInput) ResultCollection() gjson.Result { + return ri.data.Get(ResultCollectionKey) +} + // Result returns the result as a gjson object func (ri RunInput) Result() gjson.Result { - return ri.data.Get("result") + return ri.data.Get(ResultKey) } // ResultString returns the string result of the Data JSON field. diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index a77673e4c02..9cfedad1670 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -22,12 +22,22 @@ func NewRunOutputError(err error) RunOutput { } // NewRunOutputCompleteWithResult returns a new RunOutput that is complete and -// contains a result -func NewRunOutputCompleteWithResult(resultVal interface{}) RunOutput { - data, err := JSON{}.Add("result", resultVal) +// contains a result and preserves the resultCollection. +func NewRunOutputCompleteWithResult(resultVal interface{}, resultCollection gjson.Result) RunOutput { + data, err := JSON{}.Add(ResultKey, resultVal) if err != nil { panic(fmt.Sprintf("invariant violated, add should not fail on empty JSON %v", err)) } + if resultCollection.String() != "" { + collectionCopy := make([]interface{}, 0) + for _, k := range resultCollection.Array() { + collectionCopy = append(collectionCopy, k.Value()) + } + data, err = data.Add(ResultCollectionKey, collectionCopy) + if err != nil { + return NewRunOutputError(err) + } + } return NewRunOutputComplete(data) } @@ -74,9 +84,13 @@ func (ro RunOutput) HasError() bool { return ro.status == RunStatusErrored } +func (ro RunOutput) ResultCollection() gjson.Result { + return ro.data.Get(ResultCollectionKey) +} + // Result returns the result as a gjson object func (ro RunOutput) Result() gjson.Result { - return ro.data.Get("result") + return ro.data.Get(ResultKey) } // Get searches for and returns the JSON at the given path. diff --git a/core/store/models/service_agreement.go b/core/store/models/service_agreement.go index aa9be1635b1..3f41274e5ae 100644 --- a/core/store/models/service_agreement.go +++ b/core/store/models/service_agreement.go @@ -13,7 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) // Encumbrance connects job specifications with on-chain encumbrances. diff --git a/core/store/models/vrfkey/serialization.go b/core/store/models/vrfkey/serialization.go index 3d0b8b4c6f3..b6e5e1e69bd 100644 --- a/core/store/models/vrfkey/serialization.go +++ b/core/store/models/vrfkey/serialization.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/pkg/errors" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/core/utils" ) diff --git a/core/store/orm/config.go b/core/store/orm/config.go index 57dd947def6..c09fd2f6810 100644 --- a/core/store/orm/config.go +++ b/core/store/orm/config.go @@ -15,6 +15,13 @@ import ( "strconv" "time" + "github.com/multiformats/go-multiaddr" + + "github.com/libp2p/go-libp2p-core/peer" + + ocr "github.com/smartcontractkit/libocr/offchainreporting" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/store/models" @@ -34,6 +41,11 @@ import ( // this permission grants read / write accccess to file owners only const readWritePerms = os.FileMode(0600) +var ( + ErrUnset = errors.New("env var unset") + ErrInvalid = errors.New("env var invalid") +) + // Config holds parameters used by the application which can be overridden by // setting environment variables. // @@ -110,6 +122,41 @@ func (c *Config) Validate() error { if c.FeatureOffchainReporting() && c.P2PListenPort() == 0 { return errors.New("P2P_LISTEN_PORT must be set to a non-zero value if FEATURE_OFFCHAIN_REPORTING is enabled") } + + var override time.Duration + lc := ocrtypes.LocalConfig{ + BlockchainTimeout: c.OCRBlockchainTimeout(override), + ContractConfigConfirmations: c.OCRContractConfirmations(0), + ContractConfigTrackerPollInterval: c.OCRContractPollInterval(override), + ContractConfigTrackerSubscribeInterval: c.OCRContractSubscribeInterval(override), + ContractTransmitterTransmitTimeout: c.OCRContractTransmitterTransmitTimeout(), + DatabaseTimeout: c.OCRDatabaseTimeout(), + DataSourceTimeout: c.OCRObservationTimeout(override), + } + if err := ocr.SanityCheckLocalConfig(lc); err != nil { + return err + } + if _, err := c.P2PPeerID(""); errors.Cause(err) == ErrInvalid { + return err + } + if _, err := c.OCRKeyBundleID(nil); errors.Cause(err) == ErrInvalid { + return err + } + if _, err := c.OCRTransmitterAddress(nil); errors.Cause(err) == ErrInvalid { + return err + } + if peers, err := c.P2PBootstrapPeers(nil); err == nil { + for i := range peers { + if _, err := multiaddr.NewMultiaddr(peers[i]); err != nil { + return errors.Errorf("p2p bootstrap peer %d is invalid: err %v", i, err) + } + } + } + if me := c.OCRMonitoringEndpoint(""); me != "" { + if _, err := url.Parse(me); err != nil { + return errors.Wrapf(err, "invalid monitoring url: %s", me) + } + } return nil } @@ -508,6 +555,36 @@ func (c Config) OCRContractTransmitterTransmitTimeout() time.Duration { return c.viper.GetDuration(EnvVarName("OCRContractTransmitterTransmitTimeout")) } +func (c Config) getDurationWithOverride(override time.Duration, field string) time.Duration { + if override != time.Duration(0) { + return override + } + return c.viper.GetDuration(EnvVarName(field)) +} + +func (c Config) OCRObservationTimeout(override time.Duration) time.Duration { + return c.getDurationWithOverride(override, "OCRObservationTimeout") +} + +func (c Config) OCRBlockchainTimeout(override time.Duration) time.Duration { + return c.getDurationWithOverride(override, "OCRBlockchainTimeout") +} + +func (c Config) OCRContractSubscribeInterval(override time.Duration) time.Duration { + return c.getDurationWithOverride(override, "OCRContractSubscribeInterval") +} + +func (c Config) OCRContractPollInterval(override time.Duration) time.Duration { + return c.getDurationWithOverride(override, "OCRContractPollInterval") +} + +func (c Config) OCRContractConfirmations(override uint16) uint16 { + if override != uint16(0) { + return override + } + return c.getWithFallback("OCRContractConfirmations", parseUint16).(uint16) +} + func (c Config) OCRDatabaseTimeout() time.Duration { return c.viper.GetDuration(EnvVarName("OCRDatabaseTimeout")) } @@ -534,6 +611,43 @@ func (c Config) OCRTraceLogging() bool { return c.viper.GetBool(EnvVarName("OCRTraceLogging")) } +func (c Config) OCRMonitoringEndpoint(override string) string { + if override != "" { + return override + } + return c.viper.GetString(EnvVarName("OCRMonitoringEndpoint")) +} + +func (c Config) OCRTransmitterAddress(override *models.EIP55Address) (models.EIP55Address, error) { + if override != nil { + return *override, nil + } + taStr := c.viper.GetString(EnvVarName("OCRTransmitterAddress")) + if taStr != "" { + ta, err := models.NewEIP55Address(taStr) + if err != nil { + return "", errors.Wrapf(ErrInvalid, "OCR_TRANSMITTER_ADDRESS is invalid EIP55 %v", err) + } + return ta, nil + } + return "", errors.Wrap(ErrUnset, "OCR_TRANSMITTER_ADDRESS") +} + +func (c Config) OCRKeyBundleID(override *models.Sha256Hash) (models.Sha256Hash, error) { + if override != nil { + return *override, nil + } + kbStr := c.viper.GetString(EnvVarName("OCRKeyBundleID")) + if kbStr != "" { + kb, err := models.Sha256HashFromHex(kbStr) + if err != nil { + return models.Sha256Hash{}, errors.Wrapf(ErrInvalid, "OCR_KEY_BUNDLE_ID is an invalid sha256 hash hex string %v", err) + } + return kb, nil + } + return models.Sha256Hash{}, errors.Wrap(ErrUnset, "OCR_KEY_BUNDLE_ID") +} + // OperatorContractAddress represents the address where the Operator.sol // contract is deployed, this is used for filtering RunLog requests func (c Config) OperatorContractAddress() common.Address { @@ -617,10 +731,46 @@ func (c Config) P2PAnnouncePort() uint16 { return uint16(c.viper.GetUint32(EnvVarName("P2PAnnouncePort"))) } +// P2PDHTAnnouncementCounterUserPrefix can be used to restore the node's +// ability to announce its IP/port on the P2P network after a database +// rollback. Make sure to only increase this value, and *never* decrease it. +// Don't use this variable unless you really know what you're doing, since you +// could semi-permanently exclude your node from the P2P network by +// misconfiguring it. +func (c Config) P2PDHTAnnouncementCounterUserPrefix() uint32 { + return c.viper.GetUint32(EnvVarName("P2PDHTAnnouncementCounterUserPrefix")) +} + func (c Config) P2PPeerstoreWriteInterval() time.Duration { return c.viper.GetDuration(EnvVarName("P2PPeerstoreWriteInterval")) } +func (c Config) P2PPeerID(override models.PeerID) (models.PeerID, error) { + if override != "" { + return override, nil + } + pidStr := c.viper.GetString(EnvVarName("P2PPeerID")) + if pidStr != "" { + pid, err := peer.Decode(pidStr) + if err != nil { + return "", errors.Wrapf(ErrInvalid, "P2P_PEER_ID is invalid %v", err) + } + return models.PeerID(pid), nil + } + return "", errors.Wrap(ErrUnset, "P2P_PEER_ID") +} + +func (c Config) P2PBootstrapPeers(override []string) ([]string, error) { + if override != nil { + return override, nil + } + bps := c.viper.GetStringSlice(EnvVarName("P2PBootstrapPeers")) + if bps != nil { + return bps, nil + } + return nil, errors.Wrap(ErrUnset, "P2P_BOOTSTRAP_PEERS") +} + // Port represents the port Chainlink should listen on for client requests. func (c Config) Port() uint16 { return c.getWithFallback("Port", parseUint16).(uint16) diff --git a/core/store/orm/orm.go b/core/store/orm/orm.go index fa951025de3..97c0e4b72ed 100644 --- a/core/store/orm/orm.go +++ b/core/store/orm/orm.go @@ -164,8 +164,8 @@ func (orm *ORM) PendingBridgeType(jr models.JobRun) (models.BridgeType, error) { return orm.FindBridge(nextTask.TaskSpec.Type) } -// FindJob looks up a Job by its ID. -func (orm *ORM) FindJob(id *models.ID) (models.JobSpec, error) { +// FindJob looks up a JobSpec by its ID. +func (orm *ORM) FindJobSpec(id *models.ID) (models.JobSpec, error) { orm.MustEnsureAdvisoryLock() var job models.JobSpec return job, orm.preloadJobs().First(&job, "id = ?", id).Error @@ -230,31 +230,6 @@ func (orm *ORM) FindJobRun(id *models.ID) (models.JobRun, error) { return jr, err } -// AllSyncEvents returns all sync events -func (orm *ORM) AllSyncEvents(cb func(models.SyncEvent) error) error { - orm.MustEnsureAdvisoryLock() - return Batch(BatchSize, func(offset, limit uint) (uint, error) { - var events []models.SyncEvent - err := orm.DB. - Limit(limit). - Offset(offset). - Order("id, created_at asc"). - Find(&events).Error - if err != nil { - return 0, err - } - - for _, event := range events { - err = cb(event) - if err != nil { - return 0, err - } - } - - return uint(len(events)), err - }) -} - // NOTE: Copied verbatim from gorm master // Transaction start a transaction as a block, // return error will rollback, otherwise to commit. @@ -543,7 +518,7 @@ func (orm *ORM) createJob(tx *gorm.DB, job *models.JobSpec) error { // ArchiveJob soft deletes the job, job_runs and its initiator. func (orm *ORM) ArchiveJob(ID *models.ID) error { orm.MustEnsureAdvisoryLock() - j, err := orm.FindJob(ID) + j, err := orm.FindJobSpec(ID) if err != nil { return err } @@ -887,34 +862,36 @@ func (orm *ORM) JobsSorted(sort SortType, offset int, limit int) ([]models.JobSp return jobs, count, err } -// OffChainReportingJobs returns OCR job specs -func (orm *ORM) OffChainReportingJobs() ([]models.JobSpecV2, error) { +// OffChainReportingJobs returns job specs +func (orm *ORM) JobsV2() ([]models.JobSpecV2, error) { orm.MustEnsureAdvisoryLock() var jobs []models.JobSpecV2 err := orm.DB. Preload("PipelineSpec"). Preload("OffchainreportingOracleSpec"). + Preload("EthRequestEventSpec"). Preload("JobSpecErrors"). Find(&jobs). Error return jobs, err } -// FindOffChainReportingJob returns OCR job spec by ID -func (orm *ORM) FindOffChainReportingJob(id int32) (models.JobSpecV2, error) { +// FindJob returns job by ID +func (orm *ORM) FindJob(id int32) (models.JobSpecV2, error) { orm.MustEnsureAdvisoryLock() var job models.JobSpecV2 err := orm.DB. Preload("PipelineSpec"). Preload("OffchainreportingOracleSpec"). + Preload("EthRequestEventSpec"). Preload("JobSpecErrors"). First(&job, "jobs.id = ?", id). Error return job, err } -// OffChainReportingJobRuns returns OCR job runs -func (orm *ORM) OffChainReportingJobRuns(jobID int32, offset, size int) ([]pipeline.Run, int, error) { +// PipelineRunsByJobID returns pipeline runs for a job +func (orm *ORM) PipelineRunsByJobID(jobID int32, offset, size int) ([]pipeline.Run, int, error) { orm.MustEnsureAdvisoryLock() var pipelineRuns []pipeline.Run diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index 15eae571cb9..8ceb9705811 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -24,7 +24,7 @@ import ( "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" ) func TestORM_AllNotFound(t *testing.T) { @@ -44,7 +44,7 @@ func TestORM_CreateJob(t *testing.T) { j1 := cltest.NewJobWithSchedule("* * * * *") store.CreateJob(&j1) - j2, err := store.FindJob(j1.ID) + j2, err := store.FindJobSpec(j1.ID) require.NoError(t, err) require.Len(t, j2.Initiators, 1) j1.Initiators[0].CreatedAt = j2.Initiators[0].CreatedAt @@ -90,7 +90,7 @@ func TestORM_ShowJobWithMultipleTasks(t *testing.T) { assert.NoError(t, store.CreateJob(&job)) orm := store.ORM - retrievedJob, err := orm.FindJob(job.ID) + retrievedJob, err := orm.FindJobSpec(job.ID) assert.NoError(t, err) assert.Equal(t, string(retrievedJob.Tasks[0].Type), "task1") assert.Equal(t, string(retrievedJob.Tasks[1].Type), "task2") @@ -149,11 +149,11 @@ func TestORM_ArchiveJob(t *testing.T) { require.NoError(t, store.ArchiveJob(job.ID)) - require.Error(t, utils.JustError(store.FindJob(job.ID))) + require.Error(t, utils.JustError(store.FindJobSpec(job.ID))) require.Error(t, utils.JustError(store.FindJobRun(run.ID))) orm := store.ORM.Unscoped() - require.NoError(t, utils.JustError(orm.FindJob(job.ID))) + require.NoError(t, utils.JustError(orm.FindJobSpec(job.ID))) require.NoError(t, utils.JustError(orm.FindJobRun(run.ID))) } @@ -833,8 +833,7 @@ func TestORM_AllSyncEvents(t *testing.T) { require.NoError(t, err) defer explorerClient.Close() - orm := store.ORM - statsPusher := synchronization.NewStatsPusher(orm, explorerClient) + statsPusher := synchronization.NewStatsPusher(store.DB, explorerClient) require.NoError(t, statsPusher.Start()) defer statsPusher.Close() @@ -845,16 +844,16 @@ func TestORM_AllSyncEvents(t *testing.T) { oldIncompleteRun := cltest.NewJobRun(job) oldIncompleteRun.SetStatus(models.RunStatusInProgress) - err = orm.CreateJobRun(&oldIncompleteRun) + err = store.CreateJobRun(&oldIncompleteRun) require.NoError(t, err) newCompletedRun := cltest.NewJobRun(job) newCompletedRun.SetStatus(models.RunStatusCompleted) - err = orm.CreateJobRun(&newCompletedRun) + err = store.CreateJobRun(&newCompletedRun) require.NoError(t, err) events := []models.SyncEvent{} - err = orm.AllSyncEvents(func(event models.SyncEvent) error { + err = statsPusher.AllSyncEvents(func(event models.SyncEvent) error { events = append(events, event) return nil }) diff --git a/core/store/orm/schema.go b/core/store/orm/schema.go index 9d6f71f2f3d..6843abefca5 100644 --- a/core/store/orm/schema.go +++ b/core/store/orm/schema.go @@ -8,10 +8,9 @@ import ( "reflect" "time" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/store/models" - - "github.com/ethereum/go-ethereum/common" ) // ConfigSchema records the schema of configuration at the type level @@ -61,7 +60,7 @@ type ConfigSchema struct { JobPipelineMaxTaskDuration time.Duration `env:"JOB_PIPELINE_MAX_TASK_DURATION" default:"10m"` JobPipelineParallelism uint8 `env:"JOB_PIPELINE_PARALLELISM" default:"4"` JobPipelineReaperInterval time.Duration `env:"JOB_PIPELINE_REAPER_INTERVAL" default:"1h"` - JobPipelineReaperThreshold time.Duration `env:"JOB_PIPELINE_REAPER_THRESHOLD" default:"7d"` + JobPipelineReaperThreshold time.Duration `env:"JOB_PIPELINE_REAPER_THRESHOLD" default:"168h"` JSONConsole bool `env:"JSON_CONSOLE" default:"false"` LinkContractAddress string `env:"LINK_CONTRACT_ADDRESS" default:"0x514910771AF9Ca656af840dff83E8264EcF986CA"` ExplorerURL *url.URL `env:"EXPLORER_URL"` @@ -77,20 +76,31 @@ type ConfigSchema struct { MinRequiredOutgoingConfirmations uint64 `env:"MIN_OUTGOING_CONFIRMATIONS" default:"12"` MinimumContractPayment assets.Link `env:"MINIMUM_CONTRACT_PAYMENT" default:"1000000000000000000"` MinimumRequestExpiration uint64 `env:"MINIMUM_REQUEST_EXPIRATION" default:"300"` + OCRObservationTimeout time.Duration `env:"OCR_OBSERVATION_TIMEOUT" default:"10s"` + OCRBlockchainTimeout time.Duration `env:"OCR_BLOCKCHAIN_TIMEOUT" default:"20s"` + OCRContractSubscribeInterval time.Duration `env:"OCR_CONTRACT_SUBSCRIBE_INTERVAL" default:"2m"` + OCRContractPollInterval time.Duration `env:"OCR_CONTRACT_POLL_INTERVAL" default:"1m"` + OCRContractConfirmations uint `env:"OCR_CONTRACT_CONFIRMATIONS" default:"3"` OCRBootstrapCheckInterval time.Duration `env:"OCR_BOOTSTRAP_CHECK_INTERVAL" default:"20s"` OCRContractTransmitterTransmitTimeout time.Duration `env:"OCR_CONTRACT_TRANSMITTER_TRANSMIT_TIMEOUT" default:"10s"` + OCRTransmitterAddress string `env:"OCR_TRANSMITTER_ADDRESS"` + OCRKeyBundleID string `env:"OCR_KEY_BUNDLE_ID"` OCRDatabaseTimeout time.Duration `env:"OCR_DATABASE_TIMEOUT" default:"10s"` OCRIncomingMessageBufferSize int `env:"OCR_INCOMING_MESSAGE_BUFFER_SIZE" default:"10"` OCROutgoingMessageBufferSize int `env:"OCR_OUTGOING_MESSAGE_BUFFER_SIZE" default:"10"` OCRNewStreamTimeout time.Duration `env:"OCR_NEW_STREAM_TIMEOUT" default:"10s"` OCRDHTLookupInterval int `env:"OCR_DHT_LOOKUP_INTERVAL" default:"10"` OCRTraceLogging bool `env:"OCR_TRACE_LOGGING" default:"false"` + OCRMonitoringEndpoint string `env:"OCR_MONITORING_ENDPOINT"` OperatorContractAddress common.Address `env:"OPERATOR_CONTRACT_ADDRESS"` P2PAnnounceIP net.IP `env:"P2P_ANNOUNCE_IP"` P2PAnnouncePort uint16 `env:"P2P_ANNOUNCE_PORT"` + P2PDHTAnnouncementCounterUserPrefix uint32 `env:"P2P_DHT_ANNOUNCEMENT_COUNTER_USER_PREFIX" default:"0"` P2PListenIP net.IP `env:"P2P_LISTEN_IP" default:"0.0.0.0"` P2PListenPort uint16 `env:"P2P_LISTEN_PORT"` P2PPeerstoreWriteInterval time.Duration `env:"P2P_PEERSTORE_WRITE_INTERVAL" default:"5m"` + P2PPeerID models.PeerID `env:"P2P_PEER_ID"` + P2PBootstrapPeers []string `env:"P2P_BOOTSTRAP_PEERS"` Port uint16 `env:"CHAINLINK_PORT" default:"6688"` ReaperExpiration models.Duration `env:"REAPER_EXPIRATION" default:"240h"` ReplayFromBlock int64 `env:"REPLAY_FROM_BLOCK" default:"-1"` diff --git a/core/store/presenters/presenters.go b/core/store/presenters/presenters.go index f759a608997..89df082a0c9 100644 --- a/core/store/presenters/presenters.go +++ b/core/store/presenters/presenters.go @@ -14,6 +14,9 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/smartcontractkit/chainlink/core/assets" "github.com/smartcontractkit/chainlink/core/auth" "github.com/smartcontractkit/chainlink/core/logger" @@ -22,12 +25,8 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/smartcontractkit/chainlink/core/utils" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/tidwall/gjson" - "gopkg.in/guregu/null.v3" + "gopkg.in/guregu/null.v4" ) // ETHKey holds the hex representation of the address plus it's ETH & LINK balances @@ -59,7 +58,6 @@ func (k *ETHKey) SetID(value string) error { // If you add an entry here, you should update NewConfigPrinter and // ConfigPrinter#String accordingly. type ConfigPrinter struct { - AccountAddress string `json:"accountAddress"` EnvPrinter } @@ -89,7 +87,7 @@ type EnvPrinter struct { EthHeadTrackerMaxBufferSize uint `json:"ethHeadTrackerMaxBufferSize"` EthMaxGasPriceWei *big.Int `json:"ethMaxGasPriceWei"` EthereumURL string `json:"ethUrl"` - EthereumSecondaryURLs []url.URL `json:"ethSecondaryURLs"` + EthereumSecondaryURLs []string `json:"ethSecondaryUrls"` ExplorerURL string `json:"explorerUrl"` FeatureExternalInitiators bool `json:"featureExternalInitiators"` FeatureFluxMonitor bool `json:"featureFluxMonitor"` @@ -174,7 +172,7 @@ func NewConfigPrinter(store *store.Store) (ConfigPrinter, error) { EthHeadTrackerMaxBufferSize: config.EthHeadTrackerMaxBufferSize(), EthMaxGasPriceWei: config.EthMaxGasPriceWei(), EthereumURL: config.EthereumURL(), - EthereumSecondaryURLs: config.EthereumSecondaryURLs(), + EthereumSecondaryURLs: mapToStringA(config.EthereumSecondaryURLs()), ExplorerURL: explorerURL, FeatureExternalInitiators: config.FeatureExternalInitiators(), FeatureFluxMonitor: config.FeatureFluxMonitor(), @@ -231,8 +229,6 @@ func NewConfigPrinter(store *store.Store) (ConfigPrinter, error) { func (c ConfigPrinter) String() string { var buffer bytes.Buffer - buffer.WriteString(fmt.Sprintf("ACCOUNT_ADDRESS: %v\n", c.AccountAddress)) - schemaT := reflect.TypeOf(orm.ConfigSchema{}) cwlT := reflect.TypeOf(c.EnvPrinter) cwlV := reflect.ValueOf(c.EnvPrinter) @@ -276,6 +272,13 @@ func (c *ConfigPrinter) SetID(value string) error { return nil } +func mapToStringA(in []url.URL) (out []string) { + for _, url := range in { + out = append(out, url.String()) + } + return +} + // JobSpec holds the JobSpec definition together with // the total link earned from that job type JobSpec struct { diff --git a/core/store/testdata/fixtures.sql b/core/store/testdata/fixtures.sql index 11f5a8db18d..8ca3251a212 100644 --- a/core/store/testdata/fixtures.sql +++ b/core/store/testdata/fixtures.sql @@ -1,9 +1,9 @@ -- Password for all encrypted keys is 'password' -- Scrypt params are chosen to be completely insecure and very fast to decrypt -- Don't use any of these keys for anything outside of testing! -INSERT INTO "public"."keys"("address","json","created_at","updated_at","next_nonce","id","last_used","is_funding") +INSERT INTO "public"."keys"("address","json","created_at","updated_at","next_nonce","last_used","is_funding") VALUES -(DECODE('27548a32b9ad5d64c5945eae9da5337bc3169d15','hex'),E'{"id": "1ccf542e-8f4d-48a0-ad1d-b4e6a86d4c6d", "crypto": {"kdf": "scrypt", "mac": "7f31bd05768a184278c4e9f077bcfba7b2003fed585b99301374a1a4a9adff25", "cipher": "aes-128-ctr", "kdfparams": {"n": 2, "p": 1, "r": 8, "salt": "99e83bf0fdeba39bd29c343db9c52d9e0eae536fdaee472d3181eac1968aa1f9", "dklen": 32}, "ciphertext": "ac22fa788b53a5f62abda03cd432c7aee1f70053b97633e78f93709c383b2a46", "cipherparams": {"iv": "6699ba30f953728787e51a754d6f9566"}}, "address": "27548a32b9ad5d64c5945eae9da5337bc3169d15", "version": 3}',E'2020-10-29 10:29:34.553191+00',E'2020-10-29 10:29:34.553191+00',0,1,NULL,FALSE); +(DECODE('27548a32b9ad5d64c5945eae9da5337bc3169d15','hex'),E'{"id": "1ccf542e-8f4d-48a0-ad1d-b4e6a86d4c6d", "crypto": {"kdf": "scrypt", "mac": "7f31bd05768a184278c4e9f077bcfba7b2003fed585b99301374a1a4a9adff25", "cipher": "aes-128-ctr", "kdfparams": {"n": 2, "p": 1, "r": 8, "salt": "99e83bf0fdeba39bd29c343db9c52d9e0eae536fdaee472d3181eac1968aa1f9", "dklen": 32}, "ciphertext": "ac22fa788b53a5f62abda03cd432c7aee1f70053b97633e78f93709c383b2a46", "cipherparams": {"iv": "6699ba30f953728787e51a754d6f9566"}}, "address": "27548a32b9ad5d64c5945eae9da5337bc3169d15", "version": 3}',E'2020-10-29 10:29:34.553191+00',E'2020-10-29 10:29:34.553191+00',0,NULL,FALSE); INSERT INTO "public"."encrypted_ocr_key_bundles"("id","on_chain_signing_address","off_chain_public_key","encrypted_private_keys","created_at","updated_at","config_public_key") VALUES diff --git a/core/utils/utils.go b/core/utils/utils.go index 49ff6f8c3e9..d3513d15f73 100644 --- a/core/utils/utils.go +++ b/core/utils/utils.go @@ -30,7 +30,7 @@ import ( "github.com/tevino/abool" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/sha3" - null "gopkg.in/guregu/null.v3" + null "gopkg.in/guregu/null.v4" ) const ( @@ -290,49 +290,6 @@ func MinUint(first uint, vals ...uint) uint { return min } -// CoerceInterfaceMapToStringMap converts map[interface{}]interface{} (interface maps) to -// map[string]interface{} (string maps) and []interface{} with interface maps to string maps. -// Relevant when serializing between CBOR and JSON. -func CoerceInterfaceMapToStringMap(in interface{}) (interface{}, error) { - switch typed := in.(type) { - case map[string]interface{}: - for k, v := range typed { - coerced, err := CoerceInterfaceMapToStringMap(v) - if err != nil { - return nil, err - } - typed[k] = coerced - } - return typed, nil - case map[interface{}]interface{}: - m := map[string]interface{}{} - for k, v := range typed { - coercedKey, ok := k.(string) - if !ok { - return nil, fmt.Errorf("unable to coerce key %T %v to a string", k, k) - } - coerced, err := CoerceInterfaceMapToStringMap(v) - if err != nil { - return nil, err - } - m[coercedKey] = coerced - } - return m, nil - case []interface{}: - r := make([]interface{}, len(typed)) - for i, v := range typed { - coerced, err := CoerceInterfaceMapToStringMap(v) - if err != nil { - return nil, err - } - r[i] = coerced - } - return r, nil - default: - return in, nil - } -} - // UnmarshalToMap takes an input json string and returns a map[string]interface i.e. a raw object func UnmarshalToMap(input string) (map[string]interface{}, error) { var output map[string]interface{} diff --git a/core/utils/utils_test.go b/core/utils/utils_test.go index 4dce9df965a..ac0b72595be 100644 --- a/core/utils/utils_test.go +++ b/core/utils/utils_test.go @@ -2,7 +2,6 @@ package utils_test import ( "context" - "reflect" "strings" "sync" "testing" @@ -103,66 +102,6 @@ func TestUtils_DurationFromNow(t *testing.T) { assert.True(t, 0 < duration) } -func TestCoerceInterfaceMapToStringMap(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input interface{} - want interface{} - wantError bool - }{ - {"empty map", map[interface{}]interface{}{}, map[string]interface{}{}, false}, - {"simple map", map[interface{}]interface{}{"key": "value"}, map[string]interface{}{"key": "value"}, false}, - {"int map", map[int]interface{}{1: "value"}, map[int]interface{}{1: "value"}, false}, - {"error map", map[interface{}]interface{}{1: "value"}, map[int]interface{}{}, true}, - { - "nested string map map", - map[string]interface{}{"key": map[interface{}]interface{}{"nk": "nv"}}, - map[string]interface{}{"key": map[string]interface{}{"nk": "nv"}}, - false, - }, - { - "nested map map", - map[interface{}]interface{}{"key": map[interface{}]interface{}{"nk": "nv"}}, - map[string]interface{}{"key": map[string]interface{}{"nk": "nv"}}, - false, - }, - { - "nested map array", - map[interface{}]interface{}{"key": []interface{}{1, "value"}}, - map[string]interface{}{"key": []interface{}{1, "value"}}, - false, - }, - {"empty array", []interface{}{}, []interface{}{}, false}, - {"simple array", []interface{}{1, "value"}, []interface{}{1, "value"}, false}, - { - "error array", - []interface{}{map[interface{}]interface{}{1: "value"}}, - []interface{}{}, - true, - }, - { - "nested array map", - []interface{}{map[interface{}]interface{}{"key": map[interface{}]interface{}{"nk": "nv"}}}, - []interface{}{map[string]interface{}{"key": map[string]interface{}{"nk": "nv"}}}, - false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - decoded, err := utils.CoerceInterfaceMapToStringMap(test.input) - if test.wantError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.True(t, reflect.DeepEqual(test.want, decoded)) - } - }) - } -} - func TestKeccak256(t *testing.T) { t.Parallel() diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index b6aeaa1225a..070bfd7f372 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -61,7 +61,7 @@ func (jrc *JobRunsController) Create(c *gin.Context) { return } - j, err := jrc.App.GetStore().FindJob(id) + j, err := jrc.App.GetStore().FindJobSpec(id) if errors.Cause(err) == orm.ErrorNotFound { jsonAPIError(c, http.StatusNotFound, errors.New("Job not found")) return diff --git a/core/web/job_specs_controller_test.go b/core/web/job_specs_controller_test.go index 055254c2371..a14e63806e0 100644 --- a/core/web/job_specs_controller_test.go +++ b/core/web/job_specs_controller_test.go @@ -196,7 +196,7 @@ func TestJobSpecsController_Create_HappyPath(t *testing.T) { // Check ORM orm := app.GetStore().ORM - j, err = orm.FindJob(j.ID) + j, err = orm.FindJobSpec(j.ID) require.NoError(t, err) require.Len(t, j.Initiators, 1) assert.Equal(t, models.InitiatorWeb, j.Initiators[0].Type) @@ -232,43 +232,10 @@ func TestJobSpecsController_Create_CustomName(t *testing.T) { require.NoError(t, err) orm := app.GetStore().ORM - j, err = orm.FindJob(j.ID) + j, err = orm.FindJobSpec(j.ID) require.NoError(t, err) assert.Equal(t, j.Name, "CustomJobName") }) - - t.Run("it replaces a blank name with a generated one", func(t *testing.T) { - jsr, err = jsr.MultiAdd(map[string]interface{}{"name": ""}) - require.NoError(t, err) - requestBody, err = json.Marshal(jsr) - require.NoError(t, err) - - client = app.NewHTTPClient() - resp, cleanup := client.Post("/v2/specs", bytes.NewReader(requestBody)) - defer cleanup() - cltest.AssertServerResponse(t, resp, http.StatusOK) - - var j models.JobSpec - err = cltest.ParseJSONAPIResponse(t, resp, &j) - require.NoError(t, err) - - orm := app.GetStore().ORM - j, err = orm.FindJob(j.ID) - require.NoError(t, err) - assert.NotEmpty(t, j.Name) - assert.NotEqual(t, j.Name, "CustomJobName") - }) - - t.Run("it rejects an already taken name", func(t *testing.T) { - jsr, err = jsr.MultiAdd(map[string]interface{}{"name": "CustomJobName"}) - require.NoError(t, err) - requestBody, err = json.Marshal(jsr) - require.NoError(t, err) - - resp, cleanup := client.Post("/v2/specs", bytes.NewReader(requestBody)) - defer cleanup() - cltest.AssertServerResponse(t, resp, http.StatusConflict) - }) } func TestJobSpecsController_CreateExternalInitiator_Success(t *testing.T) { @@ -681,7 +648,7 @@ func TestJobSpecsController_Destroy(t *testing.T) { resp, cleanup := client.Delete("/v2/specs/" + job.ID.String()) defer cleanup() assert.Equal(t, http.StatusNoContent, resp.StatusCode) - assert.Error(t, utils.JustError(app.Store.FindJob(job.ID))) + assert.Error(t, utils.JustError(app.Store.FindJobSpec(job.ID))) assert.Equal(t, 0, len(app.ChainlinkApplication.JobSubscriber.Jobs())) } @@ -698,7 +665,7 @@ func TestJobSpecsController_DestroyAdd(t *testing.T) { resp, cleanup := client.Delete("/v2/specs/" + job.ID.String()) defer cleanup() assert.Equal(t, http.StatusNoContent, resp.StatusCode) - assert.Error(t, utils.JustError(app.Store.FindJob(job.ID))) + assert.Error(t, utils.JustError(app.Store.FindJobSpec(job.ID))) assert.Equal(t, 0, len(app.ChainlinkApplication.JobSubscriber.Jobs())) job = cltest.NewJobWithLogInitiator() @@ -709,7 +676,7 @@ func TestJobSpecsController_DestroyAdd(t *testing.T) { resp, cleanup = client.Delete("/v2/specs/" + job.ID.String()) defer cleanup() assert.Equal(t, http.StatusNoContent, resp.StatusCode) - assert.Error(t, utils.JustError(app.Store.FindJob(job.ID))) + assert.Error(t, utils.JustError(app.Store.FindJobSpec(job.ID))) assert.Equal(t, 0, len(app.ChainlinkApplication.JobSubscriber.Jobs())) } @@ -728,12 +695,12 @@ func TestJobSpecsController_Destroy_MultipleJobs(t *testing.T) { resp, cleanup := client.Delete("/v2/specs/" + job1.ID.String()) defer cleanup() assert.Equal(t, http.StatusNoContent, resp.StatusCode) - assert.Error(t, utils.JustError(app.Store.FindJob(job1.ID))) + assert.Error(t, utils.JustError(app.Store.FindJobSpec(job1.ID))) assert.Equal(t, 0, len(app.ChainlinkApplication.JobSubscriber.Jobs())) resp, cleanup = client.Delete("/v2/specs/" + job2.ID.String()) defer cleanup() assert.Equal(t, http.StatusNoContent, resp.StatusCode) - assert.Error(t, utils.JustError(app.Store.FindJob(job2.ID))) + assert.Error(t, utils.JustError(app.Store.FindJobSpec(job2.ID))) assert.Equal(t, 0, len(app.ChainlinkApplication.JobSubscriber.Jobs())) } diff --git a/core/web/jobs_controller.go b/core/web/jobs_controller.go new file mode 100644 index 00000000000..636dfa09470 --- /dev/null +++ b/core/web/jobs_controller.go @@ -0,0 +1,172 @@ +package web + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/pelletier/go-toml" + "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/services" + "github.com/smartcontractkit/chainlink/core/services/chainlink" + "github.com/smartcontractkit/chainlink/core/services/job" + "github.com/smartcontractkit/chainlink/core/services/offchainreporting" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/smartcontractkit/chainlink/core/store/orm" + "gopkg.in/guregu/null.v4" +) + +// JobsController manages jobs +type JobsController struct { + App chainlink.Application +} + +// Index lists all jobs +// Example: +// "GET /jobs" +func (jc *JobsController) Index(c *gin.Context) { + jobs, err := jc.App.GetStore().ORM.JobsV2() + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + jsonAPIResponse(c, jobs, "jobs") +} + +// Show returns the details of a job +// Example: +// "GET /jobs/:ID" +func (jc *JobsController) Show(c *gin.Context) { + jobSpec := models.JobSpecV2{} + err := jobSpec.SetID(c.Param("ID")) + if err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, err) + return + } + + jobSpec, err = jc.App.GetStore().ORM.FindJob(jobSpec.ID) + if errors.Cause(err) == orm.ErrorNotFound { + jsonAPIError(c, http.StatusNotFound, errors.New("job not found")) + return + } + + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + jsonAPIResponse(c, jobSpec, "offChainReportingJobSpec") +} + +type GenericJobSpec struct { + Type string `toml:"type"` + SchemaVersion uint32 `toml:"schemaVersion"` + Name null.String `toml:"name"` +} + +// Create validates, saves and starts a new job. +// Example: +// "POST /jobs" +func (jc *JobsController) Create(c *gin.Context) { + request := models.CreateJobSpecRequest{} + if err := c.ShouldBindJSON(&request); err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, err) + return + } + + genericJS := GenericJobSpec{} + err := toml.Unmarshal([]byte(request.TOML), &genericJS) + if err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, errors.Wrap(err, "failed to parse V2 job TOML. HINT: If you are trying to add a V1 job spec (json) via the CLI, try `job_specs create` instead")) + } + + switch genericJS.Type { + case string(offchainreporting.JobType): + jc.createOCR(c, request.TOML) + case string(models.EthRequestEventJobType): + jc.createEthRequestEvent(c, request.TOML) + default: + jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("unknown job type: %s", genericJS.Type)) + } + +} + +func (jc *JobsController) createOCR(c *gin.Context, toml string) { + jobSpec, err := services.ValidatedOracleSpecToml(jc.App.GetStore().Config, toml) + if err != nil { + jsonAPIError(c, http.StatusBadRequest, err) + return + } + config := jc.App.GetStore().Config + if jobSpec.JobType() == offchainreporting.JobType && !config.Dev() && !config.FeatureOffchainReporting() { + jsonAPIError(c, http.StatusNotImplemented, errors.New("The Offchain Reporting feature is disabled by configuration")) + return + } + + jobID, err := jc.App.AddJobV2(c.Request.Context(), jobSpec, jobSpec.Name) + if err != nil { + if errors.Cause(err) == job.ErrNoSuchKeyBundle || errors.Cause(err) == job.ErrNoSuchPeerID || errors.Cause(err) == job.ErrNoSuchTransmitterAddress { + jsonAPIError(c, http.StatusBadRequest, err) + return + } + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + job, err := jc.App.GetStore().ORM.FindJob(jobID) + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + jsonAPIResponse(c, job, "offChainReportingJobSpec") +} + +func (jc *JobsController) createEthRequestEvent(c *gin.Context, toml string) { + jobSpec, err := services.ValidatedEthRequestEventSpec(toml) + if err != nil { + jsonAPIError(c, http.StatusBadRequest, err) + return + } + jobID, err := jc.App.AddJobV2(c.Request.Context(), jobSpec, jobSpec.Name) + if err != nil { + if errors.Cause(err) == job.ErrNoSuchKeyBundle || errors.Cause(err) == job.ErrNoSuchPeerID || errors.Cause(err) == job.ErrNoSuchTransmitterAddress { + jsonAPIError(c, http.StatusBadRequest, err) + return + } + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + job, err := jc.App.GetStore().ORM.FindJob(jobID) + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + jsonAPIResponse(c, job, "ethRequestEventSpec") +} + +// Delete soft deletes an OCR job spec. +// Example: +// "DELETE /specs/:ID" +func (jc *JobsController) Delete(c *gin.Context) { + jobSpec := models.JobSpecV2{} + err := jobSpec.SetID(c.Param("ID")) + if err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, err) + return + } + + err = jc.App.DeleteJobV2(c.Request.Context(), jobSpec.ID) + if errors.Cause(err) == orm.ErrorNotFound { + jsonAPIError(c, http.StatusNotFound, errors.New("JobSpec not found")) + return + } + if err != nil { + jsonAPIError(c, http.StatusInternalServerError, err) + return + } + + jsonAPIResponseWithStatus(c, nil, "offChainReportingJobSpec", http.StatusNoContent) +} diff --git a/core/web/ocr_job_specs_controller_test.go b/core/web/jobs_controller_test.go similarity index 63% rename from core/web/ocr_job_specs_controller_test.go rename to core/web/jobs_controller_test.go index a8be49b666f..56dca4b681e 100644 --- a/core/web/ocr_job_specs_controller_test.go +++ b/core/web/jobs_controller_test.go @@ -11,17 +11,18 @@ import ( "github.com/pelletier/go-toml" - "github.com/smartcontractkit/chainlink/core/services/job" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/services" + "github.com/smartcontractkit/chainlink/core/services/job" "github.com/smartcontractkit/chainlink/core/services/offchainreporting" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" ) -func TestOCRJobSpecsController_Create_ValidationFailure(t *testing.T) { +func TestJobsController_Create_ValidationFailure(t *testing.T) { var ( contractAddress = cltest.NewEIP55Address() monitoringEndpoint = "chain.link:101" @@ -58,13 +59,13 @@ func TestOCRJobSpecsController_Create_ValidationFailure(t *testing.T) { } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - _, client, cleanup := setupOCRJobSpecsControllerTests(t) + _, client, cleanup := setupJobsControllerTests(t) defer cleanup() sp := cltest.MinimalOCRNonBootstrapSpec(contractAddress, tc.ta, tc.pid, monitoringEndpoint, tc.kb) - body, _ := json.Marshal(models.CreateOCRJobSpecRequest{ + body, _ := json.Marshal(models.CreateJobSpecRequest{ TOML: sp, }) - resp, cleanup := client.Post("/v2/ocr/specs", bytes.NewReader(body)) + resp, cleanup := client.Post("/v2/jobs", bytes.NewReader(body)) defer cleanup() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) b, err := ioutil.ReadAll(resp.Body) @@ -74,14 +75,14 @@ func TestOCRJobSpecsController_Create_ValidationFailure(t *testing.T) { } } -func TestOCRJobSpecsController_Create_HappyPath(t *testing.T) { - app, client, cleanup := setupOCRJobSpecsControllerTests(t) +func TestJobsController_Create_HappyPath_OffchainReportingSpec(t *testing.T) { + app, client, cleanup := setupJobsControllerTests(t) defer cleanup() - body, _ := json.Marshal(models.CreateOCRJobSpecRequest{ + body, _ := json.Marshal(models.CreateJobSpecRequest{ TOML: string(cltest.MustReadFile(t, "testdata/oracle-spec.toml")), }) - response, cleanup := client.Post("/v2/ocr/specs", bytes.NewReader(body)) + response, cleanup := client.Post("/v2/jobs", bytes.NewReader(body)) defer cleanup() require.Equal(t, http.StatusOK, response.StatusCode) @@ -92,7 +93,7 @@ func TestOCRJobSpecsController_Create_HappyPath(t *testing.T) { err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, response), &ocrJobSpec) assert.NoError(t, err) - assert.Equal(t, job.OffchainreportingOracleSpec.ContractAddress, ocrJobSpec.OffchainreportingOracleSpec.ContractAddress) + assert.Equal(t, "web oracle spec", job.Name.ValueOrZero()) assert.Equal(t, job.OffchainreportingOracleSpec.P2PPeerID, ocrJobSpec.OffchainreportingOracleSpec.P2PPeerID) assert.Equal(t, job.OffchainreportingOracleSpec.P2PBootstrapPeers, ocrJobSpec.OffchainreportingOracleSpec.P2PBootstrapPeers) assert.Equal(t, job.OffchainreportingOracleSpec.IsBootstrapPeer, ocrJobSpec.OffchainreportingOracleSpec.IsBootstrapPeer) @@ -110,28 +111,54 @@ func TestOCRJobSpecsController_Create_HappyPath(t *testing.T) { require.Equal(t, models.EIP55Address("0x613a38AC1659769640aaE063C651F48E0250454C"), job.OffchainreportingOracleSpec.ContractAddress) } -func TestOCRJobSpecsController_Index_HappyPath(t *testing.T) { - client, cleanup, ocrJobSpecFromFile, _ := setupOCRJobSpecsWControllerTestsWithJob(t) +func TestJobsController_Create_HappyPath_EthRequestEventSpec(t *testing.T) { + app, client, cleanup := setupJobsControllerTests(t) + defer cleanup() + + body, _ := json.Marshal(models.CreateJobSpecRequest{ + TOML: string(cltest.MustReadFile(t, "testdata/eth-request-event-spec.toml")), + }) + response, cleanup := client.Post("/v2/jobs", bytes.NewReader(body)) + defer cleanup() + require.Equal(t, http.StatusOK, response.StatusCode) + + job := models.JobSpecV2{} + require.NoError(t, app.Store.DB.Preload("EthRequestEventSpec").First(&job).Error) + + jobSpec := models.JobSpecV2{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, response), &jobSpec) + assert.NoError(t, err) + + assert.Equal(t, "example eth request event spec", job.Name.ValueOrZero()) + assert.NotNil(t, jobSpec.PipelineSpec.DotDagSource) + + // Sanity check to make sure it inserted correctly + require.Equal(t, models.EIP55Address("0x613a38AC1659769640aaE063C651F48E0250454C"), job.EthRequestEventSpec.ContractAddress) +} + +func TestJobsController_Index_HappyPath(t *testing.T) { + client, cleanup, ocrJobSpecFromFile, _, ereJobSpecFromFile, _ := setupJobSpecsControllerTestsWithJobs(t) defer cleanup() - response, cleanup := client.Get("/v2/ocr/specs") + response, cleanup := client.Get("/v2/jobs") defer cleanup() cltest.AssertServerResponse(t, response, http.StatusOK) - ocrJobSpecs := []models.JobSpecV2{} - err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, response), &ocrJobSpecs) + jobSpecs := []models.JobSpecV2{} + err := web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, response), &jobSpecs) assert.NoError(t, err) - require.Len(t, ocrJobSpecs, 1) + require.Len(t, jobSpecs, 2) - runOCRJobSpecAssertions(t, ocrJobSpecFromFile, ocrJobSpecs[0]) + runOCRJobSpecAssertions(t, ocrJobSpecFromFile, jobSpecs[0]) + runEthRequestEventJobSpecAssertions(t, ereJobSpecFromFile, jobSpecs[1]) } -func TestOCRJobSpecsController_Show_HappyPath(t *testing.T) { - client, cleanup, ocrJobSpecFromFile, jobID := setupOCRJobSpecsWControllerTestsWithJob(t) +func TestJobsController_Show_HappyPath(t *testing.T) { + client, cleanup, ocrJobSpecFromFile, jobID, ereJobSpecFromFile, jobID2 := setupJobSpecsControllerTestsWithJobs(t) defer cleanup() - response, cleanup := client.Get("/v2/ocr/specs/" + fmt.Sprintf("%v", jobID)) + response, cleanup := client.Get("/v2/jobs/" + fmt.Sprintf("%v", jobID)) defer cleanup() cltest.AssertServerResponse(t, response, http.StatusOK) @@ -140,22 +167,32 @@ func TestOCRJobSpecsController_Show_HappyPath(t *testing.T) { assert.NoError(t, err) runOCRJobSpecAssertions(t, ocrJobSpecFromFile, ocrJobSpec) + + response, cleanup = client.Get("/v2/jobs/" + fmt.Sprintf("%v", jobID2)) + defer cleanup() + cltest.AssertServerResponse(t, response, http.StatusOK) + + ereJobSpec := models.JobSpecV2{} + err = web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, response), &ereJobSpec) + assert.NoError(t, err) + + runEthRequestEventJobSpecAssertions(t, ereJobSpecFromFile, ereJobSpec) } -func TestOCRJobSpecsController_Show_InvalidID(t *testing.T) { - client, cleanup, _, _ := setupOCRJobSpecsWControllerTestsWithJob(t) +func TestJobsController_Show_InvalidID(t *testing.T) { + client, cleanup, _, _, _, _ := setupJobSpecsControllerTestsWithJobs(t) defer cleanup() - response, cleanup := client.Get("/v2/ocr/specs/uuidLikeString") + response, cleanup := client.Get("/v2/jobs/uuidLikeString") defer cleanup() cltest.AssertServerResponse(t, response, http.StatusUnprocessableEntity) } -func TestOCRJobSpecsController_Show_NonExistentID(t *testing.T) { - client, cleanup, _, _ := setupOCRJobSpecsWControllerTestsWithJob(t) +func TestJobsController_Show_NonExistentID(t *testing.T) { + client, cleanup, _, _, _, _ := setupJobSpecsControllerTestsWithJobs(t) defer cleanup() - response, cleanup := client.Get("/v2/ocr/specs/999999999") + response, cleanup := client.Get("/v2/jobs/999999999") defer cleanup() cltest.AssertServerResponse(t, response, http.StatusNotFound) } @@ -182,7 +219,17 @@ func runOCRJobSpecAssertions(t *testing.T, ocrJobSpecFromFile offchainreporting. assert.Contains(t, ocrJobSpecFromServer.OffchainreportingOracleSpec.UpdatedAt.String(), "20") } -func setupOCRJobSpecsControllerTests(t *testing.T) (*cltest.TestApplication, cltest.HTTPClientCleaner, func()) { +func runEthRequestEventJobSpecAssertions(t *testing.T, ereJobSpecFromFile services.EthRequestEventSpec, ereJobSpecFromServer models.JobSpecV2) { + assert.Equal(t, ereJobSpecFromFile.ContractAddress, ereJobSpecFromServer.EthRequestEventSpec.ContractAddress) + assert.Equal(t, ereJobSpecFromFile.Pipeline.DOTSource, ereJobSpecFromServer.PipelineSpec.DotDagSource) + // Check that create and update dates are non empty values. + // Empty date value is "0001-01-01 00:00:00 +0000 UTC" so we are checking for the + // millenia and century characters to be present + assert.Contains(t, ereJobSpecFromServer.EthRequestEventSpec.CreatedAt.String(), "20") + assert.Contains(t, ereJobSpecFromServer.EthRequestEventSpec.UpdatedAt.String(), "20") +} + +func setupJobsControllerTests(t *testing.T) (*cltest.TestApplication, cltest.HTTPClientCleaner, func()) { t.Parallel() app, cleanup := cltest.NewApplication(t, cltest.LenientEthMock) require.NoError(t, app.Start()) @@ -191,7 +238,7 @@ func setupOCRJobSpecsControllerTests(t *testing.T) (*cltest.TestApplication, clt return app, client, cleanup } -func setupOCRJobSpecsWControllerTestsWithJob(t *testing.T) (cltest.HTTPClientCleaner, func(), offchainreporting.OracleSpec, int32) { +func setupJobSpecsControllerTestsWithJobs(t *testing.T) (cltest.HTTPClientCleaner, func(), offchainreporting.OracleSpec, int32, services.EthRequestEventSpec, int32) { t.Parallel() app, cleanup := cltest.NewApplication(t, cltest.LenientEthMock) require.NoError(t, app.Start()) @@ -203,6 +250,14 @@ func setupOCRJobSpecsWControllerTestsWithJob(t *testing.T) (cltest.HTTPClientCle require.NoError(t, err) err = tree.Unmarshal(&ocrJobSpecFromFile) require.NoError(t, err) - jobID, _ := app.AddJobV2(context.Background(), ocrJobSpecFromFile) - return client, cleanup, ocrJobSpecFromFile, jobID + jobID, _ := app.AddJobV2(context.Background(), ocrJobSpecFromFile, null.String{}) + + var ereJobSpecFromFile services.EthRequestEventSpec + tree, err = toml.LoadFile("testdata/eth-request-event-spec.toml") + require.NoError(t, err) + err = tree.Unmarshal(&ereJobSpecFromFile) + require.NoError(t, err) + jobID2, _ := app.AddJobV2(context.Background(), ereJobSpecFromFile, null.String{}) + + return client, cleanup, ocrJobSpecFromFile, jobID, ereJobSpecFromFile, jobID2 } diff --git a/core/web/ocr_job_specs_controller.go b/core/web/ocr_job_specs_controller.go deleted file mode 100644 index f287f8dde0a..00000000000 --- a/core/web/ocr_job_specs_controller.go +++ /dev/null @@ -1,120 +0,0 @@ -package web - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/services/chainlink" - "github.com/smartcontractkit/chainlink/core/services/job" - "github.com/smartcontractkit/chainlink/core/services/offchainreporting" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" -) - -// OCRJobSpecsController manages OCR job spec requests. -type OCRJobSpecsController struct { - App chainlink.Application -} - -// Index lists all OCR job specs. -// Example: -// "GET /ocr/specs" -func (ocrjsc *OCRJobSpecsController) Index(c *gin.Context) { - jobs, err := ocrjsc.App.GetStore().ORM.OffChainReportingJobs() - if err != nil { - jsonAPIError(c, http.StatusInternalServerError, err) - return - } - - jsonAPIResponse(c, jobs, "offChainReportingJobSpec") -} - -// Show returns the details of a OCR job spec. -// Example: -// "GET /ocr/specs/:ID" -func (ocrjsc *OCRJobSpecsController) Show(c *gin.Context) { - jobSpec := models.JobSpecV2{} - err := jobSpec.SetID(c.Param("ID")) - if err != nil { - jsonAPIError(c, http.StatusUnprocessableEntity, err) - return - } - - jobSpec, err = ocrjsc.App.GetStore().ORM.FindOffChainReportingJob(jobSpec.ID) - if errors.Cause(err) == orm.ErrorNotFound { - jsonAPIError(c, http.StatusNotFound, errors.New("OCR job spec not found")) - return - } - - if err != nil { - jsonAPIError(c, http.StatusInternalServerError, err) - return - } - - jsonAPIResponse(c, jobSpec, "offChainReportingJobSpec") -} - -// Create validates, saves and starts a new OCR job spec. -// Example: -// "POST /ocr/specs" -func (ocrjsc *OCRJobSpecsController) Create(c *gin.Context) { - request := models.CreateOCRJobSpecRequest{} - if err := c.ShouldBindJSON(&request); err != nil { - jsonAPIError(c, http.StatusUnprocessableEntity, err) - return - } - jobSpec, err := services.ValidatedOracleSpecToml(request.TOML) - if err != nil { - jsonAPIError(c, http.StatusBadRequest, err) - return - } - config := ocrjsc.App.GetStore().Config - if jobSpec.JobType() == offchainreporting.JobType && !config.Dev() && !config.FeatureOffchainReporting() { - jsonAPIError(c, http.StatusNotImplemented, errors.New("The Offchain Reporting feature is disabled by configuration")) - return - } - - jobID, err := ocrjsc.App.AddJobV2(c.Request.Context(), jobSpec) - if err != nil { - if errors.Cause(err) == job.ErrNoSuchKeyBundle || errors.Cause(err) == job.ErrNoSuchPeerID || errors.Cause(err) == job.ErrNoSuchTransmitterAddress { - jsonAPIError(c, http.StatusBadRequest, err) - return - } - jsonAPIError(c, http.StatusInternalServerError, err) - return - } - - job, err := ocrjsc.App.GetStore().ORM.FindOffChainReportingJob(jobID) - if err != nil { - jsonAPIError(c, http.StatusInternalServerError, err) - return - } - - jsonAPIResponse(c, job, "offChainReportingJobSpec") -} - -// Delete soft deletes an OCR job spec. -// Example: -// "DELETE /ocr/specs/:ID" -func (ocrjsc *OCRJobSpecsController) Delete(c *gin.Context) { - jobSpec := models.JobSpecV2{} - err := jobSpec.SetID(c.Param("ID")) - if err != nil { - jsonAPIError(c, http.StatusUnprocessableEntity, err) - return - } - - err = ocrjsc.App.DeleteJobV2(c.Request.Context(), jobSpec.ID) - if errors.Cause(err) == orm.ErrorNotFound { - jsonAPIError(c, http.StatusNotFound, errors.New("JobSpec not found")) - return - } - if err != nil { - jsonAPIError(c, http.StatusInternalServerError, err) - return - } - - jsonAPIResponseWithStatus(c, nil, "offChainReportingJobSpec", http.StatusNoContent) -} diff --git a/core/web/ocr_job_runs_controller.go b/core/web/pipeline_runs_controller.go similarity index 60% rename from core/web/ocr_job_runs_controller.go rename to core/web/pipeline_runs_controller.go index c7cfaca2e25..6d8eadb4f4d 100644 --- a/core/web/ocr_job_runs_controller.go +++ b/core/web/pipeline_runs_controller.go @@ -10,15 +10,15 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" ) -// OCRJobRunsController manages OCR job run requests. -type OCRJobRunsController struct { +// PipelineRunsController manages V2 job run requests. +type PipelineRunsController struct { App chainlink.Application } -// Index returns all pipeline runs for an OCR job. +// Index returns all pipeline runs for a job. // Example: -// "GET /ocr/specs/:ID/runs" -func (ocrjrc *OCRJobRunsController) Index(c *gin.Context, size, page, offset int) { +// "GET /jobs/:ID/runs" +func (prc *PipelineRunsController) Index(c *gin.Context, size, page, offset int) { jobSpec := models.JobSpecV2{} err := jobSpec.SetID(c.Param("ID")) if err != nil { @@ -26,20 +26,20 @@ func (ocrjrc *OCRJobRunsController) Index(c *gin.Context, size, page, offset int return } - pipelineRuns, count, err := ocrjrc.App.GetStore().OffChainReportingJobRuns(jobSpec.ID, offset, size) + pipelineRuns, count, err := prc.App.GetStore().PipelineRunsByJobID(jobSpec.ID, offset, size) if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) return } - paginatedResponse(c, "offChainReportingJobRun", size, page, pipelineRuns, count, err) + paginatedResponse(c, "offChainReportingPipelineRun", size, page, pipelineRuns, count, err) } // Show returns a specified pipeline run. // Example: -// "GET /ocr/specs/:ID/runs/:runID" -func (ocrjrc *OCRJobRunsController) Show(c *gin.Context) { +// "GET /jobs/:ID/runs/:runID" +func (prc *PipelineRunsController) Show(c *gin.Context) { pipelineRun := pipeline.Run{} err := pipelineRun.SetID(c.Param("runID")) if err != nil { @@ -47,7 +47,7 @@ func (ocrjrc *OCRJobRunsController) Show(c *gin.Context) { return } - err = preloadPipelineRunDependencies(ocrjrc.App.GetStore().DB). + err = preloadPipelineRunDependencies(prc.App.GetStore().DB). Where("pipeline_runs.id = ?", pipelineRun.ID). First(&pipelineRun).Error @@ -56,13 +56,13 @@ func (ocrjrc *OCRJobRunsController) Show(c *gin.Context) { return } - jsonAPIResponse(c, pipelineRun, "offChainReportingJobRun") + jsonAPIResponse(c, pipelineRun, "offChainReportingPipelineRun") } -// Create triggers a pipeline run for an OCR job. +// Create triggers a pipeline run for a job. // Example: -// "POST /ocr/specs/:ID/runs" -func (ocrjrc *OCRJobRunsController) Create(c *gin.Context) { +// "POST /jobs/:ID/runs" +func (prc *PipelineRunsController) Create(c *gin.Context) { jobSpec := models.JobSpecV2{} err := jobSpec.SetID(c.Param("ID")) if err != nil { @@ -70,14 +70,14 @@ func (ocrjrc *OCRJobRunsController) Create(c *gin.Context) { return } - jobRunID, err := ocrjrc.App.RunJobV2(c, jobSpec.ID, nil) + jobRunID, err := prc.App.RunJobV2(c, jobSpec.ID, nil) if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) return } - jsonAPIResponse(c, models.OCRJobRun{ID: jobRunID}, "offChainReportingJobRun") + jsonAPIResponse(c, models.PipelineRun{ID: jobRunID}, "offChainReportingPipelineRun") } func preloadPipelineRunDependencies(db *gorm.DB) *gorm.DB { diff --git a/core/web/ocr_job_runs_controller_test.go b/core/web/pipeline_runs_controller_test.go similarity index 79% rename from core/web/ocr_job_runs_controller_test.go rename to core/web/pipeline_runs_controller_test.go index 56297887a96..f485312b8c7 100644 --- a/core/web/ocr_job_runs_controller_test.go +++ b/core/web/pipeline_runs_controller_test.go @@ -15,9 +15,10 @@ import ( "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" ) -func TestOCRJobRunsController_Create_HappyPath(t *testing.T) { +func TestPipelineRunsController_Create_HappyPath(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t, cltest.LenientEthMock) defer cleanup() @@ -31,23 +32,23 @@ func TestOCRJobRunsController_Create_HappyPath(t *testing.T) { err = tree.Unmarshal(&ocrJobSpecFromFile) require.NoError(t, err) - jobID, _ := app.AddJobV2(context.Background(), ocrJobSpecFromFile) + jobID, _ := app.AddJobV2(context.Background(), ocrJobSpecFromFile, null.String{}) - response, cleanup := client.Post("/v2/ocr/specs/"+fmt.Sprintf("%v", jobID)+"/runs", nil) + response, cleanup := client.Post("/v2/jobs/"+fmt.Sprintf("%v", jobID)+"/runs", nil) defer cleanup() cltest.AssertServerResponse(t, response, http.StatusOK) - parsedResponse := models.OCRJobRun{} + parsedResponse := models.PipelineRun{} err = web.ParseJSONAPIResponse(cltest.ParseResponseBody(t, response), &parsedResponse) assert.NoError(t, err) assert.NotNil(t, parsedResponse.ID) } -func TestOCRJobRunsController_Index_HappyPath(t *testing.T) { - client, jobID, runIDs, cleanup := setupOCRJobRunsControllerTests(t) +func TestPipelineRunsController_Index_HappyPath(t *testing.T) { + client, jobID, runIDs, cleanup := setupPipelineRunsControllerTests(t) defer cleanup() - response, cleanup := client.Get("/v2/ocr/specs/" + fmt.Sprintf("%v", jobID) + "/runs") + response, cleanup := client.Get("/v2/jobs/" + fmt.Sprintf("%v", jobID) + "/runs") defer cleanup() cltest.AssertServerResponse(t, response, http.StatusOK) @@ -65,11 +66,11 @@ func TestOCRJobRunsController_Index_HappyPath(t *testing.T) { require.Len(t, parsedResponse[1].PipelineTaskRuns, 4) } -func TestOCRJobRunsController_Index_Pagination(t *testing.T) { - client, jobID, runIDs, cleanup := setupOCRJobRunsControllerTests(t) +func TestPipelineRunsController_Index_Pagination(t *testing.T) { + client, jobID, runIDs, cleanup := setupPipelineRunsControllerTests(t) defer cleanup() - response, cleanup := client.Get("/v2/ocr/specs/" + fmt.Sprintf("%v", jobID) + "/runs?page=1&size=1") + response, cleanup := client.Get("/v2/jobs/" + fmt.Sprintf("%v", jobID) + "/runs?page=1&size=1") defer cleanup() cltest.AssertServerResponse(t, response, http.StatusOK) @@ -88,11 +89,11 @@ func TestOCRJobRunsController_Index_Pagination(t *testing.T) { require.Len(t, parsedResponse[0].PipelineTaskRuns, 4) } -func TestOCRJobRunsController_Show_HappyPath(t *testing.T) { - client, jobID, runIDs, cleanup := setupOCRJobRunsControllerTests(t) +func TestPipelineRunsController_Show_HappyPath(t *testing.T) { + client, jobID, runIDs, cleanup := setupPipelineRunsControllerTests(t) defer cleanup() - response, cleanup := client.Get("/v2/ocr/specs/" + fmt.Sprintf("%v", jobID) + "/runs/" + fmt.Sprintf("%v", runIDs[0])) + response, cleanup := client.Get("/v2/jobs/" + fmt.Sprintf("%v", jobID) + "/runs/" + fmt.Sprintf("%v", runIDs[0])) defer cleanup() cltest.AssertServerResponse(t, response, http.StatusOK) @@ -109,19 +110,19 @@ func TestOCRJobRunsController_Show_HappyPath(t *testing.T) { require.Len(t, parsedResponse.PipelineTaskRuns, 4) } -func TestOCRJobRunsController_ShowRun_InvalidID(t *testing.T) { +func TestPipelineRunsController_ShowRun_InvalidID(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t, cltest.LenientEthMock) defer cleanup() require.NoError(t, app.Start()) client := app.NewHTTPClient() - response, cleanup := client.Get("/v2/ocr/specs/1/runs/invalid-run-ID") + response, cleanup := client.Get("/v2/jobs/1/runs/invalid-run-ID") defer cleanup() cltest.AssertServerResponse(t, response, http.StatusUnprocessableEntity) } -func setupOCRJobRunsControllerTests(t *testing.T) (cltest.HTTPClientCleaner, int32, []int64, func()) { +func setupPipelineRunsControllerTests(t *testing.T) (cltest.HTTPClientCleaner, int32, []int64, func()) { t.Parallel() app, cleanup := cltest.NewApplication(t, cltest.LenientEthMock) require.NoError(t, app.Start()) @@ -152,7 +153,7 @@ func setupOCRJobRunsControllerTests(t *testing.T) (cltest.HTTPClientCleaner, int `, cltest.NewAddress().Hex(), cltest.DefaultP2PPeerID, cltest.DefaultOCRKeyBundleID, cltest.DefaultKey, mockHTTP.URL)), &ocrJobSpec) require.NoError(t, err) - jobID, err := app.AddJobV2(context.Background(), ocrJobSpec) + jobID, err := app.AddJobV2(context.Background(), ocrJobSpec, null.String{}) require.NoError(t, err) firstRunID, err := app.RunJobV2(context.Background(), jobID, nil) diff --git a/core/web/router.go b/core/web/router.go index e8b4bf8da97..c89e66141c8 100644 --- a/core/web/router.go +++ b/core/web/router.go @@ -268,19 +268,16 @@ func v2Routes(app chainlink.Application, r *gin.RouterGroup) { authv2.POST("/p2p_keys", p2pkc.Create) authv2.DELETE("/p2p_keys/:keyID", p2pkc.Delete) - ocr := authv2.Group("/ocr") - { - ocrjsc := OCRJobSpecsController{app} - ocr.GET("/specs", ocrjsc.Index) - ocr.GET("/specs/:ID", ocrjsc.Show) - ocr.POST("/specs", ocrjsc.Create) - ocr.DELETE("/specs/:ID", ocrjsc.Delete) - - ocrjrc := OCRJobRunsController{app} - ocr.GET("/specs/:ID/runs", paginatedRequest(ocrjrc.Index)) - ocr.GET("/specs/:ID/runs/:runID", ocrjrc.Show) - ocr.POST("/specs/:ID/runs", ocrjrc.Create) - } + jc := JobsController{app} + authv2.GET("/jobs", jc.Index) + authv2.GET("/jobs/:ID", jc.Show) + authv2.POST("/jobs", jc.Create) + authv2.DELETE("/jobs/:ID", jc.Delete) + + prc := PipelineRunsController{app} + authv2.GET("/jobs/:ID/runs", paginatedRequest(prc.Index)) + authv2.GET("/jobs/:ID/runs/:runID", prc.Show) + authv2.POST("/jobs/:ID/runs", prc.Create) } ping := PingController{app} diff --git a/core/web/testdata/eth-request-event-spec.toml b/core/web/testdata/eth-request-event-spec.toml new file mode 100644 index 00000000000..34f3ef350b6 --- /dev/null +++ b/core/web/testdata/eth-request-event-spec.toml @@ -0,0 +1,10 @@ +type = "ethrequestevent" +schemaVersion = 1 +name = "example eth request event spec" +contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" +observationSource = """ + ds1 [type=http method=GET url="example.com" allowunrestrictednetworkaccess="true"]; + ds1_parse [type=jsonparse path="USD"]; + ds1_multiply [type=multiply times=100]; + ds1 -> ds1_parse -> ds1_multiply; +""" diff --git a/core/web/testdata/oracle-spec.toml b/core/web/testdata/oracle-spec.toml index b881a480651..a04d23abc9c 100644 --- a/core/web/testdata/oracle-spec.toml +++ b/core/web/testdata/oracle-spec.toml @@ -1,5 +1,6 @@ type = "offchainreporting" schemaVersion = 1 +name = "web oracle spec" contractAddress = "0x613a38AC1659769640aaE063C651F48E0250454C" p2pPeerID = "12D3KooWCJUPKsYAnCRTQ7SUNULt4Z9qF8Uk1xadhCs7e9M711Lp" p2pBootstrapPeers = [ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7e890c1962d..213e3a37cd0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,8 +7,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.7] - 2020-12-14 + +### Added + +- OCR bootstrap node now sends telemetry to the endpoint specified in the OCR job spec under `MonitoringEndpoint`. +- Adds "Account addresses" table to the `/keys` page. + +### Changed + +- Old jobs now allow duplicate job names. Also, if the name field is empty we no longer generate a name. + +### Fixed + +- Brings `/runs` tab back to the operator UI. +- Signs out a user from operator UI on authentication error. + +### Changes + +- Removes broken `ACCOUNT_ADDRESS` field from `/config` page. + +#### BREAKING CHANGES + +- Commands for creating/managing legacy jobs and OCR jobs have changed, to reduce confusion and accomodate additional types of jobs using the new pipeline. + +#### V1 jobs + +`jobs archive` => `job_specs archive` +`jobs create` => `job_specs create` +`jobs list` => `job_specs list` +`jobs show` => `job_specs show` + +#### V2 jobs (currently only applies to OCR) + +`jobs createocr` => `jobs create` +`jobs deletev2` => `jobs delete` +`jobs run` => `jobs run` + ## [0.9.6] - 2020-11-23 +- OCR pipeline specs can now be configured on a per-task basis to allow unrestricted network access for http tasks. Example like so: + +``` +ds1 [type=http method=GET url="http://example.com" allowunrestrictednetworkaccess="true"]; +ds1_parse [type=jsonparse path="USD" lax="true"]; +ds1_multiply [type=multiply times=100]; +ds1 -> ds1_parse -> ds1_multiply; +``` + +- New prometheus metrics as follows: + +``` +Name: "pipeline_run_errors", +Help: "Number of errors for each pipeline spec", + +Name: "pipeline_run_total_time_to_completion", +Help: "How long each pipeline run took to finish (from the moment it was created)", + +Name: "pipeline_tasks_total_finished", +Help: "The total number of pipline tasks which have finished", + +Name: "pipeline_task_execution_time", +Help: "How long each pipeline task took to execute", + +Name: "pipeline_task_http_fetch_time", +Help: "Time taken to fully execute the HTTP request", + +Name: "pipeline_task_http_response_body_size", +Help: "Size (in bytes) of the HTTP response body", + +Name: "pipeline_runs_queued", +Help: "The total number of pipline runs that are awaiting execution", + +Name: "pipeline_task_runs_queued", +Help: "The total number of pipline task runs that are awaiting execution", +``` + ### Changed Numerous key-related UX improvements: @@ -23,6 +97,9 @@ Numerous key-related UX improvements: - Output from ETH/OCR/P2P/VRF key CLI commands now renders consistently. - Deleting an OCR/P2P/VRF key now requires confirmation from the user. To skip confirmation (e.g. in shell scripts), pass `--yes` or `-y`. - The `--ocrpassword` flag has been removed. OCR/P2P keys now share the same password at the ETH key (i.e., the password specified with the `--password` flag). + +Misc: + - Two new env variables are added `P2P_ANNOUNCE_IP` and `P2P_ANNOUNCE_PORT` which allow node operators to override locally detected values for the chainlink node's externally reachable IP/port. - `OCR_LISTEN_IP` and `OCR_LISTEN_PORT` have been renamed to `P2P_LISTEN_IP` and `P2P_LISTEN_PORT` for consistency. - Support for adding a job with the same name as one that was deleted. @@ -30,6 +107,7 @@ Numerous key-related UX improvements: ### Fixed - Fixed an issue where the HTTP adapter would send an empty body on retries. +- Changed the default `JOB_PIPELINE_REAPER_THRESHOLD` value from `7d` to `168h` (hours are the highest time unit allowed by `time.Duration`). ## [0.9.5] - 2020-11-12 diff --git a/evm-contracts/package.json b/evm-contracts/package.json index bd73053ef39..6de352f183f 100644 --- a/evm-contracts/package.json +++ b/evm-contracts/package.json @@ -12,9 +12,9 @@ "setup": "yarn compile:clean && tsc -b tsconfig.test.json tsconfig.ethers.json", "clean": "tsc -b --clean tsconfig.test.json tsconfig.ethers.json && rm -rf abi ethers truffle", "pretest": "tsc -b --clean tsconfig.ethers.json", - "lint:sol": "solhint -f table ./src/**/*.sol", + "lint:sol": "solhint -f table ./src/**/**/*.sol", "test:reset": "yarn setup && yarn test", - "test": "jest --testTimeout 80000 --forceExit", + "test": "jest --testTimeout 80000 --forceExit --detectOpenHandles", "test:ci": "./scripts/test_ci", "prepublishOnly": "yarn clean && yarn setup && yarn test:ci && yarn setup" }, diff --git a/evm-contracts/src/v0.4/Aggregator.sol b/evm-contracts/src/v0.4/Aggregator.sol index 3c078e5ce05..3668b0b6cdf 100644 --- a/evm-contracts/src/v0.4/Aggregator.sol +++ b/evm-contracts/src/v0.4/Aggregator.sol @@ -4,6 +4,7 @@ import "./ChainlinkClient.sol"; import "./interfaces/AggregatorInterface.sol"; import "./vendor/SignedSafeMath.sol"; import "./vendor/Ownable.sol"; +import "./vendor/SafeMathChainlink.sol"; /** * @title An example Chainlink contract with aggregation @@ -12,6 +13,7 @@ import "./vendor/Ownable.sol"; * as the contract receives answers. */ contract Aggregator is AggregatorInterface, ChainlinkClient, Ownable { + using SafeMathChainlink for uint256; using SignedSafeMath for int256; struct Answer { diff --git a/evm-contracts/src/v0.4/ChainlinkClient.sol b/evm-contracts/src/v0.4/ChainlinkClient.sol index fcdbc1bd614..b62664cf9bc 100644 --- a/evm-contracts/src/v0.4/ChainlinkClient.sol +++ b/evm-contracts/src/v0.4/ChainlinkClient.sol @@ -6,7 +6,6 @@ import "./interfaces/LinkTokenInterface.sol"; import "./interfaces/ChainlinkRequestInterface.sol"; import "./interfaces/PointerInterface.sol"; import { ENSResolver as ENSResolver_Chainlink } from "./vendor/ENSResolver.sol"; -import "./vendor/SafeMathChainlink.sol"; /** * @title The ChainlinkClient contract @@ -15,7 +14,6 @@ import "./vendor/SafeMathChainlink.sol"; */ contract ChainlinkClient { using Chainlink for Chainlink.Request; - using SafeMathChainlink for uint256; uint256 constant internal LINK = 10**18; uint256 constant private AMOUNT_OVERRIDE = 0; diff --git a/evm-contracts/src/v0.4/interfaces/ERC20.sol b/evm-contracts/src/v0.4/interfaces/ERC20.sol index b9e68675d42..fd978c33e81 100644 --- a/evm-contracts/src/v0.4/interfaces/ERC20.sol +++ b/evm-contracts/src/v0.4/interfaces/ERC20.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.11; -import { ERC20Basic as linkERC20Basic } from './ERC20Basic.sol'; +import { ERC20Basic as linkERC20Basic } from "./ERC20Basic.sol"; /** diff --git a/evm-contracts/src/v0.4/tests/ConcreteChainlinked.sol b/evm-contracts/src/v0.4/tests/ConcreteChainlinked.sol index 30dded657c1..bed787e9ac5 100644 --- a/evm-contracts/src/v0.4/tests/ConcreteChainlinked.sol +++ b/evm-contracts/src/v0.4/tests/ConcreteChainlinked.sol @@ -1,9 +1,10 @@ pragma solidity 0.4.24; import "../Chainlinked.sol"; - +import "../vendor/SafeMathChainlink.sol"; contract ConcreteChainlinked is Chainlinked { + using SafeMathChainlink for uint256; constructor(address _link, address _oracle) public { setLinkToken(_link); diff --git a/evm-contracts/src/v0.4/tests/MaliciousConsumer.sol b/evm-contracts/src/v0.4/tests/MaliciousConsumer.sol index 7bdcadfd6e9..b7b41d62c73 100644 --- a/evm-contracts/src/v0.4/tests/MaliciousConsumer.sol +++ b/evm-contracts/src/v0.4/tests/MaliciousConsumer.sol @@ -2,9 +2,12 @@ pragma solidity 0.4.24; import "../Chainlinked.sol"; +import "../vendor/SafeMathChainlink.sol"; contract MaliciousConsumer is Chainlinked { + using SafeMathChainlink for uint256; + uint256 constant private ORACLE_PAYMENT = 1 * LINK; uint256 private expiration; diff --git a/evm-contracts/src/v0.4/vendor/BasicToken.sol b/evm-contracts/src/v0.4/vendor/BasicToken.sol index 186321a1275..48f985002cf 100644 --- a/evm-contracts/src/v0.4/vendor/BasicToken.sol +++ b/evm-contracts/src/v0.4/vendor/BasicToken.sol @@ -1,8 +1,8 @@ pragma solidity ^0.4.24; -import { ERC20Basic as linkERC20Basic } from '../interfaces/ERC20Basic.sol'; -import { SafeMathChainlink as linkSafeMath } from './SafeMathChainlink.sol'; +import { ERC20Basic as linkERC20Basic } from "../interfaces/ERC20Basic.sol"; +import { SafeMathChainlink as linkSafeMath } from "./SafeMathChainlink.sol"; /** diff --git a/evm-contracts/src/v0.4/vendor/StandardToken.sol b/evm-contracts/src/v0.4/vendor/StandardToken.sol index b62ed840b4d..7f2b4134a52 100644 --- a/evm-contracts/src/v0.4/vendor/StandardToken.sol +++ b/evm-contracts/src/v0.4/vendor/StandardToken.sol @@ -1,8 +1,8 @@ pragma solidity ^0.4.11; -import { BasicToken as linkBasicToken } from './BasicToken.sol'; -import { ERC20 as linkERC20 } from '../interfaces/ERC20.sol'; +import { BasicToken as linkBasicToken } from "./BasicToken.sol"; +import { ERC20 as linkERC20 } from "../interfaces/ERC20.sol"; /** diff --git a/evm-contracts/src/v0.5/ChainlinkClient.sol b/evm-contracts/src/v0.5/ChainlinkClient.sol index 60547b4722e..2c4f7946a48 100644 --- a/evm-contracts/src/v0.5/ChainlinkClient.sol +++ b/evm-contracts/src/v0.5/ChainlinkClient.sol @@ -6,7 +6,6 @@ import "./interfaces/LinkTokenInterface.sol"; import "./interfaces/ChainlinkRequestInterface.sol"; import "./interfaces/PointerInterface.sol"; import { ENSResolver as ENSResolver_Chainlink } from "./vendor/ENSResolver.sol"; -import "./vendor/SafeMathChainlink.sol"; /** * @title The ChainlinkClient contract @@ -15,7 +14,6 @@ import "./vendor/SafeMathChainlink.sol"; */ contract ChainlinkClient { using Chainlink for Chainlink.Request; - using SafeMathChainlink for uint256; uint256 constant internal LINK = 10**18; uint256 constant private AMOUNT_OVERRIDE = 0; diff --git a/evm-contracts/src/v0.5/tests/MaliciousConsumer.sol b/evm-contracts/src/v0.5/tests/MaliciousConsumer.sol index a70494b0a0c..c14a4ac46c0 100644 --- a/evm-contracts/src/v0.5/tests/MaliciousConsumer.sol +++ b/evm-contracts/src/v0.5/tests/MaliciousConsumer.sol @@ -1,8 +1,11 @@ pragma solidity 0.5.0; import "../ChainlinkClient.sol"; +import "../vendor/SafeMathChainlink.sol"; contract MaliciousConsumer is ChainlinkClient { + using SafeMathChainlink for uint256; + uint256 constant private ORACLE_PAYMENT = 1 * LINK; uint256 private expiration; diff --git a/evm-contracts/src/v0.6/AccessControlledAggregator.sol b/evm-contracts/src/v0.6/AccessControlledAggregator.sol index 5a11ac99b44..aa45c2c63b9 100644 --- a/evm-contracts/src/v0.6/AccessControlledAggregator.sol +++ b/evm-contracts/src/v0.6/AccessControlledAggregator.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./FluxAggregator.sol"; diff --git a/evm-contracts/src/v0.6/AggregatorFacade.sol b/evm-contracts/src/v0.6/AggregatorFacade.sol index f41c0012917..deae4cfb411 100644 --- a/evm-contracts/src/v0.6/AggregatorFacade.sol +++ b/evm-contracts/src/v0.6/AggregatorFacade.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./interfaces/AggregatorV2V3Interface.sol"; diff --git a/evm-contracts/src/v0.6/AggregatorProxy.sol b/evm-contracts/src/v0.6/AggregatorProxy.sol index df4d6ff524b..548fd1140a6 100644 --- a/evm-contracts/src/v0.6/AggregatorProxy.sol +++ b/evm-contracts/src/v0.6/AggregatorProxy.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./Owned.sol"; diff --git a/evm-contracts/src/v0.6/Chainlink.sol b/evm-contracts/src/v0.6/Chainlink.sol index ca4dde9718e..2665e675634 100644 --- a/evm-contracts/src/v0.6/Chainlink.sol +++ b/evm-contracts/src/v0.6/Chainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import { CBORChainlink } from "./vendor/CBORChainlink.sol"; diff --git a/evm-contracts/src/v0.6/ChainlinkClient.sol b/evm-contracts/src/v0.6/ChainlinkClient.sol index 8fd5c7b4a67..079e05ef5f1 100644 --- a/evm-contracts/src/v0.6/ChainlinkClient.sol +++ b/evm-contracts/src/v0.6/ChainlinkClient.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./Chainlink.sol"; @@ -6,7 +7,6 @@ import "./interfaces/LinkTokenInterface.sol"; import "./interfaces/ChainlinkRequestInterface.sol"; import "./interfaces/PointerInterface.sol"; import { ENSResolver as ENSResolver_Chainlink } from "./vendor/ENSResolver.sol"; -import "./vendor/SafeMathChainlink.sol"; /** * @title The ChainlinkClient contract @@ -15,7 +15,6 @@ import "./vendor/SafeMathChainlink.sol"; */ contract ChainlinkClient { using Chainlink for Chainlink.Request; - using SafeMathChainlink for uint256; uint256 constant internal LINK = 10**18; uint256 constant private AMOUNT_OVERRIDE = 0; diff --git a/evm-contracts/src/v0.6/DeviationFlaggingValidator.sol b/evm-contracts/src/v0.6/DeviationFlaggingValidator.sol index 7a764924b58..df5e420b1c0 100644 --- a/evm-contracts/src/v0.6/DeviationFlaggingValidator.sol +++ b/evm-contracts/src/v0.6/DeviationFlaggingValidator.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./Owned.sol"; diff --git a/evm-contracts/src/v0.6/EACAggregatorProxy.sol b/evm-contracts/src/v0.6/EACAggregatorProxy.sol index d259394ed1a..11ef1999d78 100644 --- a/evm-contracts/src/v0.6/EACAggregatorProxy.sol +++ b/evm-contracts/src/v0.6/EACAggregatorProxy.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./AggregatorProxy.sol"; diff --git a/evm-contracts/src/v0.6/Flags.sol b/evm-contracts/src/v0.6/Flags.sol index f843d83cc11..ce8c9d5fda2 100644 --- a/evm-contracts/src/v0.6/Flags.sol +++ b/evm-contracts/src/v0.6/Flags.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; diff --git a/evm-contracts/src/v0.6/FluxAggregator.sol b/evm-contracts/src/v0.6/FluxAggregator.sol index 44b008ab414..649b05e77b2 100644 --- a/evm-contracts/src/v0.6/FluxAggregator.sol +++ b/evm-contracts/src/v0.6/FluxAggregator.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./Median.sol"; diff --git a/evm-contracts/src/v0.6/LinkTokenReceiver.sol b/evm-contracts/src/v0.6/LinkTokenReceiver.sol index 83a0d0fc4b7..6009f6a5a61 100644 --- a/evm-contracts/src/v0.6/LinkTokenReceiver.sol +++ b/evm-contracts/src/v0.6/LinkTokenReceiver.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; abstract contract LinkTokenReceiver { diff --git a/evm-contracts/src/v0.6/Median.sol b/evm-contracts/src/v0.6/Median.sol index 0cdca0b6d76..d75b5b625ae 100644 --- a/evm-contracts/src/v0.6/Median.sol +++ b/evm-contracts/src/v0.6/Median.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./vendor/SafeMathChainlink.sol"; diff --git a/evm-contracts/src/v0.6/Oracle.sol b/evm-contracts/src/v0.6/Oracle.sol index e0a92d4066d..967114f7f87 100644 --- a/evm-contracts/src/v0.6/Oracle.sol +++ b/evm-contracts/src/v0.6/Oracle.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./LinkTokenReceiver.sol"; diff --git a/evm-contracts/src/v0.6/Owned.sol b/evm-contracts/src/v0.6/Owned.sol index 6c36b799bab..10b0337288f 100644 --- a/evm-contracts/src/v0.6/Owned.sol +++ b/evm-contracts/src/v0.6/Owned.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** diff --git a/evm-contracts/src/v0.6/PreCoordinator.sol b/evm-contracts/src/v0.6/PreCoordinator.sol index 81200645ffe..ee13c72da3d 100644 --- a/evm-contracts/src/v0.6/PreCoordinator.sol +++ b/evm-contracts/src/v0.6/PreCoordinator.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./ChainlinkClient.sol"; diff --git a/evm-contracts/src/v0.6/SafeMath128.sol b/evm-contracts/src/v0.6/SafeMath128.sol index 389bcb66585..c79665bcf87 100644 --- a/evm-contracts/src/v0.6/SafeMath128.sol +++ b/evm-contracts/src/v0.6/SafeMath128.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** diff --git a/evm-contracts/src/v0.6/SafeMath32.sol b/evm-contracts/src/v0.6/SafeMath32.sol index f87605fb400..21944bb0d80 100644 --- a/evm-contracts/src/v0.6/SafeMath32.sol +++ b/evm-contracts/src/v0.6/SafeMath32.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** diff --git a/evm-contracts/src/v0.6/SafeMath64.sol b/evm-contracts/src/v0.6/SafeMath64.sol index fbcd5ec9457..2bb3b79121b 100644 --- a/evm-contracts/src/v0.6/SafeMath64.sol +++ b/evm-contracts/src/v0.6/SafeMath64.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** diff --git a/evm-contracts/src/v0.6/SignedSafeMath.sol b/evm-contracts/src/v0.6/SignedSafeMath.sol index 6d5b2e6f031..32941de704a 100644 --- a/evm-contracts/src/v0.6/SignedSafeMath.sol +++ b/evm-contracts/src/v0.6/SignedSafeMath.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; library SignedSafeMath { diff --git a/evm-contracts/src/v0.6/SimpleReadAccessController.sol b/evm-contracts/src/v0.6/SimpleReadAccessController.sol index d7b79eceb5c..c97b2e0621d 100644 --- a/evm-contracts/src/v0.6/SimpleReadAccessController.sol +++ b/evm-contracts/src/v0.6/SimpleReadAccessController.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./SimpleWriteAccessController.sol"; diff --git a/evm-contracts/src/v0.6/SimpleWriteAccessController.sol b/evm-contracts/src/v0.6/SimpleWriteAccessController.sol index d9cb472f985..6d49fd7f8c1 100644 --- a/evm-contracts/src/v0.6/SimpleWriteAccessController.sol +++ b/evm-contracts/src/v0.6/SimpleWriteAccessController.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./Owned.sol"; diff --git a/evm-contracts/src/v0.6/VRF.sol b/evm-contracts/src/v0.6/VRF.sol index 9af986c96df..9cbb145e3b8 100644 --- a/evm-contracts/src/v0.6/VRF.sol +++ b/evm-contracts/src/v0.6/VRF.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; /** **************************************************************************** diff --git a/evm-contracts/src/v0.6/VRFConsumerBase.sol b/evm-contracts/src/v0.6/VRFConsumerBase.sol index 6660ce79c05..e90adbb6ca0 100644 --- a/evm-contracts/src/v0.6/VRFConsumerBase.sol +++ b/evm-contracts/src/v0.6/VRFConsumerBase.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./vendor/SafeMathChainlink.sol"; @@ -29,8 +30,8 @@ import "./VRFRequestIDBase.sol"; * ***************************************************************************** * @dev USAGE * - * @dev Calling contracts must inherit from VRFConsumerInterface, and can - * @dev initialize VRFConsumerInterface's attributes in their constructor as + * @dev Calling contracts must inherit from VRFConsumerBase, and can + * @dev initialize VRFConsumerBase's attributes in their constructor as * @dev shown: * * @dev contract VRFConsumer { @@ -56,7 +57,8 @@ import "./VRFRequestIDBase.sol"; * @dev makeRequestId(keyHash, seed). If your contract could have concurrent * @dev requests open, you can use the requestId to track which seed is * @dev associated with which randomness. See VRFRequestIDBase.sol for more - * @dev details. + * @dev details. (See "SECURITY CONSIDERATIONS" for principles to keep in mind, + * @dev if your contract could have multiple requests in flight simultaneously.) * * @dev Colliding `requestId`s are cryptographically impossible as long as seeds * @dev differ. (Which is critical to making unpredictable randomness! See the @@ -65,19 +67,38 @@ import "./VRFRequestIDBase.sol"; * ***************************************************************************** * @dev SECURITY CONSIDERATIONS * + * @dev A method with the ability to call your fulfillRandomness method directly + * @dev could spoof a VRF response with any random value, so it's critical that + * @dev it cannot be directly called by anything other than this base contract + * @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method). + * + * @dev For your users to trust that your contract's random behavior is free + * @dev from malicious interference, it's best if you can write it so that all + * @dev behaviors implied by a VRF response are executed *during* your + * @dev fulfillRandomness method. If your contract must store the response (or + * @dev anything derived from it) and use it later, you must ensure that any + * @dev user-significant behavior which depends on that stored value cannot be + * @dev manipulated by a subsequent VRF request. + * + * @dev Similarly, both miners and the VRF oracle itself have some influence + * @dev over the order in which VRF responses appear on the blockchain, so if + * @dev your contract could have multiple VRF requests in flight simultaneously, + * @dev you must ensure that the order in which the VRF responses arrive cannot + * @dev be used to manipulate your contract's user-significant behavior. + * * @dev Since the ultimate input to the VRF is mixed with the block hash of the * @dev block in which the request is made, user-provided seeds have no impact * @dev on its economic security properties. They are only included for API * @dev compatability with previous versions of this contract. * - * @dev Since the block hash of the block which contains the requestRandomness() + * @dev Since the block hash of the block which contains the requestRandomness * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful * @dev miner could, in principle, fork the blockchain to evict the block * @dev containing the request, forcing the request to be included in a * @dev different block with a different hash, and therefore a different input * @dev to the VRF. However, such an attack would incur a substantial economic * @dev cost. This cost scales with the number of blocks the VRF oracle waits - * @dev until it calls fulfillRandomness(). + * @dev until it calls responds to a request. */ abstract contract VRFConsumerBase is VRFRequestIDBase { @@ -85,11 +106,13 @@ abstract contract VRFConsumerBase is VRFRequestIDBase { /** * @notice fulfillRandomness handles the VRF response. Your contract must - * @notice implement it. + * @notice implement it. See "SECURITY CONSIDERATIONS" above for important + * @notice principles to keep in mind when implementing your fulfillRandomness + * @notice method. * - * @dev The VRFCoordinator expects a calling contract to have a method with - * @dev this signature, and will trigger it once it has verified the proof - * @dev associated with the randomness (It is triggered via a call to + * @dev VRFConsumerBase expects its subcontracts to have a method with this + * @dev signature, and will call it once it has verified the proof + * @dev associated with the randomness. (It is triggered via a call to * @dev rawFulfillRandomness, below.) * * @param requestId The Id initially returned by requestRandomness @@ -101,8 +124,6 @@ abstract contract VRFConsumerBase is VRFRequestIDBase { /** * @notice requestRandomness initiates a request for VRF output given _seed * - * @dev See "SECURITY CONSIDERATIONS" above for more information on _seed. - * * @dev The fulfillRandomness method receives the output, once it's provided * @dev by the Oracle, and verified by the vrfCoordinator. * @@ -110,13 +131,19 @@ abstract contract VRFConsumerBase is VRFRequestIDBase { * @dev the _fee must exceed the fee specified during registration of the * @dev _keyHash. * + * @dev The _seed parameter is vestigial, and is kept only for API + * @dev compatibility with older versions. It can't *hurt* to mix in some of + * @dev your own randomness, here, but it's not necessary because the VRF + * @dev oracle will mix the hash of the block containing your request into the + * @dev VRF seed it ultimately uses. + * * @param _keyHash ID of public key against which randomness is generated * @param _fee The amount of LINK to send with the request - * @param _seed seed mixed into the input of the VRF + * @param _seed seed mixed into the input of the VRF. * * @return requestId unique ID for this request * - * @dev The returned requestId can be used to distinguish responses to * + * @dev The returned requestId can be used to distinguish responses to * @dev concurrent requests. It is passed as the first argument to * @dev fulfillRandomness. */ @@ -131,8 +158,9 @@ abstract contract VRFConsumerBase is VRFRequestIDBase { // nonces[_keyHash] must stay in sync with // VRFCoordinator.nonces[_keyHash][this], which was incremented by the above // successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest). - // This provides protection against the user repeating their input - // seed, which would result in a predictable/duplicate output. + // This provides protection against the user repeating their input seed, + // which would result in a predictable/duplicate output, if multiple such + // requests appeared in the same block. nonces[_keyHash] = nonces[_keyHash].add(1); return makeRequestId(_keyHash, vRFSeed); } @@ -143,7 +171,14 @@ abstract contract VRFConsumerBase is VRFRequestIDBase { // Nonces for each VRF key from which randomness has been requested. // // Must stay in sync with VRFCoordinator[_keyHash][this] - mapping(bytes32 /* keyHash */ => uint256 /* nonce */) public nonces; + mapping(bytes32 /* keyHash */ => uint256 /* nonce */) private nonces; + + /** + * @param _vrfCoordinator address of VRFCoordinator contract + * @param _link address of LINK token contract + * + * @dev https://docs.chain.link/docs/link-token-contracts + */ constructor(address _vrfCoordinator, address _link) public { vrfCoordinator = _vrfCoordinator; LINK = LinkTokenInterface(_link); diff --git a/evm-contracts/src/v0.6/VRFCoordinator.sol b/evm-contracts/src/v0.6/VRFCoordinator.sol index 127bc2779f5..0757ff7a755 100644 --- a/evm-contracts/src/v0.6/VRFCoordinator.sol +++ b/evm-contracts/src/v0.6/VRFCoordinator.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "./vendor/SafeMathChainlink.sol"; diff --git a/evm-contracts/src/v0.6/VRFRequestIDBase.sol b/evm-contracts/src/v0.6/VRFRequestIDBase.sol index a079f95e8e0..2668ead3200 100644 --- a/evm-contracts/src/v0.6/VRFRequestIDBase.sol +++ b/evm-contracts/src/v0.6/VRFRequestIDBase.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract VRFRequestIDBase { diff --git a/evm-contracts/src/v0.6/dev/BlockhashStore.sol b/evm-contracts/src/v0.6/dev/BlockhashStore.sol index ee780a9543e..1c7419fc3ea 100644 --- a/evm-contracts/src/v0.6/dev/BlockhashStore.sol +++ b/evm-contracts/src/v0.6/dev/BlockhashStore.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; /** diff --git a/evm-contracts/src/v0.6/examples/VRFD20.sol b/evm-contracts/src/v0.6/examples/VRFD20.sol new file mode 100644 index 00000000000..ae331db0ff7 --- /dev/null +++ b/evm-contracts/src/v0.6/examples/VRFD20.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.6; + +import "../VRFConsumerBase.sol"; +import "../Owned.sol"; + +/** + * @notice A Chainlink VRF consumer which uses randomness to mimic the rolling + * of a 20 sided die + * @dev This is only an example implementation and not necessarily suitable for mainnet. + */ +contract VRFD20 is VRFConsumerBase, Owned { + using SafeMathChainlink for uint256; + + uint256 private constant ROLL_IN_PROGRESS = 42; + + bytes32 private s_keyHash; + uint256 private s_fee; + mapping(bytes32 => address) private s_rollers; + mapping(address => uint256) private s_results; + + event DiceRolled(bytes32 indexed requestId, address indexed roller); + event DiceLanded(bytes32 indexed requestId, uint256 indexed result); + + /** + * @notice Constructor inherits VRFConsumerBase + * + * @dev NETWORK: KOVAN + * @dev Chainlink VRF Coordinator address: 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9 + * @dev LINK token address: 0xa36085F69e2889c224210F603D836748e7dC0088 + * @dev Key Hash: 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4 + * @dev Fee: 0.1 LINK (100000000000000000) + * + * @param vrfCoordinator address of the VRF Coordinator + * @param link address of the LINK token + * @param keyHash bytes32 representing the hash of the VRF job + * @param fee uint256 fee to pay the VRF oracle + */ + constructor(address vrfCoordinator, address link, bytes32 keyHash, uint256 fee) + public + VRFConsumerBase(vrfCoordinator, link) + { + s_keyHash = keyHash; + s_fee = fee; + + } + + /** + * @notice Requests randomness from a user-provided seed + * @dev Warning: if the VRF response is delayed, avoid calling requestRandomness repeatedly + * as that would give miners/VRF operators latitude about which VRF response arrives first. + * @dev You must review your implementation details with extreme care. + * + * @param userProvidedSeed uint256 unpredictable seed + * @param roller address of the roller + */ + function rollDice(uint256 userProvidedSeed, address roller) public onlyOwner returns (bytes32 requestId) { + require(LINK.balanceOf(address(this)) >= s_fee, "Not enough LINK to pay fee"); + require(s_results[roller] == 0, "Already rolled"); + requestId = requestRandomness(s_keyHash, s_fee, userProvidedSeed); + s_rollers[requestId] = roller; + s_results[roller] = ROLL_IN_PROGRESS; + emit DiceRolled(requestId, roller); + } + + /** + * @notice Callback function used by VRF Coordinator to return the random number + * to this contract. + * @dev Some action on the contract state should be taken here, like storing the result. + * @dev WARNING: take care to avoid having multiple VRF requests in flight if their order of arrival would result + * in contract states with different outcomes. Otherwise miners or the VRF operator would could take advantage + * by controlling the order. + * @dev The VRF Coordinator will only send this function verified responses, and the parent VRFConsumerBase + * contract ensures that this method only receives randomness from the designated VRFCoordinator. + * + * @param requestId bytes32 + * @param randomness The random result returned by the oracle + */ + function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override { + uint256 d20Value = randomness.mod(20).add(1); + s_results[s_rollers[requestId]] = d20Value; + emit DiceLanded(requestId, d20Value); + } + + /** + * @notice Get the house assigned to the player once the address has rolled + * @param player address + * @return house as a string + */ + function house(address player) public view returns (string memory) { + require(s_results[player] != 0, "Dice not rolled"); + require(s_results[player] != ROLL_IN_PROGRESS, "Roll in progress"); + return getHouseName(s_results[player]); + } + + /** + * @notice Withdraw LINK from this contract. + * @dev this is an example only, and in a real contract withdrawals should + * happen according to the established withdrawal pattern: + * https://docs.soliditylang.org/en/v0.4.24/common-patterns.html#withdrawal-from-contracts + * @param to the address to withdraw LINK to + * @param value the amount of LINK to withdraw + */ + function withdrawLINK(address to, uint256 value) public onlyOwner { + require(LINK.transfer(to, value), "Not enough LINK"); + } + + /** + * @notice Set the key hash for the oracle + * + * @param keyHash bytes32 + */ + function setKeyHash(bytes32 keyHash) public onlyOwner { + s_keyHash = keyHash; + } + + /** + * @notice Get the current key hash + * + * @return bytes32 + */ + function keyHash() public view returns (bytes32) { + return s_keyHash; + } + + /** + * @notice Set the oracle fee for requesting randomness + * + * @param fee uint256 + */ + function setFee(uint256 fee) public onlyOwner { + s_fee = fee; + } + + /** + * @notice Get the current fee + * + * @return uint256 + */ + function fee() public view returns (uint256) { + return s_fee; + } + + /** + * @notice Get the house namne from the id + * @param id uint256 + * @return house name string + */ + function getHouseName(uint256 id) private view returns (string memory) { + string[20] memory houseNames = [ + "Targaryen", + "Lannister", + "Stark", + "Tyrell", + "Baratheon", + "Martell", + "Tully", + "Bolton", + "Greyjoy", + "Arryn", + "Frey", + "Mormont", + "Tarley", + "Dayne", + "Umber", + "Valeryon", + "Manderly", + "Clegane", + "Glover", + "Karstark" + ]; + return houseNames[id.sub(1)]; + } +} diff --git a/evm-contracts/src/v0.6/interfaces/AccessControllerInterface.sol b/evm-contracts/src/v0.6/interfaces/AccessControllerInterface.sol index 98ddf9282ff..a925e1d8803 100644 --- a/evm-contracts/src/v0.6/interfaces/AccessControllerInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/AccessControllerInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface AccessControllerInterface { diff --git a/evm-contracts/src/v0.6/interfaces/AggregatorInterface.sol b/evm-contracts/src/v0.6/interfaces/AggregatorInterface.sol index 3483767f449..ad0b2635268 100644 --- a/evm-contracts/src/v0.6/interfaces/AggregatorInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/AggregatorInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.6.0; interface AggregatorInterface { diff --git a/evm-contracts/src/v0.6/interfaces/AggregatorV2V3Interface.sol b/evm-contracts/src/v0.6/interfaces/AggregatorV2V3Interface.sol index 8d5075f69a2..20a0c1ffbec 100644 --- a/evm-contracts/src/v0.6/interfaces/AggregatorV2V3Interface.sol +++ b/evm-contracts/src/v0.6/interfaces/AggregatorV2V3Interface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.6.0; import "./AggregatorInterface.sol"; diff --git a/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol b/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol index 260e15f758c..7389daee213 100644 --- a/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol +++ b/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.6.0; interface AggregatorV3Interface { diff --git a/evm-contracts/src/v0.6/interfaces/AggregatorValidatorInterface.sol b/evm-contracts/src/v0.6/interfaces/AggregatorValidatorInterface.sol index 03022f4e538..50c3226f6d8 100644 --- a/evm-contracts/src/v0.6/interfaces/AggregatorValidatorInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/AggregatorValidatorInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface AggregatorValidatorInterface { diff --git a/evm-contracts/src/v0.6/interfaces/BlockHashStoreInterface.sol b/evm-contracts/src/v0.6/interfaces/BlockHashStoreInterface.sol index 9a18f865450..18927e64eed 100644 --- a/evm-contracts/src/v0.6/interfaces/BlockHashStoreInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/BlockHashStoreInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; interface BlockHashStoreInterface { diff --git a/evm-contracts/src/v0.6/interfaces/ChainlinkRequestInterface.sol b/evm-contracts/src/v0.6/interfaces/ChainlinkRequestInterface.sol index 47bd98642cc..bcbd2511901 100644 --- a/evm-contracts/src/v0.6/interfaces/ChainlinkRequestInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/ChainlinkRequestInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface ChainlinkRequestInterface { diff --git a/evm-contracts/src/v0.6/interfaces/ENSInterface.sol b/evm-contracts/src/v0.6/interfaces/ENSInterface.sol index a32a8885715..158242cd0d5 100644 --- a/evm-contracts/src/v0.6/interfaces/ENSInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/ENSInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface ENSInterface { diff --git a/evm-contracts/src/v0.6/interfaces/FlagsInterface.sol b/evm-contracts/src/v0.6/interfaces/FlagsInterface.sol index 072599c44c8..ad39cae3a62 100644 --- a/evm-contracts/src/v0.6/interfaces/FlagsInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/FlagsInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface FlagsInterface { diff --git a/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol b/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol index 4b0d2908c5c..eeb944411f7 100644 --- a/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface LinkTokenInterface { diff --git a/evm-contracts/src/v0.6/interfaces/OracleInterface.sol b/evm-contracts/src/v0.6/interfaces/OracleInterface.sol index 1ed5341de09..96b49f0341d 100644 --- a/evm-contracts/src/v0.6/interfaces/OracleInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/OracleInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface OracleInterface { diff --git a/evm-contracts/src/v0.6/interfaces/PointerInterface.sol b/evm-contracts/src/v0.6/interfaces/PointerInterface.sol index 5c9c9046406..e1cac19dfea 100644 --- a/evm-contracts/src/v0.6/interfaces/PointerInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/PointerInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface PointerInterface { diff --git a/evm-contracts/src/v0.6/interfaces/WithdrawalInterface.sol b/evm-contracts/src/v0.6/interfaces/WithdrawalInterface.sol index 15e9353f396..e83d3273fe8 100644 --- a/evm-contracts/src/v0.6/interfaces/WithdrawalInterface.sol +++ b/evm-contracts/src/v0.6/interfaces/WithdrawalInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; interface WithdrawalInterface { diff --git a/evm-contracts/src/v0.6/tests/AccessControlTestHelper.sol b/evm-contracts/src/v0.6/tests/AccessControlTestHelper.sol index 840188dcac7..e6ff4e5a39f 100644 --- a/evm-contracts/src/v0.6/tests/AccessControlTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/AccessControlTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../SimpleReadAccessController.sol"; diff --git a/evm-contracts/src/v0.6/tests/AggregatorValidatorMock.sol b/evm-contracts/src/v0.6/tests/AggregatorValidatorMock.sol index 33a12c61f79..7af4717c688 100644 --- a/evm-contracts/src/v0.6/tests/AggregatorValidatorMock.sol +++ b/evm-contracts/src/v0.6/tests/AggregatorValidatorMock.sol @@ -1,6 +1,7 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; -import '../interfaces/AggregatorValidatorInterface.sol'; +import "../interfaces/AggregatorValidatorInterface.sol"; contract AggregatorValidatorMock is AggregatorValidatorInterface { uint256 public previousRoundId; diff --git a/evm-contracts/src/v0.6/tests/BasicConsumer.sol b/evm-contracts/src/v0.6/tests/BasicConsumer.sol index 3cdcb4213d4..f657fb0bdc5 100644 --- a/evm-contracts/src/v0.6/tests/BasicConsumer.sol +++ b/evm-contracts/src/v0.6/tests/BasicConsumer.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./Consumer.sol"; diff --git a/evm-contracts/src/v0.6/tests/BlockhashStoreTestHelper.sol b/evm-contracts/src/v0.6/tests/BlockhashStoreTestHelper.sol index c7e25a05682..4278c3af0b5 100644 --- a/evm-contracts/src/v0.6/tests/BlockhashStoreTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/BlockhashStoreTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "../dev/BlockhashStore.sol"; diff --git a/evm-contracts/src/v0.6/tests/CheckedMathTestHelper.sol b/evm-contracts/src/v0.6/tests/CheckedMathTestHelper.sol index 0226466ed3b..1306c53d8e4 100644 --- a/evm-contracts/src/v0.6/tests/CheckedMathTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/CheckedMathTestHelper.sol @@ -1,6 +1,7 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; -import '../CheckedMath.sol'; +import "../CheckedMath.sol"; contract CheckedMathTestHelper { using CheckedMath for int256; diff --git a/evm-contracts/src/v0.6/tests/ConcreteSignedSafeMath.sol b/evm-contracts/src/v0.6/tests/ConcreteSignedSafeMath.sol index f2072874be6..14be54685b5 100644 --- a/evm-contracts/src/v0.6/tests/ConcreteSignedSafeMath.sol +++ b/evm-contracts/src/v0.6/tests/ConcreteSignedSafeMath.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../SignedSafeMath.sol"; diff --git a/evm-contracts/src/v0.6/tests/Consumer.sol b/evm-contracts/src/v0.6/tests/Consumer.sol index 56780fefa47..1e49bcef18f 100644 --- a/evm-contracts/src/v0.6/tests/Consumer.sol +++ b/evm-contracts/src/v0.6/tests/Consumer.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../ChainlinkClient.sol"; diff --git a/evm-contracts/src/v0.6/tests/FlagsTestHelper.sol b/evm-contracts/src/v0.6/tests/FlagsTestHelper.sol index 9d5e1237aef..0ed37b6e965 100644 --- a/evm-contracts/src/v0.6/tests/FlagsTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/FlagsTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../Flags.sol"; diff --git a/evm-contracts/src/v0.6/tests/FluxAggregatorTestHelper.sol b/evm-contracts/src/v0.6/tests/FluxAggregatorTestHelper.sol index a5b9d426cd3..24af37f4992 100644 --- a/evm-contracts/src/v0.6/tests/FluxAggregatorTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/FluxAggregatorTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../FluxAggregator.sol"; diff --git a/evm-contracts/src/v0.6/tests/GasGuzzler.sol b/evm-contracts/src/v0.6/tests/GasGuzzler.sol index b4c340b098e..5b30f1bef52 100644 --- a/evm-contracts/src/v0.6/tests/GasGuzzler.sol +++ b/evm-contracts/src/v0.6/tests/GasGuzzler.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; contract GasGuzzler { diff --git a/evm-contracts/src/v0.6/tests/GasGuzzlingConsumer.sol b/evm-contracts/src/v0.6/tests/GasGuzzlingConsumer.sol index e622a63166e..8122f45dbab 100644 --- a/evm-contracts/src/v0.6/tests/GasGuzzlingConsumer.sol +++ b/evm-contracts/src/v0.6/tests/GasGuzzlingConsumer.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./Consumer.sol"; diff --git a/evm-contracts/src/v0.6/tests/MaliciousMultiWordConsumer.sol b/evm-contracts/src/v0.6/tests/MaliciousMultiWordConsumer.sol index 26b53cc7962..cd36836df12 100644 --- a/evm-contracts/src/v0.6/tests/MaliciousMultiWordConsumer.sol +++ b/evm-contracts/src/v0.6/tests/MaliciousMultiWordConsumer.sol @@ -1,8 +1,12 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../ChainlinkClient.sol"; +import "../vendor/SafeMathChainlink.sol"; contract MaliciousMultiWordConsumer is ChainlinkClient { + using SafeMathChainlink for uint256; + uint256 constant private ORACLE_PAYMENT = 1 * LINK; uint256 private expiration; diff --git a/evm-contracts/src/v0.6/tests/MedianTestHelper.sol b/evm-contracts/src/v0.6/tests/MedianTestHelper.sol index 044ae52136b..5386790f02f 100644 --- a/evm-contracts/src/v0.6/tests/MedianTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/MedianTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../Median.sol"; diff --git a/evm-contracts/src/v0.6/tests/MockV2Aggregator.sol b/evm-contracts/src/v0.6/tests/MockV2Aggregator.sol index 7722a101e49..aab46240d9e 100644 --- a/evm-contracts/src/v0.6/tests/MockV2Aggregator.sol +++ b/evm-contracts/src/v0.6/tests/MockV2Aggregator.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../interfaces/AggregatorInterface.sol"; diff --git a/evm-contracts/src/v0.6/tests/MockV3Aggregator.sol b/evm-contracts/src/v0.6/tests/MockV3Aggregator.sol index fc7c6f995c0..da92b8944d0 100644 --- a/evm-contracts/src/v0.6/tests/MockV3Aggregator.sol +++ b/evm-contracts/src/v0.6/tests/MockV3Aggregator.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../interfaces/AggregatorV2V3Interface.sol"; diff --git a/evm-contracts/src/v0.6/tests/MultiWordConsumer.sol b/evm-contracts/src/v0.6/tests/MultiWordConsumer.sol index 0ee34acd98f..a2ff5d4080e 100644 --- a/evm-contracts/src/v0.6/tests/MultiWordConsumer.sol +++ b/evm-contracts/src/v0.6/tests/MultiWordConsumer.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../ChainlinkClient.sol"; diff --git a/evm-contracts/src/v0.6/tests/OwnedTestHelper.sol b/evm-contracts/src/v0.6/tests/OwnedTestHelper.sol index 1450853efdb..c71c7bfb7f9 100644 --- a/evm-contracts/src/v0.6/tests/OwnedTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/OwnedTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "../Owned.sol"; diff --git a/evm-contracts/src/v0.6/tests/Reverter.sol b/evm-contracts/src/v0.6/tests/Reverter.sol index 3bf451e7939..a262fbe5592 100644 --- a/evm-contracts/src/v0.6/tests/Reverter.sol +++ b/evm-contracts/src/v0.6/tests/Reverter.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; contract Reverter { diff --git a/evm-contracts/src/v0.6/tests/VRFConsumer.sol b/evm-contracts/src/v0.6/tests/VRFConsumer.sol index 2a6e27d5e90..b96a91b9b9b 100644 --- a/evm-contracts/src/v0.6/tests/VRFConsumer.sol +++ b/evm-contracts/src/v0.6/tests/VRFConsumer.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "../interfaces/LinkTokenInterface.sol"; diff --git a/evm-contracts/src/v0.6/tests/VRFCoordinatorMock.sol b/evm-contracts/src/v0.6/tests/VRFCoordinatorMock.sol new file mode 100644 index 00000000000..dd8b8063bf1 --- /dev/null +++ b/evm-contracts/src/v0.6/tests/VRFCoordinatorMock.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.6; + +import "../interfaces/LinkTokenInterface.sol"; +import "../VRFConsumerBase.sol"; + +contract VRFCoordinatorMock { + + LinkTokenInterface public LINK; + + event RandomnessRequest(address indexed sender, bytes32 indexed keyHash, uint256 indexed seed); + + constructor(address linkAddress) public { + LINK = LinkTokenInterface(linkAddress); + } + + function onTokenTransfer(address sender, uint256 fee, bytes memory _data) + public + onlyLINK + { + (bytes32 keyHash, uint256 seed) = abi.decode(_data, (bytes32, uint256)); + emit RandomnessRequest(sender, keyHash, seed); + } + + function callBackWithRandomness( + bytes32 requestId, + uint256 randomness, + address consumerContract + ) public { + VRFConsumerBase v; + bytes memory resp = abi.encodeWithSelector(v.rawFulfillRandomness.selector, requestId, randomness); + uint256 b = 206000; + require(gasleft() >= b, "not enough gas for consumer"); + (bool success,) = consumerContract.call(resp); + } + + modifier onlyLINK() { + require(msg.sender == address(LINK), "Must use LINK token"); + _; + } +} \ No newline at end of file diff --git a/evm-contracts/src/v0.6/tests/VRFRequestIDBaseTestHelper.sol b/evm-contracts/src/v0.6/tests/VRFRequestIDBaseTestHelper.sol index f52d0ed8258..f1b81f210ed 100644 --- a/evm-contracts/src/v0.6/tests/VRFRequestIDBaseTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/VRFRequestIDBaseTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "../VRFRequestIDBase.sol"; diff --git a/evm-contracts/src/v0.6/tests/VRFTestHelper.sol b/evm-contracts/src/v0.6/tests/VRFTestHelper.sol index a4e2d1c9a1c..d79e71dce1b 100644 --- a/evm-contracts/src/v0.6/tests/VRFTestHelper.sol +++ b/evm-contracts/src/v0.6/tests/VRFTestHelper.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.6.6; import "../VRF.sol"; diff --git a/evm-contracts/src/v0.6/tests/VRFTestnetD20.sol b/evm-contracts/src/v0.6/tests/VRFTestnetD20.sol deleted file mode 100644 index 35c1ad47e86..00000000000 --- a/evm-contracts/src/v0.6/tests/VRFTestnetD20.sol +++ /dev/null @@ -1,53 +0,0 @@ -pragma solidity 0.6.6; - -import "../VRFConsumerBase.sol"; - -contract VRFTestnetD20 is VRFConsumerBase { - using SafeMathChainlink for uint; - uint256[] public d20Results; - bytes32 internal keyHash; - uint256 internal fee; - - /** - * @notice Constructor inherits VRFConsumerBase - * @dev Ropsten deployment params: - * @dev _vrfCoordinator: 0xf720CF1B963e0e7bE9F58fd471EFa67e7bF00cfb - * @dev _link: 0x20fE562d797A42Dcb3399062AE9546cd06f63280 - */ - constructor(address _vrfCoordinator, address _link, bytes32 _keyHash) - VRFConsumerBase(_vrfCoordinator, _link) public - { - keyHash = _keyHash; - fee = 10 ** 18; - } - - /** - * @notice Requests randomness from a user-provided seed - * @dev This is only an example implementation and not necessarily suitable for mainnet. - * @dev You must review your implementation details with extreme care. - */ - function rollDice(uint256 userProvidedSeed) public returns (bytes32 requestId) { - require(LINK.balanceOf(address(this)) >= fee, - "Not enough LINK - fill contract with faucet"); - bytes32 _requestId = requestRandomness(keyHash, fee, userProvidedSeed); - return _requestId; - } - - /** - * @notice Callback function used by VRF Coordinator - * @dev Important! Add a modifier to only allow this function to be called by the VRFCoordinator - * @dev This is where you do something with randomness! - * @dev The VRF Coordinator will only send this function verified responses. - */ - function fulfillRandomness(bytes32 /* requestId */, uint256 randomness) internal override { - uint256 d20Result = randomness.mod(20).add(1); // Simplified example - d20Results.push(d20Result); - } - - /** - * @notice Convenience function to show the latest roll - */ - function latestRoll() public view returns (uint256 d20result) { - return d20Results[d20Results.length - 1]; - } -} diff --git a/evm-contracts/src/v0.6/tests/artifacts/MaliciousMultiWordConsumer.json b/evm-contracts/src/v0.6/tests/artifacts/MaliciousMultiWordConsumer.json new file mode 100644 index 00000000000..dd3512e185f --- /dev/null +++ b/evm-contracts/src/v0.6/tests/artifacts/MaliciousMultiWordConsumer.json @@ -0,0 +1,268 @@ +{ + "deploy": { + "VM:-": { + "linkReferences": {}, + "autoDeployLib": true + }, + "main:1": { + "linkReferences": {}, + "autoDeployLib": true + }, + "ropsten:3": { + "linkReferences": {}, + "autoDeployLib": true + }, + "rinkeby:4": { + "linkReferences": {}, + "autoDeployLib": true + }, + "kovan:42": { + "linkReferences": {}, + "autoDeployLib": true + }, + "görli:5": { + "linkReferences": {}, + "autoDeployLib": true + }, + "Custom": { + "linkReferences": {}, + "autoDeployLib": true + } + }, + "data": { + "bytecode": { + "linkReferences": {}, + "object": "608060405260016004556040516115833803806115838339818101604052604081101561002b57600080fd5b8101908080519060200190929190805190602001909291905050506100558261006b60201b60201c565b610064816100af60201b60201c565b50506100f3565b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b80600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b611481806101026000396000f3fe60806040526004361061007f5760003560e01c8063c3a347c31161004e578063c3a347c314610318578063c737ecab146103ea578063ce369c0f146104bc578063f7b363df1461058e57610086565b80633f0585bf1461008b5780633fa7848c1461015d5780636aa862ef1461022f578063a7f437791461030157610086565b3661008657005b600080fd5b34801561009757600080fd5b5061015b600480360360408110156100ae57600080fd5b8101908080359060200190929190803590602001906401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610660565b005b34801561016957600080fd5b5061022d6004803603604081101561018057600080fd5b8101908080359060200190929190803590602001906401000000008111156101a757600080fd5b8201836020820111156101b957600080fd5b803590602001918460018302840111640100000000831117156101db57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061085e565b005b34801561023b57600080fd5b506102ff6004803603604081101561025257600080fd5b81019080803590602001909291908035906020019064010000000081111561027957600080fd5b82018360208201111561028b57600080fd5b803590602001918460018302840111640100000000831117156102ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061086d565b005b34801561030d57600080fd5b50610316610871565b005b34801561032457600080fd5b506103e86004803603604081101561033b57600080fd5b81019080803590602001909291908035906020019064010000000081111561036257600080fd5b82018360208201111561037457600080fd5b8035906020019184600183028401116401000000008311171561039657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061088b565b005b3480156103f657600080fd5b506104ba6004803603604081101561040d57600080fd5b81019080803590602001909291908035906020019064010000000081111561043457600080fd5b82018360208201111561044657600080fd5b8035906020019184600183028401116401000000008311171561046857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610a59565b005b3480156104c857600080fd5b5061058c600480360360408110156104df57600080fd5b81019080803590602001909291908035906020019064010000000081111561050657600080fd5b82018360208201111561051857600080fd5b8035906020019184600183028401116401000000008311171561053a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610aab565b005b34801561059a57600080fd5b5061065e600480360360408110156105b157600080fd5b8101908080359060200190929190803590602001906401000000008111156105d857600080fd5b8201836020820111156105ea57600080fd5b8035906020019184600183028401116401000000008311171561060c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610acf565b005b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610718576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806114246028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a260003073ffffffffffffffffffffffffffffffffffffffff16606460405180600001905060006040518083038185875af1925050503d80600081146107dc576040519150601f19603f3d011682016040523d82523d6000602084013e6107e1565b606091505b5050905080610858576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600b8152602001807f43616c6c206661696c656400000000000000000000000000000000000000000081525060200191505060405180910390fd5b50505050565b600260011461086957fe5b5050565b5050565b600073ffffffffffffffffffffffffffffffffffffffff16ff5b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610943576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806114246028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a260003073ffffffffffffffffffffffffffffffffffffffff166108fc60649081150290604051600060405180830381858888f19350505050905080610a53576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600b8152602001807f53656e64206661696c656400000000000000000000000000000000000000000081525060200191505060405180910390fd5b50505050565b610a61611379565b610a7383308480519060200120610c37565b9050610a8a61012c42610c6890919063ffffffff16565b600681905550610aa581670de0b6b3a7640000600102610cf0565b50505050565b610acb82670de0b6b3a764000060010263ce369c0f60e01b600654610d27565b5050565b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b87576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806114246028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a23073ffffffffffffffffffffffffffffffffffffffff166108fc60649081150290604051600060405180830381858888f19350505050158015610c31573d6000803e3d6000fd5b50505050565b610c3f611379565b610c47611379565b610c5e85858584610e6b909392919063ffffffff16565b9150509392505050565b600080828401905083811015610ce6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b6000610d1f600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168484610f1b565b905092915050565b60006005600086815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506005600086815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055847fe1fe3afa0f7f761ff0a8b89086790efd5140d2907ebd5b7ff6bfcb5e075fd4c560405160405180910390a28073ffffffffffffffffffffffffffffffffffffffff16636ee4d553868686866040518563ffffffff1660e01b815260040180858152602001848152602001837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001828152602001945050505050600060405180830381600087803b158015610e4c57600080fd5b505af1158015610e60573d6000803e3d6000fd5b505050505050505050565b610e73611379565b610e8385608001516101006111a4565b508385600001818152505082856020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508185604001907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681525050849050949350505050565b600030600454604051602001808373ffffffffffffffffffffffffffffffffffffffff1660601b815260140182815260200192505050604051602081830303815290604052805190602001209050600454836060018181525050836005600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550807fb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af960405160405180910390a2600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634000aea0858461103d876111f8565b6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156110ae578082015181840152602081019050611093565b50505050905090810190601f1680156110db5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b1580156110fc57600080fd5b505af1158015611110573d6000803e3d6000fd5b505050506040513d602081101561112657600080fd5b810190808051906020019092919050505061118c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806114016023913960400191505060405180910390fd5b60016004600082825401925050819055509392505050565b6111ac6113e6565b6000602083816111b857fe5b06146111d157602082816111c857fe5b06602003820191505b81836020018181525050604051808452600081528281016020016040525082905092915050565b6060634042994660e01b60008084600001518560200151866040015187606001516001896080015160000151604051602401808973ffffffffffffffffffffffffffffffffffffffff1681526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff168152602001857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156112dc5780820151818401526020810190506112c1565b50505050905090810190601f1680156113095780820380516001836020036101000a031916815260200191505b509950505050505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050919050565b6040518060a0016040528060008019168152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001600081526020016113e06113e6565b81525090565b60405180604001604052806060815260200160008152509056fe756e61626c6520746f207472616e73666572416e6443616c6c20746f206f7261636c65536f75726365206d75737420626520746865206f7261636c65206f66207468652072657175657374a2646970667358221220afc51f6eb4212d4255bb8b224d7b48b07296933f6b38f4902f75841a14a1e42464736f6c634300060c0033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x1 PUSH1 0x4 SSTORE PUSH1 0x40 MLOAD PUSH2 0x1583 CODESIZE SUB DUP1 PUSH2 0x1583 DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x2B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x55 DUP3 PUSH2 0x6B PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST PUSH2 0x64 DUP2 PUSH2 0xAF PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST POP POP PUSH2 0xF3 JUMP JUMPDEST DUP1 PUSH1 0x2 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP POP JUMP JUMPDEST DUP1 PUSH1 0x3 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP POP JUMP JUMPDEST PUSH2 0x1481 DUP1 PUSH2 0x102 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x7F JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xC3A347C3 GT PUSH2 0x4E JUMPI DUP1 PUSH4 0xC3A347C3 EQ PUSH2 0x318 JUMPI DUP1 PUSH4 0xC737ECAB EQ PUSH2 0x3EA JUMPI DUP1 PUSH4 0xCE369C0F EQ PUSH2 0x4BC JUMPI DUP1 PUSH4 0xF7B363DF EQ PUSH2 0x58E JUMPI PUSH2 0x86 JUMP JUMPDEST DUP1 PUSH4 0x3F0585BF EQ PUSH2 0x8B JUMPI DUP1 PUSH4 0x3FA7848C EQ PUSH2 0x15D JUMPI DUP1 PUSH4 0x6AA862EF EQ PUSH2 0x22F JUMPI DUP1 PUSH4 0xA7F43779 EQ PUSH2 0x301 JUMPI PUSH2 0x86 JUMP JUMPDEST CALLDATASIZE PUSH2 0x86 JUMPI STOP JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x97 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x15B PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0xAE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0xD5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0xE7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x109 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x660 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x169 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x22D PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x180 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x1A7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x1B9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x1DB JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x85E JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x23B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2FF PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x252 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x279 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x28B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x2AD JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x86D JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x30D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x316 PUSH2 0x871 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x324 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3E8 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x33B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x362 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x374 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x396 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x88B JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3F6 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BA PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x40D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x434 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x446 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x468 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xA59 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x4C8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x58C PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x4DF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x506 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x518 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x53A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xAAB JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x59A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x65E PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x5B1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x5D8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x5EA JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x60C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xACF JUMP JUMPDEST STOP JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x718 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1424 PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x0 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x64 PUSH1 0x40 MLOAD DUP1 PUSH1 0x0 ADD SWAP1 POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 DUP8 GAS CALL SWAP3 POP POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x7DC JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x7E1 JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 PUSH2 0x858 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0xB DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x43616C6C206661696C6564000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x2 PUSH1 0x1 EQ PUSH2 0x869 JUMPI INVALID JUMPDEST POP POP JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SELFDESTRUCT JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x943 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1424 PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x0 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH2 0x8FC PUSH1 0x64 SWAP1 DUP2 ISZERO MUL SWAP1 PUSH1 0x40 MLOAD PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 DUP9 DUP9 CALL SWAP4 POP POP POP POP SWAP1 POP DUP1 PUSH2 0xA53 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0xB DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x53656E64206661696C6564000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xA61 PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xA73 DUP4 ADDRESS DUP5 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 PUSH2 0xC37 JUMP JUMPDEST SWAP1 POP PUSH2 0xA8A PUSH2 0x12C TIMESTAMP PUSH2 0xC68 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x6 DUP2 SWAP1 SSTORE POP PUSH2 0xAA5 DUP2 PUSH8 0xDE0B6B3A7640000 PUSH1 0x1 MUL PUSH2 0xCF0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xACB DUP3 PUSH8 0xDE0B6B3A7640000 PUSH1 0x1 MUL PUSH4 0xCE369C0F PUSH1 0xE0 SHL PUSH1 0x6 SLOAD PUSH2 0xD27 JUMP JUMPDEST POP POP JUMP JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xB87 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1424 PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH2 0x8FC PUSH1 0x64 SWAP1 DUP2 ISZERO MUL SWAP1 PUSH1 0x40 MLOAD PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 DUP9 DUP9 CALL SWAP4 POP POP POP POP ISZERO DUP1 ISZERO PUSH2 0xC31 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xC3F PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xC47 PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xC5E DUP6 DUP6 DUP6 DUP5 PUSH2 0xE6B SWAP1 SWAP4 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 DUP3 DUP5 ADD SWAP1 POP DUP4 DUP2 LT ISZERO PUSH2 0xCE6 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x536166654D6174683A206164646974696F6E206F766572666C6F770000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD1F PUSH1 0x3 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 DUP5 PUSH2 0xF1B JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x5 PUSH1 0x0 DUP7 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP PUSH1 0x5 PUSH1 0x0 DUP7 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP5 PUSH32 0xE1FE3AFA0F7F761FF0A8B89086790EFD5140D2907EBD5B7FF6BFCB5E075FD4C5 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x6EE4D553 DUP7 DUP7 DUP7 DUP7 PUSH1 0x40 MLOAD DUP6 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP5 POP POP POP POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0xE4C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0xE60 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH2 0xE73 PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xE83 DUP6 PUSH1 0x80 ADD MLOAD PUSH2 0x100 PUSH2 0x11A4 JUMP JUMPDEST POP DUP4 DUP6 PUSH1 0x0 ADD DUP2 DUP2 MSTORE POP POP DUP3 DUP6 PUSH1 0x20 ADD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE POP POP DUP2 DUP6 PUSH1 0x40 ADD SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 DUP2 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE POP POP DUP5 SWAP1 POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH1 0x0 ADDRESS PUSH1 0x4 SLOAD PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP1 POP PUSH1 0x4 SLOAD DUP4 PUSH1 0x60 ADD DUP2 DUP2 MSTORE POP POP DUP4 PUSH1 0x5 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP DUP1 PUSH32 0xB5E6E01E79F91267DC17B4E6314D5D4D03593D2CEEE0FBB452B750BD70EA5AF9 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x2 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x4000AEA0 DUP6 DUP5 PUSH2 0x103D DUP8 PUSH2 0x11F8 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP5 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x10AE JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1093 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x10DB JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP5 POP POP POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x10FC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x1110 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x1126 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x118C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1401 PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x4 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x11AC PUSH2 0x13E6 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP4 DUP2 PUSH2 0x11B8 JUMPI INVALID JUMPDEST MOD EQ PUSH2 0x11D1 JUMPI PUSH1 0x20 DUP3 DUP2 PUSH2 0x11C8 JUMPI INVALID JUMPDEST MOD PUSH1 0x20 SUB DUP3 ADD SWAP2 POP JUMPDEST DUP2 DUP4 PUSH1 0x20 ADD DUP2 DUP2 MSTORE POP POP PUSH1 0x40 MLOAD DUP1 DUP5 MSTORE PUSH1 0x0 DUP2 MSTORE DUP3 DUP2 ADD PUSH1 0x20 ADD PUSH1 0x40 MSTORE POP DUP3 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x60 PUSH4 0x40429946 PUSH1 0xE0 SHL PUSH1 0x0 DUP1 DUP5 PUSH1 0x0 ADD MLOAD DUP6 PUSH1 0x20 ADD MLOAD DUP7 PUSH1 0x40 ADD MLOAD DUP8 PUSH1 0x60 ADD MLOAD PUSH1 0x1 DUP10 PUSH1 0x80 ADD MLOAD PUSH1 0x0 ADD MLOAD PUSH1 0x40 MLOAD PUSH1 0x24 ADD DUP1 DUP10 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP9 DUP2 MSTORE PUSH1 0x20 ADD DUP8 DUP2 MSTORE PUSH1 0x20 ADD DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP6 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x12DC JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x12C1 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x1309 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP10 POP POP POP POP POP POP POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND PUSH1 0x20 DUP3 ADD DUP1 MLOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 DUP2 DUP4 AND OR DUP4 MSTORE POP POP POP POP SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0xA0 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x0 DUP1 NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x13E0 PUSH2 0x13E6 JUMP JUMPDEST DUP2 MSTORE POP SWAP1 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x60 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE POP SWAP1 JUMP INVALID PUSH22 0x6E61626C6520746F207472616E73666572416E644361 PUSH13 0x6C20746F206F7261636C65536F PUSH22 0x726365206D75737420626520746865206F7261636C65 KECCAK256 PUSH16 0x66207468652072657175657374A26469 PUSH17 0x667358221220AFC51F6EB4212D4255BB8B 0x22 0x4D PUSH28 0x48B07296933F6B38F4902F75841A14A1E42464736F6C634300060C00 CALLER ", + "sourceMap": "59:1800:6:-:0;;;1211:1:1;1180:32;;203:127:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;268:24;286:5;268:17;;;:24;;:::i;:::-;298:27;317:7;298:18;;;:27;;:::i;:::-;203:127;;59:1800;;4882:94:1;4965:5;4939:4;;:32;;;;;;;;;;;;;;;;;;4882:94;:::o;4660:108::-;4755:7;4720:6;;:43;;;;;;;;;;;;;;;;;;4660:108;:::o;59:1800:6:-;;;;;;;" + }, + "deployedBytecode": { + "immutableReferences": {}, + "linkReferences": {}, + "object": "60806040526004361061007f5760003560e01c8063c3a347c31161004e578063c3a347c314610318578063c737ecab146103ea578063ce369c0f146104bc578063f7b363df1461058e57610086565b80633f0585bf1461008b5780633fa7848c1461015d5780636aa862ef1461022f578063a7f437791461030157610086565b3661008657005b600080fd5b34801561009757600080fd5b5061015b600480360360408110156100ae57600080fd5b8101908080359060200190929190803590602001906401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610660565b005b34801561016957600080fd5b5061022d6004803603604081101561018057600080fd5b8101908080359060200190929190803590602001906401000000008111156101a757600080fd5b8201836020820111156101b957600080fd5b803590602001918460018302840111640100000000831117156101db57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061085e565b005b34801561023b57600080fd5b506102ff6004803603604081101561025257600080fd5b81019080803590602001909291908035906020019064010000000081111561027957600080fd5b82018360208201111561028b57600080fd5b803590602001918460018302840111640100000000831117156102ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061086d565b005b34801561030d57600080fd5b50610316610871565b005b34801561032457600080fd5b506103e86004803603604081101561033b57600080fd5b81019080803590602001909291908035906020019064010000000081111561036257600080fd5b82018360208201111561037457600080fd5b8035906020019184600183028401116401000000008311171561039657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061088b565b005b3480156103f657600080fd5b506104ba6004803603604081101561040d57600080fd5b81019080803590602001909291908035906020019064010000000081111561043457600080fd5b82018360208201111561044657600080fd5b8035906020019184600183028401116401000000008311171561046857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610a59565b005b3480156104c857600080fd5b5061058c600480360360408110156104df57600080fd5b81019080803590602001909291908035906020019064010000000081111561050657600080fd5b82018360208201111561051857600080fd5b8035906020019184600183028401116401000000008311171561053a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610aab565b005b34801561059a57600080fd5b5061065e600480360360408110156105b157600080fd5b8101908080359060200190929190803590602001906401000000008111156105d857600080fd5b8201836020820111156105ea57600080fd5b8035906020019184600183028401116401000000008311171561060c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610acf565b005b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610718576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806114246028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a260003073ffffffffffffffffffffffffffffffffffffffff16606460405180600001905060006040518083038185875af1925050503d80600081146107dc576040519150601f19603f3d011682016040523d82523d6000602084013e6107e1565b606091505b5050905080610858576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600b8152602001807f43616c6c206661696c656400000000000000000000000000000000000000000081525060200191505060405180910390fd5b50505050565b600260011461086957fe5b5050565b5050565b600073ffffffffffffffffffffffffffffffffffffffff16ff5b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610943576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806114246028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a260003073ffffffffffffffffffffffffffffffffffffffff166108fc60649081150290604051600060405180830381858888f19350505050905080610a53576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600b8152602001807f53656e64206661696c656400000000000000000000000000000000000000000081525060200191505060405180910390fd5b50505050565b610a61611379565b610a7383308480519060200120610c37565b9050610a8a61012c42610c6890919063ffffffff16565b600681905550610aa581670de0b6b3a7640000600102610cf0565b50505050565b610acb82670de0b6b3a764000060010263ce369c0f60e01b600654610d27565b5050565b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b87576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806114246028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a23073ffffffffffffffffffffffffffffffffffffffff166108fc60649081150290604051600060405180830381858888f19350505050158015610c31573d6000803e3d6000fd5b50505050565b610c3f611379565b610c47611379565b610c5e85858584610e6b909392919063ffffffff16565b9150509392505050565b600080828401905083811015610ce6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b6000610d1f600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168484610f1b565b905092915050565b60006005600086815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506005600086815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055847fe1fe3afa0f7f761ff0a8b89086790efd5140d2907ebd5b7ff6bfcb5e075fd4c560405160405180910390a28073ffffffffffffffffffffffffffffffffffffffff16636ee4d553868686866040518563ffffffff1660e01b815260040180858152602001848152602001837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001828152602001945050505050600060405180830381600087803b158015610e4c57600080fd5b505af1158015610e60573d6000803e3d6000fd5b505050505050505050565b610e73611379565b610e8385608001516101006111a4565b508385600001818152505082856020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508185604001907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681525050849050949350505050565b600030600454604051602001808373ffffffffffffffffffffffffffffffffffffffff1660601b815260140182815260200192505050604051602081830303815290604052805190602001209050600454836060018181525050836005600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550807fb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af960405160405180910390a2600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634000aea0858461103d876111f8565b6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156110ae578082015181840152602081019050611093565b50505050905090810190601f1680156110db5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b1580156110fc57600080fd5b505af1158015611110573d6000803e3d6000fd5b505050506040513d602081101561112657600080fd5b810190808051906020019092919050505061118c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806114016023913960400191505060405180910390fd5b60016004600082825401925050819055509392505050565b6111ac6113e6565b6000602083816111b857fe5b06146111d157602082816111c857fe5b06602003820191505b81836020018181525050604051808452600081528281016020016040525082905092915050565b6060634042994660e01b60008084600001518560200151866040015187606001516001896080015160000151604051602401808973ffffffffffffffffffffffffffffffffffffffff1681526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff168152602001857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156112dc5780820151818401526020810190506112c1565b50505050905090810190601f1680156113095780820380516001836020036101000a031916815260200191505b509950505050505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050919050565b6040518060a0016040528060008019168152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001600081526020016113e06113e6565b81525090565b60405180604001604052806060815260200160008152509056fe756e61626c6520746f207472616e73666572416e6443616c6c20746f206f7261636c65536f75726365206d75737420626520746865206f7261636c65206f66207468652072657175657374a2646970667358221220afc51f6eb4212d4255bb8b224d7b48b07296933f6b38f4902f75841a14a1e42464736f6c634300060c0033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x7F JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xC3A347C3 GT PUSH2 0x4E JUMPI DUP1 PUSH4 0xC3A347C3 EQ PUSH2 0x318 JUMPI DUP1 PUSH4 0xC737ECAB EQ PUSH2 0x3EA JUMPI DUP1 PUSH4 0xCE369C0F EQ PUSH2 0x4BC JUMPI DUP1 PUSH4 0xF7B363DF EQ PUSH2 0x58E JUMPI PUSH2 0x86 JUMP JUMPDEST DUP1 PUSH4 0x3F0585BF EQ PUSH2 0x8B JUMPI DUP1 PUSH4 0x3FA7848C EQ PUSH2 0x15D JUMPI DUP1 PUSH4 0x6AA862EF EQ PUSH2 0x22F JUMPI DUP1 PUSH4 0xA7F43779 EQ PUSH2 0x301 JUMPI PUSH2 0x86 JUMP JUMPDEST CALLDATASIZE PUSH2 0x86 JUMPI STOP JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x97 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x15B PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0xAE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0xD5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0xE7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x109 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x660 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x169 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x22D PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x180 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x1A7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x1B9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x1DB JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x85E JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x23B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2FF PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x252 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x279 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x28B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x2AD JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x86D JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x30D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x316 PUSH2 0x871 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x324 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3E8 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x33B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x362 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x374 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x396 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x88B JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3F6 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BA PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x40D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x434 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x446 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x468 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xA59 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x4C8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x58C PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x4DF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x506 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x518 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x53A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xAAB JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x59A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x65E PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x5B1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x5D8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x5EA JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x60C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xACF JUMP JUMPDEST STOP JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x718 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1424 PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x0 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x64 PUSH1 0x40 MLOAD DUP1 PUSH1 0x0 ADD SWAP1 POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 DUP8 GAS CALL SWAP3 POP POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x7DC JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x7E1 JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 PUSH2 0x858 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0xB DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x43616C6C206661696C6564000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x2 PUSH1 0x1 EQ PUSH2 0x869 JUMPI INVALID JUMPDEST POP POP JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SELFDESTRUCT JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x943 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1424 PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x0 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH2 0x8FC PUSH1 0x64 SWAP1 DUP2 ISZERO MUL SWAP1 PUSH1 0x40 MLOAD PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 DUP9 DUP9 CALL SWAP4 POP POP POP POP SWAP1 POP DUP1 PUSH2 0xA53 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0xB DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x53656E64206661696C6564000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xA61 PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xA73 DUP4 ADDRESS DUP5 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 PUSH2 0xC37 JUMP JUMPDEST SWAP1 POP PUSH2 0xA8A PUSH2 0x12C TIMESTAMP PUSH2 0xC68 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x6 DUP2 SWAP1 SSTORE POP PUSH2 0xAA5 DUP2 PUSH8 0xDE0B6B3A7640000 PUSH1 0x1 MUL PUSH2 0xCF0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xACB DUP3 PUSH8 0xDE0B6B3A7640000 PUSH1 0x1 MUL PUSH4 0xCE369C0F PUSH1 0xE0 SHL PUSH1 0x6 SLOAD PUSH2 0xD27 JUMP JUMPDEST POP POP JUMP JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xB87 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1424 PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH2 0x8FC PUSH1 0x64 SWAP1 DUP2 ISZERO MUL SWAP1 PUSH1 0x40 MLOAD PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 DUP9 DUP9 CALL SWAP4 POP POP POP POP ISZERO DUP1 ISZERO PUSH2 0xC31 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xC3F PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xC47 PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xC5E DUP6 DUP6 DUP6 DUP5 PUSH2 0xE6B SWAP1 SWAP4 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 DUP3 DUP5 ADD SWAP1 POP DUP4 DUP2 LT ISZERO PUSH2 0xCE6 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x536166654D6174683A206164646974696F6E206F766572666C6F770000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD1F PUSH1 0x3 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 DUP5 PUSH2 0xF1B JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x5 PUSH1 0x0 DUP7 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP PUSH1 0x5 PUSH1 0x0 DUP7 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP5 PUSH32 0xE1FE3AFA0F7F761FF0A8B89086790EFD5140D2907EBD5B7FF6BFCB5E075FD4C5 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x6EE4D553 DUP7 DUP7 DUP7 DUP7 PUSH1 0x40 MLOAD DUP6 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP5 POP POP POP POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0xE4C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0xE60 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH2 0xE73 PUSH2 0x1379 JUMP JUMPDEST PUSH2 0xE83 DUP6 PUSH1 0x80 ADD MLOAD PUSH2 0x100 PUSH2 0x11A4 JUMP JUMPDEST POP DUP4 DUP6 PUSH1 0x0 ADD DUP2 DUP2 MSTORE POP POP DUP3 DUP6 PUSH1 0x20 ADD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE POP POP DUP2 DUP6 PUSH1 0x40 ADD SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 DUP2 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE POP POP DUP5 SWAP1 POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH1 0x0 ADDRESS PUSH1 0x4 SLOAD PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP1 POP PUSH1 0x4 SLOAD DUP4 PUSH1 0x60 ADD DUP2 DUP2 MSTORE POP POP DUP4 PUSH1 0x5 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP DUP1 PUSH32 0xB5E6E01E79F91267DC17B4E6314D5D4D03593D2CEEE0FBB452B750BD70EA5AF9 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x2 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x4000AEA0 DUP6 DUP5 PUSH2 0x103D DUP8 PUSH2 0x11F8 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP5 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x10AE JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1093 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x10DB JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP5 POP POP POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x10FC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x1110 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x1126 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x118C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1401 PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x4 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x11AC PUSH2 0x13E6 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP4 DUP2 PUSH2 0x11B8 JUMPI INVALID JUMPDEST MOD EQ PUSH2 0x11D1 JUMPI PUSH1 0x20 DUP3 DUP2 PUSH2 0x11C8 JUMPI INVALID JUMPDEST MOD PUSH1 0x20 SUB DUP3 ADD SWAP2 POP JUMPDEST DUP2 DUP4 PUSH1 0x20 ADD DUP2 DUP2 MSTORE POP POP PUSH1 0x40 MLOAD DUP1 DUP5 MSTORE PUSH1 0x0 DUP2 MSTORE DUP3 DUP2 ADD PUSH1 0x20 ADD PUSH1 0x40 MSTORE POP DUP3 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x60 PUSH4 0x40429946 PUSH1 0xE0 SHL PUSH1 0x0 DUP1 DUP5 PUSH1 0x0 ADD MLOAD DUP6 PUSH1 0x20 ADD MLOAD DUP7 PUSH1 0x40 ADD MLOAD DUP8 PUSH1 0x60 ADD MLOAD PUSH1 0x1 DUP10 PUSH1 0x80 ADD MLOAD PUSH1 0x0 ADD MLOAD PUSH1 0x40 MLOAD PUSH1 0x24 ADD DUP1 DUP10 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP9 DUP2 MSTORE PUSH1 0x20 ADD DUP8 DUP2 MSTORE PUSH1 0x20 ADD DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP6 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x12DC JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x12C1 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x1309 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP10 POP POP POP POP POP POP POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND PUSH1 0x20 DUP3 ADD DUP1 MLOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 DUP2 DUP4 AND OR DUP4 MSTORE POP POP POP POP SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0xA0 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x0 DUP1 NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x13E0 PUSH2 0x13E6 JUMP JUMPDEST DUP2 MSTORE POP SWAP1 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x60 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE POP SWAP1 JUMP INVALID PUSH22 0x6E61626C6520746F207472616E73666572416E644361 PUSH13 0x6C20746F206F7261636C65536F PUSH22 0x726365206D75737420626520746865206F7261636C65 KECCAK256 PUSH16 0x66207468652072657175657374A26469 PUSH17 0x667358221220AFC51F6EB4212D4255BB8B 0x22 0x4D PUSH28 0x48B07296933F6B38F4902F75841A14A1E42464736F6C634300060C00 CALLER ", + "sourceMap": "59:1800:6:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1083:242;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;722:80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1759:58;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1019:60;;;;;;;;;;;;;:::i;:::-;;1329:278;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;407:311;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;806:209;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1611:144;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1083:242;1173:10;8791:15:1;:27;8807:10;8791:27;;;;;;;;;;;;;;;;;;;;;8777:41;;:10;:41;;;8769:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8888:15;:27;8904:10;8888:27;;;;;;;;;;;;8881:34;;;;;;;;;;;8945:10;8926:30;;;;;;;;;;1192:12:6::1;1217:4;1209:18;;1234:3;1209:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1191:51;;;1297:7;1289:31;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;8962:1:1;1083:242:6::0;;;:::o;722:80::-;795:1;790;:6;783:14;;;;722:80;;:::o;1759:58::-;;;:::o;1019:60::-;1071:1;1050:24;;;1329:278;1419:10;8791:15:1;:27;8807:10;8791:27;;;;;;;;;;;;;;;;;;;;;8777:41;;:10;:41;;;8769:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8888:15;:27;8904:10;8888:27;;;;;;;;;;;;8881:34;;;;;;;;;;;8945:10;8926:30;;;;;;;;;;1488:12:6::1;1511:4;1503:18;;:23;1522:3;1503:23;;;;;;;;;;;;;;;;;;;;;;;1488:38;;1579:7;1571:31;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;8962:1:1;1329:278:6::0;;;:::o;407:311::-;482:28;;:::i;:::-;513:75;535:3;548:4;572:13;562:24;;;;;;513:21;:75::i;:::-;482:106;;607:18;615:9;607:3;:7;;:18;;;;:::i;:::-;594:10;:31;;;;672:41;693:3;658:6:1;160:1:6;:8;672:20;:41::i;:::-;;407:311;;;:::o;806:209::-;885:125;915:10;658:6:1;160:1:6;:8;955:36;;;999:10;;885:22;:125::i;:::-;806:209;;:::o;1611:144::-;1705:10;8791:15:1;:27;8807:10;8791:27;;;;;;;;;;;;;;;;;;;;;8777:41;;:10;:41;;;8769:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8888:15;:27;8904:10;8888:27;;;;;;;;;;;;8881:34;;;;;;;;;;;8945:10;8926:30;;;;;;;;;;1731:4:6::1;1723:22;;:27;1746:3;1723:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;1611:144:::0;;;:::o;1815:295:1:-;1963:24;;:::i;:::-;1995:28;;:::i;:::-;2036:69;2051:7;2060:16;2078:26;2036:3;:14;;:69;;;;;;:::i;:::-;2029:76;;;1815:295;;;;;:::o;831:162:10:-;889:7;904:9;920:1;916;:5;904:17;;940:1;935;:6;;927:46;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;987:1;980:8;;;831:162;;;;:::o;2417:189:1:-;2522:7;2546:55;2577:6;;;;;;;;;;;2586:4;2592:8;2546:22;:55::i;:::-;2539:62;;2417:189;;;;:::o;4128:417::-;4282:35;4346:15;:27;4362:10;4346:27;;;;;;;;;;;;;;;;;;;;;4282:92;;4387:15;:27;4403:10;4387:27;;;;;;;;;;;;4380:34;;;;;;;;;;;4444:10;4425:30;;;;;;;;;;4461:9;:29;;;4491:10;4503:8;4513:13;4528:11;4461:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4128:417;;;;;:::o;966:365:0:-;1115:24;;:::i;:::-;1147:49;1168:4;:8;;;333:3;1147:20;:49::i;:::-;;1212:3;1202:4;:7;;:13;;;;;1244:16;1221:4;:20;;:39;;;;;;;;;;;1292:17;1266:4;:23;;:43;;;;;;;;;;;;;1322:4;1315:11;;966:365;;;;;;:::o;3120:488:1:-;3244:17;3310:4;3316:12;;3293:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3283:47;;;;;;3271:59;;3349:12;;3336:4;:10;;:25;;;;;3396:7;3367:15;:26;3383:9;3367:26;;;;;;;;;;;;:36;;;;;;;;;;;;;;;;;;3433:9;3414:29;;;;;;;;;;3457:4;;;;;;;;;;;:20;;;3478:7;3487:8;3497:19;3511:4;3497:13;:19::i;:::-;3457:60;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3449:108;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3579:1;3563:12;;:17;;;;;;;;;;;3120:488;;;;;:::o;950:395:7:-;1020:13;;:::i;:::-;1062:1;1056:2;1045:8;:13;;;;;;:18;1041:71;;1102:2;1091:8;:13;;;;;;1085:2;:20;1073:32;;;;1041:71;1174:8;1159:3;:12;;:23;;;;;1222:4;1216:11;1246:3;1241;1234:16;1269:1;1264:3;1257:14;1308:8;1303:3;1299:18;1295:2;1291:27;1285:4;1278:41;1197:128;1337:3;1330:10;;950:395;;;;:::o;7612:527:1:-;7700:12;7759:29;;;767:1;711;8009:4;:7;;;8024:4;:20;;;8052:4;:23;;;8083:4;:10;;;813:1;8121:4;:8;;;:12;;;7729:405;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7722:412;;7612:527;;;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;:::o" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "1049800", + "executionCost": "infinite", + "totalCost": "infinite" + }, + "external": { + "assertFail(bytes32,bytes)": "infinite", + "cancelRequestOnFulfill(bytes32,bytes)": "infinite", + "doesNothing(bytes32,bytes)": "infinite", + "remove()": "30211", + "requestData(bytes32,bytes)": "infinite", + "stealEthCall(bytes32,bytes)": "infinite", + "stealEthSend(bytes32,bytes)": "infinite", + "stealEthTransfer(bytes32,bytes)": "infinite" + } + }, + "methodIdentifiers": { + "assertFail(bytes32,bytes)": "3fa7848c", + "cancelRequestOnFulfill(bytes32,bytes)": "ce369c0f", + "doesNothing(bytes32,bytes)": "6aa862ef", + "remove()": "a7f43779", + "requestData(bytes32,bytes)": "c737ecab", + "stealEthCall(bytes32,bytes)": "3f0585bf", + "stealEthSend(bytes32,bytes)": "c3a347c3", + "stealEthTransfer(bytes32,bytes)": "f7b363df" + } + }, + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_link", + "type": "address" + }, + { + "internalType": "address", + "name": "_oracle", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkRequested", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "assertFail", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "cancelRequestOnFulfill", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "doesNothing", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "remove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_id", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_callbackFunc", + "type": "bytes" + } + ], + "name": "requestData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "stealEthCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "stealEthSend", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "stealEthTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] +} \ No newline at end of file diff --git a/evm-contracts/src/v0.6/tests/artifacts/MaliciousMultiWordConsumer_metadata.json b/evm-contracts/src/v0.6/tests/artifacts/MaliciousMultiWordConsumer_metadata.json new file mode 100644 index 00000000000..8e52b92a2d6 --- /dev/null +++ b/evm-contracts/src/v0.6/tests/artifacts/MaliciousMultiWordConsumer_metadata.json @@ -0,0 +1,307 @@ +{ + "compiler": { + "version": "0.6.12+commit.27d51765" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_link", + "type": "address" + }, + { + "internalType": "address", + "name": "_oracle", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkRequested", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "assertFail", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "cancelRequestOnFulfill", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "doesNothing", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "remove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_id", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_callbackFunc", + "type": "bytes" + } + ], + "name": "requestData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "stealEthCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "stealEthSend", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "stealEthTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "settings": { + "compilationTarget": { + "localhost/v0.6/tests/MaliciousMultiWordConsumer.sol": "MaliciousMultiWordConsumer" + }, + "evmVersion": "istanbul", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs" + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "localhost/v0.6/Chainlink.sol": { + "keccak256": "0x7048bfd6e6fe4e60ea4af01ed44fc0494cec75df75c6b542a6f08ed34c66d39e", + "urls": [ + "bzz-raw://37efc6a4665b537657283652d75919ec8dd3d48d85150b5285041bc9614fec11", + "dweb:/ipfs/QmWKgeJEj222kNefTkauHwM5x58KTxSGgcbLseH9Fq8jed" + ] + }, + "localhost/v0.6/ChainlinkClient.sol": { + "keccak256": "0xd06166f7798c4c8cbd0e2dc642450c9bca39157c688ae95dbe079f666cc44dfa", + "urls": [ + "bzz-raw://672af493da8e01492532d7ca8660362d015f317114e405aa55c85cff24aee31c", + "dweb:/ipfs/QmNZdB2jVwwswrLFr83toTtHM5MPSugKeUdxrxfCJ4mP4K" + ] + }, + "localhost/v0.6/interfaces/ChainlinkRequestInterface.sol": { + "keccak256": "0xb6e293a74be1e484aecad7acb4339ef2a115bf6d502a0f9f1741d53aa2dd06ca", + "urls": [ + "bzz-raw://20b8a561ce38a9b9ff9147fb0511cff512c5411e556d2c1438b13eb682042228", + "dweb:/ipfs/QmT6z7c2jBH5wwfYn4uHVFmwDKwUgnbJLu3n8oDmEnfeQr" + ] + }, + "localhost/v0.6/interfaces/ENSInterface.sol": { + "keccak256": "0xf4998e886147b298eda28b4eacbdc90c58ba63ba475469651f2072e188dd5a64", + "urls": [ + "bzz-raw://c1e2334294a816b7eda9de280e39b9463ebde2db8b242410eb991b2f623b47d4", + "dweb:/ipfs/QmNY5bajahfFRmhBgcMVQ7712zHKxc6HkuN7LaiKtpjb7t" + ] + }, + "localhost/v0.6/interfaces/LinkTokenInterface.sol": { + "keccak256": "0xdbf46b45a4c9f38ba71a0391aed0e7b108854b619f292d907ae537228868bda6", + "urls": [ + "bzz-raw://3ae40466809630c4731e2e3a697d6885727c577aaf260766c8a2f534ad3f6ee8", + "dweb:/ipfs/QmTzpN5yP4Y5jvQ1ohfXFrce3sjzUiSChYJgZj9VvhVohG" + ] + }, + "localhost/v0.6/interfaces/PointerInterface.sol": { + "keccak256": "0x6458d82762d4f13c020a13efdbd9bf14500e147df707184a693aea91449c2f4f", + "urls": [ + "bzz-raw://735950f3a544fc6ef2db92405597169bfb5fdb9df83623c0d99fd3d85de8690d", + "dweb:/ipfs/QmZHxb5Qr7Kw9DHAg4VwEADue9ffNyyhbiyEZ15A5mANUN" + ] + }, + "localhost/v0.6/tests/MaliciousMultiWordConsumer.sol": { + "keccak256": "0xa083bb8da8cf961e72222dc957c4ed326cf93cf789ad08cac890518cd2d3f177", + "urls": [ + "bzz-raw://7b2dbb7ee4479167a10cac7d572ab204584c4d30879fbef961aeb4e3fed41941", + "dweb:/ipfs/QmdUvtnoCR751h44jjNjkUiPq4mJ1mSzeuCqX5dijHqKaU" + ] + }, + "localhost/v0.6/vendor/BufferChainlink.sol": { + "keccak256": "0xe4aa364f56414c4326ffe12c1121d591be6ad168afb42b24a823f6d76299dd63", + "urls": [ + "bzz-raw://e3e91a0eddb6fc6c53acdfbd59771deff1678330128d3c98014c668763efb45e", + "dweb:/ipfs/Qmbt5VNT2W2oCN44536JGNuHqAJdmYGqzEFcHvy8W1tAsY" + ] + }, + "localhost/v0.6/vendor/CBORChainlink.sol": { + "keccak256": "0xbb4d8257c1af348cac9828ee531428b148bb726517357fe6a80279ac45b658b5", + "urls": [ + "bzz-raw://8c8c5da0358946437fac595591367066b8d6e5f58414c027a79a093e1f3241c1", + "dweb:/ipfs/QmNQ5TPzaPEbj5kaX17YLuZEmhv8NGfoCrUVK3s6gQuHdA" + ] + }, + "localhost/v0.6/vendor/ENSResolver.sol": { + "keccak256": "0xdddea29d7407c1dbd1e130d885fc1a0934e98f0a7cc9f4d5bfd002bb2cfbcf82", + "urls": [ + "bzz-raw://c4c764d69c47754d7b219fab558bf4be2a6444470ede7aa0ab1e446aea01dbda", + "dweb:/ipfs/QmWp2CNUw9xt8ir2P3LhGHuydUsAXnyZ382U2BUjhoYPvy" + ] + }, + "localhost/v0.6/vendor/SafeMathChainlink.sol": { + "keccak256": "0x5e6948bb332468d8ef0704b4259babc8aef7ce5969d5997c16db8ad806222a0a", + "urls": [ + "bzz-raw://8e2bbda1e1168401d0105cc86bf5302000e8555ebb9b235cd68c148916a452e5", + "dweb:/ipfs/QmbTjbpJr8VfdWfgBknbQov1MGkXXPMysb7eD8bobGAcBV" + ] + } + }, + "version": 1 +} \ No newline at end of file diff --git a/evm-contracts/src/v0.6/tests/artifacts/MultiWordConsumer.json b/evm-contracts/src/v0.6/tests/artifacts/MultiWordConsumer.json new file mode 100644 index 00000000000..d0b9b5aae58 --- /dev/null +++ b/evm-contracts/src/v0.6/tests/artifacts/MultiWordConsumer.json @@ -0,0 +1,396 @@ +{ + "deploy": { + "VM:-": { + "linkReferences": {}, + "autoDeployLib": true + }, + "main:1": { + "linkReferences": {}, + "autoDeployLib": true + }, + "ropsten:3": { + "linkReferences": {}, + "autoDeployLib": true + }, + "rinkeby:4": { + "linkReferences": {}, + "autoDeployLib": true + }, + "kovan:42": { + "linkReferences": {}, + "autoDeployLib": true + }, + "görli:5": { + "linkReferences": {}, + "autoDeployLib": true + }, + "Custom": { + "linkReferences": {}, + "autoDeployLib": true + } + }, + "data": { + "bytecode": { + "linkReferences": {}, + "object": "6080604052600160045534801561001557600080fd5b50604051611c43380380611c438339818101604052606081101561003857600080fd5b8101908080519060200190929190805190602001909291908051906020019092919050505061006c8361008a60201b60201c565b61007b826100ce60201b60201c565b80600681905550505050610112565b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b80600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b611b22806101216000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806383db5cbc1161007157806383db5cbc1461029c5780638dc654a2146103615780639d1b464a1461036b578063c2fb8523146103ee578063e89855ba146104b3578063e8d5359d14610578576100a9565b80633df4ddf4146100ae57806353389072146100cc5780635591a6081461010e5780635a8ac02d1461019957806374961d4d146101b7575b600080fd5b6100b66105c6565b6040518082815260200191505060405180910390f35b61010c600480360360608110156100e257600080fd5b810190808035906020019092919080359060200190929190803590602001909291905050506105cc565b005b610197600480360360a081101561012457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919050505061072a565b005b6101a16107d9565b6040518082815260200191505060405180910390f35b61029a600480360360608110156101cd57600080fd5b81019080803590602001906401000000008111156101ea57600080fd5b8201836020820111156101fc57600080fd5b8035906020019184600183028401116401000000008311171561021e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506107df565b005b61035f600480360360408110156102b257600080fd5b81019080803590602001906401000000008111156102cf57600080fd5b8201836020820111156102e157600080fd5b8035906020019184600183028401116401000000008311171561030357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610925565b005b610369610934565b005b610373610b02565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103b3578082015181840152602081019050610398565b50505050905090810190601f1680156103e05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104b16004803603604081101561040457600080fd5b81019080803590602001909291908035906020019064010000000081111561042b57600080fd5b82018360208201111561043d57600080fd5b8035906020019184600183028401116401000000008311171561045f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610ba0565b005b610576600480360360408110156104c957600080fd5b81019080803590602001906401000000008111156104e657600080fd5b8201836020820111156104f857600080fd5b8035906020019184600183028401116401000000008311171561051a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610d65565b005b6105c46004803603604081101561058e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610dfa565b005b60085481565b826005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610684576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180611a7e6028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a28183857fd368a628c6f427add4c36c69828a9be4d937a803adfda79c1dbf7eb26cdf4bc460405160405180910390a4826008819055508160098190555050505050565b60008590508073ffffffffffffffffffffffffffffffffffffffff16636ee4d553868686866040518563ffffffff1660e01b815260040180858152602001848152602001837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001828152602001945050505050600060405180830381600087803b1580156107b957600080fd5b505af11580156107cd573d6000803e3d6000fd5b50505050505050505050565b60095481565b6107e7611936565b6107fb6006548363c2fb852360e01b610e08565b905061085f6040518060400160405280600381526020017f6765740000000000000000000000000000000000000000000000000000000000815250604051806080016040528060478152602001611aa66047913983610e399092919063ffffffff16565b6060600167ffffffffffffffff8111801561087957600080fd5b506040519080825280602002602001820160405280156108ad57816020015b60608152602001906001900390816108985790505b50905084816000815181106108be57fe5b60200260200101819052506109136040518060400160405280600481526020017f70617468000000000000000000000000000000000000000000000000000000008152508284610e6c9092919063ffffffff16565b61091d8285610ee6565b505050505050565b6109308282306107df565b5050565b600061093e610f1d565b90508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb338373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b1580156109c457600080fd5b505afa1580156109d8573d6000803e3d6000fd5b505050506040513d60208110156109ee57600080fd5b81019080805190602001909291905050506040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015610a5257600080fd5b505af1158015610a66573d6000803e3d6000fd5b505050506040513d6020811015610a7c57600080fd5b8101908080519060200190929190505050610aff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f556e61626c6520746f207472616e73666572000000000000000000000000000081525060200191505060405180910390fd5b50565b60078054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b985780601f10610b6d57610100808354040283529160200191610b98565b820191906000526020600020905b815481529060010190602001808311610b7b57829003601f168201915b505050505081565b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610c58576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180611a7e6028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a2816040518082805190602001908083835b60208310610cef5780518252602082019150602081019050602083039250610ccc565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020837f1a111c5dcf9a71088bd5e1797fdfaf399fec2afbb24aca247e4e3e9f4b61df9160405160405180910390a38160079080519060200190610d5f9291906119a3565b50505050565b7ecb39d6c2c520f0597db0021367767c48fef2964cf402d3c9e9d4df12e4396460405180806020018281038252600b8152602001807f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525060200191505060405180910390a1610dd4611936565b610de860065430635338907260e01b610e08565b9050610df48183610ee6565b50505050565b610e048282610f47565b5050565b610e10611936565b610e18611936565b610e2f85858584611074909392919063ffffffff16565b9150509392505050565b610e5082846080015161112490919063ffffffff16565b610e6781846080015161112490919063ffffffff16565b505050565b610e8382846080015161112490919063ffffffff16565b610e908360800151611149565b60005b8151811015610ed357610ec6828281518110610eab57fe5b6020026020010151856080015161112490919063ffffffff16565b8080600101915050610e93565b50610ee18360800151611157565b505050565b6000610f15600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168484611165565b905092915050565b6000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b80600073ffffffffffffffffffffffffffffffffffffffff166005600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461101d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601a8152602001807f5265717565737420697320616c72656164792070656e64696e6700000000000081525060200191505060405180910390fd5b826005600084815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505050565b61107c611936565b61108c85608001516101006113ee565b508385600001818152505082856020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508185604001907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681525050849050949350505050565b6111318260038351611442565b611144818361158790919063ffffffff16565b505050565b6111548160046115a9565b50565b6111628160076115a9565b50565b600030600454604051602001808373ffffffffffffffffffffffffffffffffffffffff1660601b815260140182815260200192505050604051602081830303815290604052805190602001209050600454836060018181525050836005600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550807fb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af960405160405180910390a2600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634000aea08584611287876115cb565b6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156112f85780820151818401526020810190506112dd565b50505050905090810190601f1680156113255780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b15801561134657600080fd5b505af115801561135a573d6000803e3d6000fd5b505050506040513d602081101561137057600080fd5b81019080805190602001909291905050506113d6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180611a5b6023913960400191505060405180910390fd5b60016004600082825401925050819055509392505050565b6113f6611a23565b60006020838161140257fe5b061461141b576020828161141257fe5b06602003820191505b81836020018181525050604051808452600081528281016020016040525082905092915050565b6017811161146f576114698160058460ff16901b60ff16178461174c90919063ffffffff16565b50611582565b60ff81116114b157611494601860058460ff16901b178461174c90919063ffffffff16565b506114ab8160018561176c9092919063ffffffff16565b50611581565b61ffff81116114f4576114d7601960058460ff16901b178461174c90919063ffffffff16565b506114ee8160028561176c9092919063ffffffff16565b50611580565b63ffffffff81116115395761151c601a60058460ff16901b178461174c90919063ffffffff16565b506115338160048561176c9092919063ffffffff16565b5061157f565b67ffffffffffffffff811161157e57611565601b60058460ff16901b178461174c90919063ffffffff16565b5061157c8160088561176c9092919063ffffffff16565b505b5b5b5b5b505050565b61158f611a23565b6115a18384600001515184855161178e565b905092915050565b6115c6601f60058360ff16901b178361174c90919063ffffffff16565b505050565b6060634042994660e01b60008084600001518560200151866040015187606001516001896080015160000151604051602401808973ffffffffffffffffffffffffffffffffffffffff1681526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff168152602001857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156116af578082015181840152602081019050611694565b50505050905090810190601f1680156116dc5780820380516001836020036101000a031916815260200191505b509950505050505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050919050565b611754611a23565b6117648384600001515184611847565b905092915050565b611774611a23565b611785848560000151518585611895565b90509392505050565b611796611a23565b82518211156117a457600080fd5b846020015182850111156117cf576117ce8560026117c888602001518887016118f6565b02611912565b5b6000808651805187602083010193508088870111156117ee5787860182525b60208701925050505b6020841061181a57805182526020820191506020810190506020840393506117f7565b60006001856020036101000a03905080198251168184511681811785525050879350505050949350505050565b61184f611a23565b8360200151831061186c5761186b846002866020015102611912565b5b8351805160208583010184815381861415611888576001820183525b5050508390509392505050565b61189d611a23565b846020015184830111156118bb576118ba85600286850102611912565b5b60006001836101000a03905085518386820101858319825116178152815185880111156118e85784870182525b505085915050949350505050565b6000818311156119085782905061190c565b8190505b92915050565b60608260000151905061192583836113ee565b506119308382611587565b50505050565b6040518060a0016040528060008019168152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526020016000815260200161199d611a23565b81525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106119e457805160ff1916838001178555611a12565b82800160010185558215611a12579182015b82811115611a115782518255916020019190600101906119f6565b5b509050611a1f9190611a3d565b5090565b604051806040016040528060608152602001600081525090565b5b80821115611a56576000816000905550600101611a3e565b509056fe756e61626c6520746f207472616e73666572416e6443616c6c20746f206f7261636c65536f75726365206d75737420626520746865206f7261636c65206f6620746865207265717565737468747470733a2f2f6d696e2d6170692e63727970746f636f6d706172652e636f6d2f646174612f70726963653f6673796d3d455448267473796d733d5553442c4555522c4a5059a264697066735822122015dadc9728eec5a9d5bc36b683fd140f8aec78f866bacadde0d326b03a4899f964736f6c634300060c0033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x1 PUSH1 0x4 SSTORE CALLVALUE DUP1 ISZERO PUSH2 0x15 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH2 0x1C43 CODESIZE SUB DUP1 PUSH2 0x1C43 DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x38 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x6C DUP4 PUSH2 0x8A PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST PUSH2 0x7B DUP3 PUSH2 0xCE PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST DUP1 PUSH1 0x6 DUP2 SWAP1 SSTORE POP POP POP POP PUSH2 0x112 JUMP JUMPDEST DUP1 PUSH1 0x2 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP POP JUMP JUMPDEST DUP1 PUSH1 0x3 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP POP JUMP JUMPDEST PUSH2 0x1B22 DUP1 PUSH2 0x121 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x83DB5CBC GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x83DB5CBC EQ PUSH2 0x29C JUMPI DUP1 PUSH4 0x8DC654A2 EQ PUSH2 0x361 JUMPI DUP1 PUSH4 0x9D1B464A EQ PUSH2 0x36B JUMPI DUP1 PUSH4 0xC2FB8523 EQ PUSH2 0x3EE JUMPI DUP1 PUSH4 0xE89855BA EQ PUSH2 0x4B3 JUMPI DUP1 PUSH4 0xE8D5359D EQ PUSH2 0x578 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x3DF4DDF4 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x53389072 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x5591A608 EQ PUSH2 0x10E JUMPI DUP1 PUSH4 0x5A8AC02D EQ PUSH2 0x199 JUMPI DUP1 PUSH4 0x74961D4D EQ PUSH2 0x1B7 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x5C6 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x10C PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0xE2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x5CC JUMP JUMPDEST STOP JUMPDEST PUSH2 0x197 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0xA0 DUP2 LT ISZERO PUSH2 0x124 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x72A JUMP JUMPDEST STOP JUMPDEST PUSH2 0x1A1 PUSH2 0x7D9 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x29A PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x1CD JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x1EA JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x1FC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x21E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x7DF JUMP JUMPDEST STOP JUMPDEST PUSH2 0x35F PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x2B2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x2CF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x2E1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x303 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x925 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x369 PUSH2 0x934 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x373 PUSH2 0xB02 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x3B3 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x398 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x3E0 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x4B1 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x404 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x42B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x43D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x45F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xBA0 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x576 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x4C9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x4E6 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x4F8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x51A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xD65 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x5C4 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x58E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xDFA JUMP JUMPDEST STOP JUMPDEST PUSH1 0x8 SLOAD DUP2 JUMP JUMPDEST DUP3 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x684 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1A7E PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 DUP2 DUP4 DUP6 PUSH32 0xD368A628C6F427ADD4C36C69828A9BE4D937A803ADFDA79C1DBF7EB26CDF4BC4 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG4 DUP3 PUSH1 0x8 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x9 DUP2 SWAP1 SSTORE POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP6 SWAP1 POP DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x6EE4D553 DUP7 DUP7 DUP7 DUP7 PUSH1 0x40 MLOAD DUP6 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP5 POP POP POP POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x7B9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x7CD JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x9 SLOAD DUP2 JUMP JUMPDEST PUSH2 0x7E7 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0x7FB PUSH1 0x6 SLOAD DUP4 PUSH4 0xC2FB8523 PUSH1 0xE0 SHL PUSH2 0xE08 JUMP JUMPDEST SWAP1 POP PUSH2 0x85F PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x3 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x6765740000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x40 MLOAD DUP1 PUSH1 0x80 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x47 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x1AA6 PUSH1 0x47 SWAP2 CODECOPY DUP4 PUSH2 0xE39 SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x60 PUSH1 0x1 PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT DUP1 ISZERO PUSH2 0x879 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD SWAP1 DUP1 DUP3 MSTORE DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD DUP3 ADD PUSH1 0x40 MSTORE DUP1 ISZERO PUSH2 0x8AD JUMPI DUP2 PUSH1 0x20 ADD JUMPDEST PUSH1 0x60 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 PUSH1 0x1 SWAP1 SUB SWAP1 DUP2 PUSH2 0x898 JUMPI SWAP1 POP JUMPDEST POP SWAP1 POP DUP5 DUP2 PUSH1 0x0 DUP2 MLOAD DUP2 LT PUSH2 0x8BE JUMPI INVALID JUMPDEST PUSH1 0x20 MUL PUSH1 0x20 ADD ADD DUP2 SWAP1 MSTORE POP PUSH2 0x913 PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x4 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x7061746800000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP DUP3 DUP5 PUSH2 0xE6C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH2 0x91D DUP3 DUP6 PUSH2 0xEE6 JUMP JUMPDEST POP POP POP POP POP POP JUMP JUMPDEST PUSH2 0x930 DUP3 DUP3 ADDRESS PUSH2 0x7DF JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x93E PUSH2 0xF1D JUMP JUMPDEST SWAP1 POP DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xA9059CBB CALLER DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x70A08231 ADDRESS PUSH1 0x40 MLOAD DUP3 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP7 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x9C4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS STATICCALL ISZERO DUP1 ISZERO PUSH2 0x9D8 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x9EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0x40 MLOAD DUP4 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0xA52 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0xA66 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0xA7C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xAFF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x12 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x556E61626C6520746F207472616E736665720000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH1 0x7 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 ISZERO PUSH2 0xB98 JUMPI DUP1 PUSH1 0x1F LT PUSH2 0xB6D JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0xB98 JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xB7B JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP DUP2 JUMP JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xC58 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1A7E PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 DUP2 PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0xCEF JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0xCCC JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 KECCAK256 DUP4 PUSH32 0x1A111C5DCF9A71088BD5E1797FDFAF399FEC2AFBB24ACA247E4E3E9F4B61DF91 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 DUP2 PUSH1 0x7 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH2 0xD5F SWAP3 SWAP2 SWAP1 PUSH2 0x19A3 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH31 0xCB39D6C2C520F0597DB0021367767C48FEF2964CF402D3C9E9D4DF12E43964 PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0xB DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x68656C6C6F20776F726C64000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG1 PUSH2 0xDD4 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0xDE8 PUSH1 0x6 SLOAD ADDRESS PUSH4 0x53389072 PUSH1 0xE0 SHL PUSH2 0xE08 JUMP JUMPDEST SWAP1 POP PUSH2 0xDF4 DUP2 DUP4 PUSH2 0xEE6 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xE04 DUP3 DUP3 PUSH2 0xF47 JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH2 0xE10 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0xE18 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0xE2F DUP6 DUP6 DUP6 DUP5 PUSH2 0x1074 SWAP1 SWAP4 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0xE50 DUP3 DUP5 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH2 0xE67 DUP2 DUP5 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xE83 DUP3 DUP5 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH2 0xE90 DUP4 PUSH1 0x80 ADD MLOAD PUSH2 0x1149 JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP2 MLOAD DUP2 LT ISZERO PUSH2 0xED3 JUMPI PUSH2 0xEC6 DUP3 DUP3 DUP2 MLOAD DUP2 LT PUSH2 0xEAB JUMPI INVALID JUMPDEST PUSH1 0x20 MUL PUSH1 0x20 ADD ADD MLOAD DUP6 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST DUP1 DUP1 PUSH1 0x1 ADD SWAP2 POP POP PUSH2 0xE93 JUMP JUMPDEST POP PUSH2 0xEE1 DUP4 PUSH1 0x80 ADD MLOAD PUSH2 0x1157 JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF15 PUSH1 0x3 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 DUP5 PUSH2 0x1165 JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP SWAP1 JUMP JUMPDEST DUP1 PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x5 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x101D JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x5265717565737420697320616C72656164792070656E64696E67000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP3 PUSH1 0x5 PUSH1 0x0 DUP5 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP POP POP POP JUMP JUMPDEST PUSH2 0x107C PUSH2 0x1936 JUMP JUMPDEST PUSH2 0x108C DUP6 PUSH1 0x80 ADD MLOAD PUSH2 0x100 PUSH2 0x13EE JUMP JUMPDEST POP DUP4 DUP6 PUSH1 0x0 ADD DUP2 DUP2 MSTORE POP POP DUP3 DUP6 PUSH1 0x20 ADD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE POP POP DUP2 DUP6 PUSH1 0x40 ADD SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 DUP2 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE POP POP DUP5 SWAP1 POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH2 0x1131 DUP3 PUSH1 0x3 DUP4 MLOAD PUSH2 0x1442 JUMP JUMPDEST PUSH2 0x1144 DUP2 DUP4 PUSH2 0x1587 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0x1154 DUP2 PUSH1 0x4 PUSH2 0x15A9 JUMP JUMPDEST POP JUMP JUMPDEST PUSH2 0x1162 DUP2 PUSH1 0x7 PUSH2 0x15A9 JUMP JUMPDEST POP JUMP JUMPDEST PUSH1 0x0 ADDRESS PUSH1 0x4 SLOAD PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP1 POP PUSH1 0x4 SLOAD DUP4 PUSH1 0x60 ADD DUP2 DUP2 MSTORE POP POP DUP4 PUSH1 0x5 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP DUP1 PUSH32 0xB5E6E01E79F91267DC17B4E6314D5D4D03593D2CEEE0FBB452B750BD70EA5AF9 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x2 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x4000AEA0 DUP6 DUP5 PUSH2 0x1287 DUP8 PUSH2 0x15CB JUMP JUMPDEST PUSH1 0x40 MLOAD DUP5 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x12F8 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x12DD JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x1325 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP5 POP POP POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x1346 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x135A JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x1370 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x13D6 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1A5B PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x4 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x13F6 PUSH2 0x1A23 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP4 DUP2 PUSH2 0x1402 JUMPI INVALID JUMPDEST MOD EQ PUSH2 0x141B JUMPI PUSH1 0x20 DUP3 DUP2 PUSH2 0x1412 JUMPI INVALID JUMPDEST MOD PUSH1 0x20 SUB DUP3 ADD SWAP2 POP JUMPDEST DUP2 DUP4 PUSH1 0x20 ADD DUP2 DUP2 MSTORE POP POP PUSH1 0x40 MLOAD DUP1 DUP5 MSTORE PUSH1 0x0 DUP2 MSTORE DUP3 DUP2 ADD PUSH1 0x20 ADD PUSH1 0x40 MSTORE POP DUP3 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x17 DUP2 GT PUSH2 0x146F JUMPI PUSH2 0x1469 DUP2 PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL PUSH1 0xFF AND OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1582 JUMP JUMPDEST PUSH1 0xFF DUP2 GT PUSH2 0x14B1 JUMPI PUSH2 0x1494 PUSH1 0x18 PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x14AB DUP2 PUSH1 0x1 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1581 JUMP JUMPDEST PUSH2 0xFFFF DUP2 GT PUSH2 0x14F4 JUMPI PUSH2 0x14D7 PUSH1 0x19 PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x14EE DUP2 PUSH1 0x2 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1580 JUMP JUMPDEST PUSH4 0xFFFFFFFF DUP2 GT PUSH2 0x1539 JUMPI PUSH2 0x151C PUSH1 0x1A PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1533 DUP2 PUSH1 0x4 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x157F JUMP JUMPDEST PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT PUSH2 0x157E JUMPI PUSH2 0x1565 PUSH1 0x1B PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x157C DUP2 PUSH1 0x8 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP JUMPDEST JUMPDEST JUMPDEST JUMPDEST JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0x158F PUSH2 0x1A23 JUMP JUMPDEST PUSH2 0x15A1 DUP4 DUP5 PUSH1 0x0 ADD MLOAD MLOAD DUP5 DUP6 MLOAD PUSH2 0x178E JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH2 0x15C6 PUSH1 0x1F PUSH1 0x5 DUP4 PUSH1 0xFF AND SWAP1 SHL OR DUP4 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x60 PUSH4 0x40429946 PUSH1 0xE0 SHL PUSH1 0x0 DUP1 DUP5 PUSH1 0x0 ADD MLOAD DUP6 PUSH1 0x20 ADD MLOAD DUP7 PUSH1 0x40 ADD MLOAD DUP8 PUSH1 0x60 ADD MLOAD PUSH1 0x1 DUP10 PUSH1 0x80 ADD MLOAD PUSH1 0x0 ADD MLOAD PUSH1 0x40 MLOAD PUSH1 0x24 ADD DUP1 DUP10 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP9 DUP2 MSTORE PUSH1 0x20 ADD DUP8 DUP2 MSTORE PUSH1 0x20 ADD DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP6 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x16AF JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1694 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x16DC JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP10 POP POP POP POP POP POP POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND PUSH1 0x20 DUP3 ADD DUP1 MLOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 DUP2 DUP4 AND OR DUP4 MSTORE POP POP POP POP SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0x1754 PUSH2 0x1A23 JUMP JUMPDEST PUSH2 0x1764 DUP4 DUP5 PUSH1 0x0 ADD MLOAD MLOAD DUP5 PUSH2 0x1847 JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH2 0x1774 PUSH2 0x1A23 JUMP JUMPDEST PUSH2 0x1785 DUP5 DUP6 PUSH1 0x0 ADD MLOAD MLOAD DUP6 DUP6 PUSH2 0x1895 JUMP JUMPDEST SWAP1 POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x1796 PUSH2 0x1A23 JUMP JUMPDEST DUP3 MLOAD DUP3 GT ISZERO PUSH2 0x17A4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP5 PUSH1 0x20 ADD MLOAD DUP3 DUP6 ADD GT ISZERO PUSH2 0x17CF JUMPI PUSH2 0x17CE DUP6 PUSH1 0x2 PUSH2 0x17C8 DUP9 PUSH1 0x20 ADD MLOAD DUP9 DUP8 ADD PUSH2 0x18F6 JUMP JUMPDEST MUL PUSH2 0x1912 JUMP JUMPDEST JUMPDEST PUSH1 0x0 DUP1 DUP7 MLOAD DUP1 MLOAD DUP8 PUSH1 0x20 DUP4 ADD ADD SWAP4 POP DUP1 DUP9 DUP8 ADD GT ISZERO PUSH2 0x17EE JUMPI DUP8 DUP7 ADD DUP3 MSTORE JUMPDEST PUSH1 0x20 DUP8 ADD SWAP3 POP POP POP JUMPDEST PUSH1 0x20 DUP5 LT PUSH2 0x181A JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP5 SUB SWAP4 POP PUSH2 0x17F7 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 DUP6 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB SWAP1 POP DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP2 DUP2 OR DUP6 MSTORE POP POP DUP8 SWAP4 POP POP POP POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH2 0x184F PUSH2 0x1A23 JUMP JUMPDEST DUP4 PUSH1 0x20 ADD MLOAD DUP4 LT PUSH2 0x186C JUMPI PUSH2 0x186B DUP5 PUSH1 0x2 DUP7 PUSH1 0x20 ADD MLOAD MUL PUSH2 0x1912 JUMP JUMPDEST JUMPDEST DUP4 MLOAD DUP1 MLOAD PUSH1 0x20 DUP6 DUP4 ADD ADD DUP5 DUP2 MSTORE8 DUP2 DUP7 EQ ISZERO PUSH2 0x1888 JUMPI PUSH1 0x1 DUP3 ADD DUP4 MSTORE JUMPDEST POP POP POP DUP4 SWAP1 POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x189D PUSH2 0x1A23 JUMP JUMPDEST DUP5 PUSH1 0x20 ADD MLOAD DUP5 DUP4 ADD GT ISZERO PUSH2 0x18BB JUMPI PUSH2 0x18BA DUP6 PUSH1 0x2 DUP7 DUP6 ADD MUL PUSH2 0x1912 JUMP JUMPDEST JUMPDEST PUSH1 0x0 PUSH1 0x1 DUP4 PUSH2 0x100 EXP SUB SWAP1 POP DUP6 MLOAD DUP4 DUP7 DUP3 ADD ADD DUP6 DUP4 NOT DUP3 MLOAD AND OR DUP2 MSTORE DUP2 MLOAD DUP6 DUP9 ADD GT ISZERO PUSH2 0x18E8 JUMPI DUP5 DUP8 ADD DUP3 MSTORE JUMPDEST POP POP DUP6 SWAP2 POP POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 DUP4 GT ISZERO PUSH2 0x1908 JUMPI DUP3 SWAP1 POP PUSH2 0x190C JUMP JUMPDEST DUP2 SWAP1 POP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x60 DUP3 PUSH1 0x0 ADD MLOAD SWAP1 POP PUSH2 0x1925 DUP4 DUP4 PUSH2 0x13EE JUMP JUMPDEST POP PUSH2 0x1930 DUP4 DUP3 PUSH2 0x1587 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0xA0 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x0 DUP1 NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x199D PUSH2 0x1A23 JUMP JUMPDEST DUP2 MSTORE POP SWAP1 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH1 0x1F LT PUSH2 0x19E4 JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x1A12 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x1A12 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x1A11 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x19F6 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH2 0x1A1F SWAP2 SWAP1 PUSH2 0x1A3D JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x60 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x1A56 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH2 0x1A3E JUMP JUMPDEST POP SWAP1 JUMP INVALID PUSH22 0x6E61626C6520746F207472616E73666572416E644361 PUSH13 0x6C20746F206F7261636C65536F PUSH22 0x726365206D75737420626520746865206F7261636C65 KECCAK256 PUSH16 0x66207468652072657175657374687474 PUSH17 0x733A2F2F6D696E2D6170692E6372797074 PUSH16 0x636F6D706172652E636F6D2F64617461 0x2F PUSH17 0x726963653F6673796D3D45544826747379 PUSH14 0x733D5553442C4555522C4A5059A2 PUSH5 0x6970667358 0x22 SLT KECCAK256 ISZERO 0xDA 0xDC SWAP8 0x28 0xEE 0xC5 0xA9 0xD5 0xBC CALLDATASIZE 0xB6 DUP4 REVERT EQ 0xF DUP11 0xEC PUSH25 0xF866BACADDE0D326B03A4899F964736F6C634300060C003300 ", + "sourceMap": "59:2852:6:-:0;;;1211:1:1;1180:32;;447:158:6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;521:24;539:5;521:17;;;:24;;:::i;:::-;551:27;570:7;551:18;;;:27;;:::i;:::-;593:7;584:6;:16;;;;447:158;;;59:2852;;4882:94:1;4965:5;4939:4;;:32;;;;;;;;;;;;;;;;;;4882:94;:::o;4660:108::-;4755:7;4720:6;;:43;;;;;;;;;;;;;;;;;;4660:108;:::o;59:2852:6:-;;;;;;;" + }, + "deployedBytecode": { + "immutableReferences": {}, + "linkReferences": {}, + "object": "608060405234801561001057600080fd5b50600436106100a95760003560e01c806383db5cbc1161007157806383db5cbc1461029c5780638dc654a2146103615780639d1b464a1461036b578063c2fb8523146103ee578063e89855ba146104b3578063e8d5359d14610578576100a9565b80633df4ddf4146100ae57806353389072146100cc5780635591a6081461010e5780635a8ac02d1461019957806374961d4d146101b7575b600080fd5b6100b66105c6565b6040518082815260200191505060405180910390f35b61010c600480360360608110156100e257600080fd5b810190808035906020019092919080359060200190929190803590602001909291905050506105cc565b005b610197600480360360a081101561012457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919050505061072a565b005b6101a16107d9565b6040518082815260200191505060405180910390f35b61029a600480360360608110156101cd57600080fd5b81019080803590602001906401000000008111156101ea57600080fd5b8201836020820111156101fc57600080fd5b8035906020019184600183028401116401000000008311171561021e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506107df565b005b61035f600480360360408110156102b257600080fd5b81019080803590602001906401000000008111156102cf57600080fd5b8201836020820111156102e157600080fd5b8035906020019184600183028401116401000000008311171561030357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610925565b005b610369610934565b005b610373610b02565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103b3578082015181840152602081019050610398565b50505050905090810190601f1680156103e05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104b16004803603604081101561040457600080fd5b81019080803590602001909291908035906020019064010000000081111561042b57600080fd5b82018360208201111561043d57600080fd5b8035906020019184600183028401116401000000008311171561045f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610ba0565b005b610576600480360360408110156104c957600080fd5b81019080803590602001906401000000008111156104e657600080fd5b8201836020820111156104f857600080fd5b8035906020019184600183028401116401000000008311171561051a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610d65565b005b6105c46004803603604081101561058e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610dfa565b005b60085481565b826005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610684576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180611a7e6028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a28183857fd368a628c6f427add4c36c69828a9be4d937a803adfda79c1dbf7eb26cdf4bc460405160405180910390a4826008819055508160098190555050505050565b60008590508073ffffffffffffffffffffffffffffffffffffffff16636ee4d553868686866040518563ffffffff1660e01b815260040180858152602001848152602001837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001828152602001945050505050600060405180830381600087803b1580156107b957600080fd5b505af11580156107cd573d6000803e3d6000fd5b50505050505050505050565b60095481565b6107e7611936565b6107fb6006548363c2fb852360e01b610e08565b905061085f6040518060400160405280600381526020017f6765740000000000000000000000000000000000000000000000000000000000815250604051806080016040528060478152602001611aa66047913983610e399092919063ffffffff16565b6060600167ffffffffffffffff8111801561087957600080fd5b506040519080825280602002602001820160405280156108ad57816020015b60608152602001906001900390816108985790505b50905084816000815181106108be57fe5b60200260200101819052506109136040518060400160405280600481526020017f70617468000000000000000000000000000000000000000000000000000000008152508284610e6c9092919063ffffffff16565b61091d8285610ee6565b505050505050565b6109308282306107df565b5050565b600061093e610f1d565b90508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb338373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b1580156109c457600080fd5b505afa1580156109d8573d6000803e3d6000fd5b505050506040513d60208110156109ee57600080fd5b81019080805190602001909291905050506040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015610a5257600080fd5b505af1158015610a66573d6000803e3d6000fd5b505050506040513d6020811015610a7c57600080fd5b8101908080519060200190929190505050610aff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f556e61626c6520746f207472616e73666572000000000000000000000000000081525060200191505060405180910390fd5b50565b60078054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b985780601f10610b6d57610100808354040283529160200191610b98565b820191906000526020600020905b815481529060010190602001808311610b7b57829003601f168201915b505050505081565b816005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610c58576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180611a7e6028913960400191505060405180910390fd5b6005600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055807f7cc135e0cebb02c3480ae5d74d377283180a2601f8f644edf7987b009316c63a60405160405180910390a2816040518082805190602001908083835b60208310610cef5780518252602082019150602081019050602083039250610ccc565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020837f1a111c5dcf9a71088bd5e1797fdfaf399fec2afbb24aca247e4e3e9f4b61df9160405160405180910390a38160079080519060200190610d5f9291906119a3565b50505050565b7ecb39d6c2c520f0597db0021367767c48fef2964cf402d3c9e9d4df12e4396460405180806020018281038252600b8152602001807f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525060200191505060405180910390a1610dd4611936565b610de860065430635338907260e01b610e08565b9050610df48183610ee6565b50505050565b610e048282610f47565b5050565b610e10611936565b610e18611936565b610e2f85858584611074909392919063ffffffff16565b9150509392505050565b610e5082846080015161112490919063ffffffff16565b610e6781846080015161112490919063ffffffff16565b505050565b610e8382846080015161112490919063ffffffff16565b610e908360800151611149565b60005b8151811015610ed357610ec6828281518110610eab57fe5b6020026020010151856080015161112490919063ffffffff16565b8080600101915050610e93565b50610ee18360800151611157565b505050565b6000610f15600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168484611165565b905092915050565b6000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b80600073ffffffffffffffffffffffffffffffffffffffff166005600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461101d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601a8152602001807f5265717565737420697320616c72656164792070656e64696e6700000000000081525060200191505060405180910390fd5b826005600084815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505050565b61107c611936565b61108c85608001516101006113ee565b508385600001818152505082856020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508185604001907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681525050849050949350505050565b6111318260038351611442565b611144818361158790919063ffffffff16565b505050565b6111548160046115a9565b50565b6111628160076115a9565b50565b600030600454604051602001808373ffffffffffffffffffffffffffffffffffffffff1660601b815260140182815260200192505050604051602081830303815290604052805190602001209050600454836060018181525050836005600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550807fb5e6e01e79f91267dc17b4e6314d5d4d03593d2ceee0fbb452b750bd70ea5af960405160405180910390a2600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634000aea08584611287876115cb565b6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156112f85780820151818401526020810190506112dd565b50505050905090810190601f1680156113255780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b15801561134657600080fd5b505af115801561135a573d6000803e3d6000fd5b505050506040513d602081101561137057600080fd5b81019080805190602001909291905050506113d6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180611a5b6023913960400191505060405180910390fd5b60016004600082825401925050819055509392505050565b6113f6611a23565b60006020838161140257fe5b061461141b576020828161141257fe5b06602003820191505b81836020018181525050604051808452600081528281016020016040525082905092915050565b6017811161146f576114698160058460ff16901b60ff16178461174c90919063ffffffff16565b50611582565b60ff81116114b157611494601860058460ff16901b178461174c90919063ffffffff16565b506114ab8160018561176c9092919063ffffffff16565b50611581565b61ffff81116114f4576114d7601960058460ff16901b178461174c90919063ffffffff16565b506114ee8160028561176c9092919063ffffffff16565b50611580565b63ffffffff81116115395761151c601a60058460ff16901b178461174c90919063ffffffff16565b506115338160048561176c9092919063ffffffff16565b5061157f565b67ffffffffffffffff811161157e57611565601b60058460ff16901b178461174c90919063ffffffff16565b5061157c8160088561176c9092919063ffffffff16565b505b5b5b5b5b505050565b61158f611a23565b6115a18384600001515184855161178e565b905092915050565b6115c6601f60058360ff16901b178361174c90919063ffffffff16565b505050565b6060634042994660e01b60008084600001518560200151866040015187606001516001896080015160000151604051602401808973ffffffffffffffffffffffffffffffffffffffff1681526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff168152602001857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156116af578082015181840152602081019050611694565b50505050905090810190601f1680156116dc5780820380516001836020036101000a031916815260200191505b509950505050505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050919050565b611754611a23565b6117648384600001515184611847565b905092915050565b611774611a23565b611785848560000151518585611895565b90509392505050565b611796611a23565b82518211156117a457600080fd5b846020015182850111156117cf576117ce8560026117c888602001518887016118f6565b02611912565b5b6000808651805187602083010193508088870111156117ee5787860182525b60208701925050505b6020841061181a57805182526020820191506020810190506020840393506117f7565b60006001856020036101000a03905080198251168184511681811785525050879350505050949350505050565b61184f611a23565b8360200151831061186c5761186b846002866020015102611912565b5b8351805160208583010184815381861415611888576001820183525b5050508390509392505050565b61189d611a23565b846020015184830111156118bb576118ba85600286850102611912565b5b60006001836101000a03905085518386820101858319825116178152815185880111156118e85784870182525b505085915050949350505050565b6000818311156119085782905061190c565b8190505b92915050565b60608260000151905061192583836113ee565b506119308382611587565b50505050565b6040518060a0016040528060008019168152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526020016000815260200161199d611a23565b81525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106119e457805160ff1916838001178555611a12565b82800160010185558215611a12579182015b82811115611a115782518255916020019190600101906119f6565b5b509050611a1f9190611a3d565b5090565b604051806040016040528060608152602001600081525090565b5b80821115611a56576000816000905550600101611a3e565b509056fe756e61626c6520746f207472616e73666572416e6443616c6c20746f206f7261636c65536f75726365206d75737420626520746865206f7261636c65206f6620746865207265717565737468747470733a2f2f6d696e2d6170692e63727970746f636f6d706172652e636f6d2f646174612f70726963653f6673796d3d455448267473796d733d5553442c4555522c4a5059a264697066735822122015dadc9728eec5a9d5bc36b683fd140f8aec78f866bacadde0d326b03a4899f964736f6c634300060c0033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x83DB5CBC GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x83DB5CBC EQ PUSH2 0x29C JUMPI DUP1 PUSH4 0x8DC654A2 EQ PUSH2 0x361 JUMPI DUP1 PUSH4 0x9D1B464A EQ PUSH2 0x36B JUMPI DUP1 PUSH4 0xC2FB8523 EQ PUSH2 0x3EE JUMPI DUP1 PUSH4 0xE89855BA EQ PUSH2 0x4B3 JUMPI DUP1 PUSH4 0xE8D5359D EQ PUSH2 0x578 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x3DF4DDF4 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x53389072 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x5591A608 EQ PUSH2 0x10E JUMPI DUP1 PUSH4 0x5A8AC02D EQ PUSH2 0x199 JUMPI DUP1 PUSH4 0x74961D4D EQ PUSH2 0x1B7 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x5C6 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x10C PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0xE2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x5CC JUMP JUMPDEST STOP JUMPDEST PUSH2 0x197 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0xA0 DUP2 LT ISZERO PUSH2 0x124 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x72A JUMP JUMPDEST STOP JUMPDEST PUSH2 0x1A1 PUSH2 0x7D9 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x29A PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x1CD JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x1EA JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x1FC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x21E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x7DF JUMP JUMPDEST STOP JUMPDEST PUSH2 0x35F PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x2B2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x2CF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x2E1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x303 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x925 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x369 PUSH2 0x934 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x373 PUSH2 0xB02 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x3B3 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x398 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x3E0 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x4B1 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x404 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x42B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x43D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x45F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0xBA0 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x576 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x4C9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x4E6 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x4F8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x51A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xD65 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x5C4 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x58E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xDFA JUMP JUMPDEST STOP JUMPDEST PUSH1 0x8 SLOAD DUP2 JUMP JUMPDEST DUP3 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x684 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1A7E PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 DUP2 DUP4 DUP6 PUSH32 0xD368A628C6F427ADD4C36C69828A9BE4D937A803ADFDA79C1DBF7EB26CDF4BC4 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG4 DUP3 PUSH1 0x8 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x9 DUP2 SWAP1 SSTORE POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP6 SWAP1 POP DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x6EE4D553 DUP7 DUP7 DUP7 DUP7 PUSH1 0x40 MLOAD DUP6 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP5 POP POP POP POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x7B9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x7CD JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x9 SLOAD DUP2 JUMP JUMPDEST PUSH2 0x7E7 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0x7FB PUSH1 0x6 SLOAD DUP4 PUSH4 0xC2FB8523 PUSH1 0xE0 SHL PUSH2 0xE08 JUMP JUMPDEST SWAP1 POP PUSH2 0x85F PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x3 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x6765740000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x40 MLOAD DUP1 PUSH1 0x80 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x47 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x1AA6 PUSH1 0x47 SWAP2 CODECOPY DUP4 PUSH2 0xE39 SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x60 PUSH1 0x1 PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT DUP1 ISZERO PUSH2 0x879 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD SWAP1 DUP1 DUP3 MSTORE DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD DUP3 ADD PUSH1 0x40 MSTORE DUP1 ISZERO PUSH2 0x8AD JUMPI DUP2 PUSH1 0x20 ADD JUMPDEST PUSH1 0x60 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 PUSH1 0x1 SWAP1 SUB SWAP1 DUP2 PUSH2 0x898 JUMPI SWAP1 POP JUMPDEST POP SWAP1 POP DUP5 DUP2 PUSH1 0x0 DUP2 MLOAD DUP2 LT PUSH2 0x8BE JUMPI INVALID JUMPDEST PUSH1 0x20 MUL PUSH1 0x20 ADD ADD DUP2 SWAP1 MSTORE POP PUSH2 0x913 PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x4 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x7061746800000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP DUP3 DUP5 PUSH2 0xE6C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH2 0x91D DUP3 DUP6 PUSH2 0xEE6 JUMP JUMPDEST POP POP POP POP POP POP JUMP JUMPDEST PUSH2 0x930 DUP3 DUP3 ADDRESS PUSH2 0x7DF JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x93E PUSH2 0xF1D JUMP JUMPDEST SWAP1 POP DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xA9059CBB CALLER DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x70A08231 ADDRESS PUSH1 0x40 MLOAD DUP3 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP7 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x9C4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS STATICCALL ISZERO DUP1 ISZERO PUSH2 0x9D8 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x9EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0x40 MLOAD DUP4 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0xA52 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0xA66 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0xA7C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xAFF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x12 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x556E61626C6520746F207472616E736665720000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH1 0x7 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 ISZERO PUSH2 0xB98 JUMPI DUP1 PUSH1 0x1F LT PUSH2 0xB6D JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0xB98 JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xB7B JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP DUP2 JUMP JUMPDEST DUP2 PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xC58 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x28 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1A7E PUSH1 0x28 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x5 PUSH1 0x0 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE DUP1 PUSH32 0x7CC135E0CEBB02C3480AE5D74D377283180A2601F8F644EDF7987B009316C63A PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 DUP2 PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0xCEF JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0xCCC JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 KECCAK256 DUP4 PUSH32 0x1A111C5DCF9A71088BD5E1797FDFAF399FEC2AFBB24ACA247E4E3E9F4B61DF91 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 DUP2 PUSH1 0x7 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH2 0xD5F SWAP3 SWAP2 SWAP1 PUSH2 0x19A3 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH31 0xCB39D6C2C520F0597DB0021367767C48FEF2964CF402D3C9E9D4DF12E43964 PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0xB DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x68656C6C6F20776F726C64000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG1 PUSH2 0xDD4 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0xDE8 PUSH1 0x6 SLOAD ADDRESS PUSH4 0x53389072 PUSH1 0xE0 SHL PUSH2 0xE08 JUMP JUMPDEST SWAP1 POP PUSH2 0xDF4 DUP2 DUP4 PUSH2 0xEE6 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH2 0xE04 DUP3 DUP3 PUSH2 0xF47 JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH2 0xE10 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0xE18 PUSH2 0x1936 JUMP JUMPDEST PUSH2 0xE2F DUP6 DUP6 DUP6 DUP5 PUSH2 0x1074 SWAP1 SWAP4 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0xE50 DUP3 DUP5 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH2 0xE67 DUP2 DUP5 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xE83 DUP3 DUP5 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH2 0xE90 DUP4 PUSH1 0x80 ADD MLOAD PUSH2 0x1149 JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP2 MLOAD DUP2 LT ISZERO PUSH2 0xED3 JUMPI PUSH2 0xEC6 DUP3 DUP3 DUP2 MLOAD DUP2 LT PUSH2 0xEAB JUMPI INVALID JUMPDEST PUSH1 0x20 MUL PUSH1 0x20 ADD ADD MLOAD DUP6 PUSH1 0x80 ADD MLOAD PUSH2 0x1124 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST DUP1 DUP1 PUSH1 0x1 ADD SWAP2 POP POP PUSH2 0xE93 JUMP JUMPDEST POP PUSH2 0xEE1 DUP4 PUSH1 0x80 ADD MLOAD PUSH2 0x1157 JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF15 PUSH1 0x3 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 DUP5 PUSH2 0x1165 JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP SWAP1 JUMP JUMPDEST DUP1 PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x5 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x101D JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x5265717565737420697320616C72656164792070656E64696E67000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP3 PUSH1 0x5 PUSH1 0x0 DUP5 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP POP POP POP JUMP JUMPDEST PUSH2 0x107C PUSH2 0x1936 JUMP JUMPDEST PUSH2 0x108C DUP6 PUSH1 0x80 ADD MLOAD PUSH2 0x100 PUSH2 0x13EE JUMP JUMPDEST POP DUP4 DUP6 PUSH1 0x0 ADD DUP2 DUP2 MSTORE POP POP DUP3 DUP6 PUSH1 0x20 ADD SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE POP POP DUP2 DUP6 PUSH1 0x40 ADD SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 DUP2 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE POP POP DUP5 SWAP1 POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH2 0x1131 DUP3 PUSH1 0x3 DUP4 MLOAD PUSH2 0x1442 JUMP JUMPDEST PUSH2 0x1144 DUP2 DUP4 PUSH2 0x1587 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0x1154 DUP2 PUSH1 0x4 PUSH2 0x15A9 JUMP JUMPDEST POP JUMP JUMPDEST PUSH2 0x1162 DUP2 PUSH1 0x7 PUSH2 0x15A9 JUMP JUMPDEST POP JUMP JUMPDEST PUSH1 0x0 ADDRESS PUSH1 0x4 SLOAD PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP1 POP PUSH1 0x4 SLOAD DUP4 PUSH1 0x60 ADD DUP2 DUP2 MSTORE POP POP DUP4 PUSH1 0x5 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP DUP1 PUSH32 0xB5E6E01E79F91267DC17B4E6314D5D4D03593D2CEEE0FBB452B750BD70EA5AF9 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x2 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0x4000AEA0 DUP6 DUP5 PUSH2 0x1287 DUP8 PUSH2 0x15CB JUMP JUMPDEST PUSH1 0x40 MLOAD DUP5 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x12F8 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x12DD JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x1325 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP5 POP POP POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x1346 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x135A JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x1370 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x13D6 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x23 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x1A5B PUSH1 0x23 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x4 PUSH1 0x0 DUP3 DUP3 SLOAD ADD SWAP3 POP POP DUP2 SWAP1 SSTORE POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x13F6 PUSH2 0x1A23 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP4 DUP2 PUSH2 0x1402 JUMPI INVALID JUMPDEST MOD EQ PUSH2 0x141B JUMPI PUSH1 0x20 DUP3 DUP2 PUSH2 0x1412 JUMPI INVALID JUMPDEST MOD PUSH1 0x20 SUB DUP3 ADD SWAP2 POP JUMPDEST DUP2 DUP4 PUSH1 0x20 ADD DUP2 DUP2 MSTORE POP POP PUSH1 0x40 MLOAD DUP1 DUP5 MSTORE PUSH1 0x0 DUP2 MSTORE DUP3 DUP2 ADD PUSH1 0x20 ADD PUSH1 0x40 MSTORE POP DUP3 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x17 DUP2 GT PUSH2 0x146F JUMPI PUSH2 0x1469 DUP2 PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL PUSH1 0xFF AND OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1582 JUMP JUMPDEST PUSH1 0xFF DUP2 GT PUSH2 0x14B1 JUMPI PUSH2 0x1494 PUSH1 0x18 PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x14AB DUP2 PUSH1 0x1 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1581 JUMP JUMPDEST PUSH2 0xFFFF DUP2 GT PUSH2 0x14F4 JUMPI PUSH2 0x14D7 PUSH1 0x19 PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x14EE DUP2 PUSH1 0x2 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1580 JUMP JUMPDEST PUSH4 0xFFFFFFFF DUP2 GT PUSH2 0x1539 JUMPI PUSH2 0x151C PUSH1 0x1A PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x1533 DUP2 PUSH1 0x4 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x157F JUMP JUMPDEST PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT PUSH2 0x157E JUMPI PUSH2 0x1565 PUSH1 0x1B PUSH1 0x5 DUP5 PUSH1 0xFF AND SWAP1 SHL OR DUP5 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP PUSH2 0x157C DUP2 PUSH1 0x8 DUP6 PUSH2 0x176C SWAP1 SWAP3 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP JUMPDEST JUMPDEST JUMPDEST JUMPDEST JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0x158F PUSH2 0x1A23 JUMP JUMPDEST PUSH2 0x15A1 DUP4 DUP5 PUSH1 0x0 ADD MLOAD MLOAD DUP5 DUP6 MLOAD PUSH2 0x178E JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH2 0x15C6 PUSH1 0x1F PUSH1 0x5 DUP4 PUSH1 0xFF AND SWAP1 SHL OR DUP4 PUSH2 0x174C SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x60 PUSH4 0x40429946 PUSH1 0xE0 SHL PUSH1 0x0 DUP1 DUP5 PUSH1 0x0 ADD MLOAD DUP6 PUSH1 0x20 ADD MLOAD DUP7 PUSH1 0x40 ADD MLOAD DUP8 PUSH1 0x60 ADD MLOAD PUSH1 0x1 DUP10 PUSH1 0x80 ADD MLOAD PUSH1 0x0 ADD MLOAD PUSH1 0x40 MLOAD PUSH1 0x24 ADD DUP1 DUP10 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP9 DUP2 MSTORE PUSH1 0x20 ADD DUP8 DUP2 MSTORE PUSH1 0x20 ADD DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP6 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x16AF JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1694 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x16DC JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP10 POP POP POP POP POP POP POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND PUSH1 0x20 DUP3 ADD DUP1 MLOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 DUP2 DUP4 AND OR DUP4 MSTORE POP POP POP POP SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0x1754 PUSH2 0x1A23 JUMP JUMPDEST PUSH2 0x1764 DUP4 DUP5 PUSH1 0x0 ADD MLOAD MLOAD DUP5 PUSH2 0x1847 JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH2 0x1774 PUSH2 0x1A23 JUMP JUMPDEST PUSH2 0x1785 DUP5 DUP6 PUSH1 0x0 ADD MLOAD MLOAD DUP6 DUP6 PUSH2 0x1895 JUMP JUMPDEST SWAP1 POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x1796 PUSH2 0x1A23 JUMP JUMPDEST DUP3 MLOAD DUP3 GT ISZERO PUSH2 0x17A4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP5 PUSH1 0x20 ADD MLOAD DUP3 DUP6 ADD GT ISZERO PUSH2 0x17CF JUMPI PUSH2 0x17CE DUP6 PUSH1 0x2 PUSH2 0x17C8 DUP9 PUSH1 0x20 ADD MLOAD DUP9 DUP8 ADD PUSH2 0x18F6 JUMP JUMPDEST MUL PUSH2 0x1912 JUMP JUMPDEST JUMPDEST PUSH1 0x0 DUP1 DUP7 MLOAD DUP1 MLOAD DUP8 PUSH1 0x20 DUP4 ADD ADD SWAP4 POP DUP1 DUP9 DUP8 ADD GT ISZERO PUSH2 0x17EE JUMPI DUP8 DUP7 ADD DUP3 MSTORE JUMPDEST PUSH1 0x20 DUP8 ADD SWAP3 POP POP POP JUMPDEST PUSH1 0x20 DUP5 LT PUSH2 0x181A JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP5 SUB SWAP4 POP PUSH2 0x17F7 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 DUP6 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB SWAP1 POP DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP2 DUP2 OR DUP6 MSTORE POP POP DUP8 SWAP4 POP POP POP POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH2 0x184F PUSH2 0x1A23 JUMP JUMPDEST DUP4 PUSH1 0x20 ADD MLOAD DUP4 LT PUSH2 0x186C JUMPI PUSH2 0x186B DUP5 PUSH1 0x2 DUP7 PUSH1 0x20 ADD MLOAD MUL PUSH2 0x1912 JUMP JUMPDEST JUMPDEST DUP4 MLOAD DUP1 MLOAD PUSH1 0x20 DUP6 DUP4 ADD ADD DUP5 DUP2 MSTORE8 DUP2 DUP7 EQ ISZERO PUSH2 0x1888 JUMPI PUSH1 0x1 DUP3 ADD DUP4 MSTORE JUMPDEST POP POP POP DUP4 SWAP1 POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH2 0x189D PUSH2 0x1A23 JUMP JUMPDEST DUP5 PUSH1 0x20 ADD MLOAD DUP5 DUP4 ADD GT ISZERO PUSH2 0x18BB JUMPI PUSH2 0x18BA DUP6 PUSH1 0x2 DUP7 DUP6 ADD MUL PUSH2 0x1912 JUMP JUMPDEST JUMPDEST PUSH1 0x0 PUSH1 0x1 DUP4 PUSH2 0x100 EXP SUB SWAP1 POP DUP6 MLOAD DUP4 DUP7 DUP3 ADD ADD DUP6 DUP4 NOT DUP3 MLOAD AND OR DUP2 MSTORE DUP2 MLOAD DUP6 DUP9 ADD GT ISZERO PUSH2 0x18E8 JUMPI DUP5 DUP8 ADD DUP3 MSTORE JUMPDEST POP POP DUP6 SWAP2 POP POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 DUP4 GT ISZERO PUSH2 0x1908 JUMPI DUP3 SWAP1 POP PUSH2 0x190C JUMP JUMPDEST DUP2 SWAP1 POP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x60 DUP3 PUSH1 0x0 ADD MLOAD SWAP1 POP PUSH2 0x1925 DUP4 DUP4 PUSH2 0x13EE JUMP JUMPDEST POP PUSH2 0x1930 DUP4 DUP3 PUSH2 0x1587 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0xA0 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x0 DUP1 NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x199D PUSH2 0x1A23 JUMP JUMPDEST DUP2 MSTORE POP SWAP1 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH1 0x1F LT PUSH2 0x19E4 JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x1A12 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x1A12 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x1A11 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x19F6 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH2 0x1A1F SWAP2 SWAP1 PUSH2 0x1A3D JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x60 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 DUP2 MSTORE POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x1A56 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH2 0x1A3E JUMP JUMPDEST POP SWAP1 JUMP INVALID PUSH22 0x6E61626C6520746F207472616E73666572416E644361 PUSH13 0x6C20746F206F7261636C65536F PUSH22 0x726365206D75737420626520746865206F7261636C65 KECCAK256 PUSH16 0x66207468652072657175657374687474 PUSH17 0x733A2F2F6D696E2D6170692E6372797074 PUSH16 0x636F6D706172652E636F6D2F64617461 0x2F PUSH17 0x726963653F6673796D3D45544826747379 PUSH14 0x733D5553442C4555522C4A5059A2 PUSH5 0x6970667358 0x22 SLT KECCAK256 ISZERO 0xDA 0xDC SWAP8 0x28 0xEE 0xC5 0xA9 0xD5 0xBC CALLDATASIZE 0xB6 DUP4 REVERT EQ 0xF DUP11 0xEC PUSH25 0xF866BACADDE0D326B03A4899F964736F6C634300060C003300 ", + "sourceMap": "59:2852:6:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;165:20;;;:::i;:::-;;;;;;;;;;;;;;;;;;;2452:255;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1768:332;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;189:21;;;:::i;:::-;;;;;;;;;;;;;;;;;;;770:462;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;609:157;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;2104:207;;;:::i;:::-;;135:25;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2711:198;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1271:493;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;2315:133;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;165:20;;;;:::o;2452:255::-;2582:10;8791:15:1;:27;8807:10;8791:27;;;;;;;;;;;;;;;;;;;;;8777:41;;:10;:41;;;8769:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8888:15;:27;8904:10;8888:27;;;;;;;;;;;;8881:34;;;;;;;;;;;8945:10;8926:30;;;;;;;;;;2652:7:6::1;2644:6;2632:10;2607:53;;;;;;;;;;2674:6;2666:5;:14;;;;2695:7;2686:6;:16;;;;2452:255:::0;;;;:::o;1768:332::-;1932:35;1996:7;1932:72;;2010:9;:29;;;2040:10;2052:8;2062:19;2083:11;2010:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1768:332;;;;;;:::o;189:21::-;;;;:::o;770:462::-;885:28;;:::i;:::-;916:68;938:6;;946:9;957:26;;;916:21;:68::i;:::-;885:99;;990:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:3;:7;;:89;;;;;:::i;:::-;1085:20;1121:1;1108:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1085:38;;1139:9;1129:4;1134:1;1129:7;;;;;;;;;;;;;:19;;;;1154:32;;;;;;;;;;;;;;;;;;1181:4;1154:3;:18;;:32;;;;;:::i;:::-;1192:35;1213:3;1218:8;1192:20;:35::i;:::-;;770:462;;;;;:::o;609:157::-;695:66;726:9;737:8;755:4;695:30;:66::i;:::-;609:157;;:::o;2104:207::-;2141:24;2187:23;:21;:23::i;:::-;2141:70;;2225:5;:14;;;2240:10;2252:5;:15;;;2276:4;2252:30;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2225:58;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2217:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2104:207;:::o;135:25::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;2711:198::-;2816:10;8791:15:1;:27;8807:10;8791:27;;;;;;;;;;;;;;;;;;;;;8777:41;;:10;:41;;;8769:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8888:15;:27;8904:10;8888:27;;;;;;;;;;;;8881:34;;;;;;;;;;;8945:10;8926:30;;;;;;;;;;2870:6:6::1;2841:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2858:10;2841:36;;;;;;;;;;2898:6;2883:12;:21;;;;;;;;;;;;:::i;:::-;;2711:198:::0;;;:::o;1271:493::-;1367:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1392:28;;:::i;:::-;1423:85;1445:6;;1461:4;1468:39;;;1423:21;:85::i;:::-;1392:116;;1724:35;1745:3;1750:8;1724:20;:35::i;:::-;;1271:493;;;:::o;2315:133::-;2395:48;2423:7;2432:10;2395:27;:48::i;:::-;2315:133;;:::o;1815:295:1:-;1963:24;;:::i;:::-;1995:28;;:::i;:::-;2036:69;2051:7;2060:16;2078:26;2036:3;:14;;:69;;;;;;:::i;:::-;2029:76;;;1815:295;;;;;:::o;1956:169:0:-;2058:27;2080:4;2058;:8;;;:21;;:27;;;;:::i;:::-;2091:29;2113:6;2091:4;:8;;;:21;;:29;;;;:::i;:::-;1956:169;;;:::o;3468:301::-;3584:27;3606:4;3584;:8;;;:21;;:27;;;;:::i;:::-;3617:21;:4;:8;;;:19;:21::i;:::-;3649:9;3644:93;3668:7;:14;3664:1;:18;3644:93;;;3697:33;3719:7;3727:1;3719:10;;;;;;;;;;;;;;3697:4;:8;;;:21;;:33;;;;:::i;:::-;3684:3;;;;;;;3644:93;;;;3742:22;:4;:8;;;:20;:22::i;:::-;3468:301;;;:::o;2417:189:1:-;2522:7;2546:55;2577:6;;;;;;;;;;;2586:4;2592:8;2546:22;:55::i;:::-;2539:62;;2417:189;;;;:::o;5341:110::-;5409:7;5441:4;;;;;;;;;;;5426:20;;5341:110;:::o;5964:171::-;6073:10;9190:1;9151:41;;:15;:27;9167:10;9151:27;;;;;;;;;;;;;;;;;;;;;:41;;;9143:80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6123:7:::1;6093:15;:27;6109:10;6093:27;;;;;;;;;;;;:37;;;;;;;;;;;;;;;;;;5964:171:::0;;;:::o;966:365:0:-;1115:24;;:::i;:::-;1147:49;1168:4;:8;;;333:3;1147:20;:49::i;:::-;;1212:3;1202:4;:7;;:13;;;;;1244:16;1221:4;:20;;:39;;;;;;;;;;;1292:17;1266:4;:23;;:43;;;;;;;;;;;;;1322:4;1315:11;;966:365;;;;;;:::o;1859:188:8:-;1957:55;1968:3;351:1;1998:5;1992:19;1957:10;:55::i;:::-;2018:24;2035:5;2018:3;:10;;:24;;;;:::i;:::-;;1859:188;;:::o;2051:129::-;2126:49;2153:3;398:1;2126:26;:49::i;:::-;2051:129;:::o;2313:137::-;2389:56;2416:3;497:1;2389:26;:56::i;:::-;2313:137;:::o;3120:488:1:-;3244:17;3310:4;3316:12;;3293:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3283:47;;;;;;3271:59;;3349:12;;3336:4;:10;;:25;;;;;3396:7;3367:15;:26;3383:9;3367:26;;;;;;;;;;;;:36;;;;;;;;;;;;;;;;;;3433:9;3414:29;;;;;;;;;;3457:4;;;;;;;;;;;:20;;;3478:7;3487:8;3497:19;3511:4;3497:13;:19::i;:::-;3457:60;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3449:108;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3579:1;3563:12;;:17;;;;;;;;;;;3120:488;;;;;:::o;950:395:7:-;1020:13;;:::i;:::-;1062:1;1056:2;1045:8;:13;;;;;;:18;1041:71;;1102:2;1091:8;:13;;;;;;1085:2;:20;1073:32;;;;1041:71;1174:8;1159:3;:12;;:23;;;;;1222:4;1216:11;1246:3;1241;1234:16;1269:1;1264:3;1257:14;1308:8;1303:3;1299:18;1295:2;1291:27;1285:4;1278:41;1197:128;1337:3;1330:10;;950:395;;;;:::o;503:644:8:-;614:2;605:5;:11;602:541;;626:44;663:5;658:1;649:5;:10;;;;648:20;;;626:3;:15;;:44;;;;:::i;:::-;;602:541;;;695:4;686:5;:13;683:460;;709:41;746:2;741:1;732:5;:10;;;;731:17;709:3;:15;;:41;;;;:::i;:::-;;758:23;772:5;779:1;758:3;:13;;:23;;;;;:::i;:::-;;683:460;;;806:6;797:5;:15;794:349;;822:41;859:2;854:1;845:5;:10;;;;844:17;822:3;:15;;:41;;;;:::i;:::-;;871:23;885:5;892:1;871:3;:13;;:23;;;;;:::i;:::-;;794:349;;;919:10;910:5;:19;907:236;;939:41;976:2;971:1;962:5;:10;;;;961:17;939:3;:15;;:41;;;;:::i;:::-;;988:23;1002:5;1009:1;988:3;:13;;:23;;;;;:::i;:::-;;907:236;;;1036:18;1027:5;:27;1024:119;;1064:41;1101:2;1096:1;1087:5;:10;;;;1086:17;1064:3;:15;;:41;;;;:::i;:::-;;1113:23;1127:5;1134:1;1113:3;:13;;:23;;;;;:::i;:::-;;1024:119;907:236;794:349;683:460;602:541;503:644;;;:::o;4504:155:7:-;4581:13;;:::i;:::-;4609:45;4615:3;4620;:7;;;:14;4636:4;4642;:11;4609:5;:45::i;:::-;4602:52;;4504:155;;;;:::o;1151:149:8:-;1254:41;1291:2;1286:1;1277:5;:10;;;;1276:17;1254:3;:15;;:41;;;;:::i;:::-;;1151:149;;:::o;7612:527:1:-;7700:12;7759:29;;;767:1;711;8009:4;:7;;;8024:4;:20;;;8052:4;:23;;;8083:4;:10;;;813:1;8121:4;:8;;;:12;;;7729:405;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7722:412;;7612:527;;;:::o;5819:144:7:-;5893:13;;:::i;:::-;5921:37;5932:3;5937;:7;;;:14;5953:4;5921:10;:37::i;:::-;5914:44;;5819:144;;;;:::o;9511:154::-;9592:13;;:::i;:::-;9620:40;9629:3;9634;:7;;;:14;9650:4;9656:3;9620:8;:40::i;:::-;9613:47;;9511:154;;;;;:::o;2659:1140::-;2754:13;;:::i;:::-;2790:4;:11;2783:3;:18;;2775:27;;;;;;2825:3;:12;;;2819:3;2813;:9;:24;2809:90;;;2847:45;2854:3;2890:1;2859:28;2863:3;:12;;;2883:3;2877;:9;2859:3;:28::i;:::-;:32;2847:6;:45::i;:::-;2809:90;2905:9;2920:8;3014:3;3008:10;3085:6;3079:13;3201:3;3196:2;3188:6;3184:15;3180:25;3172:33;;3285:6;3279:3;3274;3270:13;3267:25;3264:2;;;3327:3;3322;3318:13;3310:6;3303:29;3264:2;3364;3358:4;3354:13;3347:20;;2943:430;;3425:129;3439:2;3432:3;:9;3425:129;;3500:3;3494:10;3488:4;3481:24;3528:2;3520:10;;;;3545:2;3538:9;;;;3450:2;3443:9;;;;3425:129;;;3588:9;3620:1;3613:3;3608:2;:8;3600:3;:17;:21;3588:33;;3679:4;3675:9;3669:3;3663:10;3659:26;3725:4;3718;3712:11;3708:22;3763:7;3753:8;3750:21;3744:4;3737:35;3636:142;;3791:3;3784:10;;;;;2659:1140;;;;;;:::o;4953:619::-;5036:13;;:::i;:::-;5068:3;:12;;;5061:3;:19;5057:69;;5090:29;5097:3;5117:1;5102:3;:12;;;:16;5090:6;:29::i;:::-;5057:69;5212:3;5206:10;5283:6;5277:13;5395:2;5389:3;5381:6;5377:16;5373:25;5419:4;5413;5405:19;5490:6;5485:3;5482:15;5479:2;;;5535:1;5527:6;5523:14;5515:6;5508:30;5479:2;5141:411;;;5564:3;5557:10;;4953:619;;;;;:::o;8618:642::-;8707:13;;:::i;:::-;8744:3;:12;;;8738:3;8732;:9;:24;8728:73;;;8766:28;8773:3;8792:1;8785:3;8779;:9;8778:15;8766:6;:28::i;:::-;8728:73;8807:9;8832:1;8826:3;8819;:10;:14;8807:26;;8919:3;8913:10;9034:3;9028;9020:6;9016:16;9012:26;9090:4;9082;9078:9;9071:4;9065:11;9061:27;9058:37;9052:4;9045:51;9178:6;9172:13;9166:3;9161;9157:13;9154:32;9151:2;;;9221:3;9216;9212:13;9204:6;9197:29;9151:2;8848:392;;9252:3;9245:10;;;8618:642;;;;;;:::o;1897:114::-;1947:4;1967:1;1963;:5;1959:34;;;1985:1;1978:8;;;;1959:34;2005:1;1998:8;;1897:114;;;;;:::o;1740:153::-;1809:19;1831:3;:7;;;1809:29;;1844:19;1849:3;1854:8;1844:4;:19::i;:::-;;1869;1876:3;1881:6;1869;:19::i;:::-;;1740:153;;;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;:::o" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "1389200", + "executionCost": "infinite", + "totalCost": "infinite" + }, + "external": { + "addExternalRequest(address,bytes32)": "22306", + "cancelRequest(address,bytes32,uint256,bytes4,uint256)": "infinite", + "currentPrice()": "infinite", + "first()": "1006", + "fulfillBytes(bytes32,bytes)": "infinite", + "fulfillMultipleParameters(bytes32,bytes32,bytes32)": "infinite", + "requestEthereumPrice(string,uint256)": "infinite", + "requestEthereumPriceByCallback(string,uint256,address)": "infinite", + "requestMultipleParameters(string,uint256)": "infinite", + "second()": "1072", + "withdrawLink()": "infinite" + } + }, + "methodIdentifiers": { + "addExternalRequest(address,bytes32)": "e8d5359d", + "cancelRequest(address,bytes32,uint256,bytes4,uint256)": "5591a608", + "currentPrice()": "9d1b464a", + "first()": "3df4ddf4", + "fulfillBytes(bytes32,bytes)": "c2fb8523", + "fulfillMultipleParameters(bytes32,bytes32,bytes32)": "53389072", + "requestEthereumPrice(string,uint256)": "83db5cbc", + "requestEthereumPriceByCallback(string,uint256,address)": "74961d4d", + "requestMultipleParameters(string,uint256)": "e89855ba", + "second()": "5a8ac02d", + "withdrawLink()": "8dc654a2" + } + }, + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_link", + "type": "address" + }, + { + "internalType": "address", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_specId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "price", + "type": "bytes" + } + ], + "name": "RequestFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "first", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "second", + "type": "bytes32" + } + ], + "name": "RequestMultipleFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "Test", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + } + ], + "name": "addExternalRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + }, + { + "internalType": "bytes4", + "name": "_callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "_expiration", + "type": "uint256" + } + ], + "name": "cancelRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "currentPrice", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "first", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_price", + "type": "bytes" + } + ], + "name": "fulfillBytes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_first", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_second", + "type": "bytes32" + } + ], + "name": "fulfillMultipleParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_currency", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + } + ], + "name": "requestEthereumPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_currency", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_callback", + "type": "address" + } + ], + "name": "requestEthereumPriceByCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_currency", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + } + ], + "name": "requestMultipleParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "second", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawLink", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/evm-contracts/src/v0.6/tests/artifacts/MultiWordConsumer_metadata.json b/evm-contracts/src/v0.6/tests/artifacts/MultiWordConsumer_metadata.json new file mode 100644 index 00000000000..b61565e849d --- /dev/null +++ b/evm-contracts/src/v0.6/tests/artifacts/MultiWordConsumer_metadata.json @@ -0,0 +1,429 @@ +{ + "compiler": { + "version": "0.6.12+commit.27d51765" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_link", + "type": "address" + }, + { + "internalType": "address", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_specId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "ChainlinkRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "price", + "type": "bytes" + } + ], + "name": "RequestFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "first", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "second", + "type": "bytes32" + } + ], + "name": "RequestMultipleFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "Test", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + } + ], + "name": "addExternalRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + }, + { + "internalType": "bytes4", + "name": "_callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "_expiration", + "type": "uint256" + } + ], + "name": "cancelRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "currentPrice", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "first", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_price", + "type": "bytes" + } + ], + "name": "fulfillBytes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_requestId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_first", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_second", + "type": "bytes32" + } + ], + "name": "fulfillMultipleParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_currency", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + } + ], + "name": "requestEthereumPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_currency", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_callback", + "type": "address" + } + ], + "name": "requestEthereumPriceByCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_currency", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_payment", + "type": "uint256" + } + ], + "name": "requestMultipleParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "second", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawLink", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "settings": { + "compilationTarget": { + "localhost/v0.6/tests/MultiWordConsumer.sol": "MultiWordConsumer" + }, + "evmVersion": "istanbul", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs" + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "localhost/v0.6/Chainlink.sol": { + "keccak256": "0x7048bfd6e6fe4e60ea4af01ed44fc0494cec75df75c6b542a6f08ed34c66d39e", + "urls": [ + "bzz-raw://37efc6a4665b537657283652d75919ec8dd3d48d85150b5285041bc9614fec11", + "dweb:/ipfs/QmWKgeJEj222kNefTkauHwM5x58KTxSGgcbLseH9Fq8jed" + ] + }, + "localhost/v0.6/ChainlinkClient.sol": { + "keccak256": "0xd06166f7798c4c8cbd0e2dc642450c9bca39157c688ae95dbe079f666cc44dfa", + "urls": [ + "bzz-raw://672af493da8e01492532d7ca8660362d015f317114e405aa55c85cff24aee31c", + "dweb:/ipfs/QmNZdB2jVwwswrLFr83toTtHM5MPSugKeUdxrxfCJ4mP4K" + ] + }, + "localhost/v0.6/interfaces/ChainlinkRequestInterface.sol": { + "keccak256": "0xb6e293a74be1e484aecad7acb4339ef2a115bf6d502a0f9f1741d53aa2dd06ca", + "urls": [ + "bzz-raw://20b8a561ce38a9b9ff9147fb0511cff512c5411e556d2c1438b13eb682042228", + "dweb:/ipfs/QmT6z7c2jBH5wwfYn4uHVFmwDKwUgnbJLu3n8oDmEnfeQr" + ] + }, + "localhost/v0.6/interfaces/ENSInterface.sol": { + "keccak256": "0xf4998e886147b298eda28b4eacbdc90c58ba63ba475469651f2072e188dd5a64", + "urls": [ + "bzz-raw://c1e2334294a816b7eda9de280e39b9463ebde2db8b242410eb991b2f623b47d4", + "dweb:/ipfs/QmNY5bajahfFRmhBgcMVQ7712zHKxc6HkuN7LaiKtpjb7t" + ] + }, + "localhost/v0.6/interfaces/LinkTokenInterface.sol": { + "keccak256": "0xdbf46b45a4c9f38ba71a0391aed0e7b108854b619f292d907ae537228868bda6", + "urls": [ + "bzz-raw://3ae40466809630c4731e2e3a697d6885727c577aaf260766c8a2f534ad3f6ee8", + "dweb:/ipfs/QmTzpN5yP4Y5jvQ1ohfXFrce3sjzUiSChYJgZj9VvhVohG" + ] + }, + "localhost/v0.6/interfaces/PointerInterface.sol": { + "keccak256": "0x6458d82762d4f13c020a13efdbd9bf14500e147df707184a693aea91449c2f4f", + "urls": [ + "bzz-raw://735950f3a544fc6ef2db92405597169bfb5fdb9df83623c0d99fd3d85de8690d", + "dweb:/ipfs/QmZHxb5Qr7Kw9DHAg4VwEADue9ffNyyhbiyEZ15A5mANUN" + ] + }, + "localhost/v0.6/tests/MultiWordConsumer.sol": { + "keccak256": "0x164888c86f26da0c73f0b692ac8468284adc8f8ac5658f7dd92bb52f05465d33", + "urls": [ + "bzz-raw://c6c5cb38be4b2d10f2792c5ab42b12c4e72a05f2b36557beeaae49cf727f7a8f", + "dweb:/ipfs/QmS1619u3gAWQHjAZKkc9qQoEXxhwJd2ykZLWo9Y2fjdPb" + ] + }, + "localhost/v0.6/vendor/BufferChainlink.sol": { + "keccak256": "0xe4aa364f56414c4326ffe12c1121d591be6ad168afb42b24a823f6d76299dd63", + "urls": [ + "bzz-raw://e3e91a0eddb6fc6c53acdfbd59771deff1678330128d3c98014c668763efb45e", + "dweb:/ipfs/Qmbt5VNT2W2oCN44536JGNuHqAJdmYGqzEFcHvy8W1tAsY" + ] + }, + "localhost/v0.6/vendor/CBORChainlink.sol": { + "keccak256": "0xbb4d8257c1af348cac9828ee531428b148bb726517357fe6a80279ac45b658b5", + "urls": [ + "bzz-raw://8c8c5da0358946437fac595591367066b8d6e5f58414c027a79a093e1f3241c1", + "dweb:/ipfs/QmNQ5TPzaPEbj5kaX17YLuZEmhv8NGfoCrUVK3s6gQuHdA" + ] + }, + "localhost/v0.6/vendor/ENSResolver.sol": { + "keccak256": "0xdddea29d7407c1dbd1e130d885fc1a0934e98f0a7cc9f4d5bfd002bb2cfbcf82", + "urls": [ + "bzz-raw://c4c764d69c47754d7b219fab558bf4be2a6444470ede7aa0ab1e446aea01dbda", + "dweb:/ipfs/QmWp2CNUw9xt8ir2P3LhGHuydUsAXnyZ382U2BUjhoYPvy" + ] + }, + "localhost/v0.6/vendor/SafeMathChainlink.sol": { + "keccak256": "0x5e6948bb332468d8ef0704b4259babc8aef7ce5969d5997c16db8ad806222a0a", + "urls": [ + "bzz-raw://8e2bbda1e1168401d0105cc86bf5302000e8555ebb9b235cd68c148916a452e5", + "dweb:/ipfs/QmbTjbpJr8VfdWfgBknbQov1MGkXXPMysb7eD8bobGAcBV" + ] + } + }, + "version": 1 +} \ No newline at end of file diff --git a/evm-contracts/src/v0.6/vendor/BufferChainlink.sol b/evm-contracts/src/v0.6/vendor/BufferChainlink.sol index 5e94eed30b4..2ef5342b6f0 100644 --- a/evm-contracts/src/v0.6/vendor/BufferChainlink.sol +++ b/evm-contracts/src/v0.6/vendor/BufferChainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** diff --git a/evm-contracts/src/v0.6/vendor/CBORChainlink.sol b/evm-contracts/src/v0.6/vendor/CBORChainlink.sol index ae0e0a7596d..c1d5cd240e4 100644 --- a/evm-contracts/src/v0.6/vendor/CBORChainlink.sol +++ b/evm-contracts/src/v0.6/vendor/CBORChainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import { BufferChainlink } from "./BufferChainlink.sol"; diff --git a/evm-contracts/src/v0.6/vendor/ENSResolver.sol b/evm-contracts/src/v0.6/vendor/ENSResolver.sol index 61b9c068230..a2aff795186 100644 --- a/evm-contracts/src/v0.6/vendor/ENSResolver.sol +++ b/evm-contracts/src/v0.6/vendor/ENSResolver.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; abstract contract ENSResolver { diff --git a/evm-contracts/src/v0.6/vendor/Ownable.sol b/evm-contracts/src/v0.6/vendor/Ownable.sol index 817386b2ba9..f0299db3ec8 100644 --- a/evm-contracts/src/v0.6/vendor/Ownable.sol +++ b/evm-contracts/src/v0.6/vendor/Ownable.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** diff --git a/evm-contracts/src/v0.6/vendor/SafeMathChainlink.sol b/evm-contracts/src/v0.6/vendor/SafeMathChainlink.sol index a69407e9f95..39d73a5e85c 100644 --- a/evm-contracts/src/v0.6/vendor/SafeMathChainlink.sol +++ b/evm-contracts/src/v0.6/vendor/SafeMathChainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** diff --git a/evm-contracts/src/v0.7/Chainlink.sol b/evm-contracts/src/v0.7/Chainlink.sol index 15e835cda75..51cf882b053 100644 --- a/evm-contracts/src/v0.7/Chainlink.sol +++ b/evm-contracts/src/v0.7/Chainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import { CBORChainlink } from "./vendor/CBORChainlink.sol"; @@ -24,21 +25,21 @@ library Chainlink { * @notice Initializes a Chainlink request * @dev Sets the ID, callback address, and callback function signature on the request * @param self The uninitialized request - * @param _id The Job Specification ID - * @param _callbackAddress The callback address - * @param _callbackFunction The callback function signature + * @param jobId The Job Specification ID + * @param callbackAddr The callback address + * @param callbackFunc The callback function signature * @return The initialized request */ function initialize( Request memory self, - bytes32 _id, - address _callbackAddress, - bytes4 _callbackFunction + bytes32 jobId, + address callbackAddr, + bytes4 callbackFunc ) internal pure returns (Chainlink.Request memory) { BufferChainlink.init(self.buf, defaultBufferSize); - self.id = _id; - self.callbackAddress = _callbackAddress; - self.callbackFunctionId = _callbackFunction; + self.id = jobId; + self.callbackAddress = callbackAddr; + self.callbackFunctionId = callbackFunc; return self; } @@ -46,80 +47,80 @@ library Chainlink { * @notice Sets the data for the buffer without encoding CBOR on-chain * @dev CBOR can be closed with curly-brackets {} or they can be left off * @param self The initialized request - * @param _data The CBOR data + * @param data The CBOR data */ - function setBuffer(Request memory self, bytes memory _data) + function setBuffer(Request memory self, bytes memory data) internal pure { - BufferChainlink.init(self.buf, _data.length); - BufferChainlink.append(self.buf, _data); + BufferChainlink.init(self.buf, data.length); + BufferChainlink.append(self.buf, data); } /** * @notice Adds a string value to the request with a given key name * @param self The initialized request - * @param _key The name of the key - * @param _value The string value to add + * @param key The name of the key + * @param value The string value to add */ - function add(Request memory self, string memory _key, string memory _value) + function add(Request memory self, string memory key, string memory value) internal pure { - self.buf.encodeString(_key); - self.buf.encodeString(_value); + self.buf.encodeString(key); + self.buf.encodeString(value); } /** * @notice Adds a bytes value to the request with a given key name * @param self The initialized request - * @param _key The name of the key - * @param _value The bytes value to add + * @param key The name of the key + * @param value The bytes value to add */ - function addBytes(Request memory self, string memory _key, bytes memory _value) + function addBytes(Request memory self, string memory key, bytes memory value) internal pure { - self.buf.encodeString(_key); - self.buf.encodeBytes(_value); + self.buf.encodeString(key); + self.buf.encodeBytes(value); } /** * @notice Adds a int256 value to the request with a given key name * @param self The initialized request - * @param _key The name of the key - * @param _value The int256 value to add + * @param key The name of the key + * @param value The int256 value to add */ - function addInt(Request memory self, string memory _key, int256 _value) + function addInt(Request memory self, string memory key, int256 value) internal pure { - self.buf.encodeString(_key); - self.buf.encodeInt(_value); + self.buf.encodeString(key); + self.buf.encodeInt(value); } /** * @notice Adds a uint256 value to the request with a given key name * @param self The initialized request - * @param _key The name of the key - * @param _value The uint256 value to add + * @param key The name of the key + * @param value The uint256 value to add */ - function addUint(Request memory self, string memory _key, uint256 _value) + function addUint(Request memory self, string memory key, uint256 value) internal pure { - self.buf.encodeString(_key); - self.buf.encodeUInt(_value); + self.buf.encodeString(key); + self.buf.encodeUInt(value); } /** * @notice Adds an array of strings to the request with a given key name * @param self The initialized request - * @param _key The name of the key - * @param _values The array of string values to add + * @param key The name of the key + * @param values The array of string values to add */ - function addStringArray(Request memory self, string memory _key, string[] memory _values) + function addStringArray(Request memory self, string memory key, string[] memory values) internal pure { - self.buf.encodeString(_key); + self.buf.encodeString(key); self.buf.startArray(); - for (uint256 i = 0; i < _values.length; i++) { - self.buf.encodeString(_values[i]); + for (uint256 i = 0; i < values.length; i++) { + self.buf.encodeString(values[i]); } self.buf.endSequence(); } diff --git a/evm-contracts/src/v0.7/ChainlinkClient.sol b/evm-contracts/src/v0.7/ChainlinkClient.sol index b4404247c60..09c98aba80f 100644 --- a/evm-contracts/src/v0.7/ChainlinkClient.sol +++ b/evm-contracts/src/v0.7/ChainlinkClient.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "./Chainlink.sol"; @@ -5,7 +6,6 @@ import "./interfaces/ENSInterface.sol"; import "./interfaces/LinkTokenInterface.sol"; import "./interfaces/ChainlinkRequestInterface.sol"; import "./interfaces/PointerInterface.sol"; -import "./vendor/SafeMathChainlink.sol"; import { ENSResolver as ENSResolver_Chainlink } from "./vendor/ENSResolver.sol"; /** @@ -15,9 +15,8 @@ import { ENSResolver as ENSResolver_Chainlink } from "./vendor/ENSResolver.sol"; */ contract ChainlinkClient { using Chainlink for Chainlink.Request; - using SafeMathChainlink for uint256; - uint256 constant internal LINK = 10**18; + uint256 constant internal LINK_DIVISIBILITY = 10**18; uint256 constant private AMOUNT_OVERRIDE = 0; address constant private SENDER_OVERRIDE = address(0); uint256 constant private ARGS_VERSION = 2; @@ -38,32 +37,32 @@ contract ChainlinkClient { /** * @notice Creates a request that can hold additional parameters - * @param _specId The Job Specification ID that the request will be created for - * @param _callbackAddress The callback address that the response will be sent to - * @param _callbackFunctionSignature The callback function signature to use for the callback address + * @param specId The Job Specification ID that the request will be created for + * @param callbackAddress The callback address that the response will be sent to + * @param callbackFunctionSignature The callback function signature to use for the callback address * @return A Chainlink Request struct in memory */ function buildChainlinkRequest( - bytes32 _specId, - address _callbackAddress, - bytes4 _callbackFunctionSignature + bytes32 specId, + address callbackAddress, + bytes4 callbackFunctionSignature ) internal pure returns (Chainlink.Request memory) { Chainlink.Request memory req; - return req.initialize(_specId, _callbackAddress, _callbackFunctionSignature); + return req.initialize(specId, callbackAddress, callbackFunctionSignature); } /** * @notice Creates a Chainlink request to the stored oracle address * @dev Calls `chainlinkRequestTo` with the stored oracle address - * @param _req The initialized Chainlink Request - * @param _payment The amount of LINK to send for the request + * @param req The initialized Chainlink Request + * @param payment The amount of LINK to send for the request * @return requestId The request ID */ - function sendChainlinkRequest(Chainlink.Request memory _req, uint256 _payment) + function sendChainlinkRequest(Chainlink.Request memory req, uint256 payment) internal returns (bytes32) { - return sendChainlinkRequestTo(address(oracle), _req, _payment); + return sendChainlinkRequestTo(address(oracle), req, payment); } /** @@ -71,20 +70,20 @@ contract ChainlinkClient { * @dev Generates and stores a request ID, increments the local nonce, and uses `transferAndCall` to * send LINK which creates a request on the target oracle contract. * Emits ChainlinkRequested event. - * @param _oracle The address of the oracle for the request - * @param _req The initialized Chainlink Request - * @param _payment The amount of LINK to send for the request + * @param oracleAddress The address of the oracle for the request + * @param req The initialized Chainlink Request + * @param payment The amount of LINK to send for the request * @return requestId The request ID */ - function sendChainlinkRequestTo(address _oracle, Chainlink.Request memory _req, uint256 _payment) + function sendChainlinkRequestTo(address oracleAddress, Chainlink.Request memory req, uint256 payment) internal returns (bytes32 requestId) { requestId = keccak256(abi.encodePacked(this, requestCount)); - _req.nonce = requestCount; - pendingRequests[requestId] = _oracle; + req.nonce = requestCount; + pendingRequests[requestId] = oracleAddress; emit ChainlinkRequested(requestId); - require(link.transferAndCall(_oracle, _payment, encodeRequest(_req, ARGS_VERSION)), "unable to transferAndCall to oracle"); + require(link.transferAndCall(oracleAddress, payment, encodeRequest(req, ARGS_VERSION)), "unable to transferAndCall to oracle"); requestCount += 1; return requestId; @@ -95,39 +94,39 @@ contract ChainlinkClient { * @dev Requires keeping track of the expiration value emitted from the oracle contract. * Deletes the request from the `pendingRequests` mapping. * Emits ChainlinkCancelled event. - * @param _requestId The request ID - * @param _payment The amount of LINK sent for the request - * @param _callbackFunc The callback function specified for the request - * @param _expiration The time of the expiration for the request + * @param requestId The request ID + * @param payment The amount of LINK sent for the request + * @param callbackFunc The callback function specified for the request + * @param expiration The time of the expiration for the request */ function cancelChainlinkRequest( - bytes32 _requestId, - uint256 _payment, - bytes4 _callbackFunc, - uint256 _expiration + bytes32 requestId, + uint256 payment, + bytes4 callbackFunc, + uint256 expiration ) internal { - ChainlinkRequestInterface requested = ChainlinkRequestInterface(pendingRequests[_requestId]); - delete pendingRequests[_requestId]; - emit ChainlinkCancelled(_requestId); - requested.cancelOracleRequest(_requestId, _payment, _callbackFunc, _expiration); + ChainlinkRequestInterface requested = ChainlinkRequestInterface(pendingRequests[requestId]); + delete pendingRequests[requestId]; + emit ChainlinkCancelled(requestId); + requested.cancelOracleRequest(requestId, payment, callbackFunc, expiration); } /** * @notice Sets the stored oracle address - * @param _oracle The address of the oracle contract + * @param oracleAddress The address of the oracle contract */ - function setChainlinkOracle(address _oracle) internal { - oracle = ChainlinkRequestInterface(_oracle); + function setChainlinkOracle(address oracleAddress) internal { + oracle = ChainlinkRequestInterface(oracleAddress); } /** * @notice Sets the LINK token address - * @param _link The address of the LINK token contract + * @param linkAddress The address of the LINK token contract */ - function setChainlinkToken(address _link) internal { - link = LinkTokenInterface(_link); + function setChainlinkToken(address linkAddress) internal { + link = LinkTokenInterface(linkAddress); } /** @@ -165,27 +164,27 @@ contract ChainlinkClient { /** * @notice Allows for a request which was created on another contract to be fulfilled * on this contract - * @param _oracle The address of the oracle contract that will fulfill the request - * @param _requestId The request ID used for the response + * @param oracleAddress The address of the oracle contract that will fulfill the request + * @param requestId The request ID used for the response */ - function addChainlinkExternalRequest(address _oracle, bytes32 _requestId) + function addChainlinkExternalRequest(address oracleAddress, bytes32 requestId) internal - notPendingRequest(_requestId) + notPendingRequest(requestId) { - pendingRequests[_requestId] = _oracle; + pendingRequests[requestId] = oracleAddress; } /** * @notice Sets the stored oracle and LINK token contracts with the addresses resolved by ENS * @dev Accounts for subnodes having different resolvers - * @param _ens The address of the ENS contract - * @param _node The ENS node hash + * @param ensAddress The address of the ENS contract + * @param node The ENS node hash */ - function useChainlinkWithENS(address _ens, bytes32 _node) + function useChainlinkWithENS(address ensAddress, bytes32 node) internal { - ens = ENSInterface(_ens); - ensNode = _node; + ens = ENSInterface(ensAddress); + ensNode = node; bytes32 linkSubnode = keccak256(abi.encodePacked(ensNode, ENS_TOKEN_SUBNAME)); ENSResolver_Chainlink resolver = ENSResolver_Chainlink(ens.resolver(linkSubnode)); setChainlinkToken(resolver.addr(linkSubnode)); @@ -208,10 +207,11 @@ contract ChainlinkClient { * @notice Encodes the request to be sent to the oracle contract * @dev The Chainlink node expects values to be in order for the request to be picked up. Order of types * will be validated in the oracle contract. - * @param _req The initialized Chainlink Request + * @param req The initialized Chainlink Request + * @param dataVersion The request data version * @return The bytes payload for the `transferAndCall` method */ - function encodeRequest(Chainlink.Request memory _req, uint256 _dataVersion) + function encodeRequest(Chainlink.Request memory req, uint256 dataVersion) private view returns (bytes memory) @@ -220,44 +220,44 @@ contract ChainlinkClient { oracle.oracleRequest.selector, SENDER_OVERRIDE, // Sender value - overridden by onTokenTransfer by the requesting contract's address AMOUNT_OVERRIDE, // Amount value - overridden by onTokenTransfer by the actual amount of LINK sent - _req.id, - _req.callbackAddress, - _req.callbackFunctionId, - _req.nonce, - _dataVersion, - _req.buf.buf); + req.id, + req.callbackAddress, + req.callbackFunctionId, + req.nonce, + dataVersion, + req.buf.buf); } /** * @notice Ensures that the fulfillment is valid for this contract * @dev Use if the contract developer prefers methods instead of modifiers for validation - * @param _requestId The request ID for fulfillment + * @param requestId The request ID for fulfillment */ - function validateChainlinkCallback(bytes32 _requestId) + function validateChainlinkCallback(bytes32 requestId) internal - recordChainlinkFulfillment(_requestId) + recordChainlinkFulfillment(requestId) // solhint-disable-next-line no-empty-blocks {} /** * @dev Reverts if the sender is not the oracle of the request. * Emits ChainlinkFulfilled event. - * @param _requestId The request ID for fulfillment + * @param requestId The request ID for fulfillment */ - modifier recordChainlinkFulfillment(bytes32 _requestId) { - require(msg.sender == pendingRequests[_requestId], + modifier recordChainlinkFulfillment(bytes32 requestId) { + require(msg.sender == pendingRequests[requestId], "Source must be the oracle of the request"); - delete pendingRequests[_requestId]; - emit ChainlinkFulfilled(_requestId); + delete pendingRequests[requestId]; + emit ChainlinkFulfilled(requestId); _; } /** * @dev Reverts if the request is already pending - * @param _requestId The request ID for fulfillment + * @param requestId The request ID for fulfillment */ - modifier notPendingRequest(bytes32 _requestId) { - require(pendingRequests[_requestId] == address(0), "Request is already pending"); + modifier notPendingRequest(bytes32 requestId) { + require(pendingRequests[requestId] == address(0), "Request is already pending"); _; } } diff --git a/evm-contracts/src/v0.7/dev/AggregatorProxy.sol b/evm-contracts/src/v0.7/dev/AggregatorProxy.sol new file mode 100644 index 00000000000..b03edf9fbeb --- /dev/null +++ b/evm-contracts/src/v0.7/dev/AggregatorProxy.sol @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.0; + +import "./ConfirmedOwner.sol"; +import "../interfaces/AggregatorProxyInterface.sol"; + +/** + * @title A trusted proxy for updating where current answers are read from + * @notice This contract provides a consistent address for the + * CurrentAnwerInterface but delegates where it reads from to the owner, who is + * trusted to update it. + */ +contract AggregatorProxy is AggregatorProxyInterface, ConfirmedOwner { + + struct Phase { + uint16 id; + AggregatorProxyInterface aggregator; + } + AggregatorProxyInterface private s_proposedAggregator; + mapping(uint16 => AggregatorProxyInterface) private s_phaseAggregators; + Phase private s_currentPhase; + + uint256 constant private PHASE_OFFSET = 64; + uint256 constant private PHASE_SIZE = 16; + uint256 constant private MAX_ID = 2**(PHASE_OFFSET+PHASE_SIZE) - 1; + + event AggregatorProposed(address indexed current, address indexed proposed); + event AggregatorConfirmed(address indexed previous, address indexed latest); + + constructor(address aggregatorAddress) public ConfirmedOwner(msg.sender) { + setAggregator(aggregatorAddress); + } + + /** + * @notice Reads the current answer from aggregator delegated to. + * + * @dev #[deprecated] Use latestRoundData instead. This does not error if no + * answer has been reached, it will simply return 0. Either wait to point to + * an already answered Aggregator or use the recommended latestRoundData + * instead which includes better verification information. + */ + function latestAnswer() + public + view + virtual + override + returns (int256 answer) + { + return s_currentPhase.aggregator.latestAnswer(); + } + + /** + * @notice Reads the last updated height from aggregator delegated to. + * + * @dev #[deprecated] Use latestRoundData instead. This does not error if no + * answer has been reached, it will simply return 0. Either wait to point to + * an already answered Aggregator or use the recommended latestRoundData + * instead which includes better verification information. + */ + function latestTimestamp() + public + view + virtual + override + returns (uint256 updatedAt) + { + return s_currentPhase.aggregator.latestTimestamp(); + } + + /** + * @notice get past rounds answers + * @param roundId the answer number to retrieve the answer for + * + * @dev #[deprecated] Use getRoundData instead. This does not error if no + * answer has been reached, it will simply return 0. Either wait to point to + * an already answered Aggregator or use the recommended getRoundData + * instead which includes better verification information. + */ + function getAnswer(uint256 roundId) + public + view + virtual + override + returns (int256 answer) + { + if (roundId > MAX_ID) return 0; + + (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); + AggregatorProxyInterface aggregator = s_phaseAggregators[phaseId]; + if (address(aggregator) == address(0)) return 0; + + return aggregator.getAnswer(aggregatorRoundId); + } + + /** + * @notice get block timestamp when an answer was last updated + * @param roundId the answer number to retrieve the updated timestamp for + * + * @dev #[deprecated] Use getRoundData instead. This does not error if no + * answer has been reached, it will simply return 0. Either wait to point to + * an already answered Aggregator or use the recommended getRoundData + * instead which includes better verification information. + */ + function getTimestamp(uint256 roundId) + public + view + virtual + override + returns (uint256 updatedAt) + { + if (roundId > MAX_ID) return 0; + + (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); + AggregatorProxyInterface aggregator = s_phaseAggregators[phaseId]; + if (address(aggregator) == address(0)) return 0; + + return aggregator.getTimestamp(aggregatorRoundId); + } + + /** + * @notice get the latest completed round where the answer was updated. This + * ID includes the proxy's phase, to make sure round IDs increase even when + * switching to a newly deployed aggregator. + * + * @dev #[deprecated] Use latestRoundData instead. This does not error if no + * answer has been reached, it will simply return 0. Either wait to point to + * an already answered Aggregator or use the recommended latestRoundData + * instead which includes better verification information. + */ + function latestRound() + public + view + virtual + override + returns (uint256 roundId) + { + Phase memory phase = s_currentPhase; // cache storage reads + return addPhase(phase.id, uint64(phase.aggregator.latestRound())); + } + + /** + * @notice get data about a round. Consumers are encouraged to check + * that they're receiving fresh data by inspecting the updatedAt and + * answeredInRound return values. + * Note that different underlying implementations of AggregatorV3Interface + * have slightly different semantics for some of the return values. Consumers + * should determine what implementations they expect to receive + * data from and validate that they can properly handle return data from all + * of them. + * @param roundId the requested round ID as presented through the proxy, this + * is made up of the aggregator's round ID with the phase ID encoded in the + * two highest order bytes + * @return id is the round ID from the aggregator for which the data was + * retrieved combined with an phase to ensure that round IDs get larger as + * time moves forward. + * @return answer is the answer for the given round + * @return startedAt is the timestamp when the round was started. + * (Only some AggregatorV3Interface implementations return meaningful values) + * @return updatedAt is the timestamp when the round last was updated (i.e. + * answer was last computed) + * @return answeredInRound is the round ID of the round in which the answer + * was computed. + * (Only some AggregatorV3Interface implementations return meaningful values) + * @dev Note that answer and updatedAt may change between queries. + */ + function getRoundData(uint80 roundId) + public + view + virtual + override + returns ( + uint80 id, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + (uint16 phaseId, uint64 aggregatorRoundId) = parseIds(roundId); + + ( + uint80 id, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 ansIn + ) = s_phaseAggregators[phaseId].getRoundData(aggregatorRoundId); + + return addPhaseIds(id, answer, startedAt, updatedAt, ansIn, phaseId); + } + + /** + * @notice get data about the latest round. Consumers are encouraged to check + * that they're receiving fresh data by inspecting the updatedAt and + * answeredInRound return values. + * Note that different underlying implementations of AggregatorV3Interface + * have slightly different semantics for some of the return values. Consumers + * should determine what implementations they expect to receive + * data from and validate that they can properly handle return data from all + * of them. + * @return id is the round ID from the aggregator for which the data was + * retrieved combined with an phase to ensure that round IDs get larger as + * time moves forward. + * @return answer is the answer for the given round + * @return startedAt is the timestamp when the round was started. + * (Only some AggregatorV3Interface implementations return meaningful values) + * @return updatedAt is the timestamp when the round last was updated (i.e. + * answer was last computed) + * @return answeredInRound is the round ID of the round in which the answer + * was computed. + * (Only some AggregatorV3Interface implementations return meaningful values) + * @dev Note that answer and updatedAt may change between queries. + */ + function latestRoundData() + public + view + virtual + override + returns ( + uint80 id, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + Phase memory current = s_currentPhase; // cache storage reads + + ( + uint80 id, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 ansIn + ) = current.aggregator.latestRoundData(); + + return addPhaseIds(id, answer, startedAt, updatedAt, ansIn, current.id); + } + + /** + * @notice Used if an aggregator contract has been proposed. + * @param roundId the round ID to retrieve the round data for + * @return id is the round ID for which data was retrieved + * @return answer is the answer for the given round + * @return startedAt is the timestamp when the round was started. + * (Only some AggregatorV3Interface implementations return meaningful values) + * @return updatedAt is the timestamp when the round last was updated (i.e. + * answer was last computed) + * @return answeredInRound is the round ID of the round in which the answer + * was computed. + */ + function proposedGetRoundData(uint80 roundId) + external + view + virtual + override + hasProposal() + returns ( + uint80 id, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return s_proposedAggregator.getRoundData(roundId); + } + + /** + * @notice Used if an aggregator contract has been proposed. + * @return id is the round ID for which data was retrieved + * @return answer is the answer for the given round + * @return startedAt is the timestamp when the round was started. + * (Only some AggregatorV3Interface implementations return meaningful values) + * @return updatedAt is the timestamp when the round last was updated (i.e. + * answer was last computed) + * @return answeredInRound is the round ID of the round in which the answer + * was computed. + */ + function proposedLatestRoundData() + external + view + virtual + override + hasProposal() + returns ( + uint80 id, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return s_proposedAggregator.latestRoundData(); + } + + /** + * @notice returns the current phase's aggregator address. + */ + function aggregator() + external + view + override + returns (address) + { + return address(s_currentPhase.aggregator); + } + + /** + * @notice returns the current phase's ID. + */ + function phaseId() + external + view + override + returns (uint16) + { + return s_currentPhase.id; + } + + /** + * @notice represents the number of decimals the aggregator responses represent. + */ + function decimals() + external + view + override + returns (uint8) + { + return s_currentPhase.aggregator.decimals(); + } + + /** + * @notice the version number representing the type of aggregator the proxy + * points to. + */ + function version() + external + view + override + returns (uint256) + { + return s_currentPhase.aggregator.version(); + } + + /** + * @notice returns the description of the aggregator the proxy points to. + */ + function description() + external + view + override + returns (string memory) + { + return s_currentPhase.aggregator.description(); + } + + /** + * @notice returns the current proposed aggregator + */ + function proposedAggregator() + external + view + override + returns (address) + { + return address(s_proposedAggregator); + } + + /** + * @notice return a phase aggregator using the phaseId + * + * @param phaseId uint16 + */ + function phaseAggregators(uint16 phaseId) + external + view + override + returns (address) + { + return address(s_phaseAggregators[phaseId]); + } + + /** + * @notice Allows the owner to propose a new address for the aggregator + * @param aggregatorAddress The new address for the aggregator contract + */ + function proposeAggregator(address aggregatorAddress) + external + onlyOwner() + { + s_proposedAggregator = AggregatorProxyInterface(aggregatorAddress); + emit AggregatorProposed(address(s_currentPhase.aggregator), aggregatorAddress); + } + + /** + * @notice Allows the owner to confirm and change the address + * to the proposed aggregator + * @dev Reverts if the given address doesn't match what was previously + * proposed + * @param aggregatorAddress The new address for the aggregator contract + */ + function confirmAggregator(address aggregatorAddress) + external + onlyOwner() + { + require(aggregatorAddress == address(s_proposedAggregator), "Invalid proposed aggregator"); + address previousAggregator = address(s_currentPhase.aggregator); + delete s_proposedAggregator; + setAggregator(aggregatorAddress); + emit AggregatorConfirmed(previousAggregator, aggregatorAddress); + } + + + /* + * Internal + */ + + function setAggregator(address aggregatorAddress) + internal + { + uint16 id = s_currentPhase.id + 1; + s_currentPhase = Phase(id, AggregatorProxyInterface(aggregatorAddress)); + s_phaseAggregators[id] = AggregatorProxyInterface(aggregatorAddress); + } + + function addPhase( + uint16 phase, + uint64 originalId + ) + internal + view + returns (uint80) + { + return uint80(uint256(phase) << PHASE_OFFSET | originalId); + } + + function parseIds( + uint256 roundId + ) + internal + view + returns (uint16, uint64) + { + uint16 phaseId = uint16(roundId >> PHASE_OFFSET); + uint64 aggregatorRoundId = uint64(roundId); + + return (phaseId, aggregatorRoundId); + } + + function addPhaseIds( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound, + uint16 phaseId + ) + internal + view + returns (uint80, int256, uint256, uint256, uint80) + { + return ( + addPhase(phaseId, uint64(roundId)), + answer, + startedAt, + updatedAt, + addPhase(phaseId, uint64(answeredInRound)) + ); + } + + /* + * Modifiers + */ + + modifier hasProposal() { + require(address(s_proposedAggregator) != address(0), "No proposed aggregator present"); + _; + } + +} \ No newline at end of file diff --git a/evm-contracts/src/v0.7/dev/ChainlinkOperatorFactory.sol b/evm-contracts/src/v0.7/dev/ChainlinkOperatorFactory.sol new file mode 100644 index 00000000000..b3386e921a6 --- /dev/null +++ b/evm-contracts/src/v0.7/dev/ChainlinkOperatorFactory.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import "./Operator.sol"; + +/** + * @title Operator Factory + * @notice Creates Operator contracts for node operators + */ +contract ChainlinkOperatorFactory { + + address public link; + + event OperatorCreated(address indexed operator, address indexed owner); + + /** + * @param linkAddress address + */ + constructor(address linkAddress) public { + link = linkAddress; + } + + /** + * @notice fallback to create a new Operator contract with the msg.sender as owner + */ + fallback() external { + Operator operator = new Operator(link, msg.sender); + emit OperatorCreated(address(operator), msg.sender); + } +} \ No newline at end of file diff --git a/evm-contracts/src/v0.7/dev/ConfirmedOwner.sol b/evm-contracts/src/v0.7/dev/ConfirmedOwner.sol new file mode 100644 index 00000000000..6fabaf5ab80 --- /dev/null +++ b/evm-contracts/src/v0.7/dev/ConfirmedOwner.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title The ConfirmedOwner contract + * @notice A contract with helpers for basic contract ownership. + */ +contract ConfirmedOwner { + + address private s_owner; + address private s_pendingOwner; + + event OwnershipTransferRequested( + address indexed from, + address indexed to + ); + event OwnershipTransferred( + address indexed from, + address indexed to + ); + + constructor(address newOwner) { + s_owner = newOwner; + } + + /** + * @notice Allows an owner to begin transferring ownership to a new address, + * pending. + */ + function transferOwnership(address to) + external + onlyOwner() + { + require(to != msg.sender, "Cannot transfer to self"); + + s_pendingOwner = to; + + emit OwnershipTransferRequested(s_owner, to); + } + + /** + * @notice Allows an ownership transfer to be completed by the recipient. + */ + function acceptOwnership() + external + { + require(msg.sender == s_pendingOwner, "Must be proposed owner"); + + address oldOwner = s_owner; + s_owner = msg.sender; + s_pendingOwner = address(0); + + emit OwnershipTransferred(oldOwner, msg.sender); + } + + /** + * @notice Get the current owner + */ + function owner() public view returns (address) { + return s_owner; + } + + /** + * @notice Reverts if called by anyone other than the contract owner. + */ + modifier onlyOwner() { + require(msg.sender == s_owner, "Only callable by owner"); + _; + } + +} diff --git a/evm-contracts/src/v0.7/dev/LinkTokenReceiver.sol b/evm-contracts/src/v0.7/dev/LinkTokenReceiver.sol index 977c88ba3b2..eb17e9fe5cf 100644 --- a/evm-contracts/src/v0.7/dev/LinkTokenReceiver.sol +++ b/evm-contracts/src/v0.7/dev/LinkTokenReceiver.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; abstract contract LinkTokenReceiver { @@ -8,30 +9,30 @@ abstract contract LinkTokenReceiver { uint256 constant private MINIMUM_REQUEST_LENGTH = SELECTOR_LENGTH + (32 * EXPECTED_REQUEST_WORDS); /** * @notice Called when LINK is sent to the contract via `transferAndCall` - * @dev The data payload's first 2 words will be overwritten by the `_sender` and `_amount` + * @dev The data payload's first 2 words will be overwritten by the `sender` and `amount` * values to ensure correctness. Calls oracleRequest. - * @param _sender Address of the sender - * @param _amount Amount of LINK sent (specified in wei) - * @param _data Payload of the transaction + * @param sender Address of the sender + * @param amount Amount of LINK sent (specified in wei) + * @param data Payload of the transaction */ function onTokenTransfer( - address _sender, - uint256 _amount, - bytes memory _data + address sender, + uint256 amount, + bytes memory data ) public onlyLINK - validRequestLength(_data) - permittedFunctionsForLINK(_data) + validRequestLength(data) + permittedFunctionsForLINK(data) { assembly { // solhint-disable-next-line avoid-low-level-calls - mstore(add(_data, 36), _sender) // ensure correct sender is passed + mstore(add(data, 36), sender) // ensure correct sender is passed // solhint-disable-next-line avoid-low-level-calls - mstore(add(_data, 68), _amount) // ensure correct amount is passed + mstore(add(data, 68), amount) // ensure correct amount is passed } // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(this).delegatecall(_data); // calls oracleRequest + (bool success, ) = address(this).delegatecall(data); // calls oracleRequest require(success, "Unable to create request"); } @@ -47,13 +48,13 @@ abstract contract LinkTokenReceiver { /** * @dev Reverts if the given data does not begin with the `oracleRequest` function selector - * @param _data The data payload of the request + * @param data The data payload of the request */ - modifier permittedFunctionsForLINK(bytes memory _data) { + modifier permittedFunctionsForLINK(bytes memory data) { bytes4 funcSelector; assembly { // solhint-disable-next-line avoid-low-level-calls - funcSelector := mload(add(_data, 32)) + funcSelector := mload(add(data, 32)) } require(funcSelector == ORACLE_REQUEST_SELECTOR, "Must use whitelisted functions"); _; @@ -61,10 +62,10 @@ abstract contract LinkTokenReceiver { /** * @dev Reverts if the given payload is less than needed to create a request - * @param _data The request payload + * @param data The request payload */ - modifier validRequestLength(bytes memory _data) { - require(_data.length >= MINIMUM_REQUEST_LENGTH, "Invalid request length"); + modifier validRequestLength(bytes memory data) { + require(data.length >= MINIMUM_REQUEST_LENGTH, "Invalid request length"); _; } } diff --git a/evm-contracts/src/v0.7/dev/Operator.sol b/evm-contracts/src/v0.7/dev/Operator.sol index 2b1342d9a98..6f7795d5bf4 100644 --- a/evm-contracts/src/v0.7/dev/Operator.sol +++ b/evm-contracts/src/v0.7/dev/Operator.sol @@ -1,7 +1,8 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.7.0; import "./LinkTokenReceiver.sol"; -import "./Owned.sol"; +import "./ConfirmedOwner.sol"; import "../interfaces/ChainlinkRequestInterface.sol"; import "../interfaces/OracleInterface.sol"; import "../interfaces/OracleInterface2.sol"; @@ -15,7 +16,7 @@ import "../vendor/SafeMathChainlink.sol"; */ contract Operator is LinkTokenReceiver, - Owned, + ConfirmedOwner, ChainlinkRequestInterface, OracleInterface, OracleInterface2, @@ -37,7 +38,7 @@ contract Operator is LinkTokenInterface internal immutable linkToken; mapping(bytes32 => Commitment) private s_commitments; - mapping(address => bool) private s_authorizedNodes; + mapping(address => bool) private s_authorizedSenders; uint256 private s_withdrawableTokens = ONE_FOR_CONSISTENT_GAS_COST; event OracleRequest( @@ -64,9 +65,10 @@ contract Operator is * @notice Deploy with the address of the LINK token * @dev Sets the LinkToken address for the imported LinkTokenInterface * @param link The address of the LINK token + * @param owner The address of the owner */ - constructor(address link) - Owned() + constructor(address link, address owner) + ConfirmedOwner(owner) { linkToken = LinkTokenInterface(link); // external but already deployed and unalterable } @@ -144,7 +146,7 @@ contract Operator is ) external override - onlyAuthorizedNode() + onlyAuthorizedSender() isValidRequest(requestId) returns (bool) { @@ -188,7 +190,7 @@ contract Operator is ) external override - onlyAuthorizedNode() + onlyAuthorizedSender() isValidRequest(requestId) isValidMultiWord(requestId, data) returns (bool) @@ -215,13 +217,13 @@ contract Operator is * @param node The address of the Chainlink node * @return The authorization status of the node */ - function getAuthorizationStatus(address node) + function isAuthorizedSender(address node) external view override returns (bool) { - return s_authorizedNodes[node]; + return s_authorizedSenders[node]; } /** @@ -229,12 +231,12 @@ contract Operator is * @param node The address of the Chainlink node * @param allowed Bool value to determine if the node can fulfill requests */ - function setFulfillmentPermission(address node, bool allowed) + function setAuthorizedSender(address node, bool allowed) external override onlyOwner() { - s_authorizedNodes[node] = allowed; + s_authorizedSenders[node] = allowed; } /** @@ -311,12 +313,12 @@ contract Operator is return address(linkToken); } - function forward(address _to, bytes calldata _data) + function forward(address to, bytes calldata data) public - onlyAuthorizedNode() + onlyAuthorizedSender() { - require(_to != address(linkToken), "Cannot use #forward to send messages to Link token"); - (bool status,) = _to.call(_data); + require(to != address(linkToken), "Cannot use #forward to send messages to Link token"); + (bool status,) = to.call(data); require(status, "Forwarded call failed."); } @@ -447,8 +449,8 @@ contract Operator is /** * @dev Reverts if `msg.sender` is not authorized to fulfill requests */ - modifier onlyAuthorizedNode() { - require(s_authorizedNodes[msg.sender], "Not an authorized node to fulfill requests"); + modifier onlyAuthorizedSender() { + require(s_authorizedSenders[msg.sender], "Not an authorized node to fulfill requests"); _; } diff --git a/evm-contracts/src/v0.7/dev/Owned.sol b/evm-contracts/src/v0.7/dev/Owned.sol deleted file mode 100644 index a550fc2d86b..00000000000 --- a/evm-contracts/src/v0.7/dev/Owned.sol +++ /dev/null @@ -1,63 +0,0 @@ -pragma solidity ^0.7.0; - -/** - * @title The Owned contract - * @notice A contract with helpers for basic contract ownership. - */ -contract Owned { - - address public owner; - address private pendingOwner; - - event OwnershipTransferRequested( - address indexed from, - address indexed to - ); - event OwnershipTransferred( - address indexed from, - address indexed to - ); - - constructor() { - owner = msg.sender; - } - - /** - * @dev Allows an owner to begin transferring ownership to a new address, - * pending. - */ - function transferOwnership(address _to) - external - onlyOwner() - { - require(_to != msg.sender, "Cannot transfer to self"); - - pendingOwner = _to; - - emit OwnershipTransferRequested(owner, _to); - } - - /** - * @dev Allows an ownership transfer to be completed by the recipient. - */ - function acceptOwnership() - external - { - require(msg.sender == pendingOwner, "Must be proposed owner"); - - address oldOwner = owner; - owner = msg.sender; - pendingOwner = address(0); - - emit OwnershipTransferred(oldOwner, msg.sender); - } - - /** - * @dev Reverts if called by anyone other than the contract owner. - */ - modifier onlyOwner() { - require(msg.sender == owner, "Only callable by owner"); - _; - } - -} diff --git a/evm-contracts/src/v0.7/dev/artifacts/Operator.json b/evm-contracts/src/v0.7/dev/artifacts/Operator.json new file mode 100644 index 00000000000..b01bc44e056 --- /dev/null +++ b/evm-contracts/src/v0.7/dev/artifacts/Operator.json @@ -0,0 +1,592 @@ +{ + "deploy": { + "VM:-": { + "linkReferences": {}, + "autoDeployLib": true + }, + "main:1": { + "linkReferences": {}, + "autoDeployLib": true + }, + "ropsten:3": { + "linkReferences": {}, + "autoDeployLib": true + }, + "rinkeby:4": { + "linkReferences": {}, + "autoDeployLib": true + }, + "kovan:42": { + "linkReferences": {}, + "autoDeployLib": true + }, + "görli:5": { + "linkReferences": {}, + "autoDeployLib": true + }, + "Custom": { + "linkReferences": {}, + "autoDeployLib": true + } + }, + "data": { + "bytecode": { + "linkReferences": {}, + "object": "60a0604052600160045534801561001557600080fd5b5060405161283e38038061283e8339818101604052604081101561003857600080fd5b81019080805190602001909291908051906020019092919050505080806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550508173ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1660601b81525050505060805160601c61273f6100ff6000398061077d528061084c52806112fb52806114765280611fa7525061273f6000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80636fadcf7211610097578063f2fde38b11610066578063f2fde38b1461063d578063f3dfc2a914610681578063f3fef3a3146106d1578063fa00763a1461071f576100f5565b80636fadcf721461048157806379ba50971461051a5780638da5cb5b14610524578063a4c0ed3614610558576100f5565b80634b602282116100d35780634b602282146102e457806350188301146103025780636ae0bc76146103205780636ee4d55314610416576100f5565b8063165d35e1146100fa578063404299461461012e5780634ab0d19014610239575b600080fd5b610102610779565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610237600480360361010081101561014557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919080359060200190929190803590602001906401000000008111156101f357600080fd5b82018360208201111561020557600080fd5b8035906020019184600183028401116401000000008311171561022757600080fd5b90919293919293905050506107a1565b005b6102cc600480360360c081101561024f57600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919080359060200190929190505050610a16565b60405180821515815260200191505060405180910390f35b6102ec610d4e565b6040518082815260200191505060405180910390f35b61030a610d54565b6040518082815260200191505060405180910390f35b6103fe600480360360c081101561033657600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916906020019092919080359060200190929190803590602001906401000000008111156103ba57600080fd5b8201836020820111156103cc57600080fd5b803590602001918460018302840111640100000000831117156103ee57600080fd5b9091929391929390505050610d71565b60405180821515815260200191505060405180910390f35b61047f6004803603608081101561042c57600080fd5b81019080803590602001909291908035906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919050505061114a565b005b6105186004803603604081101561049757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156104d457600080fd5b8201836020820111156104e657600080fd5b8035906020019184600183028401116401000000008311171561050857600080fd5b90919293919293905050506113d2565b005b610522611608565b005b61052c6117d0565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61063b6004803603606081101561056e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105b557600080fd5b8201836020820111156105c757600080fd5b803590602001918460018302840111640100000000831117156105e957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506117f4565b005b61067f6004803603602081101561065357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611b1d565b005b6106cf6004803603604081101561069757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803515159060200190929190505050611d3e565b005b61071d600480360360408110156106e757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611e5a565b005b6107616004803603602081101561073557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061207c565b60405180821515815260200191505060405180910390f35b60007f0000000000000000000000000000000000000000000000000000000000000000905090565b6107a9610779565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610849576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f4d75737420757365204c494e4b20746f6b656e0000000000000000000000000081525060200191505060405180910390fd5b857f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141561090c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260178152602001807f43616e6e6f742063616c6c6261636b20746f204c494e4b00000000000000000081525060200191505060405180910390fd5b60008061091d8c8c8b8b8b8b6120d2565b91509150897fd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c658d848e8d8d878d8d8d604051808a73ffffffffffffffffffffffffffffffffffffffff1681526020018981526020018881526020018773ffffffffffffffffffffffffffffffffffffffff168152602001867bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509a505050505050505050505060405180910390a2505050505050505050505050565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610aba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806126e0602a913960400191505060405180910390fd5b86600060081b6002600083815260200190815260200160002060000160009054906101000a900460081b60ff19161415610b5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f4d757374206861766520612076616c696420726571756573744964000000000081525060200191505060405180910390fd5b610b6b8888888888600161228c565b877f9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a6460405160405180910390a262061a805a1015610c11576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4d7573742070726f7669646520636f6e73756d657220656e6f7567682067617381525060200191505060405180910390fd5b60008673ffffffffffffffffffffffffffffffffffffffff16868a866040516024018083815260200182815260200192505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610cd45780518252602082019150602081019050602083039250610cb1565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610d36576040519150601f19603f3d011682016040523d82523d6000602084013e610d3b565b606091505b5050905080925050509695505050505050565b61012c81565b6000610d6c600160045461246390919063ffffffff16565b905090565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610e15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806126e0602a913960400191505060405180910390fd5b87600060081b6002600083815260200190815260200160002060000160009054906101000a900460081b60ff19161415610eb7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f4d757374206861766520612076616c696420726571756573744964000000000081525060200191505060405180910390fd5b8884848080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050600060208201519050808314610f7b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f466972737420776f7264206d757374206265207265717565737449640000000081525060200191505060405180910390fd5b610f8a8c8c8c8c8c600261228c565b8b7f9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a6460405160405180910390a262061a805a1015611030576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4d7573742070726f7669646520636f6e73756d657220656e6f7567682067617381525060200191505060405180910390fd5b60008a73ffffffffffffffffffffffffffffffffffffffff168a898960405160200180847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526004018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b602083106110cc57805182526020820191506020810190506020830392506110a9565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461112e576040519150601f19603f3d011682016040523d82523d6000602084013e611133565b606091505b505090508095505050505050979650505050505050565b6000611158843385856124ec565b90508060ff19166002600087815260200190815260200160002060000160009054906101000a900460081b60ff1916146111fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f506172616d7320646f206e6f74206d617463682072657175657374204944000081525060200191505060405180910390fd5b42821115611270576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f52657175657374206973206e6f7420657870697265640000000000000000000081525060200191505060405180910390fd5b60026000868152602001908152602001600020600080820160006101000a8154907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021916905560008201601f6101000a81549060ff02191690555050847fa7842b9ec549398102c0d91b1b9919b2f20558aefdadf57528a95c6cd3292e9360405160405180910390a27f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15801561138a57600080fd5b505af115801561139e573d6000803e3d6000fd5b505050506040513d60208110156113b457600080fd5b81019080805190602001909291905050506113cb57fe5b5050505050565b600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16611474576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806126e0602a913960400191505060405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611519576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001806126796032913960400191505060405180910390fd5b60008373ffffffffffffffffffffffffffffffffffffffff1683836040518083838082843780830192505050925050506000604051808303816000865af19150503d8060008114611586576040519150601f19603f3d011682016040523d82523d6000602084013e61158b565b606091505b5050905080611602576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f466f727761726465642063616c6c206661696c65642e0000000000000000000081525060200191505060405180910390fd5b50505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146116cb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4d7573742062652070726f706f736564206f776e65720000000000000000000081525060200191505060405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a350565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6117fc610779565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461189c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f4d75737420757365204c494e4b20746f6b656e0000000000000000000000000081525060200191505060405180910390fd5b8060026020026004018151101561191b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f496e76616c69642072657175657374206c656e6774680000000000000000000081525060200191505060405180910390fd5b81600060208201519050634042994660e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146119df576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f4d757374207573652077686974656c69737465642066756e6374696f6e73000081525060200191505060405180910390fd5b85602485015284604485015260003073ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611a385780518252602082019150602081019050602083039250611a15565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855af49150503d8060008114611a98576040519150601f19603f3d011682016040523d82523d6000602084013e611a9d565b606091505b5050905080611b14576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f556e61626c6520746f206372656174652072657175657374000000000000000081525060200191505060405180910390fd5b50505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611bde576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415611c80576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260178152602001807f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000081525060200191505060405180910390fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae127860405160405180910390a350565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611dff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000081525060200191505060405180910390fd5b80600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611f1b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000081525060200191505060405180910390fd5b80611f3060018261256f90919063ffffffff16565b6004541015611f8a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260358152602001806126ab6035913960400191505060405180910390fd5b611f9f8260045461246390919063ffffffff16565b6004819055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15801561203657600080fd5b505af115801561204a573d6000803e3d6000fd5b505050506040513d602081101561206057600080fd5b810190808051906020019092919050505061207757fe5b505050565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff169050919050565b6000808784604051602001808373ffffffffffffffffffffffffffffffffffffffff1660601b815260140182815260200192505050604051602081830303815290604052805190602001209150600060081b6002600084815260200190815260200160002060000160009054906101000a900460081b60ff1916146121bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f4d75737420757365206120756e6971756520494400000000000000000000000081525060200191505060405180910390fd5b6121d461012c4261256f90919063ffffffff16565b905060006121e4888888856124ec565b905060405180604001604052808260ff19168152602001612204866125f7565b60ff168152506002600085815260200190815260200160002060008201518160000160006101000a8154817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021916908360081c0217905550602082015181600001601f6101000a81548160ff021916908360ff16021790555090505050965096945050505050565b600061229a868686866124ec565b90508060ff19166002600089815260200190815260200160002060000160009054906101000a900460081b60ff19161461233c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f506172616d7320646f206e6f74206d617463682072657175657374204944000081525060200191505060405180910390fd5b612345826125f7565b60ff1660026000898152602001908152602001600020600001601f9054906101000a900460ff1660ff1611156123e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f446174612076657273696f6e73206d757374206d61746368000000000000000081525060200191505060405180910390fd5b6123f88660045461256f90919063ffffffff16565b60048190555060026000888152602001908152602001600020600080820160006101000a8154907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021916905560008201601f6101000a81549060ff0219169055505050505050505050565b6000828211156124db576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525060200191505060405180910390fd5b600082840390508091505092915050565b600084848484604051602001808581526020018473ffffffffffffffffffffffffffffffffffffffff1660601b8152601401837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600401828152602001945050505050604051602081830303815290604052805190602001209050949350505050565b6000808284019050838110156125ed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b60006101008210612670576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f6e756d62657220746f6f2062696720746f20636173740000000000000000000081525060200191505060405180910390fd5b81905091905056fe43616e6e6f74207573652023666f727761726420746f2073656e64206d6573736167657320746f204c696e6b20746f6b656e416d6f756e74207265717565737465642069732067726561746572207468616e20776974686472617761626c652062616c616e63654e6f7420616e20617574686f72697a6564206e6f646520746f2066756c66696c6c207265717565737473a26469706673582212201ee0a53524752811cb080c8ea9d6bab7f7ef04f3d6411b337cfedbcd1fcdc34464736f6c63430007000033", + "opcodes": "PUSH1 0xA0 PUSH1 0x40 MSTORE PUSH1 0x1 PUSH1 0x4 SSTORE CALLVALUE DUP1 ISZERO PUSH2 0x15 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH2 0x283E CODESIZE SUB DUP1 PUSH2 0x283E DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x38 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP DUP1 DUP1 PUSH1 0x0 DUP1 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x80 DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE POP POP POP POP PUSH1 0x80 MLOAD PUSH1 0x60 SHR PUSH2 0x273F PUSH2 0xFF PUSH1 0x0 CODECOPY DUP1 PUSH2 0x77D MSTORE DUP1 PUSH2 0x84C MSTORE DUP1 PUSH2 0x12FB MSTORE DUP1 PUSH2 0x1476 MSTORE DUP1 PUSH2 0x1FA7 MSTORE POP PUSH2 0x273F PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xF5 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x6FADCF72 GT PUSH2 0x97 JUMPI DUP1 PUSH4 0xF2FDE38B GT PUSH2 0x66 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x63D JUMPI DUP1 PUSH4 0xF3DFC2A9 EQ PUSH2 0x681 JUMPI DUP1 PUSH4 0xF3FEF3A3 EQ PUSH2 0x6D1 JUMPI DUP1 PUSH4 0xFA00763A EQ PUSH2 0x71F JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x6FADCF72 EQ PUSH2 0x481 JUMPI DUP1 PUSH4 0x79BA5097 EQ PUSH2 0x51A JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x524 JUMPI DUP1 PUSH4 0xA4C0ED36 EQ PUSH2 0x558 JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x4B602282 GT PUSH2 0xD3 JUMPI DUP1 PUSH4 0x4B602282 EQ PUSH2 0x2E4 JUMPI DUP1 PUSH4 0x50188301 EQ PUSH2 0x302 JUMPI DUP1 PUSH4 0x6AE0BC76 EQ PUSH2 0x320 JUMPI DUP1 PUSH4 0x6EE4D553 EQ PUSH2 0x416 JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x165D35E1 EQ PUSH2 0xFA JUMPI DUP1 PUSH4 0x40429946 EQ PUSH2 0x12E JUMPI DUP1 PUSH4 0x4AB0D190 EQ PUSH2 0x239 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x102 PUSH2 0x779 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x237 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH2 0x100 DUP2 LT ISZERO PUSH2 0x145 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x1F3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x205 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x227 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 SWAP2 SWAP3 SWAP4 SWAP1 POP POP POP PUSH2 0x7A1 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x2CC PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0xC0 DUP2 LT ISZERO PUSH2 0x24F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xA16 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 ISZERO ISZERO DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x2EC PUSH2 0xD4E JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x30A PUSH2 0xD54 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x3FE PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0xC0 DUP2 LT ISZERO PUSH2 0x336 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x3BA JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x3CC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x3EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 SWAP2 SWAP3 SWAP4 SWAP1 POP POP POP PUSH2 0xD71 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 ISZERO ISZERO DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x47F PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x80 DUP2 LT ISZERO PUSH2 0x42C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x114A JUMP JUMPDEST STOP JUMPDEST PUSH2 0x518 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x497 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x4D4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x4E6 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x508 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 SWAP2 SWAP3 SWAP4 SWAP1 POP POP POP PUSH2 0x13D2 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x522 PUSH2 0x1608 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x52C PUSH2 0x17D0 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x63B PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x56E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x5B5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x5C7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x5E9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x17F4 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x67F PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x653 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x1B1D JUMP JUMPDEST STOP JUMPDEST PUSH2 0x6CF PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x697 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD ISZERO ISZERO SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x1D3E JUMP JUMPDEST STOP JUMPDEST PUSH2 0x71D PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x6E7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x1E5A JUMP JUMPDEST STOP JUMPDEST PUSH2 0x761 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x735 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x207C JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 ISZERO ISZERO DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x0 PUSH32 0x0 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH2 0x7A9 PUSH2 0x779 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x849 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x13 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D75737420757365204C494E4B20746F6B656E00000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP6 PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x90C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x17 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x43616E6E6F742063616C6C6261636B20746F204C494E4B000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP1 PUSH2 0x91D DUP13 DUP13 DUP12 DUP12 DUP12 DUP12 PUSH2 0x20D2 JUMP JUMPDEST SWAP2 POP SWAP2 POP DUP10 PUSH32 0xD8D7ECC4800D25FA53CE0372F13A416D98907A7EF3D8D3BDD79CF4FE75529C65 DUP14 DUP5 DUP15 DUP14 DUP14 DUP8 DUP14 DUP14 DUP14 PUSH1 0x40 MLOAD DUP1 DUP11 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP10 DUP2 MSTORE PUSH1 0x20 ADD DUP9 DUP2 MSTORE PUSH1 0x20 ADD DUP8 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP7 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP5 DUP5 DUP3 DUP2 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP SWAP11 POP POP POP POP POP POP POP POP POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 POP POP POP POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x3 PUSH1 0x0 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH2 0xABA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x2A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26E0 PUSH1 0x2A SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP7 PUSH1 0x0 PUSH1 0x8 SHL PUSH1 0x2 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ ISZERO PUSH2 0xB5C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D757374206861766520612076616C6964207265717565737449640000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xB6B DUP9 DUP9 DUP9 DUP9 DUP9 PUSH1 0x1 PUSH2 0x228C JUMP JUMPDEST DUP8 PUSH32 0x9E9BC7616D42C2835D05AE617E508454E63B30B934BE8AA932EBC125E0E58A64 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH3 0x61A80 GAS LT ISZERO PUSH2 0xC11 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x20 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D7573742070726F7669646520636F6E73756D657220656E6F75676820676173 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP7 DUP11 DUP7 PUSH1 0x40 MLOAD PUSH1 0x24 ADD DUP1 DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND PUSH1 0x20 DUP3 ADD DUP1 MLOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 DUP2 DUP4 AND OR DUP4 MSTORE POP POP POP POP PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0xCD4 JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0xCB1 JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP7 GAS CALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0xD36 JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0xD3B JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 SWAP3 POP POP POP SWAP7 SWAP6 POP POP POP POP POP POP JUMP JUMPDEST PUSH2 0x12C DUP2 JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD6C PUSH1 0x1 PUSH1 0x4 SLOAD PUSH2 0x2463 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x3 PUSH1 0x0 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH2 0xE15 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x2A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26E0 PUSH1 0x2A SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP8 PUSH1 0x0 PUSH1 0x8 SHL PUSH1 0x2 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ ISZERO PUSH2 0xEB7 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D757374206861766520612076616C6964207265717565737449640000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP9 DUP5 DUP5 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP PUSH1 0x0 PUSH1 0x20 DUP3 ADD MLOAD SWAP1 POP DUP1 DUP4 EQ PUSH2 0xF7B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1C DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x466972737420776F7264206D7573742062652072657175657374496400000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xF8A DUP13 DUP13 DUP13 DUP13 DUP13 PUSH1 0x2 PUSH2 0x228C JUMP JUMPDEST DUP12 PUSH32 0x9E9BC7616D42C2835D05AE617E508454E63B30B934BE8AA932EBC125E0E58A64 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH3 0x61A80 GAS LT ISZERO PUSH2 0x1030 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x20 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D7573742070726F7669646520636F6E73756D657220656E6F75676820676173 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP11 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP11 DUP10 DUP10 PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP5 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x4 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY DUP1 DUP4 ADD SWAP3 POP POP POP SWAP4 POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0x10CC JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0x10A9 JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP7 GAS CALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x112E JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x1133 JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 SWAP6 POP POP POP POP POP POP SWAP8 SWAP7 POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x1158 DUP5 CALLER DUP6 DUP6 PUSH2 0x24EC JUMP JUMPDEST SWAP1 POP DUP1 PUSH1 0xFF NOT AND PUSH1 0x2 PUSH1 0x0 DUP8 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ PUSH2 0x11FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x506172616D7320646F206E6F74206D6174636820726571756573742049440000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST TIMESTAMP DUP3 GT ISZERO PUSH2 0x1270 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x52657175657374206973206E6F74206578706972656400000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x2 PUSH1 0x0 DUP7 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP1 DUP3 ADD PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH31 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE PUSH1 0x0 DUP3 ADD PUSH1 0x1F PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH1 0xFF MUL NOT AND SWAP1 SSTORE POP POP DUP5 PUSH32 0xA7842B9EC549398102C0D91B1B9919B2F20558AEFDADF57528A95C6CD3292E93 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xA9059CBB CALLER DUP7 PUSH1 0x40 MLOAD DUP4 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x138A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x139E JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x13B4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x13CB JUMPI INVALID JUMPDEST POP POP POP POP POP JUMP JUMPDEST PUSH1 0x3 PUSH1 0x0 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH2 0x1474 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x2A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26E0 PUSH1 0x2A SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x1519 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x32 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x2679 PUSH1 0x32 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 DUP4 PUSH1 0x40 MLOAD DUP1 DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY DUP1 DUP4 ADD SWAP3 POP POP POP SWAP3 POP POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP7 GAS CALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x1586 JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x158B JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 PUSH2 0x1602 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x466F727761726465642063616C6C206661696C65642E00000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x1 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x16CB JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D7573742062652070726F706F736564206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP CALLER PUSH1 0x0 DUP1 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8BE0079C531659141344CD1FD0A4F28419497F9722A3DAAFE3B4186F6B6457E0 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH2 0x17FC PUSH2 0x779 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x189C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x13 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D75737420757365204C494E4B20746F6B656E00000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x2 PUSH1 0x20 MUL PUSH1 0x4 ADD DUP2 MLOAD LT ISZERO PUSH2 0x191B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x496E76616C69642072657175657374206C656E67746800000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 PUSH1 0x0 PUSH1 0x20 DUP3 ADD MLOAD SWAP1 POP PUSH4 0x40429946 PUSH1 0xE0 SHL PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND EQ PUSH2 0x19DF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D757374207573652077686974656C69737465642066756E6374696F6E730000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP6 PUSH1 0x24 DUP6 ADD MSTORE DUP5 PUSH1 0x44 DUP6 ADD MSTORE PUSH1 0x0 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP6 PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0x1A38 JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0x1A15 JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 GAS DELEGATECALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x1A98 JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x1A9D JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 PUSH2 0x1B14 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x18 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x556E61626C6520746F2063726561746520726571756573740000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x1BDE JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4F6E6C792063616C6C61626C65206279206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x1C80 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x17 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x43616E6E6F74207472616E7366657220746F2073656C66000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xED8889F560326EB138920D842192F0EB3DD22B4F139C87A2C57538E05BAE1278 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x1DFF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4F6E6C792063616C6C61626C65206279206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x3 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH1 0xFF MUL NOT AND SWAP1 DUP4 ISZERO ISZERO MUL OR SWAP1 SSTORE POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x1F1B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4F6E6C792063616C6C61626C65206279206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH2 0x1F30 PUSH1 0x1 DUP3 PUSH2 0x256F SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x4 SLOAD LT ISZERO PUSH2 0x1F8A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x35 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26AB PUSH1 0x35 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x1F9F DUP3 PUSH1 0x4 SLOAD PUSH2 0x2463 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x4 DUP2 SWAP1 SSTORE POP PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xA9059CBB DUP5 DUP5 PUSH1 0x40 MLOAD DUP4 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x2036 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x204A JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x2060 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x2077 JUMPI INVALID JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x3 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 DUP8 DUP5 PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP2 POP PUSH1 0x0 PUSH1 0x8 SHL PUSH1 0x2 PUSH1 0x0 DUP5 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ PUSH2 0x21BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x14 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D75737420757365206120756E69717565204944000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x21D4 PUSH2 0x12C TIMESTAMP PUSH2 0x256F SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP1 POP PUSH1 0x0 PUSH2 0x21E4 DUP9 DUP9 DUP9 DUP6 PUSH2 0x24EC JUMP JUMPDEST SWAP1 POP PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 DUP3 PUSH1 0xFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x2204 DUP7 PUSH2 0x25F7 JUMP JUMPDEST PUSH1 0xFF AND DUP2 MSTORE POP PUSH1 0x2 PUSH1 0x0 DUP6 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 ADD MLOAD DUP2 PUSH1 0x0 ADD PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH31 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH1 0x8 SHR MUL OR SWAP1 SSTORE POP PUSH1 0x20 DUP3 ADD MLOAD DUP2 PUSH1 0x0 ADD PUSH1 0x1F PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH1 0xFF MUL NOT AND SWAP1 DUP4 PUSH1 0xFF AND MUL OR SWAP1 SSTORE POP SWAP1 POP POP POP SWAP7 POP SWAP7 SWAP5 POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x229A DUP7 DUP7 DUP7 DUP7 PUSH2 0x24EC JUMP JUMPDEST SWAP1 POP DUP1 PUSH1 0xFF NOT AND PUSH1 0x2 PUSH1 0x0 DUP10 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ PUSH2 0x233C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x506172616D7320646F206E6F74206D6174636820726571756573742049440000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x2345 DUP3 PUSH2 0x25F7 JUMP JUMPDEST PUSH1 0xFF AND PUSH1 0x2 PUSH1 0x0 DUP10 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x1F SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH1 0xFF AND GT ISZERO PUSH2 0x23E3 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x18 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x446174612076657273696F6E73206D757374206D617463680000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x23F8 DUP7 PUSH1 0x4 SLOAD PUSH2 0x256F SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x4 DUP2 SWAP1 SSTORE POP PUSH1 0x2 PUSH1 0x0 DUP9 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP1 DUP3 ADD PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH31 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE PUSH1 0x0 DUP3 ADD PUSH1 0x1F PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH1 0xFF MUL NOT AND SWAP1 SSTORE POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 GT ISZERO PUSH2 0x24DB JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x536166654D6174683A207375627472616374696F6E206F766572666C6F770000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP3 DUP5 SUB SWAP1 POP DUP1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP5 DUP5 DUP5 DUP5 PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP4 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x4 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP5 POP POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP1 POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 DUP3 DUP5 ADD SWAP1 POP DUP4 DUP2 LT ISZERO PUSH2 0x25ED JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x536166654D6174683A206164646974696F6E206F766572666C6F770000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x100 DUP3 LT PUSH2 0x2670 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x6E756D62657220746F6F2062696720746F206361737400000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP INVALID NUMBER PUSH2 0x6E6E PUSH16 0x74207573652023666F72776172642074 PUSH16 0x2073656E64206D657373616765732074 PUSH16 0x204C696E6B20746F6B656E416D6F756E PUSH21 0x207265717565737465642069732067726561746572 KECCAK256 PUSH21 0x68616E20776974686472617761626C652062616C61 PUSH15 0x63654E6F7420616E20617574686F72 PUSH10 0x7A6564206E6F64652074 PUSH16 0x2066756C66696C6C2072657175657374 PUSH20 0xA26469706673582212201EE0A53524752811CB08 0xC DUP15 0xA9 0xD6 0xBA 0xB7 0xF7 0xEF DIV RETURN 0xD6 COINBASE SHL CALLER PUSH29 0xFEDBCD1FCDC34464736F6C634300070000330000000000000000000000 ", + "sourceMap": "495:14968:1:-:0;;;1081:1;1250:66;;1927:156;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1978:5;423:8:2;415:5;;:16;;;;;;;;;;;;;;;;;;379:57;2024:4:1::1;1993:36;;;;;;;;;;::::0;::::1;1927:156:::0;;495:14968;;;;;;;;;;;" + }, + "deployedBytecode": { + "immutableReferences": { + "151": [ + { + "length": 32, + "start": 1917 + }, + { + "length": 32, + "start": 2124 + }, + { + "length": 32, + "start": 4859 + }, + { + "length": 32, + "start": 5238 + }, + { + "length": 32, + "start": 8103 + } + ] + }, + "linkReferences": {}, + "object": "608060405234801561001057600080fd5b50600436106100f55760003560e01c80636fadcf7211610097578063f2fde38b11610066578063f2fde38b1461063d578063f3dfc2a914610681578063f3fef3a3146106d1578063fa00763a1461071f576100f5565b80636fadcf721461048157806379ba50971461051a5780638da5cb5b14610524578063a4c0ed3614610558576100f5565b80634b602282116100d35780634b602282146102e457806350188301146103025780636ae0bc76146103205780636ee4d55314610416576100f5565b8063165d35e1146100fa578063404299461461012e5780634ab0d19014610239575b600080fd5b610102610779565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610237600480360361010081101561014557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919080359060200190929190803590602001906401000000008111156101f357600080fd5b82018360208201111561020557600080fd5b8035906020019184600183028401116401000000008311171561022757600080fd5b90919293919293905050506107a1565b005b6102cc600480360360c081101561024f57600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919080359060200190929190505050610a16565b60405180821515815260200191505060405180910390f35b6102ec610d4e565b6040518082815260200191505060405180910390f35b61030a610d54565b6040518082815260200191505060405180910390f35b6103fe600480360360c081101561033657600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916906020019092919080359060200190929190803590602001906401000000008111156103ba57600080fd5b8201836020820111156103cc57600080fd5b803590602001918460018302840111640100000000831117156103ee57600080fd5b9091929391929390505050610d71565b60405180821515815260200191505060405180910390f35b61047f6004803603608081101561042c57600080fd5b81019080803590602001909291908035906020019092919080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291908035906020019092919050505061114a565b005b6105186004803603604081101561049757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156104d457600080fd5b8201836020820111156104e657600080fd5b8035906020019184600183028401116401000000008311171561050857600080fd5b90919293919293905050506113d2565b005b610522611608565b005b61052c6117d0565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61063b6004803603606081101561056e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105b557600080fd5b8201836020820111156105c757600080fd5b803590602001918460018302840111640100000000831117156105e957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506117f4565b005b61067f6004803603602081101561065357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611b1d565b005b6106cf6004803603604081101561069757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803515159060200190929190505050611d3e565b005b61071d600480360360408110156106e757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611e5a565b005b6107616004803603602081101561073557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061207c565b60405180821515815260200191505060405180910390f35b60007f0000000000000000000000000000000000000000000000000000000000000000905090565b6107a9610779565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610849576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f4d75737420757365204c494e4b20746f6b656e0000000000000000000000000081525060200191505060405180910390fd5b857f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141561090c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260178152602001807f43616e6e6f742063616c6c6261636b20746f204c494e4b00000000000000000081525060200191505060405180910390fd5b60008061091d8c8c8b8b8b8b6120d2565b91509150897fd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c658d848e8d8d878d8d8d604051808a73ffffffffffffffffffffffffffffffffffffffff1681526020018981526020018881526020018773ffffffffffffffffffffffffffffffffffffffff168152602001867bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509a505050505050505050505060405180910390a2505050505050505050505050565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610aba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806126e0602a913960400191505060405180910390fd5b86600060081b6002600083815260200190815260200160002060000160009054906101000a900460081b60ff19161415610b5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f4d757374206861766520612076616c696420726571756573744964000000000081525060200191505060405180910390fd5b610b6b8888888888600161228c565b877f9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a6460405160405180910390a262061a805a1015610c11576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4d7573742070726f7669646520636f6e73756d657220656e6f7567682067617381525060200191505060405180910390fd5b60008673ffffffffffffffffffffffffffffffffffffffff16868a866040516024018083815260200182815260200192505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610cd45780518252602082019150602081019050602083039250610cb1565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610d36576040519150601f19603f3d011682016040523d82523d6000602084013e610d3b565b606091505b5050905080925050509695505050505050565b61012c81565b6000610d6c600160045461246390919063ffffffff16565b905090565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610e15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806126e0602a913960400191505060405180910390fd5b87600060081b6002600083815260200190815260200160002060000160009054906101000a900460081b60ff19161415610eb7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f4d757374206861766520612076616c696420726571756573744964000000000081525060200191505060405180910390fd5b8884848080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050600060208201519050808314610f7b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f466972737420776f7264206d757374206265207265717565737449640000000081525060200191505060405180910390fd5b610f8a8c8c8c8c8c600261228c565b8b7f9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a6460405160405180910390a262061a805a1015611030576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4d7573742070726f7669646520636f6e73756d657220656e6f7567682067617381525060200191505060405180910390fd5b60008a73ffffffffffffffffffffffffffffffffffffffff168a898960405160200180847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526004018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b602083106110cc57805182526020820191506020810190506020830392506110a9565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461112e576040519150601f19603f3d011682016040523d82523d6000602084013e611133565b606091505b505090508095505050505050979650505050505050565b6000611158843385856124ec565b90508060ff19166002600087815260200190815260200160002060000160009054906101000a900460081b60ff1916146111fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f506172616d7320646f206e6f74206d617463682072657175657374204944000081525060200191505060405180910390fd5b42821115611270576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f52657175657374206973206e6f7420657870697265640000000000000000000081525060200191505060405180910390fd5b60026000868152602001908152602001600020600080820160006101000a8154907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021916905560008201601f6101000a81549060ff02191690555050847fa7842b9ec549398102c0d91b1b9919b2f20558aefdadf57528a95c6cd3292e9360405160405180910390a27f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15801561138a57600080fd5b505af115801561139e573d6000803e3d6000fd5b505050506040513d60208110156113b457600080fd5b81019080805190602001909291905050506113cb57fe5b5050505050565b600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16611474576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806126e0602a913960400191505060405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611519576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001806126796032913960400191505060405180910390fd5b60008373ffffffffffffffffffffffffffffffffffffffff1683836040518083838082843780830192505050925050506000604051808303816000865af19150503d8060008114611586576040519150601f19603f3d011682016040523d82523d6000602084013e61158b565b606091505b5050905080611602576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f466f727761726465642063616c6c206661696c65642e0000000000000000000081525060200191505060405180910390fd5b50505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146116cb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4d7573742062652070726f706f736564206f776e65720000000000000000000081525060200191505060405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a350565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6117fc610779565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461189c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f4d75737420757365204c494e4b20746f6b656e0000000000000000000000000081525060200191505060405180910390fd5b8060026020026004018151101561191b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f496e76616c69642072657175657374206c656e6774680000000000000000000081525060200191505060405180910390fd5b81600060208201519050634042994660e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146119df576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f4d757374207573652077686974656c69737465642066756e6374696f6e73000081525060200191505060405180910390fd5b85602485015284604485015260003073ffffffffffffffffffffffffffffffffffffffff16856040518082805190602001908083835b60208310611a385780518252602082019150602081019050602083039250611a15565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855af49150503d8060008114611a98576040519150601f19603f3d011682016040523d82523d6000602084013e611a9d565b606091505b5050905080611b14576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f556e61626c6520746f206372656174652072657175657374000000000000000081525060200191505060405180910390fd5b50505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611bde576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415611c80576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260178152602001807f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000081525060200191505060405180910390fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae127860405160405180910390a350565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611dff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000081525060200191505060405180910390fd5b80600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611f1b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000081525060200191505060405180910390fd5b80611f3060018261256f90919063ffffffff16565b6004541015611f8a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260358152602001806126ab6035913960400191505060405180910390fd5b611f9f8260045461246390919063ffffffff16565b6004819055507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15801561203657600080fd5b505af115801561204a573d6000803e3d6000fd5b505050506040513d602081101561206057600080fd5b810190808051906020019092919050505061207757fe5b505050565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff169050919050565b6000808784604051602001808373ffffffffffffffffffffffffffffffffffffffff1660601b815260140182815260200192505050604051602081830303815290604052805190602001209150600060081b6002600084815260200190815260200160002060000160009054906101000a900460081b60ff1916146121bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f4d75737420757365206120756e6971756520494400000000000000000000000081525060200191505060405180910390fd5b6121d461012c4261256f90919063ffffffff16565b905060006121e4888888856124ec565b905060405180604001604052808260ff19168152602001612204866125f7565b60ff168152506002600085815260200190815260200160002060008201518160000160006101000a8154817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021916908360081c0217905550602082015181600001601f6101000a81548160ff021916908360ff16021790555090505050965096945050505050565b600061229a868686866124ec565b90508060ff19166002600089815260200190815260200160002060000160009054906101000a900460081b60ff19161461233c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f506172616d7320646f206e6f74206d617463682072657175657374204944000081525060200191505060405180910390fd5b612345826125f7565b60ff1660026000898152602001908152602001600020600001601f9054906101000a900460ff1660ff1611156123e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f446174612076657273696f6e73206d757374206d61746368000000000000000081525060200191505060405180910390fd5b6123f88660045461256f90919063ffffffff16565b60048190555060026000888152602001908152602001600020600080820160006101000a8154907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021916905560008201601f6101000a81549060ff0219169055505050505050505050565b6000828211156124db576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525060200191505060405180910390fd5b600082840390508091505092915050565b600084848484604051602001808581526020018473ffffffffffffffffffffffffffffffffffffffff1660601b8152601401837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600401828152602001945050505050604051602081830303815290604052805190602001209050949350505050565b6000808284019050838110156125ed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b60006101008210612670576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f6e756d62657220746f6f2062696720746f20636173740000000000000000000081525060200191505060405180910390fd5b81905091905056fe43616e6e6f74207573652023666f727761726420746f2073656e64206d6573736167657320746f204c696e6b20746f6b656e416d6f756e74207265717565737465642069732067726561746572207468616e20776974686472617761626c652062616c616e63654e6f7420616e20617574686f72697a6564206e6f646520746f2066756c66696c6c207265717565737473a26469706673582212201ee0a53524752811cb080c8ea9d6bab7f7ef04f3d6411b337cfedbcd1fcdc34464736f6c63430007000033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xF5 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x6FADCF72 GT PUSH2 0x97 JUMPI DUP1 PUSH4 0xF2FDE38B GT PUSH2 0x66 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x63D JUMPI DUP1 PUSH4 0xF3DFC2A9 EQ PUSH2 0x681 JUMPI DUP1 PUSH4 0xF3FEF3A3 EQ PUSH2 0x6D1 JUMPI DUP1 PUSH4 0xFA00763A EQ PUSH2 0x71F JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x6FADCF72 EQ PUSH2 0x481 JUMPI DUP1 PUSH4 0x79BA5097 EQ PUSH2 0x51A JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x524 JUMPI DUP1 PUSH4 0xA4C0ED36 EQ PUSH2 0x558 JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x4B602282 GT PUSH2 0xD3 JUMPI DUP1 PUSH4 0x4B602282 EQ PUSH2 0x2E4 JUMPI DUP1 PUSH4 0x50188301 EQ PUSH2 0x302 JUMPI DUP1 PUSH4 0x6AE0BC76 EQ PUSH2 0x320 JUMPI DUP1 PUSH4 0x6EE4D553 EQ PUSH2 0x416 JUMPI PUSH2 0xF5 JUMP JUMPDEST DUP1 PUSH4 0x165D35E1 EQ PUSH2 0xFA JUMPI DUP1 PUSH4 0x40429946 EQ PUSH2 0x12E JUMPI DUP1 PUSH4 0x4AB0D190 EQ PUSH2 0x239 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x102 PUSH2 0x779 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x237 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH2 0x100 DUP2 LT ISZERO PUSH2 0x145 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x1F3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x205 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x227 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 SWAP2 SWAP3 SWAP4 SWAP1 POP POP POP PUSH2 0x7A1 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x2CC PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0xC0 DUP2 LT ISZERO PUSH2 0x24F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xA16 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 ISZERO ISZERO DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x2EC PUSH2 0xD4E JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x30A PUSH2 0xD54 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x3FE PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0xC0 DUP2 LT ISZERO PUSH2 0x336 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x3BA JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x3CC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x3EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 SWAP2 SWAP3 SWAP4 SWAP1 POP POP POP PUSH2 0xD71 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 ISZERO ISZERO DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x47F PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x80 DUP2 LT ISZERO PUSH2 0x42C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x114A JUMP JUMPDEST STOP JUMPDEST PUSH2 0x518 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x497 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x4D4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x4E6 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x508 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 SWAP2 SWAP3 SWAP4 SWAP1 POP POP POP PUSH2 0x13D2 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x522 PUSH2 0x1608 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x52C PUSH2 0x17D0 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x63B PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x60 DUP2 LT ISZERO PUSH2 0x56E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x5B5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x5C7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0x5E9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x17F4 JUMP JUMPDEST STOP JUMPDEST PUSH2 0x67F PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x653 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x1B1D JUMP JUMPDEST STOP JUMPDEST PUSH2 0x6CF PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x697 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD ISZERO ISZERO SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x1D3E JUMP JUMPDEST STOP JUMPDEST PUSH2 0x71D PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x6E7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x1E5A JUMP JUMPDEST STOP JUMPDEST PUSH2 0x761 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x735 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x207C JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 ISZERO ISZERO DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x0 PUSH32 0x0 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH2 0x7A9 PUSH2 0x779 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x849 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x13 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D75737420757365204C494E4B20746F6B656E00000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP6 PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x90C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x17 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x43616E6E6F742063616C6C6261636B20746F204C494E4B000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP1 PUSH2 0x91D DUP13 DUP13 DUP12 DUP12 DUP12 DUP12 PUSH2 0x20D2 JUMP JUMPDEST SWAP2 POP SWAP2 POP DUP10 PUSH32 0xD8D7ECC4800D25FA53CE0372F13A416D98907A7EF3D8D3BDD79CF4FE75529C65 DUP14 DUP5 DUP15 DUP14 DUP14 DUP8 DUP14 DUP14 DUP14 PUSH1 0x40 MLOAD DUP1 DUP11 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP10 DUP2 MSTORE PUSH1 0x20 ADD DUP9 DUP2 MSTORE PUSH1 0x20 ADD DUP8 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP7 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP5 DUP5 DUP3 DUP2 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP SWAP11 POP POP POP POP POP POP POP POP POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 POP POP POP POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x3 PUSH1 0x0 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH2 0xABA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x2A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26E0 PUSH1 0x2A SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP7 PUSH1 0x0 PUSH1 0x8 SHL PUSH1 0x2 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ ISZERO PUSH2 0xB5C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D757374206861766520612076616C6964207265717565737449640000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xB6B DUP9 DUP9 DUP9 DUP9 DUP9 PUSH1 0x1 PUSH2 0x228C JUMP JUMPDEST DUP8 PUSH32 0x9E9BC7616D42C2835D05AE617E508454E63B30B934BE8AA932EBC125E0E58A64 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH3 0x61A80 GAS LT ISZERO PUSH2 0xC11 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x20 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D7573742070726F7669646520636F6E73756D657220656E6F75676820676173 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP7 DUP11 DUP7 PUSH1 0x40 MLOAD PUSH1 0x24 ADD DUP1 DUP4 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE SWAP1 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND PUSH1 0x20 DUP3 ADD DUP1 MLOAD PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 DUP2 DUP4 AND OR DUP4 MSTORE POP POP POP POP PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0xCD4 JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0xCB1 JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP7 GAS CALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0xD36 JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0xD3B JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 SWAP3 POP POP POP SWAP7 SWAP6 POP POP POP POP POP POP JUMP JUMPDEST PUSH2 0x12C DUP2 JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD6C PUSH1 0x1 PUSH1 0x4 SLOAD PUSH2 0x2463 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x3 PUSH1 0x0 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH2 0xE15 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x2A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26E0 PUSH1 0x2A SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP8 PUSH1 0x0 PUSH1 0x8 SHL PUSH1 0x2 PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ ISZERO PUSH2 0xEB7 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D757374206861766520612076616C6964207265717565737449640000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP9 DUP5 DUP5 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP PUSH1 0x0 PUSH1 0x20 DUP3 ADD MLOAD SWAP1 POP DUP1 DUP4 EQ PUSH2 0xF7B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1C DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x466972737420776F7264206D7573742062652072657175657374496400000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xF8A DUP13 DUP13 DUP13 DUP13 DUP13 PUSH1 0x2 PUSH2 0x228C JUMP JUMPDEST DUP12 PUSH32 0x9E9BC7616D42C2835D05AE617E508454E63B30B934BE8AA932EBC125E0E58A64 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH3 0x61A80 GAS LT ISZERO PUSH2 0x1030 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x20 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D7573742070726F7669646520636F6E73756D657220656E6F75676820676173 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP11 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP11 DUP10 DUP10 PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP5 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x4 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY DUP1 DUP4 ADD SWAP3 POP POP POP SWAP4 POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0x10CC JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0x10A9 JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP7 GAS CALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x112E JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x1133 JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 SWAP6 POP POP POP POP POP POP SWAP8 SWAP7 POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x1158 DUP5 CALLER DUP6 DUP6 PUSH2 0x24EC JUMP JUMPDEST SWAP1 POP DUP1 PUSH1 0xFF NOT AND PUSH1 0x2 PUSH1 0x0 DUP8 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ PUSH2 0x11FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x506172616D7320646F206E6F74206D6174636820726571756573742049440000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST TIMESTAMP DUP3 GT ISZERO PUSH2 0x1270 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x52657175657374206973206E6F74206578706972656400000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x2 PUSH1 0x0 DUP7 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP1 DUP3 ADD PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH31 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE PUSH1 0x0 DUP3 ADD PUSH1 0x1F PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH1 0xFF MUL NOT AND SWAP1 SSTORE POP POP DUP5 PUSH32 0xA7842B9EC549398102C0D91B1B9919B2F20558AEFDADF57528A95C6CD3292E93 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xA9059CBB CALLER DUP7 PUSH1 0x40 MLOAD DUP4 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x138A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x139E JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x13B4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x13CB JUMPI INVALID JUMPDEST POP POP POP POP POP JUMP JUMPDEST PUSH1 0x3 PUSH1 0x0 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH2 0x1474 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x2A DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26E0 PUSH1 0x2A SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x1519 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x32 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x2679 PUSH1 0x32 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 DUP4 PUSH1 0x40 MLOAD DUP1 DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY DUP1 DUP4 ADD SWAP3 POP POP POP SWAP3 POP POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP7 GAS CALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x1586 JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x158B JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 PUSH2 0x1602 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x466F727761726465642063616C6C206661696C65642E00000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x1 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x16CB JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D7573742062652070726F706F736564206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP CALLER PUSH1 0x0 DUP1 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8BE0079C531659141344CD1FD0A4F28419497F9722A3DAAFE3B4186F6B6457E0 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH2 0x17FC PUSH2 0x779 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x189C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x13 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D75737420757365204C494E4B20746F6B656E00000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x2 PUSH1 0x20 MUL PUSH1 0x4 ADD DUP2 MLOAD LT ISZERO PUSH2 0x191B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x496E76616C69642072657175657374206C656E67746800000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 PUSH1 0x0 PUSH1 0x20 DUP3 ADD MLOAD SWAP1 POP PUSH4 0x40429946 PUSH1 0xE0 SHL PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND EQ PUSH2 0x19DF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D757374207573652077686974656C69737465642066756E6374696F6E730000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP6 PUSH1 0x24 DUP6 ADD MSTORE DUP5 PUSH1 0x44 DUP6 ADD MSTORE PUSH1 0x0 ADDRESS PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP6 PUSH1 0x40 MLOAD DUP1 DUP3 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 JUMPDEST PUSH1 0x20 DUP4 LT PUSH2 0x1A38 JUMPI DUP1 MLOAD DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH1 0x20 DUP4 SUB SWAP3 POP PUSH2 0x1A15 JUMP JUMPDEST PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB DUP1 NOT DUP3 MLOAD AND DUP2 DUP5 MLOAD AND DUP1 DUP3 OR DUP6 MSTORE POP POP POP POP POP POP SWAP1 POP ADD SWAP2 POP POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 DUP6 GAS DELEGATECALL SWAP2 POP POP RETURNDATASIZE DUP1 PUSH1 0x0 DUP2 EQ PUSH2 0x1A98 JUMPI PUSH1 0x40 MLOAD SWAP2 POP PUSH1 0x1F NOT PUSH1 0x3F RETURNDATASIZE ADD AND DUP3 ADD PUSH1 0x40 MSTORE RETURNDATASIZE DUP3 MSTORE RETURNDATASIZE PUSH1 0x0 PUSH1 0x20 DUP5 ADD RETURNDATACOPY PUSH2 0x1A9D JUMP JUMPDEST PUSH1 0x60 SWAP2 POP JUMPDEST POP POP SWAP1 POP DUP1 PUSH2 0x1B14 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x18 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x556E61626C6520746F2063726561746520726571756573740000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x1BDE JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4F6E6C792063616C6C61626C65206279206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x1C80 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x17 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x43616E6E6F74207472616E7366657220746F2073656C66000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xED8889F560326EB138920D842192F0EB3DD22B4F139C87A2C57538E05BAE1278 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x1DFF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4F6E6C792063616C6C61626C65206279206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x3 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH1 0xFF MUL NOT AND SWAP1 DUP4 ISZERO ISZERO MUL OR SWAP1 SSTORE POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x1F1B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4F6E6C792063616C6C61626C65206279206F776E657200000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH2 0x1F30 PUSH1 0x1 DUP3 PUSH2 0x256F SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x4 SLOAD LT ISZERO PUSH2 0x1F8A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x35 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH2 0x26AB PUSH1 0x35 SWAP2 CODECOPY PUSH1 0x40 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x1F9F DUP3 PUSH1 0x4 SLOAD PUSH2 0x2463 SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x4 DUP2 SWAP1 SSTORE POP PUSH32 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xA9059CBB DUP5 DUP5 PUSH1 0x40 MLOAD DUP4 PUSH4 0xFFFFFFFF AND PUSH1 0xE0 SHL DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x2036 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x204A JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x2060 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0x2077 JUMPI INVALID JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x3 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 DUP8 DUP5 PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP2 POP PUSH1 0x0 PUSH1 0x8 SHL PUSH1 0x2 PUSH1 0x0 DUP5 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ PUSH2 0x21BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x14 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x4D75737420757365206120756E69717565204944000000000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x21D4 PUSH2 0x12C TIMESTAMP PUSH2 0x256F SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST SWAP1 POP PUSH1 0x0 PUSH2 0x21E4 DUP9 DUP9 DUP9 DUP6 PUSH2 0x24EC JUMP JUMPDEST SWAP1 POP PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 DUP3 PUSH1 0xFF NOT AND DUP2 MSTORE PUSH1 0x20 ADD PUSH2 0x2204 DUP7 PUSH2 0x25F7 JUMP JUMPDEST PUSH1 0xFF AND DUP2 MSTORE POP PUSH1 0x2 PUSH1 0x0 DUP6 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 ADD MLOAD DUP2 PUSH1 0x0 ADD PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH31 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH1 0x8 SHR MUL OR SWAP1 SSTORE POP PUSH1 0x20 DUP3 ADD MLOAD DUP2 PUSH1 0x0 ADD PUSH1 0x1F PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH1 0xFF MUL NOT AND SWAP1 DUP4 PUSH1 0xFF AND MUL OR SWAP1 SSTORE POP SWAP1 POP POP POP SWAP7 POP SWAP7 SWAP5 POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x229A DUP7 DUP7 DUP7 DUP7 PUSH2 0x24EC JUMP JUMPDEST SWAP1 POP DUP1 PUSH1 0xFF NOT AND PUSH1 0x2 PUSH1 0x0 DUP10 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0x8 SHL PUSH1 0xFF NOT AND EQ PUSH2 0x233C JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x506172616D7320646F206E6F74206D6174636820726571756573742049440000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x2345 DUP3 PUSH2 0x25F7 JUMP JUMPDEST PUSH1 0xFF AND PUSH1 0x2 PUSH1 0x0 DUP10 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 ADD PUSH1 0x1F SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH1 0xFF AND PUSH1 0xFF AND GT ISZERO PUSH2 0x23E3 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x18 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x446174612076657273696F6E73206D757374206D617463680000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x23F8 DUP7 PUSH1 0x4 SLOAD PUSH2 0x256F SWAP1 SWAP2 SWAP1 PUSH4 0xFFFFFFFF AND JUMP JUMPDEST PUSH1 0x4 DUP2 SWAP1 SSTORE POP PUSH1 0x2 PUSH1 0x0 DUP9 DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP1 DUP3 ADD PUSH1 0x0 PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH31 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 SSTORE PUSH1 0x0 DUP3 ADD PUSH1 0x1F PUSH2 0x100 EXP DUP2 SLOAD SWAP1 PUSH1 0xFF MUL NOT AND SWAP1 SSTORE POP POP POP POP POP POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 GT ISZERO PUSH2 0x24DB JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1E DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x536166654D6174683A207375627472616374696F6E206F766572666C6F770000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 DUP3 DUP5 SUB SWAP1 POP DUP1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP5 DUP5 DUP5 DUP5 PUSH1 0x40 MLOAD PUSH1 0x20 ADD DUP1 DUP6 DUP2 MSTORE PUSH1 0x20 ADD DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x60 SHL DUP2 MSTORE PUSH1 0x14 ADD DUP4 PUSH28 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF NOT AND DUP2 MSTORE PUSH1 0x4 ADD DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP5 POP POP POP POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 DUP4 SUB SUB DUP2 MSTORE SWAP1 PUSH1 0x40 MSTORE DUP1 MLOAD SWAP1 PUSH1 0x20 ADD KECCAK256 SWAP1 POP SWAP5 SWAP4 POP POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 DUP3 DUP5 ADD SWAP1 POP DUP4 DUP2 LT ISZERO PUSH2 0x25ED JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x1B DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x536166654D6174683A206164646974696F6E206F766572666C6F770000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x100 DUP3 LT PUSH2 0x2670 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE PUSH1 0x16 DUP2 MSTORE PUSH1 0x20 ADD DUP1 PUSH32 0x6E756D62657220746F6F2062696720746F206361737400000000000000000000 DUP2 MSTORE POP PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP INVALID NUMBER PUSH2 0x6E6E PUSH16 0x74207573652023666F72776172642074 PUSH16 0x2073656E64206D657373616765732074 PUSH16 0x204C696E6B20746F6B656E416D6F756E PUSH21 0x207265717565737465642069732067726561746572 KECCAK256 PUSH21 0x68616E20776974686472617761626C652062616C61 PUSH15 0x63654E6F7420616E20617574686F72 PUSH10 0x7A6564206E6F64652074 PUSH16 0x2066756C66696C6C2072657175657374 PUSH20 0xA26469706673582212201EE0A53524752811CB08 0xC DUP15 0xA9 0xD6 0xBA 0xB7 0xF7 0xEF DIV RETURN 0xD6 COINBASE SHL CALLER PUSH29 0xFEDBCD1FCDC34464736F6C634300070000330000000000000000000000 ", + "sourceMap": "495:14968:1:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10292:122;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;2761:673;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;4286:986;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;754:47;;;:::i;:::-;;;;;;;;;;;;;;;;;;;8733:192;;;:::i;:::-;;;;;;;;;;;;;;;;;;;6148:1015;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;9488:601;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;10418:276;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;841:254:2;;;:::i;:::-;;147:20;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;718:663:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;542:210:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;7749:147:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;8209:282;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;7359:141;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;10292:122;10367:7;10399:9;10384:25;;10292:122;:::o;2761:673::-;1566:19:0;:17;:19::i;:::-;1552:33;;:10;:33;;;1544:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3044:15:1::1;15410:9;15396:24;;:2;:24;;;;15388:60;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;3070:17:::2;3089:18:::0;3111:135:::2;3138:6;3152:7;3167:15;3190:18;3216:5;3229:11;3111:19;:135::i;:::-;3069:177;;;;3278:6;3257:172;3292:6;3306:9;3323:7;3338:15;3361:18;3387:10;3405:11;3424:4;;3257:172;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;15454:1;;1615::0::1;2761:673:1::0;;;;;;;;;:::o;4286:986::-;4562:4;15138:19;:31;15158:10;15138:31;;;;;;;;;;;;;;;;;;;;;;;;;15130:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4538:9:::1;14959:1;14920:40;;:13;:24;14934:9;14920:24;;;;;;;;;;;:35;;;;;;;;;;;;:40;;;;;14912:80;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;4576:134:::2;4604:9;4621:7;4636:15;4659:18;4685:10;4703:1;4576:20;:134::i;:::-;4736:9;4721:25;;;;;;;;;;914:6;4760:9;:39;;4752:84;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;5102:12;5120:15;:20;;5164:18;5184:9;5195:4;5141:59;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5120:81;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5101:100;;;5260:7;5253:14;;;15222:1:::1;4286:986:::0;;;;;;;;:::o;754:47::-;792:9;754:47;:::o;8733:192::-;8843:7;8867:53;1081:1;8867:20;;:24;;:53;;;;:::i;:::-;8860:60;;8733:192;:::o;6148:1015::-;6470:4;15138:19;:31;15158:10;15138:31;;;;;;;;;;;;;;;;;;;;;;;;;15130:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6408:9:::1;14959:1;14920:40;;:13;:24;14934:9;14920:24;;;;;;;;;;;:35;;;;;;;;;;;;:40;;;;;14912:80;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;6440:9:::2;6451:4;;14139:230;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;14209:17;14277:4;14271;14267:15;14261:22;14248:35;;14315:9;14302;:22;14294:63;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;6484:134:::3;6512:9;6529:7;6544:15;6567:18;6593:10;6611:1;6484:20;:134::i;:::-;6644:9;6629:25;;;;;;;;;;914:6;6668:9;:39;;6660:84;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::3;;;;;;;;;;;;;7010:12;7028:15;:20;;7066:18;7086:4;;7049:42;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7028:64;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7009:83;;;7151:7;7144:14;;;14998:1:::2;;;15222::::1;6148:1015:::0;;;;;;;;;:::o;9488:601::-;9648:18;9669:64;9687:7;9696:10;9708:12;9722:10;9669:17;:64::i;:::-;9648:85;;9786:10;9747:49;;;:13;:24;9761:9;9747:24;;;;;;;;;;;:35;;;;;;;;;;;;:49;;;;9739:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9909:15;9895:10;:29;;9887:64;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9965:13;:24;9979:9;9965:24;;;;;;;;;;;;9958:31;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10020:9;10000:30;;;;;;;;;;10044:9;:18;;;10063:10;10075:7;10044:39;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10037:47;;;;9488:601;;;;;:::o;10418:276::-;15138:19;:31;15158:10;15138:31;;;;;;;;;;;;;;;;;;;;;;;;;15130:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10539:9:::1;10524:25;;:3;:25;;;;10516:88;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10611:11;10627:3;:8;;10636:5;;10627:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10610:32;;;10656:6;10648:41;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;15222:1;10418:276:::0;;;:::o;841:254:2:-;911:12;;;;;;;;;;;897:26;;:10;:26;;;889:61;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;957:16;976:5;;;;;;;;;;;957:24;;995:10;987:5;;:18;;;;;;;;;;;;;;;;;;1034:1;1011:12;;:25;;;;;;;;;;;;;;;;;;1079:10;1048:42;;1069:8;1048:42;;;;;;;;;;;;841:254;:::o;147:20::-;;;;;;;;;;;;:::o;718:663:0:-;1566:19;:17;:19::i;:::-;1552:33;;:10;:33;;;1544:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;860:5:::1;228:1;302:2;:27;173:1;283:47;2278:5;:12;:38;;2270:73;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;897:5:::2;1842:19;1974:2;1967:5;1963:14;1957:21;1941:37;;116:10;2013:23;;1997:39;;;:12;:39;;;;1989:82;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;1009:7:::3;1004:2;997:5;993:14;986:31;1139:7;1134:2;1127:5;1123:14;1116:31;1252:12;1278:4;1270:26;;1297:5;1270:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1251:52;;;1340:7;1332:44;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::3;;;;;;;;;;;;;2077:1;2349::::2;;1615::::1;718:663:::0;;;:::o;542:210:2:-;1229:5;;;;;;;;;;1215:19;;:10;:19;;;1207:54;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;634:10:::1;627:17;;:3;:17;;;;619:53;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;694:3;679:12;;:18;;;;;;;;;;;;;;;;;;743:3;709:38;;736:5;::::0;::::1;;;;;;;;709:38;;;;;;;;;;;;542:210:::0;:::o;7749:147:1:-;1229:5:2;;;;;;;;;;1215:19;;:10;:19;;;1207:54;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7884:7:1::1;7856:19;:25;7876:4;7856:25;;;;;;;;;;;;;;;;:35;;;;;;;;;;;;;;;;;;7749:147:::0;;:::o;8209:282::-;1229:5:2;;;;;;;;;;1215:19;;:10;:19;;;1207:54;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8364:6:1::1;14615:39;1081:1;14615:6;:10;;:39;;;;:::i;:::-;14591:20;;:63;;14583:129;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8403:32:::2;8428:6;8403:20;;:24;;:32;;;;:::i;:::-;8380:20;:55;;;;8448:9;:18;;;8467:9;8478:6;8448:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;;::::0;::::2;;;;;;;;;;;;;;;;;;8441:45;;;;1267:1:2::1;8209:282:1::0;;:::o;7359:141::-;7449:4;7470:19;:25;7490:4;7470:25;;;;;;;;;;;;;;;;;;;;;;;;;7463:32;;7359:141;;;:::o;11075:703::-;11271:17;11290:18;11355:6;11363:5;11338:31;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11328:42;;;;;;11316:54;;11423:1;11384:40;;:13;:24;11398:9;11384:24;;;;;;;;;;;:35;;;;;;;;;;;;:40;;;;11376:73;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11518:32;792:9;11518:15;:19;;:32;;;;:::i;:::-;11505:45;;11556:18;11577:75;11595:7;11604:15;11621:18;11641:10;11577:17;:75::i;:::-;11556:96;;11685:52;;;;;;;;11696:10;11685:52;;;;;;;11708:28;11724:11;11708:15;:28::i;:::-;11685:52;;;;;11658:13;:24;11672:9;11658:24;;;;;;;;;;;:79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11743:30;11075:703;;;;;;;;;:::o;12264:615::-;12470:18;12491:75;12509:7;12518:15;12535:18;12555:10;12491:17;:75::i;:::-;12470:96;;12619:10;12580:49;;;:13;:24;12594:9;12580:24;;;;;;;;;;;:35;;;;;;;;;;;;:49;;;;12572:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12718:28;12734:11;12718:15;:28::i;:::-;12678:68;;:13;:24;12692:9;12678:24;;;;;;;;;;;:36;;;;;;;;;;;;:68;;;;12670:105;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12804:33;12829:7;12804:20;;:24;;:33;;;;:::i;:::-;12781:20;:56;;;;12850:13;:24;12864:9;12850:24;;;;;;;;;;;;12843:31;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12264:615;;;;;;;:::o;1245:165:8:-;1303:7;1331:1;1326;:6;;1318:49;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1373:9;1389:1;1385;:5;1373:17;;1404:1;1397:8;;;1245:165;;;;:::o;13358:333:1:-;13516:7;13591;13608:15;13633:18;13661:10;13565:114;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13548:137;;;;;;13533:153;;13358:333;;;;;;:::o;831:162:8:-;889:7;904:9;920:1;916;:5;904:17;;940:1;935;:6;;927:46;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;987:1;980:8;;;831:162;;;;:::o;13802:167:1:-;13861:5;853:3;13882:6;:29;13874:64;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13957:6;13944:20;;13802:167;;;:::o" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "2009400", + "executionCost": "infinite", + "totalCost": "infinite" + }, + "external": { + "EXPIRY_TIME()": "228", + "acceptOwnership()": "45185", + "cancelOracleRequest(bytes32,uint256,bytes4,uint256)": "infinite", + "forward(address,bytes)": "infinite", + "fulfillOracleRequest(bytes32,uint256,address,bytes4,uint256,bytes32)": "infinite", + "fulfillOracleRequest2(bytes32,uint256,address,bytes4,uint256,bytes)": "infinite", + "getChainlinkToken()": "infinite", + "isAuthorizedSender(address)": "1326", + "onTokenTransfer(address,uint256,bytes)": "infinite", + "oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)": "infinite", + "owner()": "1111", + "setAuthorizedSender(address,bool)": "22137", + "transferOwnership(address)": "24405", + "withdraw(address,uint256)": "infinite", + "withdrawable()": "infinite" + }, + "internal": { + "buildFunctionHash(uint256,address,bytes4,uint256)": "infinite", + "safeCastToUint8(uint256)": "infinite", + "verifyOracleRequest(address,uint256,address,bytes4,uint256,uint256)": "infinite", + "verifyOracleResponse(bytes32,uint256,address,bytes4,uint256,uint256)": "infinite" + } + }, + "methodIdentifiers": { + "EXPIRY_TIME()": "4b602282", + "acceptOwnership()": "79ba5097", + "cancelOracleRequest(bytes32,uint256,bytes4,uint256)": "6ee4d553", + "forward(address,bytes)": "6fadcf72", + "fulfillOracleRequest(bytes32,uint256,address,bytes4,uint256,bytes32)": "4ab0d190", + "fulfillOracleRequest2(bytes32,uint256,address,bytes4,uint256,bytes)": "6ae0bc76", + "getChainlinkToken()": "165d35e1", + "isAuthorizedSender(address)": "fa00763a", + "onTokenTransfer(address,uint256,bytes)": "a4c0ed36", + "oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)": "40429946", + "owner()": "8da5cb5b", + "setAuthorizedSender(address,bool)": "f3dfc2a9", + "transferOwnership(address)": "f2fde38b", + "withdraw(address,uint256)": "f3fef3a3", + "withdrawable()": "50188301" + } + }, + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "link", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + } + ], + "name": "CancelOracleRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "specId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "callbackAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cancelExpiration", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dataVersion", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "OracleRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + } + ], + "name": "OracleResponse", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "EXPIRY_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "bytes4", + "name": "callbackFunc", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "expiration", + "type": "uint256" + } + ], + "name": "cancelOracleRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "forward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "address", + "name": "callbackAddress", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "expiration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "name": "fulfillOracleRequest", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "address", + "name": "callbackAddress", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "expiration", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "fulfillOracleRequest2", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getChainlinkToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "node", + "type": "address" + } + ], + "name": "isAuthorizedSender", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "onTokenTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "specId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callbackAddress", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dataVersion", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "oracleRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setAuthorizedSender", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/evm-contracts/src/v0.7/dev/artifacts/Operator_metadata.json b/evm-contracts/src/v0.7/dev/artifacts/Operator_metadata.json new file mode 100644 index 00000000000..cefae06814a --- /dev/null +++ b/evm-contracts/src/v0.7/dev/artifacts/Operator_metadata.json @@ -0,0 +1,712 @@ +{ + "compiler": { + "version": "0.7.0+commit.9e61f92b" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "link", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + } + ], + "name": "CancelOracleRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "specId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "callbackAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cancelExpiration", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dataVersion", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "OracleRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + } + ], + "name": "OracleResponse", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "EXPIRY_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "bytes4", + "name": "callbackFunc", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "expiration", + "type": "uint256" + } + ], + "name": "cancelOracleRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "forward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "address", + "name": "callbackAddress", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "expiration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "name": "fulfillOracleRequest", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "address", + "name": "callbackAddress", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "expiration", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "fulfillOracleRequest2", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getChainlinkToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "node", + "type": "address" + } + ], + "name": "isAuthorizedSender", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "onTokenTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "payment", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "specId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callbackAddress", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "callbackFunctionId", + "type": "bytes4" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dataVersion", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "oracleRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "setAuthorizedSender", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "devdoc": { + "kind": "dev", + "methods": { + "acceptOwnership()": { + "details": "Allows an ownership transfer to be completed by the recipient." + }, + "cancelOracleRequest(bytes32,uint256,bytes4,uint256)": { + "details": "Given params must hash to a commitment stored on the contract in order for the request to be valid Emits CancelOracleRequest event.", + "params": { + "callbackFunc": "The requester's specified callback address", + "expiration": "The time of the expiration for the request", + "payment": "The amount of payment given (specified in wei)", + "requestId": "The request ID" + } + }, + "constructor": { + "details": "Sets the LinkToken address for the imported LinkTokenInterface", + "params": { + "link": "The address of the LINK token", + "owner": "The address of the owner" + } + }, + "fulfillOracleRequest(bytes32,uint256,address,bytes4,uint256,bytes32)": { + "details": "Given params must hash back to the commitment stored from `oracleRequest`. Will call the callback address' callback function without bubbling up error checking in a `require` so that the node can get paid.", + "params": { + "callbackAddress": "The callback address to call for fulfillment", + "callbackFunctionId": "The callback function ID to use for fulfillment", + "data": "The data to return to the consuming contract", + "expiration": "The expiration that the node should respond by before the requester can cancel", + "payment": "The payment amount that will be released for the oracle (specified in wei)", + "requestId": "The fulfillment request ID that must match the requester's" + }, + "returns": { + "_0": "Status if the external call was successful" + } + }, + "fulfillOracleRequest2(bytes32,uint256,address,bytes4,uint256,bytes)": { + "details": "Given params must hash back to the commitment stored from `oracleRequest`. Will call the callback address' callback function without bubbling up error checking in a `require` so that the node can get paid.", + "params": { + "callbackAddress": "The callback address to call for fulfillment", + "callbackFunctionId": "The callback function ID to use for fulfillment", + "data": "The data to return to the consuming contract", + "expiration": "The expiration that the node should respond by before the requester can cancel", + "payment": "The payment amount that will be released for the oracle (specified in wei)", + "requestId": "The fulfillment request ID that must match the requester's" + }, + "returns": { + "_0": "Status if the external call was successful" + } + }, + "getChainlinkToken()": { + "details": "This is the public implementation for chainlinkTokenAddress, which is an internal method of the ChainlinkClient contract" + }, + "isAuthorizedSender(address)": { + "params": { + "node": "The address of the Chainlink node" + }, + "returns": { + "_0": "The authorization status of the node" + } + }, + "onTokenTransfer(address,uint256,bytes)": { + "details": "The data payload's first 2 words will be overwritten by the `_sender` and `_amount` values to ensure correctness. Calls oracleRequest.", + "params": { + "_amount": "Amount of LINK sent (specified in wei)", + "_data": "Payload of the transaction", + "_sender": "Address of the sender" + } + }, + "oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)": { + "details": "Stores the hash of the params as the on-chain commitment for the request. Emits OracleRequest event for the Chainlink node to detect.", + "params": { + "callbackAddress": "The callback address for the response", + "callbackFunctionId": "The callback function ID for the response", + "data": "The CBOR payload of the request", + "dataVersion": "The specified data version", + "nonce": "The nonce sent by the requester", + "payment": "The amount of payment given (specified in wei)", + "sender": "The sender of the request", + "specId": "The Job Specification ID" + } + }, + "setAuthorizedSender(address,bool)": { + "params": { + "allowed": "Bool value to determine if the node can fulfill requests", + "node": "The address of the Chainlink node" + } + }, + "transferOwnership(address)": { + "details": "Allows an owner to begin transferring ownership to a new address, pending." + }, + "withdraw(address,uint256)": { + "details": "The owner of the contract can be another wallet and does not have to be a Chainlink node", + "params": { + "amount": "The amount to send (specified in wei)", + "recipient": "The address to send the LINK token to" + } + }, + "withdrawable()": { + "details": "We use `ONE_FOR_CONSISTENT_GAS_COST` in place of 0 in storage", + "returns": { + "_0": "The amount of withdrawable LINK on the contract" + } + } + }, + "title": "The Chainlink Operator contract", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "cancelOracleRequest(bytes32,uint256,bytes4,uint256)": { + "notice": "Allows requesters to cancel requests sent to this oracle contract. Will transfer the LINK sent for the request back to the requester's address." + }, + "constructor": { + "notice": "Deploy with the address of the LINK token" + }, + "fulfillOracleRequest(bytes32,uint256,address,bytes4,uint256,bytes32)": { + "notice": "Called by the Chainlink node to fulfill requests" + }, + "fulfillOracleRequest2(bytes32,uint256,address,bytes4,uint256,bytes)": { + "notice": "Called by the Chainlink node to fulfill requests with multi-word support" + }, + "getChainlinkToken()": { + "notice": "Returns the address of the LINK token" + }, + "isAuthorizedSender(address)": { + "notice": "Use this to check if a node is authorized for fulfilling requests" + }, + "onTokenTransfer(address,uint256,bytes)": { + "notice": "Called when LINK is sent to the contract via `transferAndCall`" + }, + "oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)": { + "notice": "Creates the Chainlink request" + }, + "setAuthorizedSender(address,bool)": { + "notice": "Sets the fulfillment permission for a given node. Use `true` to allow, `false` to disallow." + }, + "withdraw(address,uint256)": { + "notice": "Allows the node operator to withdraw earned LINK to a given address" + }, + "withdrawable()": { + "notice": "Displays the amount of LINK that is available for the node operator to withdraw" + } + }, + "notice": "Node operators can deploy this contract to fulfill requests sent to them", + "version": 1 + } + }, + "settings": { + "compilationTarget": { + "localhost/v0.7/dev/Operator.sol": "Operator" + }, + "evmVersion": "istanbul", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs" + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "localhost/v0.7/dev/LinkTokenReceiver.sol": { + "keccak256": "0x5c8714f2a301d96283a7d92e3cfe4d5714260defb577aceb66c6bafa9ffe52d8", + "urls": [ + "bzz-raw://a34b6ae22b46f297c09523dd85a0bcbd209fdd7eb1440f7249116224d98e673a", + "dweb:/ipfs/Qmf9UmzsXosYqwaHWPhHufvCYctHuVLN2uwbz68fwabTfN" + ] + }, + "localhost/v0.7/dev/Operator.sol": { + "keccak256": "0x296feec55d5e1b75233320b415b6a6417a1bdf90f93299bffd1bdb5342bc9d2a", + "urls": [ + "bzz-raw://14098becc4fabe7bbe1101c2c7504ed8568024bb86951c7af0434a4d30bd0f0a", + "dweb:/ipfs/QmT7MJtBCN81FZxwjy7qgoT72jgYyBusQTP6Lt7he34zG5" + ] + }, + "localhost/v0.7/dev/Owned.sol": { + "keccak256": "0x1af63c3c8a7a68213f487d920a5691a46c1e8dc9450f79bf289ce6e84fc4c153", + "urls": [ + "bzz-raw://0f36d359be66e04289560b2133dfa672b55b7503ac1a6882dd2b1663fa27cd1c", + "dweb:/ipfs/QmaDWhkUgF46TK4pboHEiUU4XNL99Nq8ZGwtRiZGG96xxP" + ] + }, + "localhost/v0.7/interfaces/ChainlinkRequestInterface.sol": { + "keccak256": "0x129fc8d7da0b908d1997fc913eae30409b9dd32b7d2b8009257adb2fedc268ab", + "urls": [ + "bzz-raw://cf18658b6aec33eebf3ef5785671d0883addf4f01b0fead108af3ece7b0f0a5f", + "dweb:/ipfs/QmRapsoLWjf5kRQ79ZDyF18wcVRYrJwKiwqPUCiyCGymGf" + ] + }, + "localhost/v0.7/interfaces/LinkTokenInterface.sol": { + "keccak256": "0x54a9439f3f39533dfcb90cd1c494f63e70b4a3e5dcb32389aa3365c1475800e9", + "urls": [ + "bzz-raw://07603544ad7fe88253b5253dd92d981e9cde503abf99b0416f24b2abd36ed6ec", + "dweb:/ipfs/QmPgeSFmSALoQw9XBxK9Jm3CW5TQs59u5vNafyW1yX9r2A" + ] + }, + "localhost/v0.7/interfaces/OracleInterface.sol": { + "keccak256": "0x983efaba106b16c1bbe1e25e8e03ef5d49627e70730dbc81da662a5ea0501c22", + "urls": [ + "bzz-raw://259cabdcc7e5cd35dd19fe31e7c83d986213559a9669864314cb93ae9e16b88c", + "dweb:/ipfs/QmPSjdNNLKo5fkL8mTXKEnChfTpzrgE9fEeoH47t2nZgsf" + ] + }, + "localhost/v0.7/interfaces/OracleInterface2.sol": { + "keccak256": "0xbe6fbfa07f35a3fe67591a8f384ddf10c83e9277f5d2e6f0f094dfe92587254e", + "urls": [ + "bzz-raw://40dc815b6ccd5186738defc9510715cb361f4f142fb8d2ed52d0649756fbcf65", + "dweb:/ipfs/QmaoUZPhHCuKCyX7KXY5oLDv3ewx1Pcg2xFnHPJaiKdkYs" + ] + }, + "localhost/v0.7/interfaces/WithdrawalInterface.sol": { + "keccak256": "0x3db82a471a7330013f7322b26ac7e25712bf91044b18a1066a1661ac55b2fb50", + "urls": [ + "bzz-raw://f9085507e9b88108a73edd7e0c6061392379b43520543dcd2016a8b67d2ec14f", + "dweb:/ipfs/QmZCxm2xQfuXcd4zJsNNddvpr7Hx15eieHsDin4RT7G7yg" + ] + }, + "localhost/v0.7/vendor/SafeMathChainlink.sol": { + "keccak256": "0xfd4a989c8540bbfedb2e375d7d24990f2d480f8ca64c058f4771ba2007aaef2b", + "urls": [ + "bzz-raw://0992d39314ea2d5723f9b0e1dc8aa1e65198f11d313e86c0caba6ca7620b31b3", + "dweb:/ipfs/QmXyfnVsAs14x2jfL1ZrdZdMj7zCv5PCy3SiCKTXaH97LD" + ] + } + }, + "version": 1 +} \ No newline at end of file diff --git a/evm-contracts/src/v0.7/interfaces/AggregatorInterface.sol b/evm-contracts/src/v0.7/interfaces/AggregatorInterface.sol new file mode 100644 index 00000000000..338cd9de76b --- /dev/null +++ b/evm-contracts/src/v0.7/interfaces/AggregatorInterface.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +interface AggregatorInterface { + function latestAnswer() external view returns (int256); + function latestTimestamp() external view returns (uint256); + function latestRound() external view returns (uint256); + function getAnswer(uint256 roundId) external view returns (int256); + function getTimestamp(uint256 roundId) external view returns (uint256); + + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); + event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); +} diff --git a/evm-contracts/src/v0.7/interfaces/AggregatorProxyInterface.sol b/evm-contracts/src/v0.7/interfaces/AggregatorProxyInterface.sol new file mode 100644 index 00000000000..57961062d46 --- /dev/null +++ b/evm-contracts/src/v0.7/interfaces/AggregatorProxyInterface.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +import "./AggregatorV2V3Interface.sol"; + +interface AggregatorProxyInterface is AggregatorV2V3Interface { + function phaseAggregators(uint16 phaseId) external view returns (address); + function phaseId() external view returns (uint16); + function proposedAggregator() external view returns (address); + function proposedGetRoundData(uint80 roundId) external view returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); + function proposedLatestRoundData() external view returns (uint80 id, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); + function aggregator() external view returns (address); +} diff --git a/evm-contracts/src/v0.7/interfaces/AggregatorV2V3Interface.sol b/evm-contracts/src/v0.7/interfaces/AggregatorV2V3Interface.sol new file mode 100644 index 00000000000..38866fcf922 --- /dev/null +++ b/evm-contracts/src/v0.7/interfaces/AggregatorV2V3Interface.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +import "./AggregatorInterface.sol"; +import "./AggregatorV3Interface.sol"; + +interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface +{ +} diff --git a/evm-contracts/src/v0.7/interfaces/AggregatorV3Interface.sol b/evm-contracts/src/v0.7/interfaces/AggregatorV3Interface.sol new file mode 100644 index 00000000000..ba6fa501fb7 --- /dev/null +++ b/evm-contracts/src/v0.7/interfaces/AggregatorV3Interface.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +interface AggregatorV3Interface { + + function decimals() external view returns (uint8); + function description() external view returns (string memory); + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData(uint80 _roundId) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + +} diff --git a/evm-contracts/src/v0.7/interfaces/ChainlinkRequestInterface.sol b/evm-contracts/src/v0.7/interfaces/ChainlinkRequestInterface.sol index 829e9a87797..77d2b6e0f36 100644 --- a/evm-contracts/src/v0.7/interfaces/ChainlinkRequestInterface.sol +++ b/evm-contracts/src/v0.7/interfaces/ChainlinkRequestInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface ChainlinkRequestInterface { diff --git a/evm-contracts/src/v0.7/interfaces/ENSInterface.sol b/evm-contracts/src/v0.7/interfaces/ENSInterface.sol index be8cb38ac53..2af6f323173 100644 --- a/evm-contracts/src/v0.7/interfaces/ENSInterface.sol +++ b/evm-contracts/src/v0.7/interfaces/ENSInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface ENSInterface { @@ -15,10 +16,10 @@ interface ENSInterface { event NewTTL(bytes32 indexed node, uint64 ttl); - function setSubnodeOwner(bytes32 node, bytes32 label, address _owner) external; - function setResolver(bytes32 node, address _resolver) external; - function setOwner(bytes32 node, address _owner) external; - function setTTL(bytes32 node, uint64 _ttl) external; + function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external; + function setResolver(bytes32 node, address resolver) external; + function setOwner(bytes32 node, address owner) external; + function setTTL(bytes32 node, uint64 ttl) external; function owner(bytes32 node) external view returns (address); function resolver(bytes32 node) external view returns (address); function ttl(bytes32 node) external view returns (uint64); diff --git a/evm-contracts/src/v0.7/interfaces/LinkTokenInterface.sol b/evm-contracts/src/v0.7/interfaces/LinkTokenInterface.sol index 86d157f2b97..32bd6dc42de 100644 --- a/evm-contracts/src/v0.7/interfaces/LinkTokenInterface.sol +++ b/evm-contracts/src/v0.7/interfaces/LinkTokenInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface LinkTokenInterface { diff --git a/evm-contracts/src/v0.7/interfaces/OracleInterface.sol b/evm-contracts/src/v0.7/interfaces/OracleInterface.sol index 88765c2e91e..9ddd7fb7a89 100644 --- a/evm-contracts/src/v0.7/interfaces/OracleInterface.sol +++ b/evm-contracts/src/v0.7/interfaces/OracleInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface OracleInterface { @@ -9,8 +10,8 @@ interface OracleInterface { uint256 expiration, bytes32 data ) external returns (bool); - function getAuthorizationStatus(address node) external view returns (bool); - function setFulfillmentPermission(address node, bool allowed) external; + function isAuthorizedSender(address node) external view returns (bool); + function setAuthorizedSender(address node, bool allowed) external; function withdraw(address recipient, uint256 amount) external; function withdrawable() external view returns (uint256); } diff --git a/evm-contracts/src/v0.7/interfaces/OracleInterface2.sol b/evm-contracts/src/v0.7/interfaces/OracleInterface2.sol index 08f0f13aebf..2835c5bfd69 100644 --- a/evm-contracts/src/v0.7/interfaces/OracleInterface2.sol +++ b/evm-contracts/src/v0.7/interfaces/OracleInterface2.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface OracleInterface2 { diff --git a/evm-contracts/src/v0.7/interfaces/PointerInterface.sol b/evm-contracts/src/v0.7/interfaces/PointerInterface.sol index 0c128eefdb1..ee3d8ae9ced 100644 --- a/evm-contracts/src/v0.7/interfaces/PointerInterface.sol +++ b/evm-contracts/src/v0.7/interfaces/PointerInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface PointerInterface { diff --git a/evm-contracts/src/v0.7/interfaces/WithdrawalInterface.sol b/evm-contracts/src/v0.7/interfaces/WithdrawalInterface.sol index 8a688d8920d..8049850a796 100644 --- a/evm-contracts/src/v0.7/interfaces/WithdrawalInterface.sol +++ b/evm-contracts/src/v0.7/interfaces/WithdrawalInterface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; interface WithdrawalInterface { diff --git a/evm-contracts/src/v0.7/tests/ConfirmedOwnerTestHelper.sol b/evm-contracts/src/v0.7/tests/ConfirmedOwnerTestHelper.sol new file mode 100644 index 00000000000..5319729b1aa --- /dev/null +++ b/evm-contracts/src/v0.7/tests/ConfirmedOwnerTestHelper.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import "../dev/ConfirmedOwner.sol"; + +contract ConfirmedOwnerTestHelper is ConfirmedOwner { + + event Here(); + + constructor() ConfirmedOwner(msg.sender) {} + + function modifierOnlyOwner() + public + onlyOwner() + { + emit Here(); + } + +} diff --git a/evm-contracts/src/v0.7/tests/Consumer.sol b/evm-contracts/src/v0.7/tests/Consumer.sol index c299bdcfa00..dc2a9584254 100644 --- a/evm-contracts/src/v0.7/tests/Consumer.sol +++ b/evm-contracts/src/v0.7/tests/Consumer.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "../ChainlinkClient.sol"; diff --git a/evm-contracts/src/v0.7/tests/MockV2Aggregator.sol b/evm-contracts/src/v0.7/tests/MockV2Aggregator.sol new file mode 100644 index 00000000000..6934a603c51 --- /dev/null +++ b/evm-contracts/src/v0.7/tests/MockV2Aggregator.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import "../interfaces/AggregatorInterface.sol"; + +/** + * @title MockV2Aggregator + * @notice Based on the HistoricAggregator contract + * @notice Use this contract when you need to test + * other contract's ability to read data from an + * aggregator contract, but how the aggregator got + * its answer is unimportant + */ +contract MockV2Aggregator is AggregatorInterface { + int256 public override latestAnswer; + uint256 public override latestTimestamp; + uint256 public override latestRound; + + mapping(uint256 => int256) public override getAnswer; + mapping(uint256 => uint256) public override getTimestamp; + mapping(uint256 => uint256) private getStartedAt; + + constructor( + int256 _initialAnswer + ) public { + updateAnswer(_initialAnswer); + } + + function updateAnswer( + int256 _answer + ) public { + latestAnswer = _answer; + latestTimestamp = block.timestamp; + latestRound++; + getAnswer[latestRound] = _answer; + getTimestamp[latestRound] = block.timestamp; + } + + function updateRoundData( + uint256 _roundId, + int256 _answer, + uint256 _timestamp, + uint256 _startedAt + ) public { + latestRound = _roundId; + latestAnswer = _answer; + latestTimestamp = _timestamp; + getAnswer[latestRound] = _answer; + getTimestamp[latestRound] = _timestamp; + getStartedAt[latestRound] = _startedAt; + } +} diff --git a/evm-contracts/src/v0.7/tests/MockV3Aggregator.sol b/evm-contracts/src/v0.7/tests/MockV3Aggregator.sol new file mode 100644 index 00000000000..84802ae68e9 --- /dev/null +++ b/evm-contracts/src/v0.7/tests/MockV3Aggregator.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import "../interfaces/AggregatorV2V3Interface.sol"; + +/** + * @title MockV3Aggregator + * @notice Based on the FluxAggregator contract + * @notice Use this contract when you need to test + * other contract's ability to read data from an + * aggregator contract, but how the aggregator got + * its answer is unimportant + */ +contract MockV3Aggregator is AggregatorV2V3Interface { + uint256 constant public override version = 0; + + uint8 public override decimals; + int256 public override latestAnswer; + uint256 public override latestTimestamp; + uint256 public override latestRound; + + mapping(uint256 => int256) public override getAnswer; + mapping(uint256 => uint256) public override getTimestamp; + mapping(uint256 => uint256) private getStartedAt; + + constructor( + uint8 _decimals, + int256 _initialAnswer + ) public { + decimals = _decimals; + updateAnswer(_initialAnswer); + } + + function updateAnswer( + int256 _answer + ) public { + latestAnswer = _answer; + latestTimestamp = block.timestamp; + latestRound++; + getAnswer[latestRound] = _answer; + getTimestamp[latestRound] = block.timestamp; + getStartedAt[latestRound] = block.timestamp; + } + + function updateRoundData( + uint80 _roundId, + int256 _answer, + uint256 _timestamp, + uint256 _startedAt + ) public { + latestRound = _roundId; + latestAnswer = _answer; + latestTimestamp = _timestamp; + getAnswer[latestRound] = _answer; + getTimestamp[latestRound] = _timestamp; + getStartedAt[latestRound] = _startedAt; + } + + function getRoundData(uint80 _roundId) + external + view + override + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return ( + _roundId, + getAnswer[_roundId], + getStartedAt[_roundId], + getTimestamp[_roundId], + _roundId + ); + } + + function latestRoundData() + external + view + override + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return ( + uint80(latestRound), + getAnswer[latestRound], + getStartedAt[latestRound], + getTimestamp[latestRound], + uint80(latestRound) + ); + } + + function description() + external + view + override + returns (string memory) + { + return "v0.6/tests/MockV3Aggregator.sol"; + } +} diff --git a/evm-contracts/src/v0.7/tests/MultiWordConsumer.sol b/evm-contracts/src/v0.7/tests/MultiWordConsumer.sol new file mode 100644 index 00000000000..2b1eb4db052 --- /dev/null +++ b/evm-contracts/src/v0.7/tests/MultiWordConsumer.sol @@ -0,0 +1,86 @@ +pragma solidity ^0.7.0; + +import "../ChainlinkClient.sol"; + +contract MultiWordConsumer is ChainlinkClient{ + bytes32 internal specId; + bytes public currentPrice; + + bytes32 public usd; + bytes32 public eur; + bytes32 public jpy; + + event RequestFulfilled( + bytes32 indexed requestId, // User-defined ID + bytes indexed price + ); + + event RequestMultipleFulfilled( + bytes32 indexed requestId, + bytes32 indexed usd, + bytes32 indexed eur, + bytes32 jpy + ); + + constructor(address _link, address _oracle, bytes32 _specId) public { + setChainlinkToken(_link); + setChainlinkOracle(_oracle); + specId = _specId; + } + + function setSpecID(bytes32 _specId) public { + specId = _specId; + } + + function requestEthereumPrice(string memory _currency, uint256 _payment) public { + requestEthereumPriceByCallback(_currency, _payment, address(this)); + } + + function requestEthereumPriceByCallback(string memory _currency, uint256 _payment, address _callback) public { + Chainlink.Request memory req = buildChainlinkRequest(specId, _callback, this.fulfillBytes.selector); + sendChainlinkRequest(req, _payment); + } + + function requestMultipleParameters(string memory _currency, uint256 _payment) public { + Chainlink.Request memory req = buildChainlinkRequest(specId, address(this), this.fulfillMultipleParameters.selector); + sendChainlinkRequest(req, _payment); + } + + function cancelRequest( + address _oracle, + bytes32 _requestId, + uint256 _payment, + bytes4 _callbackFunctionId, + uint256 _expiration + ) public { + ChainlinkRequestInterface requested = ChainlinkRequestInterface(_oracle); + requested.cancelOracleRequest(_requestId, _payment, _callbackFunctionId, _expiration); + } + + function withdrawLink() public { + LinkTokenInterface _link = LinkTokenInterface(chainlinkTokenAddress()); + require(_link.transfer(msg.sender, _link.balanceOf(address(this))), "Unable to transfer"); + } + + function addExternalRequest(address _oracle, bytes32 _requestId) external { + addChainlinkExternalRequest(_oracle, _requestId); + } + + function fulfillMultipleParameters(bytes32 _requestId, bytes32 _usd, bytes32 _eur, bytes32 _jpy) + public + recordChainlinkFulfillment(_requestId) + { + emit RequestMultipleFulfilled(_requestId, _usd, _eur, _jpy); + usd = _usd; + eur = _eur; + jpy = _jpy; + } + + function fulfillBytes(bytes32 _requestId, bytes memory _price) + public + recordChainlinkFulfillment(_requestId) + { + emit RequestFulfilled(_requestId, _price); + currentPrice = _price; + } +} diff --git a/evm-contracts/src/v0.7/tests/OwnedTestHelper.sol b/evm-contracts/src/v0.7/tests/OwnedTestHelper.sol deleted file mode 100644 index 8a86f4618f8..00000000000 --- a/evm-contracts/src/v0.7/tests/OwnedTestHelper.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity ^0.7.0; - -import "../dev/Owned.sol"; - -contract OwnedTestHelper is Owned { - - event Here(); - - function modifierOnlyOwner() - public - onlyOwner() - { - emit Here(); - } - -} diff --git a/evm-contracts/src/v0.7/vendor/BufferChainlink.sol b/evm-contracts/src/v0.7/vendor/BufferChainlink.sol index c6f28041746..ef19ffc5024 100644 --- a/evm-contracts/src/v0.7/vendor/BufferChainlink.sol +++ b/evm-contracts/src/v0.7/vendor/BufferChainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; /** diff --git a/evm-contracts/src/v0.7/vendor/CBORChainlink.sol b/evm-contracts/src/v0.7/vendor/CBORChainlink.sol index 7d41e7766b3..6e8409fc422 100644 --- a/evm-contracts/src/v0.7/vendor/CBORChainlink.sol +++ b/evm-contracts/src/v0.7/vendor/CBORChainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import { BufferChainlink } from "./BufferChainlink.sol"; diff --git a/evm-contracts/src/v0.7/vendor/ENSResolver.sol b/evm-contracts/src/v0.7/vendor/ENSResolver.sol index 253046f8ec3..d5cbc6727bf 100644 --- a/evm-contracts/src/v0.7/vendor/ENSResolver.sol +++ b/evm-contracts/src/v0.7/vendor/ENSResolver.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; abstract contract ENSResolver { diff --git a/evm-contracts/src/v0.7/vendor/SafeMathChainlink.sol b/evm-contracts/src/v0.7/vendor/SafeMathChainlink.sol index 5300783ae1b..3345db7c963 100644 --- a/evm-contracts/src/v0.7/vendor/SafeMathChainlink.sol +++ b/evm-contracts/src/v0.7/vendor/SafeMathChainlink.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; /** diff --git a/evm-contracts/test/v0.6/VRFD20.test.ts b/evm-contracts/test/v0.6/VRFD20.test.ts new file mode 100644 index 00000000000..806c335d1cc --- /dev/null +++ b/evm-contracts/test/v0.6/VRFD20.test.ts @@ -0,0 +1,289 @@ +import { contract, setup, helpers, matchers } from '@chainlink/test-helpers' +import { assert } from 'chai' +import { ContractTransaction } from 'ethers' +import { VRFD20Factory } from '../../ethers/v0.6/VRFD20Factory' +import { VRFCoordinatorMockFactory } from '../../ethers/v0.6/VRFCoordinatorMockFactory' +import { bigNumberify } from 'ethers/utils' + +let roles: setup.Roles +let personas: setup.Personas +const provider = setup.provider() +const linkTokenFactory = new contract.LinkTokenFactory() +const vrfCoordinatorMockFactory = new VRFCoordinatorMockFactory() +const vrfD20Factory = new VRFD20Factory() + +beforeAll(async () => { + const users = await setup.users(provider) + + roles = users.roles + personas = users.personas +}) + +describe('VRFD20', () => { + const deposit = helpers.toWei('1') + const fee = helpers.toWei('0.1') + const keyHash = helpers.toBytes32String('keyHash') + const seed = 12345 + + const requestId = + '0x66f86cab16b057baa86d6171b59e4c356197fcebc0e2cd2a744fc2d2f4dacbfe' + + let link: contract.Instance + let vrfCoordinator: contract.Instance + let vrfD20: contract.Instance + + const deployment = setup.snapshot(provider, async () => { + link = await linkTokenFactory.connect(roles.defaultAccount).deploy() + vrfCoordinator = await vrfCoordinatorMockFactory + .connect(roles.defaultAccount) + .deploy(link.address) + vrfD20 = await vrfD20Factory + .connect(roles.defaultAccount) + .deploy(vrfCoordinator.address, link.address, keyHash, fee) + await link.transfer(vrfD20.address, deposit) + }) + + beforeEach(async () => { + await deployment() + }) + + it('has a limited public interface', () => { + matchers.publicAbi(vrfD20Factory, [ + // Owned + 'acceptOwnership', + 'owner', + 'transferOwnership', + //VRFConsumerBase + 'rawFulfillRandomness', + // VRFD20 + 'rollDice', + 'house', + 'withdrawLINK', + 'keyHash', + 'fee', + 'setKeyHash', + 'setFee', + ]) + }) + + describe('#withdrawLINK', () => { + describe('failure', () => { + it('reverts when called by a non-owner', async () => { + await matchers.evmRevert(async () => { + await vrfD20 + .connect(roles.stranger) + .withdrawLINK(roles.stranger.address, deposit), + 'Only callable by owner' + }) + }) + + it('reverts when not enough LINK in the contract', async () => { + const withdrawAmount = deposit.mul(2) + await matchers.evmRevert(async () => { + await vrfD20 + .connect(roles.defaultAccount) + .withdrawLINK(roles.defaultAccount.address, withdrawAmount), + 'Not enough LINK' + }) + }) + }) + + describe('success', () => { + it('withdraws LINK', async () => { + const startingAmount = await link.balanceOf( + roles.defaultAccount.address, + ) + const expectedAmount = bigNumberify(startingAmount).add(deposit) + await vrfD20 + .connect(roles.defaultAccount) + .withdrawLINK(roles.defaultAccount.address, deposit) + const actualAmount = await link.balanceOf(roles.defaultAccount.address) + assert.equal(actualAmount.toString(), expectedAmount.toString()) + }) + }) + }) + + describe('#setKeyHash', () => { + const newHash = helpers.toBytes32String('newhash') + + describe('failure', () => { + it('reverts when called by a non-owner', async () => { + await matchers.evmRevert(async () => { + await vrfD20.connect(roles.stranger).setKeyHash(newHash), + 'Only callable by owner' + }) + }) + }) + + describe('success', () => { + it('sets the key hash', async () => { + await vrfD20.setKeyHash(newHash) + const actualHash = await vrfD20.keyHash() + assert.equal(actualHash, newHash) + }) + }) + }) + + describe('#setFee', () => { + const newFee = 1234 + + describe('failure', () => { + it('reverts when called by a non-owner', async () => { + await matchers.evmRevert(async () => { + await vrfD20.connect(roles.stranger).setFee(newFee), + 'Only callable by owner' + }) + }) + }) + + describe('success', () => { + it('sets the fee', async () => { + await vrfD20.setFee(newFee) + const actualFee = await vrfD20.fee() + assert.equal(actualFee.toString(), newFee.toString()) + }) + }) + }) + + describe('#house', () => { + describe('failure', () => { + it('reverts when dice not rolled', async () => { + await matchers.evmRevert(async () => { + await vrfD20.house(personas.Nancy.address), 'Dice not rolled' + }) + }) + + it('reverts when dice roll is in progress', async () => { + await vrfD20.rollDice(seed, personas.Nancy.address) + await matchers.evmRevert(async () => { + await vrfD20.house(personas.Nancy.address), 'Roll in progress' + }) + }) + }) + + describe('success', () => { + it('returns the correct house', async () => { + const randomness = 98765 + const expectedHouse = 'Martell' + const tx = await vrfD20.rollDice(seed, personas.Nancy.address) + const log = await helpers.getLog(tx, 3) + const eventRequestId = log?.topics?.[1] + await vrfCoordinator.callBackWithRandomness( + eventRequestId, + randomness, + vrfD20.address, + ) + const response = await vrfD20.house(personas.Nancy.address) + assert.equal(response.toString(), expectedHouse) + }) + }) + }) + + describe('#rollDice', () => { + describe('success', () => { + let tx: ContractTransaction + beforeEach(async () => { + tx = await vrfD20.rollDice(seed, personas.Nancy.address) + }) + + it('emits a RandomnessRequest event from the VRFCoordinator', async () => { + const log = await helpers.getLog(tx, 2) + const topics = log?.topics + assert.equal(helpers.evmWordToAddress(topics?.[1]), vrfD20.address) + assert.equal(topics?.[2], keyHash) + assert.equal(topics?.[3], helpers.numToBytes32(seed)) + }) + }) + + describe('failure', () => { + it('reverts when LINK balance is zero', async () => { + const vrfD202 = await vrfD20Factory + .connect(roles.defaultAccount) + .deploy(vrfCoordinator.address, link.address, keyHash, fee) + await matchers.evmRevert(async () => { + await vrfD202.rollDice(seed, personas.Nancy.address), + 'Not enough LINK to pay fee' + }) + }) + + it('reverts when called by a non-owner', async () => { + await matchers.evmRevert(async () => { + await vrfD20 + .connect(roles.stranger) + .rollDice(seed, personas.Nancy.address), + 'Only callable by owner' + }) + }) + + it('reverts when the roller rolls more than once', async () => { + await vrfD20.rollDice(seed, personas.Nancy.address) + await matchers.evmRevert(async () => { + await vrfD20.rollDice(seed, personas.Nancy.address), 'Already rolled' + }) + }) + }) + }) + + describe('#fulfillRandomness', () => { + const randomness = 98765 + const expectedModResult = (randomness % 20) + 1 + const expectedHouse = 'Martell' + let eventRequestId: string + beforeEach(async () => { + const tx = await vrfD20.rollDice(seed, personas.Nancy.address) + const log = await helpers.getLog(tx, 3) + eventRequestId = log?.topics?.[1] + }) + + describe('success', () => { + let tx: ContractTransaction + beforeEach(async () => { + tx = await vrfCoordinator.callBackWithRandomness( + eventRequestId, + randomness, + vrfD20.address, + ) + }) + + it('emits a DiceLanded event', async () => { + const log = await helpers.getLog(tx, 0) + assert.equal(log?.topics[1], requestId) + assert.equal(log?.topics[2], helpers.numToBytes32(expectedModResult)) + }) + + it('sets the correct dice roll result', async () => { + const response = await vrfD20.house(personas.Nancy.address) + assert.equal(response.toString(), expectedHouse) + }) + + it('allows someone else to roll', async () => { + const secondRandomness = 55555 + const secondSeed = 54321 + tx = await vrfD20.rollDice(secondSeed, personas.Ned.address) + const log = await helpers.getLog(tx, 3) + eventRequestId = log?.topics?.[1] + tx = await vrfCoordinator.callBackWithRandomness( + eventRequestId, + secondRandomness, + vrfD20.address, + ) + }) + }) + + describe('failure', () => { + it('does not fulfill when fulfilled by the wrong VRFcoordinator', async () => { + const vrfCoordinator2 = await vrfCoordinatorMockFactory + .connect(roles.defaultAccount) + .deploy(link.address) + + const tx = await vrfCoordinator2.callBackWithRandomness( + eventRequestId, + randomness, + vrfD20.address, + ) + const logs = await helpers.getLogs(tx) + assert.equal(logs.length, 0) + }) + }) + }) +}) diff --git a/evm-contracts/test/v0.7/AggregatorProxy.test.ts b/evm-contracts/test/v0.7/AggregatorProxy.test.ts new file mode 100644 index 00000000000..b2bc842267c --- /dev/null +++ b/evm-contracts/test/v0.7/AggregatorProxy.test.ts @@ -0,0 +1,720 @@ +import { + contract, + helpers as h, + matchers, + setup, +} from '@chainlink/test-helpers' +import { assert } from 'chai' +import { ethers } from 'ethers' +import { BigNumber } from 'ethers/utils' +import { MockV2AggregatorFactory } from '../../ethers/v0.6/MockV2AggregatorFactory' +import { MockV3AggregatorFactory } from '../../ethers/v0.6/MockV3AggregatorFactory' +import { AggregatorProxyFactory } from '../../ethers/v0.7/AggregatorProxyFactory' +import { AggregatorFacadeFactory } from '../../ethers/v0.6/AggregatorFacadeFactory' +import { FluxAggregatorFactory } from '../../ethers/v0.6/FluxAggregatorFactory' +import { ReverterFactory } from '../../ethers/v0.6/ReverterFactory' + +let personas: setup.Personas +let defaultAccount: ethers.Wallet + +const provider = setup.provider() +const linkTokenFactory = new contract.LinkTokenFactory() +const aggregatorFactory = new MockV3AggregatorFactory() +const historicAggregatorFactory = new MockV2AggregatorFactory() +const aggregatorFacadeFactory = new AggregatorFacadeFactory() +const aggregatorProxyFactory = new AggregatorProxyFactory() +const fluxAggregatorFactory = new FluxAggregatorFactory() +const reverterFactory = new ReverterFactory() + +beforeAll(async () => { + const users = await setup.users(provider) + + personas = users.personas + defaultAccount = users.roles.defaultAccount +}) + +describe('AggregatorProxy', () => { + const deposit = h.toWei('100') + const response = h.numToBytes32(54321) + const response2 = h.numToBytes32(67890) + const decimals = 18 + const phaseBase = h.bigNum(2).pow(64) + + let link: contract.Instance + let aggregator: contract.Instance + let aggregator2: contract.Instance + let historicAggregator: contract.Instance + let proxy: contract.Instance + let flux: contract.Instance + let reverter: contract.Instance + + const deployment = setup.snapshot(provider, async () => { + link = await linkTokenFactory.connect(defaultAccount).deploy() + aggregator = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response) + await link.transfer(aggregator.address, deposit) + proxy = await aggregatorProxyFactory + .connect(defaultAccount) + .deploy(aggregator.address) + const emptyAddress = '0x0000000000000000000000000000000000000000' + flux = await fluxAggregatorFactory + .connect(personas.Carol) + .deploy(link.address, 0, 0, emptyAddress, 0, 0, 18, 'TEST / LINK') + }) + + beforeEach(async () => { + await deployment() + }) + + it('has a limited public interface', () => { + matchers.publicAbi(aggregatorProxyFactory, [ + 'aggregator', + 'confirmAggregator', + 'decimals', + 'description', + 'getAnswer', + 'getRoundData', + 'getTimestamp', + 'latestAnswer', + 'latestRound', + 'latestRoundData', + 'latestTimestamp', + 'phaseAggregators', + 'phaseId', + 'proposeAggregator', + 'proposedAggregator', + 'proposedGetRoundData', + 'proposedLatestRoundData', + 'version', + // Ownable methods: + 'acceptOwnership', + 'owner', + 'transferOwnership', + ]) + }) + + describe('constructor', () => { + it('sets the proxy phase and aggregator', async () => { + matchers.bigNum(1, await proxy.phaseId()) + assert.equal(aggregator.address, await proxy.phaseAggregators(1)) + }) + }) + + describe('#latestRound', () => { + it('pulls the rate from the aggregator', async () => { + matchers.bigNum(phaseBase.add(1), await proxy.latestRound()) + }) + }) + + describe('#latestAnswer', () => { + it('pulls the rate from the aggregator', async () => { + matchers.bigNum(response, await proxy.latestAnswer()) + const latestRound = await proxy.latestRound() + matchers.bigNum(response, await proxy.getAnswer(latestRound)) + }) + + describe('after being updated to another contract', () => { + beforeEach(async () => { + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + await link.transfer(aggregator2.address, deposit) + matchers.bigNum(response2, await aggregator2.latestAnswer()) + + await proxy.proposeAggregator(aggregator2.address) + await proxy.confirmAggregator(aggregator2.address) + }) + + it('pulls the rate from the new aggregator', async () => { + matchers.bigNum(response2, await proxy.latestAnswer()) + const latestRound = await proxy.latestRound() + matchers.bigNum(response2, await proxy.getAnswer(latestRound)) + }) + }) + + describe('when the relevant info is not available', () => { + beforeEach(async () => { + await proxy.proposeAggregator(flux.address) + await proxy.confirmAggregator(flux.address) + }) + + it('does not revert when called with a non existent ID', async () => { + const actual = await proxy.latestAnswer() + matchers.bigNum(0, actual) + }) + }) + }) + + describe('#getAnswer', () => { + describe('when the relevant round is not available', () => { + beforeEach(async () => { + await proxy.proposeAggregator(flux.address) + await proxy.confirmAggregator(flux.address) + }) + + it('does not revert when called with a non existent ID', async () => { + const proxyId = phaseBase.mul(await proxy.phaseId()).add(1) + const actual = await proxy.getAnswer(proxyId) + matchers.bigNum(0, actual) + }) + }) + + describe('when the answer reverts in a non-predicted way', () => { + it('reverts', async () => { + reverter = await reverterFactory.connect(defaultAccount).deploy() + await proxy.proposeAggregator(reverter.address) + await proxy.confirmAggregator(reverter.address) + assert.equal(reverter.address, await proxy.aggregator()) + + const proxyId = phaseBase.mul(await proxy.phaseId()) + + await matchers.evmRevert( + proxy.getAnswer(proxyId), + 'Raised by Reverter.sol', + ) + }) + }) + + describe('after being updated to another contract', () => { + let preUpdateRoundId: BigNumber + let preUpdateAnswer: BigNumber + + beforeEach(async () => { + preUpdateRoundId = await proxy.latestRound() + preUpdateAnswer = await proxy.latestAnswer() + + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + await link.transfer(aggregator2.address, deposit) + matchers.bigNum(response2, await aggregator2.latestAnswer()) + + await proxy.proposeAggregator(aggregator2.address) + await proxy.confirmAggregator(aggregator2.address) + }) + + it('reports answers for previous phases', async () => { + const actualAnswer = await proxy.getAnswer(preUpdateRoundId) + matchers.bigNum(preUpdateAnswer, actualAnswer) + }) + }) + + describe('when the relevant info is not available', () => { + it('returns 0', async () => { + const actual = await proxy.getAnswer(phaseBase.mul(777)) + matchers.bigNum(0, actual) + }) + }) + + describe('when the round ID is too large', () => { + const overflowRoundId = h + .bigNum(2) + .pow(255) + .add(phaseBase) // get the original phase + .add(1) // get the original round + it('returns 0', async () => { + const actual = await proxy.getTimestamp(overflowRoundId) + matchers.bigNum(0, actual) + }) + }) + }) + + describe('#getTimestamp', () => { + describe('when the relevant round is not available', () => { + beforeEach(async () => { + await proxy.proposeAggregator(flux.address) + await proxy.confirmAggregator(flux.address) + }) + + it('does not revert when called with a non existent ID', async () => { + const proxyId = phaseBase.mul(await proxy.phaseId()).add(1) + const actual = await proxy.getTimestamp(proxyId) + matchers.bigNum(0, actual) + }) + }) + + describe('when the relevant info is not available', () => { + it('returns 0', async () => { + const actual = await proxy.getTimestamp(phaseBase.mul(777)) + matchers.bigNum(0, actual) + }) + }) + + describe('when the round ID is too large', () => { + const overflowRoundId = h + .bigNum(2) + .pow(255) + .add(phaseBase) // get the original phase + .add(1) // get the original round + + it('returns 0', async () => { + const actual = await proxy.getTimestamp(overflowRoundId) + matchers.bigNum(0, actual) + }) + }) + }) + + describe('#latestTimestamp', () => { + beforeEach(async () => { + const height = await aggregator.latestTimestamp() + assert.notEqual('0', height.toString()) + }) + + it('pulls the timestamp from the aggregator', async () => { + matchers.bigNum( + await aggregator.latestTimestamp(), + await proxy.latestTimestamp(), + ) + const latestRound = await proxy.latestRound() + matchers.bigNum( + await aggregator.latestTimestamp(), + await proxy.getTimestamp(latestRound), + ) + }) + + describe('after being updated to another contract', () => { + beforeEach(async () => { + await h.increaseTimeBy(30, provider) + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + + const height2 = await aggregator2.latestTimestamp() + assert.notEqual('0', height2.toString()) + + const height1 = await aggregator.latestTimestamp() + assert.notEqual( + height1.toString(), + height2.toString(), + 'Height1 and Height2 should not be equal', + ) + + await proxy.proposeAggregator(aggregator2.address) + await proxy.confirmAggregator(aggregator2.address) + }) + + it('pulls the timestamp from the new aggregator', async () => { + matchers.bigNum( + await aggregator2.latestTimestamp(), + await proxy.latestTimestamp(), + ) + const latestRound = await proxy.latestRound() + matchers.bigNum( + await aggregator2.latestTimestamp(), + await proxy.getTimestamp(latestRound), + ) + }) + }) + }) + + describe('#getRoundData', () => { + describe('when pointed at a Historic Aggregator', () => { + beforeEach(async () => { + historicAggregator = await historicAggregatorFactory + .connect(defaultAccount) + .deploy(response2) + await proxy.proposeAggregator(historicAggregator.address) + await proxy.confirmAggregator(historicAggregator.address) + }) + + it('reverts', async () => { + const latestRoundId = await historicAggregator.latestRound() + await matchers.evmRevert(proxy.getRoundData(latestRoundId)) + }) + + describe('when pointed at an Aggregator Facade', () => { + beforeEach(async () => { + const facade = await aggregatorFacadeFactory + .connect(defaultAccount) + .deploy(aggregator.address, 18, 'LINK/USD: Aggregator Facade') + await proxy.proposeAggregator(facade.address) + await proxy.confirmAggregator(facade.address) + }) + + it('works for a valid roundId', async () => { + const aggId = await aggregator.latestRound() + const phaseId = phaseBase.mul(await proxy.phaseId()) + const proxyId = phaseId.add(aggId) + + const round = await proxy.getRoundData(proxyId) + matchers.bigNum(proxyId, round.id) + matchers.bigNum(response, round.answer) + const nowSeconds = new Date().valueOf() / 1000 + assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) + matchers.bigNum(round.updatedAt, round.startedAt) + matchers.bigNum(proxyId, round.answeredInRound) + }) + }) + }) + + describe('when pointed at a FluxAggregator', () => { + beforeEach(async () => { + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + + await proxy.proposeAggregator(aggregator2.address) + await proxy.confirmAggregator(aggregator2.address) + }) + + it('works for a valid round ID', async () => { + const aggId = phaseBase.sub(2) + await aggregator2 + .connect(personas.Carol) + .updateRoundData(aggId, response2, 77, 42) + + const phaseId = phaseBase.mul(await proxy.phaseId()) + const proxyId = phaseId.add(aggId) + + const round = await proxy.getRoundData(proxyId) + matchers.bigNum(proxyId, round.id) + matchers.bigNum(response2, round.answer) + matchers.bigNum(42, round.startedAt) + matchers.bigNum(77, round.updatedAt) + matchers.bigNum(proxyId, round.answeredInRound) + }) + }) + + it('reads round ID of a previous phase', async () => { + const oldphaseId = phaseBase.mul(await proxy.phaseId()) + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + + await proxy.proposeAggregator(aggregator2.address) + await proxy.confirmAggregator(aggregator2.address) + + const aggId = await aggregator.latestRound() + const proxyId = oldphaseId.add(aggId) + + const round = await proxy.getRoundData(proxyId) + matchers.bigNum(proxyId, round.id) + matchers.bigNum(response, round.answer) + const nowSeconds = new Date().valueOf() / 1000 + assert.isAbove(round.startedAt.toNumber(), nowSeconds - 120) + assert.isBelow(round.startedAt.toNumber(), nowSeconds) + matchers.bigNum(round.startedAt, round.updatedAt) + matchers.bigNum(proxyId, round.answeredInRound) + }) + }) + + describe('#latestRoundData', () => { + describe('when pointed at a Historic Aggregator', () => { + beforeEach(async () => { + historicAggregator = await historicAggregatorFactory + .connect(defaultAccount) + .deploy(response2) + await proxy.proposeAggregator(historicAggregator.address) + await proxy.confirmAggregator(historicAggregator.address) + }) + + it('reverts', async () => { + await matchers.evmRevert(proxy.latestRoundData()) + }) + + describe('when pointed at an Aggregator Facade', () => { + beforeEach(async () => { + const facade = await aggregatorFacadeFactory + .connect(defaultAccount) + .deploy( + historicAggregator.address, + 17, + 'DOGE/ZWL: Aggregator Facade', + ) + await proxy.proposeAggregator(facade.address) + await proxy.confirmAggregator(facade.address) + }) + + it('does not revert', async () => { + const aggId = await historicAggregator.latestRound() + const phaseId = phaseBase.mul(await proxy.phaseId()) + const proxyId = phaseId.add(aggId) + + const round = await proxy.latestRoundData() + matchers.bigNum(proxyId, round.id) + matchers.bigNum(response2, round.answer) + const nowSeconds = new Date().valueOf() / 1000 + assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) + matchers.bigNum(round.updatedAt, round.startedAt) + matchers.bigNum(proxyId, round.answeredInRound) + }) + + it('uses the decimals set in the constructor', async () => { + matchers.bigNum(17, await proxy.decimals()) + }) + + it('uses the description set in the constructor', async () => { + assert.equal('DOGE/ZWL: Aggregator Facade', await proxy.description()) + }) + + it('sets the version to 2', async () => { + matchers.bigNum(2, await proxy.version()) + }) + }) + }) + + describe('when pointed at a FluxAggregator', () => { + beforeEach(async () => { + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + + await proxy.proposeAggregator(aggregator2.address) + await proxy.confirmAggregator(aggregator2.address) + }) + + it('does not revert', async () => { + const aggId = phaseBase.sub(2) + await aggregator2 + .connect(personas.Carol) + .updateRoundData(aggId, response2, 77, 42) + + const phaseId = phaseBase.mul(await proxy.phaseId()) + const proxyId = phaseId.add(aggId) + + const round = await proxy.latestRoundData() + matchers.bigNum(proxyId, round.id) + matchers.bigNum(response2, round.answer) + matchers.bigNum(42, round.startedAt) + matchers.bigNum(77, round.updatedAt) + matchers.bigNum(proxyId, round.answeredInRound) + }) + + it('uses the decimals of the aggregator', async () => { + matchers.bigNum(18, await proxy.decimals()) + }) + + it('uses the description of the aggregator', async () => { + assert.equal( + 'v0.6/tests/MockV3Aggregator.sol', + await proxy.description(), + ) + }) + + it('uses the version of the aggregator', async () => { + matchers.bigNum(0, await proxy.version()) + }) + }) + }) + + describe('#proposeAggregator', () => { + beforeEach(async () => { + await proxy.transferOwnership(personas.Carol.address) + await proxy.connect(personas.Carol).acceptOwnership() + + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, 1) + + assert.equal(aggregator.address, await proxy.aggregator()) + }) + + describe('when called by the owner', () => { + it('sets the address of the proposed aggregator', async () => { + await proxy + .connect(personas.Carol) + .proposeAggregator(aggregator2.address) + + assert.equal(aggregator2.address, await proxy.proposedAggregator()) + }) + + it('emits an AggregatorProposed event', async () => { + const tx = await proxy + .connect(personas.Carol) + .proposeAggregator(aggregator2.address) + const receipt = await tx.wait() + const eventLog = receipt?.events + + assert.equal(eventLog?.length, 1) + assert.equal(eventLog?.[0].event, 'AggregatorProposed') + assert.equal(eventLog?.[0].args?.[0], aggregator.address) + assert.equal(eventLog?.[0].args?.[1], aggregator2.address) + }) + }) + + describe('when called by a non-owner', () => { + it('does not update', async () => { + await matchers.evmRevert( + proxy.connect(personas.Neil).proposeAggregator(aggregator2.address), + 'Only callable by owner', + ) + + assert.equal(aggregator.address, await proxy.aggregator()) + }) + }) + }) + + describe('#confirmAggregator', () => { + beforeEach(async () => { + await proxy.transferOwnership(personas.Carol.address) + await proxy.connect(personas.Carol).acceptOwnership() + + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, 1) + + assert.equal(aggregator.address, await proxy.aggregator()) + }) + + describe('when called by the owner', () => { + beforeEach(async () => { + await proxy + .connect(personas.Carol) + .proposeAggregator(aggregator2.address) + }) + + it('sets the address of the new aggregator', async () => { + await proxy + .connect(personas.Carol) + .confirmAggregator(aggregator2.address) + + assert.equal(aggregator2.address, await proxy.aggregator()) + }) + + it('increases the phase', async () => { + matchers.bigNum(1, await proxy.phaseId()) + + await proxy + .connect(personas.Carol) + .confirmAggregator(aggregator2.address) + + matchers.bigNum(2, await proxy.phaseId()) + }) + + it('increases the round ID', async () => { + matchers.bigNum(phaseBase.add(1), await proxy.latestRound()) + + await proxy + .connect(personas.Carol) + .confirmAggregator(aggregator2.address) + + matchers.bigNum(phaseBase.mul(2).add(1), await proxy.latestRound()) + }) + + it('sets the proxy phase and aggregator', async () => { + assert.equal( + '0x0000000000000000000000000000000000000000', + await proxy.phaseAggregators(2), + ) + + await proxy + .connect(personas.Carol) + .confirmAggregator(aggregator2.address) + + assert.equal(aggregator2.address, await proxy.phaseAggregators(2)) + }) + + it('emits an AggregatorConfirmed event', async () => { + const tx = await proxy + .connect(personas.Carol) + .confirmAggregator(aggregator2.address) + const receipt = await tx.wait() + const eventLog = receipt?.events + + assert.equal(eventLog?.length, 1) + assert.equal(eventLog?.[0].event, 'AggregatorConfirmed') + assert.equal(eventLog?.[0].args?.[0], aggregator.address) + assert.equal(eventLog?.[0].args?.[1], aggregator2.address) + }) + }) + + describe('when called by a non-owner', () => { + beforeEach(async () => { + await proxy + .connect(personas.Carol) + .proposeAggregator(aggregator2.address) + }) + + it('does not update', async () => { + await matchers.evmRevert( + proxy.connect(personas.Neil).confirmAggregator(aggregator2.address), + 'Only callable by owner', + ) + + assert.equal(aggregator.address, await proxy.aggregator()) + }) + }) + }) + + describe('#proposedGetRoundData', () => { + beforeEach(async () => { + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + }) + + describe('when an aggregator has been proposed', () => { + beforeEach(async () => { + await proxy + .connect(defaultAccount) + .proposeAggregator(aggregator2.address) + assert.equal(await proxy.proposedAggregator(), aggregator2.address) + }) + + it('returns the data for the proposed aggregator', async () => { + const roundId = await aggregator2.latestRound() + const round = await proxy.proposedGetRoundData(roundId) + matchers.bigNum(roundId, round.id) + matchers.bigNum(response2, round.answer) + }) + + describe('after the aggregator has been confirmed', () => { + beforeEach(async () => { + await proxy + .connect(defaultAccount) + .confirmAggregator(aggregator2.address) + assert.equal(await proxy.aggregator(), aggregator2.address) + }) + + it('reverts', async () => { + const roundId = await aggregator2.latestRound() + await matchers.evmRevert( + proxy.proposedGetRoundData(roundId), + 'No proposed aggregator present', + ) + }) + }) + }) + }) + + describe('#proposedLatestRoundData', () => { + beforeEach(async () => { + aggregator2 = await aggregatorFactory + .connect(defaultAccount) + .deploy(decimals, response2) + }) + + describe('when an aggregator has been proposed', () => { + beforeEach(async () => { + await proxy + .connect(defaultAccount) + .proposeAggregator(aggregator2.address) + assert.equal(await proxy.proposedAggregator(), aggregator2.address) + }) + + it('returns the data for the proposed aggregator', async () => { + const roundId = await aggregator2.latestRound() + const round = await proxy.proposedLatestRoundData() + matchers.bigNum(roundId, round.id) + matchers.bigNum(response2, round.answer) + }) + + describe('after the aggregator has been confirmed', () => { + beforeEach(async () => { + await proxy + .connect(defaultAccount) + .confirmAggregator(aggregator2.address) + assert.equal(await proxy.aggregator(), aggregator2.address) + }) + + it('reverts', async () => { + await matchers.evmRevert( + proxy.proposedLatestRoundData(), + 'No proposed aggregator present', + ) + }) + }) + }) + }) +}) diff --git a/evm-contracts/test/v0.7/ChainlinkOperatorFactory.test.ts b/evm-contracts/test/v0.7/ChainlinkOperatorFactory.test.ts new file mode 100644 index 00000000000..c34f4566c4d --- /dev/null +++ b/evm-contracts/test/v0.7/ChainlinkOperatorFactory.test.ts @@ -0,0 +1,64 @@ +import { contract, setup, helpers } from '@chainlink/test-helpers' +import { assert } from 'chai' +import { ContractReceipt } from 'ethers/contract' +import { OperatorFactory } from '../../ethers/v0.7/OperatorFactory' +import { ChainlinkOperatorFactoryFactory } from '../../ethers/v0.7/ChainlinkOperatorFactoryFactory' + +const linkTokenFactory = new contract.LinkTokenFactory() +const operatorGeneratorFactory = new ChainlinkOperatorFactoryFactory() +const operatorFactory = new OperatorFactory() + +let roles: setup.Roles +const provider = setup.provider() + +beforeAll(async () => { + const users = await setup.users(provider) + + roles = users.roles +}) + +describe('ChainlinkOperatorFactory', () => { + let link: contract.Instance + let operatorGenerator: contract.Instance + let operator: contract.Instance + + const deployment = setup.snapshot(provider, async () => { + link = await linkTokenFactory.connect(roles.defaultAccount).deploy() + operatorGenerator = await operatorGeneratorFactory + .connect(roles.defaultAccount) + .deploy(link.address) + }) + + beforeEach(async () => { + await deployment() + }) + + describe('#createOperator', () => { + let receipt: ContractReceipt + + beforeEach(async () => { + const tx = await operatorGenerator.connect(roles.oracleNode).fallback() + + receipt = await tx.wait() + }) + + it('emits an event', async () => { + const emittedOwner = helpers.evmWordToAddress( + receipt.logs?.[0].topics?.[2], + ) + assert.equal(emittedOwner, roles.oracleNode.address) + }) + + it('sets the correct owner', async () => { + const emittedAddress = helpers.evmWordToAddress( + receipt.logs?.[0].topics?.[1], + ) + + operator = await operatorFactory + .connect(roles.defaultAccount) + .attach(emittedAddress) + const ownerString = await operator.owner() + assert.equal(ownerString, roles.oracleNode.address) + }) + }) +}) diff --git a/evm-contracts/test/v0.7/Owned.test.ts b/evm-contracts/test/v0.7/ConfirmedOwner.test.ts similarity index 63% rename from evm-contracts/test/v0.7/Owned.test.ts rename to evm-contracts/test/v0.7/ConfirmedOwner.test.ts index 90b4aa77bc6..47bc6843203 100644 --- a/evm-contracts/test/v0.7/Owned.test.ts +++ b/evm-contracts/test/v0.7/ConfirmedOwner.test.ts @@ -6,9 +6,9 @@ import { } from '@chainlink/test-helpers' import { assert } from 'chai' import { ethers } from 'ethers' -import { OwnedTestHelperFactory } from '../../ethers/v0.7/OwnedTestHelperFactory' +import { ConfirmedOwnerTestHelperFactory } from '../../ethers/v0.7/ConfirmedOwnerTestHelperFactory' -const ownedTestHelperFactory = new OwnedTestHelperFactory() +const confirmedOwnerTestHelperFactory = new ConfirmedOwnerTestHelperFactory() const provider = setup.provider() let personas: setup.Personas @@ -24,16 +24,18 @@ beforeAll(async () => { newOwner = personas.Ned }) -describe('Owned', () => { - let owned: contract.Instance - const ownedEvents = ownedTestHelperFactory.interface.events +describe('ConfirmedOwner', () => { + let confirmedOwner: contract.Instance + const confirmedOwnerEvents = confirmedOwnerTestHelperFactory.interface.events beforeEach(async () => { - owned = await ownedTestHelperFactory.connect(owner).deploy() + confirmedOwner = await confirmedOwnerTestHelperFactory + .connect(owner) + .deploy() }) it('has a limited public interface', () => { - matchers.publicAbi(ownedTestHelperFactory, [ + matchers.publicAbi(confirmedOwnerTestHelperFactory, [ 'acceptOwnership', 'owner', 'transferOwnership', @@ -46,7 +48,7 @@ describe('Owned', () => { it('assigns ownership to the deployer', async () => { const [actual, expected] = await Promise.all([ owner.getAddress(), - owned.owner(), + confirmedOwner.owner(), ]) assert.equal(actual, expected) @@ -56,30 +58,32 @@ describe('Owned', () => { describe('#onlyOwner modifier', () => { describe('when called by an owner', () => { it('successfully calls the method', async () => { - const tx = await owned.connect(owner).modifierOnlyOwner() + const tx = await confirmedOwner.connect(owner).modifierOnlyOwner() const receipt = await tx.wait() - expect(h.findEventIn(receipt, ownedEvents.Here)).toBeDefined() + expect(h.findEventIn(receipt, confirmedOwnerEvents.Here)).toBeDefined() }) }) describe('when called by anyone but the owner', () => { it('reverts', () => - matchers.evmRevert(owned.connect(nonOwner).modifierOnlyOwner())) + matchers.evmRevert( + confirmedOwner.connect(nonOwner).modifierOnlyOwner(), + )) }) }) describe('#transferOwnership', () => { describe('when called by an owner', () => { it('emits a log', async () => { - const tx = await owned + const tx = await confirmedOwner .connect(owner) .transferOwnership(newOwner.address) const receipt = await tx.wait() const event = h.findEventIn( receipt, - ownedEvents.OwnershipTransferRequested, + confirmedOwnerEvents.OwnershipTransferRequested, ) expect(h.eventArgs(event).to).toEqual(newOwner.address) expect(h.eventArgs(event).from).toEqual(owner.address) @@ -87,7 +91,7 @@ describe('Owned', () => { it('does not allow ownership transfer to self', async () => { await matchers.evmRevert( - owned.connect(owner).transferOwnership(owner.address), + confirmedOwner.connect(owner).transferOwnership(owner.address), 'Cannot transfer to self', ) }) @@ -97,27 +101,30 @@ describe('Owned', () => { describe('when called by anyone but the owner', () => { it('successfully calls the method', () => matchers.evmRevert( - owned.connect(nonOwner).transferOwnership(newOwner.address), + confirmedOwner.connect(nonOwner).transferOwnership(newOwner.address), )) }) describe('#acceptOwnership', () => { describe('after #transferOwnership has been called', () => { beforeEach(async () => { - await owned.connect(owner).transferOwnership(newOwner.address) + await confirmedOwner.connect(owner).transferOwnership(newOwner.address) }) it('allows the recipient to call it', async () => { - const tx = await owned.connect(newOwner).acceptOwnership() + const tx = await confirmedOwner.connect(newOwner).acceptOwnership() const receipt = await tx.wait() - const event = h.findEventIn(receipt, ownedEvents.OwnershipTransferred) + const event = h.findEventIn( + receipt, + confirmedOwnerEvents.OwnershipTransferred, + ) expect(h.eventArgs(event).to).toEqual(newOwner.address) expect(h.eventArgs(event).from).toEqual(owner.address) }) it('does not allow a non-recipient to call it', () => - matchers.evmRevert(owned.connect(nonOwner).acceptOwnership())) + matchers.evmRevert(confirmedOwner.connect(nonOwner).acceptOwnership())) }) }) }) diff --git a/evm-contracts/test/v0.7/Operator.test.ts b/evm-contracts/test/v0.7/Operator.test.ts index 8f47df0963e..b7172625a13 100644 --- a/evm-contracts/test/v0.7/Operator.test.ts +++ b/evm-contracts/test/v0.7/Operator.test.ts @@ -48,8 +48,8 @@ describe('Operator', () => { link = await linkTokenFactory.connect(roles.defaultAccount).deploy() operator = await operatorFactory .connect(roles.defaultAccount) - .deploy(link.address) - await operator.setFulfillmentPermission(roles.oracleNode.address, true) + .deploy(link.address, roles.defaultAccount.address) + await operator.setAuthorizedSender(roles.oracleNode.address, true) }) beforeEach(async () => { @@ -63,11 +63,11 @@ describe('Operator', () => { 'forward', 'fulfillOracleRequest', 'fulfillOracleRequest2', - 'getAuthorizationStatus', + 'isAuthorizedSender', 'getChainlinkToken', 'onTokenTransfer', 'oracleRequest', - 'setFulfillmentPermission', + 'setAuthorizedSender', 'withdraw', 'withdrawable', // Ownable methods: @@ -77,16 +77,16 @@ describe('Operator', () => { ]) }) - describe('#setFulfillmentPermission', () => { + describe('#setAuthorizedSender', () => { describe('when called by the owner', () => { beforeEach(async () => { await operator .connect(roles.defaultAccount) - .setFulfillmentPermission(roles.stranger.address, true) + .setAuthorizedSender(roles.stranger.address, true) }) it('adds an authorized node', async () => { - const authorized = await operator.getAuthorizationStatus( + const authorized = await operator.isAuthorizedSender( roles.stranger.address, ) assert.equal(true, authorized) @@ -95,8 +95,8 @@ describe('Operator', () => { it('removes an authorized node', async () => { await operator .connect(roles.defaultAccount) - .setFulfillmentPermission(roles.stranger.address, false) - const authorized = await operator.getAuthorizationStatus( + .setAuthorizedSender(roles.stranger.address, false) + const authorized = await operator.isAuthorizedSender( roles.stranger.address, ) assert.equal(false, authorized) @@ -108,7 +108,7 @@ describe('Operator', () => { await matchers.evmRevert(async () => { await operator .connect(roles.stranger) - .setFulfillmentPermission(roles.stranger.address, true) + .setAuthorizedSender(roles.stranger.address, true) }) }) }) @@ -381,7 +381,7 @@ describe('Operator', () => { beforeEach(async () => { assert.equal( false, - await operator.getAuthorizationStatus(roles.stranger.address), + await operator.isAuthorizedSender(roles.stranger.address), ) }) @@ -814,7 +814,7 @@ describe('Operator', () => { beforeEach(async () => { assert.equal( false, - await operator.getAuthorizationStatus(roles.stranger.address), + await operator.isAuthorizedSender(roles.stranger.address), ) }) @@ -1304,7 +1304,7 @@ describe('Operator', () => { beforeEach(async () => { assert.equal( false, - await operator.getAuthorizationStatus(roles.stranger.address), + await operator.isAuthorizedSender(roles.stranger.address), ) }) @@ -1794,7 +1794,7 @@ describe('Operator', () => { beforeEach(async () => { assert.equal( false, - await operator.getAuthorizationStatus(roles.stranger.address), + await operator.isAuthorizedSender(roles.stranger.address), ) }) diff --git a/evm-test-helpers/src/setup.ts b/evm-test-helpers/src/setup.ts index 47abe702bcc..285a547e641 100644 --- a/evm-test-helpers/src/setup.ts +++ b/evm-test-helpers/src/setup.ts @@ -145,8 +145,8 @@ export async function users( Nelly: accounts[3], Nancy: accounts[4], Norbert: accounts[5], - Carol: accounts[4], - Eddy: accounts[5], + Carol: accounts[6], + Eddy: accounts[7], } const roles: Roles = { diff --git a/go.mod b/go.mod index 3d6b7484d2c..f90aedd514c 100644 --- a/go.mod +++ b/go.mod @@ -5,18 +5,14 @@ go 1.15 require ( github.com/DATA-DOG/go-txdb v0.1.3 github.com/Depado/ginprom v1.2.1-0.20200115153638-53bbba851bd8 - github.com/allegro/bigcache v1.2.1 // indirect github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 - github.com/aristanetworks/goarista v0.0.0-20191023202215-f096da5361bb // indirect github.com/bitly/go-simplejson v0.5.0 - github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect + github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 github.com/btcsuite/btcd v0.21.0-beta github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e - github.com/ethereum/go-ethereum v1.9.22 + github.com/ethereum/go-ethereum v1.9.24 github.com/fatih/color v1.10.0 - github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect github.com/fxamacker/cbor/v2 v2.2.0 - github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gin-contrib/cors v1.3.1 github.com/gin-contrib/expvar v0.0.0-20181230111036-f23b556cc79f github.com/gin-contrib/size v0.0.0-20190528085907-355431950c57 @@ -26,37 +22,29 @@ require ( github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.2.1 github.com/gorilla/websocket v1.4.2 - github.com/guregu/null v3.5.0+incompatible - github.com/ipfs/go-datastore v0.4.5 // indirect github.com/jinzhu/gorm v1.9.16 - github.com/jinzhu/now v1.1.1 // indirect github.com/jpillora/backoff v1.0.0 - github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9 // indirect github.com/lib/pq v1.8.0 github.com/libp2p/go-libp2p-core v0.6.1 github.com/libp2p/go-libp2p-peerstore v0.2.6 github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/mapstructure v1.3.3 + github.com/mitchellh/mapstructure v1.4.0 github.com/multiformats/go-multiaddr v0.3.1 github.com/olekukonko/tablewriter v0.0.4 github.com/onsi/gomega v1.10.3 github.com/pelletier/go-toml v1.8.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.8.0 - github.com/prometheus/tsdb v0.10.0 // indirect github.com/robfig/cron/v3 v3.0.1 - github.com/rs/cors v1.7.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/shopspring/decimal v1.2.0 - github.com/smartcontractkit/libocr v0.0.0-20201104141745-a805eb2bc4fc + github.com/smartcontractkit/libocr v0.0.0-20201209002813-4110928c10ff github.com/spf13/viper v1.7.1 - github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48 // indirect github.com/stretchr/testify v1.6.1 github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 github.com/tidwall/gjson v1.6.3 github.com/tidwall/sjson v1.1.2 - github.com/tyler-smith/go-bip39 v1.0.2 // indirect github.com/ulule/limiter v0.0.0-20190417201358-7873d115fc4e github.com/unrolled/secure v0.0.0-20190624173513-716474489ad3 github.com/urfave/cli v1.22.5 @@ -67,10 +55,7 @@ require ( golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 golang.org/x/text v0.3.4 - golang.org/x/tools v0.0.0-20201118215654-4d9c4f8a78b0 // indirect - gonum.org/v1/gonum v0.8.1 + gonum.org/v1/gonum v0.8.2 gopkg.in/gormigrate.v1 v1.6.0 - gopkg.in/guregu/null.v3 v3.5.0 gopkg.in/guregu/null.v4 v4.0.0 - honnef.co/go/tools v0.0.1-2020.1.6 // indirect ) diff --git a/go.sum b/go.sum index e75bb0e170b..a5edee02ed4 100644 --- a/go.sum +++ b/go.sum @@ -214,8 +214,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/ethereum/go-ethereum v1.9.18/go.mod h1:JSSTypSMTkGZtAdAChH2wP5dZEvPGh3nUTuDpH+hNrg= -github.com/ethereum/go-ethereum v1.9.22 h1:/Fea9n2EWJuNJ9oahMq9luqjRBcbW7QWdThbcJl13ek= -github.com/ethereum/go-ethereum v1.9.22/go.mod h1:FQjK3ZwD8C5DYn7ukTmFee36rq1dOMESiUfXr5RUc1w= +github.com/ethereum/go-ethereum v1.9.24 h1:6AK+ORt3EMDO+FTjzXy/AQwHMbu52J2nYHIjyQX9azQ= +github.com/ethereum/go-ethereum v1.9.24/go.mod h1:JIfVb6esrqALTExdz9hRYvrP0xBDf6wCncIu1hNwHpM= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= @@ -265,6 +265,7 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -835,8 +836,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -943,6 +944,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -1087,8 +1089,11 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartcontractkit/chainlink v0.8.10-0.20200825114219-81dd2fc95bac/go.mod h1:j7qIYHGCN4QqMXdO8g8A9dmUT5vKFmkxPSbjAIfrfNU= -github.com/smartcontractkit/libocr v0.0.0-20201104141745-a805eb2bc4fc h1:8Og6pkrK+O/KUTfG8BCOnDKPMmowJLU/TWhzqYNT9e4= -github.com/smartcontractkit/libocr v0.0.0-20201104141745-a805eb2bc4fc/go.mod h1:Yfu8T/45S0pf1qQ6JLHMNxXKBNqOfPZLtkA4Urk7njI= +github.com/smartcontractkit/chainlink v0.9.5-0.20201207211610-6c7fee37d5b7/go.mod h1:kmdLJbVZRCnBLiL6gG+U+1+0ofT3bB48DOF8tjQvcoI= +github.com/smartcontractkit/libocr v0.0.0-20201203233047-5d9b24f0cbb5 h1:nIjd4ebsU5dphoziTp/F79RNv8x3wOZmrn6A/5oYHI0= +github.com/smartcontractkit/libocr v0.0.0-20201203233047-5d9b24f0cbb5/go.mod h1:bfdSuLnBWCkafDvPGsQ1V6nrXhg046gh227MKi4zkpc= +github.com/smartcontractkit/libocr v0.0.0-20201209002813-4110928c10ff h1:Oae3c2S40byotDVqIBZ5RRuoSLeC4IMpUf8b8DiicMo= +github.com/smartcontractkit/libocr v0.0.0-20201209002813-4110928c10ff/go.mod h1:HTs6XN84o17vgbw3F8XEl3pal91qxpSzlJYjsoqMpzw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -1246,6 +1251,8 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -1299,6 +1306,8 @@ golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9t golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 h1:2/QncOxxpPAdiH+E00abYw/SaQG353gltz79Nl1zrYE= +golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1311,6 +1320,8 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= @@ -1321,6 +1332,10 @@ golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hM golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170324220409-6c2325251549/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1374,8 +1389,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170325170518-afadfcc7779c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1415,6 +1428,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1481,20 +1495,23 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200930143006-c8c0a1c0177f h1:t2THPowp6vxGakJZai3oPWos75chgqqu4x0G9fqZchc= -golang.org/x/tools v0.0.0-20200930143006-c8c0a1c0177f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201118215654-4d9c4f8a78b0 h1:ZE8TbQqVy3d5tnnRBBhbtisccQkE4JMRX0YdHumcbNc= -golang.org/x/tools v0.0.0-20201118215654-4d9c4f8a78b0/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124202034-299f270db459/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201202200335-bef1c476418a h1:TYqOq/v+Ri5aADpldxXOj6PmvcPMOJbLjdALzZDQT2M= +golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201203230154-39497347062d h1:ChJAHTAuCcMx4RHS9P/KnYnJ1UEgJDZNRtvF0TJ0wbg= +golang.org/x/tools v0.0.0-20201203230154-39497347062d/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.1 h1:wGtP3yGpc5mCLOLeTeBdjeui9oZSz5De0eOjMLC/QuQ= -gonum.org/v1/gonum v0.8.1/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= diff --git a/integration/apocalypse/scenarios/lib/chainlink.js b/integration/apocalypse/scenarios/lib/chainlink.js index 2965fa023ef..064aabcdb68 100644 --- a/integration/apocalypse/scenarios/lib/chainlink.js +++ b/integration/apocalypse/scenarios/lib/chainlink.js @@ -10,7 +10,7 @@ module.exports = { } async function chainlinkLogin(nodeURL, tmpdir) { - let resp = await run( + const resp = await run( `chainlink -j admin login -f ${__dirname}/../../chainlink/apicredentials`, { CLIENT_NODE_URL: nodeURL, ROOT: tmpdir }, ) @@ -18,11 +18,14 @@ async function chainlinkLogin(nodeURL, tmpdir) { } async function addJobSpec(nodeURL, jobSpec, tmpdir) { - let jobSpecJSON = JSON.stringify(jobSpec) - let resp = await run(['chainlink', '-j', 'jobs', 'create', jobSpecJSON], { - CLIENT_NODE_URL: nodeURL, - ROOT: tmpdir, - }) + const jobSpecJSON = JSON.stringify(jobSpec) + const resp = await run( + ['chainlink', '-j', 'job_specs', 'create', jobSpecJSON], + { + CLIENT_NODE_URL: nodeURL, + ROOT: tmpdir, + }, + ) try { console.log('RESP ~>', resp) return JSON.parse(resp) @@ -47,7 +50,7 @@ async function setPriceFeedValue(externalAdapterURL, value) { async function successfulJobRuns(nodeURL, jobSpecID, num, tmpdir) { while (true) { - let runs = JSON.parse( + const runs = JSON.parse( await run(`chainlink -j runs list`, { CLIENT_NODE_URL: nodeURL, ROOT: tmpdir, @@ -123,7 +126,7 @@ const tasks = [ function makeJobSpecFluxMonitor(aggregatorContractAddress, feedAddr) { return { - initiators: initiators, - tasks: tasks, + initiators, + tasks, } } diff --git a/integration/cypress/integration/getConfig.spec.ts b/integration/cypress/integration/getConfig.spec.ts index 5147e4c1b6a..bbc6c28f435 100644 --- a/integration/cypress/integration/getConfig.spec.ts +++ b/integration/cypress/integration/getConfig.spec.ts @@ -1,6 +1,5 @@ // sample of keys to test for const CONFIG_KEYS = [ - 'ACCOUNT_ADDRESS', 'CHAINLINK_TLS_REDIRECT', 'CHAINLINK_TLS_PORT', 'ETH_CHAIN_ID', diff --git a/integration/ethlog_test b/integration/ethlog_test index ea0deef16b3..2f676210798 100755 --- a/integration/ethlog_test +++ b/integration/ethlog_test @@ -6,7 +6,7 @@ title 'Ethlog test.' ethlog_test() { expected_echo_count=$(expr $(curl -sS $ECHO_SERVER_URL) + 1) - expected_job_count=$(expr $(chainlink -j jobs list | jq length) + 1) + expected_job_count=$(expr $(chainlink -j job_specs list | jq length) + 1) local log=$LOG_PATH/send_ethlog_transaction.log yarn workspace @chainlink/integration-scripts send-ethlog-transaction | tee $log @@ -17,7 +17,7 @@ ethlog_test() { assert "Echo count" "curl -sS $ECHO_SERVER_URL" $expected_echo_count # Check job counts - assert "Jobs count" "chainlink -j jobs list | jq length" $expected_job_count + assert "Jobs count" "chainlink -j job_specs list | jq length" $expected_job_count # Check job runs assert "EthLog Runs count" "chainlink -j runs list --jobid $jid | jq length" 1 diff --git a/integration/forks/test_helpers b/integration/forks/test_helpers index 71be6c48ead..10900031892 100644 --- a/integration/forks/test_helpers +++ b/integration/forks/test_helpers @@ -25,7 +25,7 @@ login() { create_job() { login ETH_LOG_JOB=`cat fixtures/eth_log_job.json` - docker exec -it forks_chainlink chainlink jobs create "$ETH_LOG_JOB" + docker exec -it forks_chainlink chainlink job_specs create "$ETH_LOG_JOB" } create_contract() { diff --git a/integration/runlog_test b/integration/runlog_test index 1cbbf193bd2..c257d4257fa 100755 --- a/integration/runlog_test +++ b/integration/runlog_test @@ -6,7 +6,7 @@ title 'Runlog test.' runlog_test() { expected_echo_count=$(expr $(curl -sS "$ECHO_SERVER_URL") + 1) - expected_job_count=$(expr $(chainlink -j jobs list | jq length) + 1) + expected_job_count=$(expr $(chainlink -j job_specs list | jq length) + 1) local log=$LOG_PATH/send_runlog_transaction.log yarn workspace @chainlink/integration-scripts send-runlog-transaction | tee $log @@ -15,10 +15,10 @@ runlog_test() { assert "Echo count" "curl -sS $ECHO_SERVER_URL" $expected_echo_count ## Check job counts using jq to parse json: https://stedolan.github.io/jq/ - assert "Jobs count" "chainlink -j jobs list | jq length" $expected_job_count + assert "Jobs count" "chainlink -j job_specs list | jq length" $expected_job_count # Check job runs - jid=`chainlink -j jobs list | jq 'last | .id' | tr -d '"'` + jid=`chainlink -j job_specs list | jq 'last | .id' | tr -d '"'` echo "Test created Job: $jid" assert "RunLog Runs count" "chainlink -j runs list --jobid $jid | jq 'length'" 1 diff --git a/operator_ui/@types/core/store/models.d.ts b/operator_ui/@types/core/store/models.d.ts index 36cc8fbade3..915d5bcf5dd 100644 --- a/operator_ui/@types/core/store/models.d.ts +++ b/operator_ui/@types/core/store/models.d.ts @@ -168,11 +168,13 @@ declare module 'core/store/models' { */ export interface TaskRun { id: string - result: RunResult + result: + | { data: { result: string }; error: null } + | { data: {}; error: string } status: RunStatus task: TaskSpec - minimumConfirmations: clnull.Uint32 - confirmations: clnull.Uint32 + minimumConfirmations?: number | null + confirmations?: number | null } /** @@ -468,10 +470,11 @@ declare module 'core/store/models' { toml: string } - type OcrTaskOutput = ?string - type OcrTaskError = ?string + export type PipelineTaskOutput = string | null + export type PipelineTaskError = string | null export interface OcrJobSpec { + name: string | null errors: JobSpecError[] offChainReportingOracleSpec: { contractAddress: common.Address @@ -488,7 +491,6 @@ declare module 'core/store/models' { contractConfigConfirmations: number createdAt: time.Time updatedAt: time.Time - name?: string // Upcoming field } pipelineSpec: { dotDagSource: string @@ -496,19 +498,24 @@ declare module 'core/store/models' { } export interface OcrJobRun { - outputs: OcrTaskOutput[] - errors: OcrTaskError[] - taskRuns: OcrTaskRun[] + outputs: PipelineTaskOutput[] + errors: PipelineTaskError[] + taskRuns: PipelineTaskRun[] createdAt: time.Time finishedAt: nullable.Time + pipelineSpec: { + ID: number + CreatedAt: time.Time + DotDagSource: string + } } } -export interface OcrTaskRun { +export interface PipelineTaskRun { createdAt: time.Time - error: OcrTaskError + error: PipelineTaskError finishedAt: nullable.Time - output: OcrTaskOutput + output: PipelineTaskOutput taskSpec: { dotId: string } diff --git a/operator_ui/@types/core/store/presenters.d.ts b/operator_ui/@types/core/store/presenters.d.ts index bbf2cd9b35c..fab7dc3522b 100644 --- a/operator_ui/@types/core/store/presenters.d.ts +++ b/operator_ui/@types/core/store/presenters.d.ts @@ -14,6 +14,8 @@ declare module 'core/store/presenters' { address: string ethBalance: Pointer linkBalance: Pointer + createdAt: string + isFunding: boolean } /** diff --git a/operator_ui/@types/graphlib-dot.d.ts b/operator_ui/@types/graphlib-dot.d.ts new file mode 100644 index 00000000000..01d26b3cde8 --- /dev/null +++ b/operator_ui/@types/graphlib-dot.d.ts @@ -0,0 +1 @@ +declare module 'graphlib-dot' diff --git a/operator_ui/package.json b/operator_ui/package.json index c8d340dd167..8c33831a37c 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -31,9 +31,12 @@ "bignumber.js": "^9.0.0", "change-case": "^4.1.1", "classnames": "^2.2.6", - "cookie": "^0.4.0", + "cookie": "^0.4.1", "core-js": "^3.4.0", + "d3": "^6.2.0", + "d3-dag": "^0.4.7", "formik": "^2.1.4", + "graphlib-dot": "^0.6.4", "humps": "^2.0.1", "isomorphic-unfetch": "^3.0.0", "javascript-stringify": "^2.0.1", @@ -72,6 +75,7 @@ "@chainlink/ts-helpers": "0.0.3", "@types/classnames": "^2.2.8", "@types/cookie": "^0.4.0", + "@types/d3": "^6.2.0", "@types/enzyme": "^3.10.8", "@types/fetch-mock": "^7.3.3", "@types/jest": "^26.0.8", @@ -81,7 +85,7 @@ "@types/path-to-regexp": "^1.7.0", "@types/react": "^16.9.34", "@types/react-dom": "^16.9.8", - "@types/react-redux": "^7.1.8", + "@types/react-redux": "^7.1.11", "@types/react-resize-detector": "^4.0.1", "@types/react-router": "^5.1.4", "@types/react-router-dom": "^5.1.6", @@ -92,7 +96,7 @@ "css-loader": "^3.6.0", "depcheck": "^0.9.2", "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.2", + "enzyme-adapter-react-16": "^1.15.5", "fetch-mock": "^9.0.0", "file-loader": "^6.2.0", "html-webpack-plugin": "^4.4.1", @@ -104,7 +108,7 @@ "redux-mock-store": "^1.5.3", "rimraf": "^3.0.2", "serve": "^11.2.0", - "style-loader": "^1.2.1", + "style-loader": "^2.0.0", "ts-jest": "^24.1.0", "tsconfig-paths-webpack-plugin": "^3.2.0", "typescript": "^3.7.4", diff --git a/operator_ui/src/Private.tsx b/operator_ui/src/Private.tsx index 8150e26d3cc..58912a8c53d 100644 --- a/operator_ui/src/Private.tsx +++ b/operator_ui/src/Private.tsx @@ -28,6 +28,16 @@ const DashboardsIndex: UniversalComponent< recentlyCreatedPageSize: number } > = universal(import('./pages/Dashboards/Index'), uniOpts) +const JobRunsIndex: UniversalComponent< + Pick< + { + classes: any + }, + never + > & { + pagePath: string + } +> = universal(import('./pages/JobRuns/Index'), uniOpts) const JobsIndex = universal(import('./pages/JobsIndex/JobsIndex'), uniOpts) const JobsShow = universal(import('./pages/Jobs/Show'), uniOpts) const JobsNew = universal(import('./pages/Jobs/New'), uniOpts) @@ -35,15 +45,7 @@ const BridgesIndex = universal(import('./pages/Bridges/Index'), uniOpts) const BridgesNew = universal(import('./pages/Bridges/New'), uniOpts) const BridgesShow = universal(import('./pages/Bridges/Show'), uniOpts) const BridgesEdit = universal(import('./pages/Bridges/Edit'), uniOpts) -const JobRunsShowOverview = universal( - import('./pages/JobRuns/Show/Overview'), - uniOpts, -) -const JobRunsShowJson = universal(import('./pages/JobRuns/Show/Json'), uniOpts) -const JobRunsShowErrorLog = universal( - import('./pages/JobRuns/Show/ErrorLog'), - uniOpts, -) +const JobRunsShowOverview = universal(import('./pages/Jobs/Runs/Show'), uniOpts) const TransactionsIndex = universal( import('./pages/Transactions/Index'), uniOpts, @@ -97,22 +99,25 @@ const Private = ({ classes }: { classes: { content: string } }) => { + ( + + )} /> ( + + )} /> - - + ; ( error: Error, ) => { if (error instanceof jsonapi.AuthenticationError) { - sendSignOut() + sendSignOut(dispatch) } else { dispatch(createErrorAction(error, type)) } @@ -108,14 +108,12 @@ export const receiveSignoutSuccess = () => ({ authenticated: false, }) -function sendSignOut() { - return (dispatch: Dispatch) => { - dispatch({ type: AuthActionType.REQUEST_SIGNOUT }) - return api.sessions - .destroySession() - .then(() => dispatch(receiveSignoutSuccess())) - .catch(curryErrorHandler(dispatch, AuthActionType.RECEIVE_SIGNIN_ERROR)) - } +function sendSignOut(dispatch: Dispatch) { + dispatch({ type: AuthActionType.REQUEST_SIGNOUT }) + return api.sessions + .destroySession() + .then(() => dispatch(receiveSignoutSuccess())) + .catch(curryErrorHandler(dispatch, AuthActionType.RECEIVE_SIGNIN_ERROR)) } const RECEIVE_CREATE_SUCCESS_ACTION = { @@ -134,7 +132,7 @@ const receiveUpdateSuccess = (response: Response) => ({ export const submitSignIn = (data: Parameter) => sendSignIn(data) -export const submitSignOut = () => sendSignOut() +export const submitSignOut = () => sendSignOut export const deleteJobSpec = ( id: string, @@ -244,7 +242,7 @@ export const updateBridge = ( // The calls above will be converted gradually. const handleError = (dispatch: Dispatch) => (error: Error) => { if (error instanceof jsonapi.AuthenticationError) { - sendSignOut() + sendSignOut(dispatch) } else { dispatch(notifyError(({ msg }: any) => msg, error)) } diff --git a/operator_ui/src/api/v2/ocrRuns.ts b/operator_ui/src/api/v2/ocrRuns.ts index 537fcb21f7a..82ec5a3fe18 100644 --- a/operator_ui/src/api/v2/ocrRuns.ts +++ b/operator_ui/src/api/v2/ocrRuns.ts @@ -2,7 +2,8 @@ import * as jsonapi from '@chainlink/json-api-client' import { boundMethod } from 'autobind-decorator' import * as models from 'core/store/models' -export const ENDPOINT = '/v2/ocr/specs/:jobSpecId/runs' +export const ENDPOINT = '/v2/jobs/:jobSpecId/runs' +const SHOW_ENDPOINT = `${ENDPOINT}/:runId` export class OcrRuns { constructor(private api: jsonapi.Api) {} @@ -18,5 +19,25 @@ export class OcrRuns { return this.index({ page, size }, { jobSpecId }) } + @boundMethod + public getJobSpecRun({ + jobSpecId, + runId, + }: { + jobSpecId: string + runId: string + }): Promise> { + return this.show({}, { jobSpecId, runId }) + } + private index = this.api.fetchResource<{}, models.OcrJobRun[]>(ENDPOINT) + + private show = this.api.fetchResource< + {}, + models.OcrJobRun, + { + jobSpecId: string + runId: string + } + >(SHOW_ENDPOINT) } diff --git a/operator_ui/src/api/v2/ocrSpecs.ts b/operator_ui/src/api/v2/ocrSpecs.ts index 3373447ce0a..db91d39dbc5 100644 --- a/operator_ui/src/api/v2/ocrSpecs.ts +++ b/operator_ui/src/api/v2/ocrSpecs.ts @@ -2,7 +2,7 @@ import * as jsonapi from '@chainlink/json-api-client' import { boundMethod } from 'autobind-decorator' import * as models from 'core/store/models' -export const ENDPOINT = '/v2/ocr/specs' +export const ENDPOINT = '/v2/jobs' const SHOW_ENDPOINT = `${ENDPOINT}/:specId` const DESTROY_ENDPOINT = `${ENDPOINT}/:specId` diff --git a/operator_ui/src/api/v2/user/balances.ts b/operator_ui/src/api/v2/user/balances.ts index 7230379a6bb..a0954e598db 100644 --- a/operator_ui/src/api/v2/user/balances.ts +++ b/operator_ui/src/api/v2/user/balances.ts @@ -7,7 +7,7 @@ import * as presenters from 'core/store/presenters' * * @example "/user/balances" */ -const ACCOUNT_BALANCES_ENDPOINT = '/v2/user/balances' +export const ACCOUNT_BALANCES_ENDPOINT = '/v2/user/balances' export class Balances { constructor(private api: jsonapi.Api) {} diff --git a/operator_ui/src/components/Dashboards/Activity.test.tsx b/operator_ui/src/components/Dashboards/Activity.test.tsx index a06769a53f8..c283313ea7d 100644 --- a/operator_ui/src/components/Dashboards/Activity.test.tsx +++ b/operator_ui/src/components/Dashboards/Activity.test.tsx @@ -13,6 +13,23 @@ describe('components/Dashboards/Activity', () => { expect(component.text()).toContain('Run: runA') }) + it('displays a "View More" link when there is more than 1 page of runs', () => { + const runs = [ + partialAsFull({ id: 'runA', createdAt: CREATED_AT }), + partialAsFull({ id: 'runB', createdAt: CREATED_AT }), + ] + + const componentWithMore = mountWithTheme( + , + ) + expect(componentWithMore.text()).toContain('View More') + + const componentWithoutMore = mountWithTheme( + , + ) + expect(componentWithoutMore.text()).not.toContain('View More') + }) + it('can show a loading message', () => { const component = mountWithTheme() expect(component.text()).toContain('Loading ...') diff --git a/operator_ui/src/components/Dashboards/Activity.tsx b/operator_ui/src/components/Dashboards/Activity.tsx index 85d3e3f8c07..6e52e85cfed 100644 --- a/operator_ui/src/components/Dashboards/Activity.tsx +++ b/operator_ui/src/components/Dashboards/Activity.tsx @@ -11,13 +11,14 @@ import { import Table from '@material-ui/core/Table' import TableBody from '@material-ui/core/TableBody' import TableCell from '@material-ui/core/TableCell' +import TableFooter from '@material-ui/core/TableFooter' import TableRow from '@material-ui/core/TableRow' import Typography from '@material-ui/core/Typography' import { JobRun, JobRuns } from 'operator_ui' import React from 'react' import BaseLink from '../BaseLink' import Button from '../Button' -import StatusIcon from '../JobRuns/StatusIcon' +import StatusIcon from 'components/StatusIcon' import Link from '../Link' import NoContentLogo from '../Logos/NoContent' @@ -103,7 +104,7 @@ interface Props extends WithStyles { count?: number } -const Activity = ({ classes, runs }: Props) => { +const Activity = ({ classes, runs, count, pageSize }: Props) => { let activity if (!runs) { @@ -140,7 +141,7 @@ const Activity = ({ classes, runs }: Props) => { - + { ))} + {count && count > pageSize && ( + + + + + + + + )} ) } diff --git a/operator_ui/src/components/JobRuns/StatusIcon.tsx b/operator_ui/src/components/StatusIcon.tsx similarity index 60% rename from operator_ui/src/components/JobRuns/StatusIcon.tsx rename to operator_ui/src/components/StatusIcon.tsx index 51b2184ca6d..e128572dc21 100644 --- a/operator_ui/src/components/JobRuns/StatusIcon.tsx +++ b/operator_ui/src/components/StatusIcon.tsx @@ -1,7 +1,8 @@ import React from 'react' -import SuccessIcon from '../Icons/Success' -import ErrorIcon from '../Icons/Error' -import PendingIcon from '../Icons/Pending' +import SuccessIcon from 'components/Icons/Success' +import ErrorIcon from 'components/Icons/Error' +import PendingIcon from 'components/Icons/Pending' +import ListIcon from 'components/Icons/ListIcon' interface Props { children: React.ReactNode @@ -14,6 +15,8 @@ const StatusIcon = ({ children, width, height }: Props) => { return } else if (children === 'errored') { return + } else if (children === 'not_run') { + return } return diff --git a/operator_ui/src/hooks/useErrorHandler.tsx b/operator_ui/src/hooks/useErrorHandler.tsx index ea5de4b36ba..a7d37bef84e 100644 --- a/operator_ui/src/hooks/useErrorHandler.tsx +++ b/operator_ui/src/hooks/useErrorHandler.tsx @@ -28,7 +28,12 @@ export const useErrorHandler = (): { }, [dispatch, error, history]) const ErrorComponent: React.FC = error - ? () =>
Error: {JSON.stringify(error)}
+ ? () => ( +
+ Error:{' '} + {error instanceof Error ? error.message : JSON.stringify(error)} +
+ ) : () => null return { error, ErrorComponent, setError } diff --git a/operator_ui/src/hooks/useLoadingPlaceholder.test.tsx b/operator_ui/src/hooks/useLoadingPlaceholder.test.tsx index 273ba1d2ee0..fbe32ec1519 100644 --- a/operator_ui/src/hooks/useLoadingPlaceholder.test.tsx +++ b/operator_ui/src/hooks/useLoadingPlaceholder.test.tsx @@ -7,7 +7,7 @@ describe('useLoadingPlaceholder', () => { const { LoadingPlaceholder } = useLoadingPlaceholder(true) const wrapper = shallow() - expect(wrapper.text()).toContain('Loading...') + expect(wrapper.text()).toContain('') }) it('defaults to false and renders an empty component', () => { diff --git a/operator_ui/src/hooks/useLoadingPlaceholder.tsx b/operator_ui/src/hooks/useLoadingPlaceholder.tsx index f9da4d1be4b..f8954aa47a3 100644 --- a/operator_ui/src/hooks/useLoadingPlaceholder.tsx +++ b/operator_ui/src/hooks/useLoadingPlaceholder.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { Typography } from '@material-ui/core' export const useLoadingPlaceholder = ( isLoading = false, @@ -7,7 +8,7 @@ export const useLoadingPlaceholder = ( LoadingPlaceholder: React.FC } => { const LoadingPlaceholder: React.FC = isLoading - ? () =>
Loading...
+ ? () => Loading... : () => null return { diff --git a/operator_ui/src/index.js b/operator_ui/src/index.js index 20493f59f5d..3e29c3bc7a5 100644 --- a/operator_ui/src/index.js +++ b/operator_ui/src/index.js @@ -1,5 +1,4 @@ -import { theme } from '@chainlink/styleguide' -import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles' +import { MuiThemeProvider } from '@material-ui/core/styles' import JavascriptTimeAgo from 'javascript-time-ago' import en from 'javascript-time-ago/locale/en' import moment from 'moment' @@ -8,6 +7,7 @@ import React from 'react' import ReactDOM from 'react-dom' import { AppContainer } from 'react-hot-loader' import App from './App' +import { theme } from './theme' promiseFinally.shim(Promise) @@ -22,7 +22,7 @@ if (typeof document !== 'undefined') { const render = (Comp) => { renderMethod( - + , diff --git a/operator_ui/src/pages/Dashboards/Index.test.js b/operator_ui/src/pages/Dashboards/Index.test.js index f33cd802562..38a062f973b 100644 --- a/operator_ui/src/pages/Dashboards/Index.test.js +++ b/operator_ui/src/pages/Dashboards/Index.test.js @@ -1,6 +1,6 @@ /* eslint-env jest */ import Index from 'pages/Dashboards/Index' -import accountBalanceFactory from 'factories/accountBalance' +import { accountBalances } from 'factories/accountBalance' import React from 'react' import mountWithTheme from 'test-helpers/mountWithTheme' import syncFetch from 'test-helpers/syncFetch' @@ -60,10 +60,12 @@ describe('pages/Dashboards/Index', () => { } global.fetch.getOnce(globPath('/v2/specs'), recentlyCreatedJobsResponse) - const accountBalanceResponse = accountBalanceFactory( - '10123456000000000000000', - '7467870000000000000000', - ) + const accountBalanceResponse = accountBalances([ + { + ethBalance: '10123456000000000000000', + linkBalance: '7467870000000000000000', + }, + ]) global.fetch.getOnce(globPath('/v2/user/balances'), accountBalanceResponse) const wrapper = mountIndex() diff --git a/operator_ui/src/pages/Header.tsx b/operator_ui/src/pages/Header.tsx index 913da7715b7..00348e8be6d 100644 --- a/operator_ui/src/pages/Header.tsx +++ b/operator_ui/src/pages/Header.tsx @@ -31,6 +31,7 @@ import fetchCountSelector from '../selectors/fetchCount' const SHARED_NAV_ITEMS = [ ['/jobs', 'Jobs'], + ['/runs', 'Runs'], ['/bridges', 'Bridges'], ['/transactions', 'Transactions'], ['/keys', 'Keys'], diff --git a/operator_ui/src/pages/JobRuns/Index.js b/operator_ui/src/pages/JobRuns/Index.js new file mode 100644 index 00000000000..1dc455374bc --- /dev/null +++ b/operator_ui/src/pages/JobRuns/Index.js @@ -0,0 +1,127 @@ +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { withStyles } from '@material-ui/core/styles' +import Card from '@material-ui/core/Card' +import TablePagination from '@material-ui/core/TablePagination' +import matchRouteAndMapDispatchToProps from 'utils/matchRouteAndMapDispatchToProps' +import { fetchJobRuns } from 'actionCreators' +import jobRunsSelector from 'selectors/jobRuns' +import jobRunsCountSelector from 'selectors/jobRunsCount' +import List from '../Jobs/JobRunsList' +import TableButtons, { FIRST_PAGE } from 'components/TableButtons' +import Title from 'components/Title' +import Content from 'components/Content' + +const styles = (theme) => ({ + breadcrumb: { + marginTop: theme.spacing.unit * 5, + marginBottom: theme.spacing.unit * 5, + }, +}) + +const renderLatestRuns = (props, state, handleChangePage) => { + const { jobSpecId, latestJobRuns, jobRunsCount = 0, pageSize } = props + const pagePath = props.pagePath.replace(':jobSpecId', jobSpecId) + + const TableButtonsWithProps = () => ( + + ) + return ( + + + {} /* handler required by component, so make it a no-op */ + } + onChangeRowsPerPage={ + () => {} /* handler required by component, so make it a no-op */ + } + ActionsComponent={TableButtonsWithProps} + /> + + ) +} + +const Fetching = () =>
Fetching...
+ +const renderDetails = (props, state, handleChangePage) => { + if (props.latestJobRuns) { + return renderLatestRuns(props, state, handleChangePage) + } else { + return + } +} + +export const Index = (props) => { + const { jobSpecId, fetchJobRuns, pageSize, match } = props + const [page, setPage] = useState(FIRST_PAGE) + + useEffect(() => { + document.title = 'Job Runs' + const queryPage = parseInt(match?.params.jobRunsPage, 10) || FIRST_PAGE + setPage(queryPage) + fetchJobRuns({ jobSpecId, page: queryPage, size: pageSize }) + }, [fetchJobRuns, jobSpecId, pageSize, match]) + const handleChangePage = (_, pageNum) => { + fetchJobRuns({ jobSpecId, page: pageNum, size: pageSize }) + setPage(pageNum) + } + + return ( + + Runs + + {renderDetails(props, { page }, handleChangePage)} + + ) +} + +Index.propTypes = { + classes: PropTypes.object.isRequired, + latestJobRuns: PropTypes.array, + jobRunsCount: PropTypes.number, + pageSize: PropTypes.number.isRequired, + pagePath: PropTypes.string.isRequired, +} + +Index.defaultProps = { + latestJobRuns: [], + pageSize: 25, +} + +const mapStateToProps = (state, ownProps) => { + const jobSpecId = ownProps.match.params.jobSpecId + const jobRunsCount = jobRunsCountSelector(state) + const latestJobRuns = jobRunsSelector(state, jobSpecId) + + return { + jobSpecId, + latestJobRuns, + jobRunsCount, + } +} + +export const ConnectedIndex = connect( + mapStateToProps, + matchRouteAndMapDispatchToProps({ fetchJobRuns }), +)(Index) + +export default withStyles(styles)(ConnectedIndex) diff --git a/operator_ui/src/pages/JobRuns/Index.test.js b/operator_ui/src/pages/JobRuns/Index.test.js new file mode 100644 index 00000000000..9ae49cd4858 --- /dev/null +++ b/operator_ui/src/pages/JobRuns/Index.test.js @@ -0,0 +1,113 @@ +import createStore from 'createStore' +import { ConnectedIndex as Index } from 'pages/JobRuns/Index' +import jsonApiJobSpecRunFactory from 'factories/jsonApiJobSpecRuns' +import React from 'react' +import { Provider } from 'react-redux' +import { MemoryRouter } from 'react-router-dom' +import clickFirstPage from 'test-helpers/clickFirstPage' +import clickLastPage from 'test-helpers/clickLastPage' +import clickNextPage from 'test-helpers/clickNextPage' +import clickPreviousPage from 'test-helpers/clickPreviousPage' +import mountWithTheme from 'test-helpers/mountWithTheme' +import syncFetch from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' + +const classes = {} +const mountIndex = (props) => + mountWithTheme( + + + + + , + ) + +describe('pages/JobRuns/Index', () => { + const jobSpecId = 'c60b9927eeae43168ddbe92584937b1b' + + it('renders the runs for the job spec', async () => { + expect.assertions(2) + + const runsResponse = jsonApiJobSpecRunFactory([{ jobId: jobSpecId }]) + global.fetch.getOnce(globPath('/v2/runs'), runsResponse) + + const props = { match: { params: { jobSpecId } } } + const wrapper = mountIndex(props) + + await syncFetch(wrapper) + expect(wrapper.text()).toContain(runsResponse.data[0].id) + expect(wrapper.text()).toContain('Complete') + }) + + it('can page through the list of runs', async () => { + expect.assertions(12) + + const pageOneResponse = jsonApiJobSpecRunFactory( + [{ id: 'ID-ON-FIRST-PAGE', jobId: jobSpecId }], + 3, + ) + global.fetch.getOnce(globPath('/v2/runs'), pageOneResponse) + + const props = { match: { params: { jobSpecId } }, pageSize: 1 } + const wrapper = mountIndex(props) + + await syncFetch(wrapper) + expect(wrapper.text()).toContain('ID-ON-FIRST-PAGE') + expect(wrapper.text()).not.toContain('ID-ON-SECOND-PAGE') + + const pageTwoResponse = jsonApiJobSpecRunFactory( + [{ id: 'ID-ON-SECOND-PAGE', jobId: jobSpecId }], + 3, + ) + global.fetch.getOnce(globPath('/v2/runs'), pageTwoResponse) + clickNextPage(wrapper) + + await syncFetch(wrapper) + expect(wrapper.text()).not.toContain('ID-ON-FIRST-PAGE') + expect(wrapper.text()).toContain('ID-ON-SECOND-PAGE') + + global.fetch.getOnce(globPath('/v2/runs'), pageOneResponse) + clickPreviousPage(wrapper) + + await syncFetch(wrapper) + expect(wrapper.text()).toContain('ID-ON-FIRST-PAGE') + expect(wrapper.text()).not.toContain('ID-ON-SECOND-PAGE') + + const pageThreeResponse = jsonApiJobSpecRunFactory( + [{ id: 'ID-ON-THIRD-PAGE', jobId: jobSpecId }], + 3, + ) + global.fetch.getOnce(globPath('/v2/runs'), pageThreeResponse) + clickLastPage(wrapper) + + await syncFetch(wrapper) + expect(wrapper.text()).toContain('ID-ON-THIRD-PAGE') + expect(wrapper.text()).not.toContain('ID-ON-FIRST-PAGE') + expect(wrapper.text()).not.toContain('ID-ON-SECOND-PAGE') + + global.fetch.getOnce(globPath('/v2/runs'), pageOneResponse) + clickFirstPage(wrapper) + + await syncFetch(wrapper) + expect(wrapper.text()).not.toContain('ID-ON-SECOND-PAGE') + expect(wrapper.text()).not.toContain('ID-ON-THIRD-PAGE') + expect(wrapper.text()).toContain('ID-ON-FIRST-PAGE') + }) + + it('displays an empty message', async () => { + expect.assertions(1) + + const runsResponse = jsonApiJobSpecRunFactory([]) + await global.fetch.getOnce(globPath('/v2/runs'), runsResponse) + + const props = { match: { params: { jobSpecId } } } + const wrapper = mountIndex(props) + + await syncFetch(wrapper) + expect(wrapper.text()).toContain('No jobs have been run yet') + }) +}) diff --git a/operator_ui/src/pages/JobRuns/Show/ErrorLog.tsx b/operator_ui/src/pages/JobRuns/Show/ErrorLog.tsx deleted file mode 100644 index 7ecb807605d..00000000000 --- a/operator_ui/src/pages/JobRuns/Show/ErrorLog.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import Grid from '@material-ui/core/Grid' -import { Theme, withStyles, WithStyles } from '@material-ui/core/styles' -import Typography from '@material-ui/core/Typography' -import { fetchJobRun } from 'actionCreators' -import Content from 'components/Content' -import StatusCard from 'components/JobRuns/StatusCard' -import { AppState } from 'src/reducers' -import { JobRun, TaskRun } from 'operator_ui' -import React, { useEffect } from 'react' -import { connect } from 'react-redux' -import jobRunSelector from 'selectors/jobRun' -import matchRouteAndMapDispatchToProps from 'utils/matchRouteAndMapDispatchToProps' -import RegionalNav from './RegionalNav' - -const filterErrorTaskRuns = (jobRun: JobRun) => { - return jobRun.taskRuns.filter((tr: TaskRun) => { - return tr.status === 'errored' - }) -} - -const detailsStyles = ({ spacing }: Theme) => ({ - list: { - marginTop: spacing.unit * 4, - }, -}) - -interface DetailsProps extends WithStyles { - jobRun?: JobRun -} - -const Details = withStyles(detailsStyles)( - ({ jobRun, classes }: DetailsProps) => { - if (!jobRun) { - return
Fetching job run...
- } - - const errorTaskRuns = filterErrorTaskRuns(jobRun) - - return ( - - - -
    - {errorTaskRuns.map((tr: TaskRun) => ( -
  • - {tr.result.error} -
  • - ))} -
-
-
-
- ) - }, -) - -interface Props { - jobSpecId: string - jobRunId: string - jobRun?: JobRun - fetchJobRun: (id: string) => Promise -} - -const ShowErrorLog: React.FC = ({ - jobRunId, - jobSpecId, - jobRun, - fetchJobRun, -}) => { - useEffect(() => { - fetchJobRun(jobRunId) - }, [fetchJobRun, jobRunId]) - - return ( -
- - - -
- -
- ) -} - -interface Match { - params: { - jobSpecId: string - jobRunId: string - } -} - -const mapStateToProps = (state: AppState, ownProps: { match: Match }) => { - const { jobSpecId, jobRunId } = ownProps.match.params - const jobRun = jobRunSelector(state, jobRunId) - - return { - jobSpecId, - jobRunId, - jobRun, - } -} - -export const ConnectedShowErrorLog = connect( - mapStateToProps, - matchRouteAndMapDispatchToProps({ fetchJobRun }), -)(ShowErrorLog) - -export default ConnectedShowErrorLog diff --git a/operator_ui/src/pages/JobRuns/Show/Json.js b/operator_ui/src/pages/JobRuns/Show/Json.js deleted file mode 100644 index ee893f8ce4d..00000000000 --- a/operator_ui/src/pages/JobRuns/Show/Json.js +++ /dev/null @@ -1,65 +0,0 @@ -import { PaddedCard } from '@chainlink/styleguide' -import Grid from '@material-ui/core/Grid' -import { fetchJobRun } from 'actionCreators' -import Content from 'components/Content' -import StatusCard from 'components/JobRuns/StatusCard' -import PrettyJson from 'components/PrettyJson' -import React, { useEffect } from 'react' -import { connect } from 'react-redux' -import jobRunSelector from 'selectors/jobRun' -import matchRouteAndMapDispatchToProps from 'utils/matchRouteAndMapDispatchToProps' -import RegionalNav from './RegionalNav' - -const renderDetails = ({ fetching, jobRun }) => { - if (fetching || !jobRun) { - return
Fetching job run...
- } - - return ( - - - - - - - - - - - ) -} - -const Show = (props) => { - const { fetchJobRun, jobRunId } = props - - useEffect(() => { - fetchJobRun(jobRunId) - }, [fetchJobRun, jobRunId]) - - return ( -
- - {renderDetails(props)} -
- ) -} - -const mapStateToProps = (state, ownProps) => { - const { jobSpecId, jobRunId } = ownProps.match.params - const jobRun = jobRunSelector(state, jobRunId) - const fetching = state.jobRuns.fetching - - return { - jobSpecId, - jobRunId, - jobRun, - fetching, - } -} - -export const ConnectedShow = connect( - mapStateToProps, - matchRouteAndMapDispatchToProps({ fetchJobRun }), -)(Show) - -export default ConnectedShow diff --git a/operator_ui/src/pages/JobRuns/Show/Overview.js b/operator_ui/src/pages/JobRuns/Show/Overview.js deleted file mode 100644 index f97783fec67..00000000000 --- a/operator_ui/src/pages/JobRuns/Show/Overview.js +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useEffect } from 'react' -import { connect } from 'react-redux' -import { withStyles } from '@material-ui/core/styles' -import Grid from '@material-ui/core/Grid' -import Card from '@material-ui/core/Card' -import matchRouteAndMapDispatchToProps from 'utils/matchRouteAndMapDispatchToProps' -import { fetchJobRun } from 'actionCreators' -import jobRunSelector from 'selectors/jobRun' -import Content from 'components/Content' -import StatusCard from 'components/JobRuns/StatusCard' -import TaskExpansionPanel from 'components/JobRuns/TaskExpansionPanel' -import RegionalNav from './RegionalNav' - -const styles = (theme) => ({ - breadcrumb: { - marginTop: theme.spacing.unit * 5, - marginBottom: theme.spacing.unit * 5, - }, -}) - -const renderDetails = ({ fetching, jobRun }) => { - if (fetching || !jobRun) { - return
Fetching job run...
- } - - return ( - - - - - - - - - - - ) -} - -export const Show = (props) => { - const { fetchJobRun, jobRunId } = props - useEffect(() => { - document.title = 'Show Job Run' - fetchJobRun(jobRunId) - }, [fetchJobRun, jobRunId]) - - return ( -
- - {renderDetails(props)} -
- ) -} - -const mapStateToProps = (state, ownProps) => { - const { jobSpecId, jobRunId } = ownProps.match.params - const jobRun = jobRunSelector(state, jobRunId) - const fetching = state.jobRuns.fetching - - return { - jobSpecId, - jobRunId, - jobRun, - fetching, - } -} - -export const ConnectedShow = connect( - mapStateToProps, - matchRouteAndMapDispatchToProps({ fetchJobRun }), -)(Show) - -export default withStyles(styles)(ConnectedShow) diff --git a/operator_ui/src/pages/JobRuns/Show/Overview.test.js b/operator_ui/src/pages/JobRuns/Show/Overview.test.js deleted file mode 100644 index e65f703a66f..00000000000 --- a/operator_ui/src/pages/JobRuns/Show/Overview.test.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react' -import createStore from 'createStore' -import syncFetch from 'test-helpers/syncFetch' -import jsonApiJobSpecRunFactory from 'factories/jsonApiJobSpecRun' -import { Provider } from 'react-redux' -import { MemoryRouter } from 'react-router-dom' -import { ConnectedShow as Show } from 'pages/JobRuns/Show/Overview' -import isoDate, { MINUTE_MS } from 'test-helpers/isoDate' -import mountWithTheme from 'test-helpers/mountWithTheme' -import globPath from 'test-helpers/globPath' - -const classes = {} -const mountShow = (props) => - mountWithTheme( - - - - - , - ) - -describe('pages/JobRuns/Show/Overview', () => { - const jobSpecId = '942e8b218d414e10a053-000455fdd470' - const jobRunId = 'ad24b72c12f441b99b9877bcf6cb506e' - - it('renders the details of the job spec and its latest runs', async () => { - expect.assertions(3) - - const minuteAgo = isoDate(Date.now() - MINUTE_MS) - const jobRunResponse = jsonApiJobSpecRunFactory({ - id: jobRunId, - createdAt: minuteAgo, - jobId: jobSpecId, - initiator: { - type: 'web', - params: {}, - }, - taskRuns: [ - { - id: 'taskRunA', - status: 'completed', - task: { type: 'noop', params: {} }, - }, - ], - result: { - data: { - value: - '0x05070f7f6a40e4ce43be01fa607577432c68730c2cb89a0f50b665e980d926b5', - }, - }, - }) - global.fetch.getOnce(globPath(`/v2/runs/${jobRunId}`), jobRunResponse) - - const props = { - match: { params: { jobSpecId, jobRunId } }, - } - const wrapper = mountShow(props) - - await syncFetch(wrapper) - expect(wrapper.text()).toContain('Web') - expect(wrapper.text()).toContain('Noop') - expect(wrapper.text()).toContain('Completed') - }) -}) diff --git a/operator_ui/src/pages/JobRuns/Show/RegionalNav.test.js b/operator_ui/src/pages/JobRuns/Show/RegionalNav.test.js deleted file mode 100644 index 423bfbd7dfe..00000000000 --- a/operator_ui/src/pages/JobRuns/Show/RegionalNav.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import RegionalNav from 'pages/JobRuns/Show/RegionalNav' -import mountWithTheme from 'test-helpers/mountWithTheme' - -const CREATED_AT = '2019-06-11T14:37:42.077995-07:00' - -describe('pages/JobRuns/Show/RegionalNav', () => { - it('displays an overview & json tab by default', () => { - const component = mountWithTheme() - - expect(component.text()).toContain('Overview') - expect(component.text()).toContain('JSON') - expect(component.text()).not.toContain('Error Log') - }) - - it('also displays an error log tab when the status is "errored"', () => { - const jobRun = { status: 'errored', createdAt: CREATED_AT } - const component = mountWithTheme() - - expect(component.text()).toContain('Error Log') - }) -}) diff --git a/operator_ui/src/pages/Jobs/Errors.tsx b/operator_ui/src/pages/Jobs/Errors.tsx index a132ab90c5f..2fc8068af27 100644 --- a/operator_ui/src/pages/Jobs/Errors.tsx +++ b/operator_ui/src/pages/Jobs/Errors.tsx @@ -11,7 +11,7 @@ import { import { v2 } from 'api' import Button from 'components/Button' import Content from 'components/Content' -import { localizedTimestamp, TimeAgo } from '@chainlink/styleguide' +import { TimeAgo } from '@chainlink/styleguide' import { JobData } from './sharedTypes' export const JobsErrors: React.FC<{ @@ -84,20 +84,12 @@ export const JobsErrors: React.FC<{ - - {localizedTimestamp( - jobSpecError.createdAt.toString(), - )} - + {jobSpecError.createdAt} - - {localizedTimestamp( - jobSpecError.updatedAt.toString(), - )} - + {jobSpecError.updatedAt} diff --git a/operator_ui/src/components/JobRuns/List.tsx b/operator_ui/src/pages/Jobs/JobRunsList.tsx similarity index 52% rename from operator_ui/src/components/JobRuns/List.tsx rename to operator_ui/src/pages/Jobs/JobRunsList.tsx index 78f6e35bdc2..816b188d789 100644 --- a/operator_ui/src/components/JobRuns/List.tsx +++ b/operator_ui/src/pages/Jobs/JobRunsList.tsx @@ -1,3 +1,5 @@ +import React from 'react' +import { useHistory } from 'react-router-dom' import { TimeAgo } from '@chainlink/styleguide' import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles' import Table from '@material-ui/core/Table' @@ -7,9 +9,8 @@ import TableRow from '@material-ui/core/TableRow' import Typography from '@material-ui/core/Typography' import classNames from 'classnames' import { RunStatus } from 'core/store/models' -import React from 'react' -import titleize from '../../utils/titleize' -import Link from '../Link' +import titleize from 'utils/titleize' +import { PipelineTaskRunStatus } from './sharedTypes' const styles = (theme: any) => createStyles({ @@ -72,93 +73,77 @@ const classFromStatus = (classes: any, status: string) => { return classes[status.toLowerCase()] } -const renderRuns = ( - runs: { - createdAt: string - id: string - status: RunStatus - jobId: string - }[], - classes: any, - hideLinks: undefined | boolean, -) => { - if (runs && runs.length === 0) { - return ( - - - - No jobs have been run yet - - - - ) - } else if (runs) { - return runs.map((r) => ( - - -
- {hideLinks ? ( - - {r.id} - - ) : ( - - - {r.id} - - - )} -
-
- - - Created {r.createdAt} - - - - - {titleize(r.status)} - - -
- )) - } - - return ( - - ... - - ) -} - interface Props extends WithStyles { runs: { createdAt: string id: string - status: RunStatus + status: RunStatus | PipelineTaskRunStatus jobId: string }[] - hideLinks?: boolean } -const List = ({ runs, classes, hideLinks }: Props) => { +const List = ({ runs, classes }: Props) => { + const history = useHistory() + return ( - {renderRuns(runs, classes, hideLinks)} + + {runs && runs.length === 0 && ( + + + + No jobs have been run yet + + + + )} + {runs && + runs.length > 0 && + runs.map((r) => ( + history.push(`/jobs/${r.jobId}/runs/${r.id}`)} + > + +
+ + {r.id} + +
+
+ + + Created {r.createdAt} + + + + + {titleize(r.status)} + + +
+ ))} + {!runs && ( + + ... + + )} +
) } diff --git a/operator_ui/src/pages/Jobs/RecentRuns.test.tsx b/operator_ui/src/pages/Jobs/RecentRuns.test.tsx index 00857516663..de6aa12945e 100644 --- a/operator_ui/src/pages/Jobs/RecentRuns.test.tsx +++ b/operator_ui/src/pages/Jobs/RecentRuns.test.tsx @@ -1,6 +1,8 @@ import React from 'react' import { Route } from 'react-router-dom' import { JobsShow } from 'pages/Jobs/Show' +import { jsonApiOcrJobSpec } from 'factories/jsonApiOcrJobSpec' +import { jsonApiOcrJobRuns } from 'factories/jsonApiOcrJobRuns' import jsonApiJobSpecFactory from 'factories/jsonApiJobSpec' import jsonApiJobSpecRunsFactory from 'factories/jsonApiJobSpecRuns' import { mountWithProviders } from 'test-helpers/mountWithTheme' @@ -8,6 +10,7 @@ import { syncFetch } from 'test-helpers/syncFetch' import globPath from 'test-helpers/globPath' const JOB_SPEC_ID = 'c60b9927eeae43168ddbe92584937b1b' +const OCR_JOB_SPEC_ID = '1' describe('pages/Jobs/RecentRuns', () => { it('displays a view more link if there are more runs than the display count', async () => { @@ -37,4 +40,66 @@ describe('pages/Jobs/RecentRuns', () => { await syncFetch(wrapper) expect(wrapper.text()).toContain('View more') }) + + it('renders OCR job tasks visualisation', async () => { + const runs = [ + { id: 'runA', jobId: OCR_JOB_SPEC_ID }, + { id: 'runB', jobId: OCR_JOB_SPEC_ID }, + { id: 'runC', jobId: OCR_JOB_SPEC_ID }, + { id: 'runD', jobId: OCR_JOB_SPEC_ID }, + { id: 'runE', jobId: OCR_JOB_SPEC_ID }, + ] + + const taskNames = ['testFetch', 'testParse', 'testMultiply'] + + global.fetch.getOnce( + globPath(`/v2/jobs/${OCR_JOB_SPEC_ID}/runs`), + jsonApiOcrJobRuns(runs, 10), + ) + global.fetch.getOnce( + globPath(`/v2/jobs/${OCR_JOB_SPEC_ID}`), + jsonApiOcrJobSpec({ + id: OCR_JOB_SPEC_ID, + dotDagSource: ` ${taskNames[0]} [type=http method=POST url="http://localhost:8001" requestData="{\\"hi\\": \\"hello\\"}"];\n ${taskNames[1]} [type=jsonparse path="data,result"];\n ${taskNames[2]} [type=multiply times=100];\n ${taskNames[0]} -\u003e ${taskNames[1]} -\u003e ${taskNames[2]};\n`, + }), + ) + + const wrapper = mountWithProviders( + , + { + initialEntries: [`/jobs/${OCR_JOB_SPEC_ID}`], + }, + ) + + await syncFetch(wrapper) + expect(wrapper.text()).toContain('View more') + expect(wrapper.text()).toContain(taskNames[0]) + expect(wrapper.text()).toContain(taskNames[1]) + expect(wrapper.text()).toContain(taskNames[2]) + }) + + it('works with no tasks (bootstrap node)', async () => { + global.fetch.getOnce( + globPath(`/v2/jobs/${OCR_JOB_SPEC_ID}/runs`), + jsonApiOcrJobRuns(), + ) + global.fetch.getOnce( + globPath(`/v2/jobs/${OCR_JOB_SPEC_ID}`), + jsonApiOcrJobSpec({ + id: OCR_JOB_SPEC_ID, + dotDagSource: '', + }), + ) + + const wrapper = mountWithProviders( + , + { + initialEntries: [`/jobs/${OCR_JOB_SPEC_ID}`], + }, + ) + + await syncFetch(wrapper) + expect(wrapper.text()).toContain('Recent job runs') + expect(wrapper.text()).not.toContain('Task list') + }) }) diff --git a/operator_ui/src/pages/Jobs/RecentRuns.tsx b/operator_ui/src/pages/Jobs/RecentRuns.tsx index f2377901b14..481c9ebca7c 100644 --- a/operator_ui/src/pages/Jobs/RecentRuns.tsx +++ b/operator_ui/src/pages/Jobs/RecentRuns.tsx @@ -11,13 +11,15 @@ import { import Button from 'components/Button' import BaseLink from 'components/BaseLink' import Content from 'components/Content' -import JobRunsList from 'components/JobRuns/List' +import JobRunsList from './JobRunsList' +import TaskListDag from './TaskListDag' import TaskList from 'components/Jobs/TaskList' import React from 'react' import { GWEI_PER_TOKEN } from 'utils/constants' import formatMinPayment from 'utils/formatWeiAsset' import { formatInitiators } from 'utils/jobSpecInitiators' import { DirectRequestJob, JobData } from './sharedTypes' +import { parseDot } from './parseDot' const totalLinkEarned = (job: DirectRequestJob) => { const zero = '0.000000' @@ -56,7 +58,6 @@ interface Props extends WithStyles { error: unknown getJobSpecRuns: (props?: { page?: number; size?: number }) => Promise job?: JobData['job'] - jobSpec?: JobData['jobSpec'] recentRuns?: JobData['recentRuns'] recentRunsCount: JobData['recentRunsCount'] showJobRunsCount?: number @@ -70,7 +71,6 @@ export const RecentRuns = withStyles(chartCardStyles)( error, getJobSpecRuns, job, - jobSpec, recentRuns, recentRunsCount, showJobRunsCount = 5, @@ -90,17 +90,14 @@ export const RecentRuns = withStyles(chartCardStyles)( {!error && job && ( - + Recent job runs {recentRuns && ( <> - + {recentRunsCount > showJobRunsCount && (
- {children} ) } diff --git a/operator_ui/src/pages/Jobs/Runs/augmentOcrTasksList.test.ts b/operator_ui/src/pages/Jobs/Runs/augmentOcrTasksList.test.ts new file mode 100644 index 00000000000..6df4790265b --- /dev/null +++ b/operator_ui/src/pages/Jobs/Runs/augmentOcrTasksList.test.ts @@ -0,0 +1,185 @@ +import { augmentOcrTasksList } from './augmentOcrTasksList' +import { PipelineJobRun } from '../sharedTypes' + +describe('augmentOcrTasksList', () => { + it('adds error, output and status attributes', () => { + const jobRun: PipelineJobRun = { + pipelineSpec: { + DotDagSource: + ' fetch [type=http method=GET url="https://bitstamp.net/api/ticker/"];\n parseLast [type=jsonparse path="last"];\n multiplyLast [type=multiply times=100];\n\n fetch2 [type=http method=GET url="https://bitstamp.net/api/ticker/"];\n parseOpen [type=jsonparse path="open"];\n multiplyOpen [type=multiply times=100];\n\n\n fetch -> parseLast -> multiplyLast -> answer;\n fetch2 -> parseOpen -> multiplyOpen -> answer;\n\nanswer [type=median index=0];\nanswer [type=median index=1];\n\n', + }, + errors: [ + 'majority of fetchers in median failed: error making http request: reason; error making http request: reason: bad input for task', + ], + outputs: [null], + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.211725Z', + taskRuns: [ + { + type: 'median', + output: null, + error: + 'majority of fetchers in median failed: error making http request: reason; error making http request: reason: bad input for task', + taskSpec: { + dotId: 'answer', + }, + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.19516Z', + status: 'errored', + }, + { + type: 'multiply', + output: null, + error: 'error making http request: reason', + taskSpec: { + dotId: 'multiplyLast', + }, + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.171678Z', + status: 'not_run', + }, + { + type: 'multiply', + output: null, + error: 'error making http request: reason', + taskSpec: { + dotId: 'multiplyOpen', + }, + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.176633Z', + status: 'not_run', + }, + { + type: 'jsonparse', + output: null, + error: 'error making http request: reason', + taskSpec: { + dotId: 'parseLast', + }, + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.154488Z', + status: 'not_run', + }, + { + type: 'jsonparse', + output: null, + error: 'error making http request: reason', + taskSpec: { + dotId: 'parseOpen', + }, + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.15558Z', + status: 'not_run', + }, + { + type: 'http', + output: null, + error: 'error making http request: reason', + taskSpec: { + dotId: 'fetch', + }, + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.12949Z', + status: 'errored', + }, + { + type: 'http', + output: null, + error: 'error making http request: reason', + taskSpec: { + dotId: 'fetch2', + }, + createdAt: '2020-11-24T11:38:36.100272Z', + finishedAt: '2020-11-24T11:39:26.127941Z', + status: 'errored', + }, + ], + id: '321', + jobId: '2', + status: 'errored', + type: 'Off-chain reporting job run', + } + expect(augmentOcrTasksList({ jobRun })).toEqual([ + { + attributes: { + error: 'error making http request: reason', + method: 'GET', + output: null, + status: 'errored', + type: 'http', + url: 'https://bitstamp.net/api/ticker/', + }, + id: 'fetch', + parentIds: [], + }, + { + attributes: { + error: 'error making http request: reason', + output: null, + path: 'last', + status: 'not_run', + type: 'jsonparse', + }, + id: 'parseLast', + parentIds: ['fetch'], + }, + { + attributes: { + error: 'error making http request: reason', + output: null, + status: 'not_run', + times: '100', + type: 'multiply', + }, + id: 'multiplyLast', + parentIds: ['parseLast'], + }, + { + attributes: { + error: 'error making http request: reason', + method: 'GET', + output: null, + status: 'errored', + type: 'http', + url: 'https://bitstamp.net/api/ticker/', + }, + id: 'fetch2', + parentIds: [], + }, + { + attributes: { + error: 'error making http request: reason', + output: null, + path: 'open', + status: 'not_run', + type: 'jsonparse', + }, + id: 'parseOpen', + parentIds: ['fetch2'], + }, + { + attributes: { + error: 'error making http request: reason', + output: null, + status: 'not_run', + times: '100', + type: 'multiply', + }, + id: 'multiplyOpen', + parentIds: ['parseOpen'], + }, + { + attributes: { + error: + 'majority of fetchers in median failed: error making http request: reason; error making http request: reason: bad input for task', + index: '1', + output: null, + status: 'errored', + type: 'median', + }, + id: 'answer', + parentIds: ['multiplyLast', 'multiplyOpen'], + }, + ]) + }) +}) diff --git a/operator_ui/src/pages/Jobs/Runs/augmentOcrTasksList.ts b/operator_ui/src/pages/Jobs/Runs/augmentOcrTasksList.ts new file mode 100644 index 00000000000..d4dbd08b233 --- /dev/null +++ b/operator_ui/src/pages/Jobs/Runs/augmentOcrTasksList.ts @@ -0,0 +1,43 @@ +import { PipelineTaskError, PipelineTaskOutput } from 'core/store/models' +import { parseDot, Stratify } from '../parseDot' +import { PipelineJobRun } from '../sharedTypes' + +type AugmentedStratify = Stratify & { + attributes: { + error: PipelineTaskError + output: PipelineTaskOutput + status: PipelineJobRun['taskRuns'][0]['status'] + [key: string]: any + } +} + +function assignAttributes(stratify: Stratify): AugmentedStratify { + if (stratify.attributes === undefined) { + stratify.attributes = {} + } + + return stratify as AugmentedStratify +} + +export function augmentOcrTasksList({ jobRun }: { jobRun: PipelineJobRun }) { + const graph = parseDot(`digraph {${jobRun.pipelineSpec.DotDagSource}}`) + + return graph.map((stratifyNode) => { + const stratifyNodeCopy = assignAttributes( + JSON.parse(JSON.stringify(stratifyNode)), + ) + + const taskRun = jobRun.taskRuns.find( + ({ taskSpec }) => taskSpec.dotId === stratifyNodeCopy.id, + ) + + stratifyNodeCopy.attributes = { + ...stratifyNodeCopy.attributes, + error: taskRun?.error, + output: taskRun?.output, + status: taskRun?.status || 'not_run', + } + + return stratifyNodeCopy + }) +} diff --git a/operator_ui/src/pages/Jobs/Show.test.tsx b/operator_ui/src/pages/Jobs/Show.test.tsx index 386bc500745..37c5623cfde 100644 --- a/operator_ui/src/pages/Jobs/Show.test.tsx +++ b/operator_ui/src/pages/Jobs/Show.test.tsx @@ -52,7 +52,7 @@ describe('pages/Jobs/Show', () => { }) describe('RegionalNav', () => { - it('clicking on "Run" button triggers a new job and updates the recent jobs list', async () => { + it('clicking on "Run" button triggers a new job and updates the recent job_specs list', async () => { const runs = [{ id: 'runA', jobId: JOB_SPEC_ID }] global.fetch.getOnce( diff --git a/operator_ui/src/pages/Jobs/Show.tsx b/operator_ui/src/pages/Jobs/Show.tsx index 44684b4c5d7..c1b57ab7393 100644 --- a/operator_ui/src/pages/Jobs/Show.tsx +++ b/operator_ui/src/pages/Jobs/Show.tsx @@ -7,14 +7,17 @@ import { generateJSONDefinition, generateTOMLDefinition, } from './generateJobSpecDefinition' -import { PaginatedApiResponse } from '@chainlink/json-api-client' -import { OcrJobRun, RunStatus } from 'core/store/models' import { JobData } from './sharedTypes' import { JobsDefinition } from './Definition' import { JobsErrors } from './Errors' import { RecentRuns } from './RecentRuns' import { RegionalNav } from './RegionalNav' import { Runs as JobRuns } from './Runs' +import { isOcrJob } from './utils' +import { + transformDirectRequestJobRun, + transformPipelineJobRun, +} from './transformJobRuns' type Props = RouteComponentProps<{ jobSpecId: string @@ -23,18 +26,6 @@ type Props = RouteComponentProps<{ const DEFAULT_PAGE = 1 const RECENT_RUNS_COUNT = 5 -function getOcrJobStatus({ - attributes: { finishedAt, errors }, -}: NonNullable>['data'][0]) { - if (finishedAt === null) { - return RunStatus.IN_PROGRESS - } - if (errors[0] !== null) { - return RunStatus.ERRORED - } - return RunStatus.COMPLETED -} - export const JobsShow: React.FC = ({ match }) => { const [state, setState] = React.useState({ recentRuns: [], @@ -45,9 +36,6 @@ export const JobsShow: React.FC = ({ match }) => { const { LoadingPlaceholder } = useLoadingPlaceholder(!error && !jobSpec) const { jobSpecId } = match.params - // `isNaN` actually accepts strings and we don't want to `parseInt` or `parseFloat` - // as it doesn't have the behaviour we want. - const isOcrJob = !isNaN((jobSpecId as unknown) as number) const getJobSpecRuns = React.useCallback( ({ page = DEFAULT_PAGE, size = RECENT_RUNS_COUNT } = {}) => { @@ -56,18 +44,15 @@ export const JobsShow: React.FC = ({ match }) => { page, size, } - if (isOcrJob) { + if (isOcrJob(jobSpecId)) { return v2.ocrRuns .getJobSpecRuns(requestParams) .then((jobSpecRunsResponse) => { setState((s) => ({ ...s, - recentRuns: jobSpecRunsResponse.data.map((jobRun) => ({ - createdAt: jobRun.attributes.createdAt, - id: jobRun.id, - status: getOcrJobStatus(jobRun), - jobId: jobSpecId, - })), + recentRuns: jobSpecRunsResponse.data.map( + transformPipelineJobRun(jobSpecId), + ), recentRunsCount: jobSpecRunsResponse.meta.count, })) }) @@ -78,23 +63,20 @@ export const JobsShow: React.FC = ({ match }) => { .then((jobSpecRunsResponse) => { setState((s) => ({ ...s, - recentRuns: jobSpecRunsResponse.data.map((jobRun) => ({ - createdAt: jobRun.attributes.createdAt, - id: jobRun.id, - status: jobRun.attributes.status, - jobId: jobSpecId, - })), + recentRuns: jobSpecRunsResponse.data.map( + transformDirectRequestJobRun(jobSpecId), + ), recentRunsCount: jobSpecRunsResponse.meta.count, })) }) .catch(setError) } }, - [isOcrJob, jobSpecId, setError], + [jobSpecId, setError], ) const getJobSpec = React.useCallback(async () => { - if (isOcrJob) { + if (isOcrJob(jobSpecId)) { return v2.ocrSpecs .getJobSpec(jobSpecId) .then((response) => { @@ -103,9 +85,10 @@ export const JobsShow: React.FC = ({ match }) => { ...s, jobSpec, job: { + ...jobSpec.attributes, ...jobSpec.attributes.offChainReportingOracleSpec, + ...jobSpec.attributes.pipelineSpec, id: jobSpec.id, - errors: jobSpec.attributes.errors, definition: generateTOMLDefinition(jobSpec.attributes), type: 'Off-chain reporting', }, @@ -130,7 +113,7 @@ export const JobsShow: React.FC = ({ match }) => { }) .catch(setError) } - }, [isOcrJob, jobSpecId, setError]) + }, [jobSpecId, setError]) React.useEffect(() => { getJobSpec() @@ -146,6 +129,7 @@ export const JobsShow: React.FC = ({ match }) => { /> ( = ({ match }) => { )} /> ( = ({ match }) => { )} /> ( ()(stratify) + + d3dag + .sugiyama() + .size([width - 150, height]) + .layering(d3dag.layeringSimplex()) + .decross(d3dag.decrossOpt()) + .coord(d3dag.coordVert())(dag) + + const line = d3 + .line() + .curve(d3.curveCatmullRom) + .x((node) => node.x) + .y((node) => node.y) + + // Styling links + groupSelection + .append('g') + .selectAll('path') + .data(dag.links()) + .enter() + .append('path') + .attr('d', ({ points }) => line(points)) + .attr('fill', 'none') + .attr('stroke-width', 2) + .attr('stroke', theme.palette.grey['300']) + + const nodes = groupSelection + .append('g') + .selectAll('g') + .data(dag.descendants()) + .enter() + .append('g') + .attr('style', 'cursor: default') + .attr('id', (node) => { + setIcon((s: TaskNodes) => ({ + ...s, + [node.id]: node, + })) + return node.id + }) + .attr('transform', ({ x, y }: any) => `translate(${x}, ${y})`) + .on('mouseover', (_, node) => { + setTooltip(node) + d3.select(`#circle-${node.data.id}`) + .transition() + .attr('r', nodeRadius + 7) + .duration(50) + }) + .on('mouseout', (_, node) => { + setTooltip(null) + d3.select(`#circle-${node.data.id}`) + .transition() + .attr('r', nodeRadius) + .duration(50) + }) + + // Styling dots + nodes + .append('circle') + .attr('id', (node) => { + return `circle-${node.data.id}` + }) + .attr('r', nodeRadius) + .attr('fill', 'black') + .attr('stroke', 'white') + .attr('stroke-width', 6) + .attr('fill', (node) => { + switch (node.data.attributes?.status) { + case 'in_progress': + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore because material UI doesn't update theme types with options + return theme.palette.warning.main + case 'completed': + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore because material UI doesn't update theme types with options + return theme.palette.success.main + case 'errored': + return theme.palette.error.main + default: + return theme.palette.grey['500'] + } + }) + + nodes + .append('text') + .text((node) => node.data.id) + .attr('x', 30) + .attr('font-weight', 'normal') + .attr('font-family', 'sans-serif') + .attr('text-anchor', 'start') + .attr('font-size', '1em') + .attr('alignment-baseline', 'middle') + .attr('fill', 'black') +} + +interface Props { + stratify: Stratify[] +} + +export const TaskList = ({ stratify }: Props) => { + const [tooltip, setTooltip] = React.useState() + const [icons, setIcon] = React.useState({}) + const graph = React.useRef(null) + + React.useEffect(() => { + function handleResize() { + if (graph.current) { + createDag({ stratify, ref: graph.current, setTooltip, setIcon }) + } + } + + handleResize() + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, [graph, stratify]) + + return ( +
+ {tooltip && ( +
+ + {tooltip.data.id} + + {tooltip.data?.attributes && + Object.entries(tooltip.data.attributes) + // We want to filter errors and outputs out as they can get quite long + .filter(([key]) => !['error', 'output'].includes(key)) + .map(([key, value]) => ( +
+ + {key}: {value} + +
+ ))} +
+ )} + {Object.values(icons).map((icon) => ( + + + {icon.data.attributes?.status || 'not_run'} + + + ))} +
+
+ ) +} + +export default TaskList diff --git a/operator_ui/src/pages/Jobs/generateJobSpecDefinition.test.ts b/operator_ui/src/pages/Jobs/generateJobSpecDefinition.test.ts index 779d7982d4f..687c660a49a 100644 --- a/operator_ui/src/pages/Jobs/generateJobSpecDefinition.test.ts +++ b/operator_ui/src/pages/Jobs/generateJobSpecDefinition.test.ts @@ -176,6 +176,7 @@ describe('generateJSONDefinition', () => { describe('generateTOMLDefinition', () => { it('generates valid definition', () => { const jobSpecAttributesInput = { + name: 'Job spec v2', offChainReportingOracleSpec: { contractAddress: '0x1469877c88F19E273EFC7Ef3C9D944574583B8a0', p2pPeerID: '12D3KooWL4zx7Tu92wNuK14LT2BV4mXxNoNK3zuxE7iKNgiazJFm', diff --git a/operator_ui/src/pages/Jobs/parseDot.test.ts b/operator_ui/src/pages/Jobs/parseDot.test.ts new file mode 100644 index 00000000000..72189ea3492 --- /dev/null +++ b/operator_ui/src/pages/Jobs/parseDot.test.ts @@ -0,0 +1,58 @@ +import { parseDot } from './parseDot' + +describe('components/Jobs/parseDot', () => { + it('correctly adds node attributes', () => { + const digraph1 = `digraph { + fetch [type=http method=POST url="http://localhost:8001" params="{\\"hi\\": \\"hello\\"}"]; + parse [type=jsonparse path="data,result"]; + multiply [type=multiply times=100]; + fetch -> parse -> multiply; + }` + + const expected1 = [ + { + id: 'fetch', + parentIds: [], + attributes: { + type: 'http', + method: 'POST', + url: 'http://localhost:8001', + params: '{"hi": "hello"}', + }, + }, + { + id: 'parse', + parentIds: ['fetch'], + attributes: { type: 'jsonparse', path: 'data,result' }, + }, + { + id: 'multiply', + parentIds: ['parse'], + attributes: { type: 'multiply', times: '100' }, + }, + ] + + const stratify1 = parseDot(digraph1) + expect(stratify1).toEqual(expected1) + }) + + it('correctly assigns multiple parentIds', () => { + const digraph2 = `digraph { + exercise -> sleep; + learn -> sleep; + sleep -> eat; + eat -> learn; + eat -> exercise; + }` + + const expected2 = [ + { id: 'exercise', parentIds: ['eat'] }, + { id: 'sleep', parentIds: ['exercise', 'learn'] }, + { id: 'learn', parentIds: ['eat'] }, + { id: 'eat', parentIds: ['sleep'] }, + ] + + const stratify2 = parseDot(digraph2) + expect(stratify2).toEqual(expected2) + }) +}) diff --git a/operator_ui/src/pages/Jobs/parseDot.ts b/operator_ui/src/pages/Jobs/parseDot.ts new file mode 100644 index 00000000000..5f6a45109b8 --- /dev/null +++ b/operator_ui/src/pages/Jobs/parseDot.ts @@ -0,0 +1,32 @@ +import graphlibDot from 'graphlib-dot' + +export type Stratify = { + id: string + parentIds: string[] + attributes?: { [key: string]: string } +} + +type Edge = { + v: string + w: string +} + +export function parseDot(dot: string): Stratify[] { + const digraph = graphlibDot.read(dot) + const edges = digraph.edges() + + return digraph.nodes().map((id: string) => { + const nodeInformation: Stratify = { + id, + parentIds: edges + .filter((edge: Edge) => edge.w === id) + .map((edge: Edge) => edge.v), + } + + if (Object.keys(digraph.node(id)).length > 0) { + nodeInformation.attributes = digraph.node(id) + } + + return nodeInformation + }) +} diff --git a/operator_ui/src/pages/Jobs/sharedTypes.ts b/operator_ui/src/pages/Jobs/sharedTypes.ts index 1f06674a301..35c971263f9 100644 --- a/operator_ui/src/pages/Jobs/sharedTypes.ts +++ b/operator_ui/src/pages/Jobs/sharedTypes.ts @@ -6,9 +6,12 @@ import { JobSpecError, OcrJobRun, OcrJobSpec, - TaskSpec, + RunResult, RunStatus, + TaskRun, + TaskSpec, } from 'core/store/models' +import * as time from 'time' export type JobRunsResponse = | PaginatedApiResponse @@ -21,17 +24,11 @@ export type BaseJob = { definition: string errors: JobSpecError[] id: string - name?: string -} - -export type BaseJobRun = { - createdAt: string - id: string - status: RunStatus - jobId: string + name: string | null } export type OffChainReportingJob = BaseJob & { + dotDagSource: string type: 'Off-chain reporting' } @@ -45,9 +42,48 @@ export type DirectRequestJob = BaseJob & { type: 'Direct request' } +export type BaseJobRun = { + createdAt: time.Time + finishedAt: time.Time | null + id: string + jobId: string +} + +export type DirectRequestJobRun = BaseJobRun & { + initiator: Initiator + overrides: RunResult + result: RunResult + taskRuns: TaskRun[] + payment: string | null + status: RunStatus + type: 'Direct request job run' +} + +export type PipelineJobRunStatus = 'in_progress' | 'errored' | 'completed' +export type PipelineTaskRunStatus = + | 'in_progress' + | 'errored' + | 'completed' + | 'not_run' + +export type PipelineTaskRun = OcrJobRun['taskRuns'][0] & { + status: PipelineTaskRunStatus +} + +export type PipelineJobRun = BaseJobRun & { + outputs: null | (string | null)[] + errors: null | (string | null)[] + pipelineSpec: { + DotDagSource: string + } + status: PipelineJobRunStatus + taskRuns: PipelineTaskRun[] + type: 'Off-chain reporting job run' +} + export type JobData = { job?: DirectRequestJob | OffChainReportingJob jobSpec?: JobSpecResponse['data'] - recentRuns?: BaseJobRun[] + recentRuns?: PipelineJobRun[] | DirectRequestJobRun[] recentRunsCount: number } diff --git a/operator_ui/src/pages/Jobs/transformJobRuns.test.ts b/operator_ui/src/pages/Jobs/transformJobRuns.test.ts new file mode 100644 index 00000000000..6e6c320d9a7 --- /dev/null +++ b/operator_ui/src/pages/Jobs/transformJobRuns.test.ts @@ -0,0 +1,98 @@ +import { ApiResponse } from '@chainlink/json-api-client' +import { JobRun } from 'core/store/models' +import jsonApiJobSpecRun from 'factories/jsonApiJobSpecRun' +import { jsonApiOcrJobRun } from 'factories/jsonApiOcrJobRun' +import { + transformDirectRequestJobRun, + transformPipelineJobRun, +} from './transformJobRuns' + +describe('transformPipelineJobRun', () => { + it('transforms api response to PipelineJobRun', () => { + const apiResponse = jsonApiOcrJobRun({ + id: '1', + }) + + expect(transformPipelineJobRun('1')(apiResponse.data)).toEqual({ + createdAt: '2020-09-22T11:48:20.410Z', + errors: [], + finishedAt: '2020-09-22T11:48:20.410Z', + id: '1', + jobId: '1', + outputs: [null], + pipelineSpec: { + CreatedAt: '2020-11-19T14:01:24.989522Z', + DotDagSource: ` fetch [type=http method=POST url="http://localhost:8001" requestData="{\\"hi\\": \\"hello\\"}"]; + parse [type=jsonparse path="data,result"]; + multiply [type=multiply times=100]; + fetch -> parse -> multiply; +`, + ID: 1, + }, + status: 'errored', + taskRuns: [ + { + createdAt: '2020-11-19T14:01:24.989522Z', + error: + 'error making http request: Post "http://localhost:8001": dial tcp 127.0.0.1:8001: connect: connection refused', + finishedAt: '2020-11-19T14:01:25.015681Z', + output: null, + status: 'not_run', + taskSpec: { + dotId: 'multiply', + }, + type: 'multiply', + }, + { + createdAt: '2020-11-19T14:01:24.989522Z', + error: + 'error making http request: Post "http://localhost:8001": dial tcp 127.0.0.1:8001: connect: connection refused', + finishedAt: '2020-11-19T14:01:25.005568Z', + output: null, + status: 'not_run', + taskSpec: { + dotId: 'parse', + }, + type: 'jsonparse', + }, + { + createdAt: '2020-11-19T14:01:24.989522Z', + error: + 'error making http request: Post "http://localhost:8001": dial tcp 127.0.0.1:8001: connect: connection refused', + finishedAt: '2020-11-19T14:01:24.997068Z', + output: null, + status: 'errored', + taskSpec: { + dotId: 'fetch', + }, + type: 'http', + }, + ], + type: 'Off-chain reporting job run', + }) + }) +}) + +describe('transformDirectRequestJobRun', () => { + it('transforms api response to DirectRequestJobRun', () => { + const apiResponse = jsonApiJobSpecRun({ + id: '1', + }) as ApiResponse + + expect(transformDirectRequestJobRun('1')(apiResponse.data)).toEqual({ + createdAt: '2018-06-19T15:39:53.315919143-07:00', + id: '1', + initiator: { params: {}, type: 'web' }, + jobId: '1', + result: { + data: { + value: + '0x05070f7f6a40e4ce43be01fa607577432c68730c2cb89a0f50b665e980d926b5', + }, + }, + status: 'completed', + taskRuns: [], + type: 'Direct request job run', + }) + }) +}) diff --git a/operator_ui/src/pages/Jobs/transformJobRuns.ts b/operator_ui/src/pages/Jobs/transformJobRuns.ts new file mode 100644 index 00000000000..d8544a7b5df --- /dev/null +++ b/operator_ui/src/pages/Jobs/transformJobRuns.ts @@ -0,0 +1,86 @@ +import { ApiResponse } from '@chainlink/json-api-client' +import { JobRun, OcrJobRun } from 'core/store/models' +import { parseDot, Stratify } from './parseDot' +import { + DirectRequestJobRun, + PipelineJobRun, + PipelineTaskRun, +} from './sharedTypes' +import { getOcrJobStatus } from './utils' + +function getTaskStatus({ + taskRun: { + taskSpec: { dotId }, + finishedAt, + error, + }, + stratify, + taskRuns, +}: { + taskRun: OcrJobRun['taskRuns'][0] + stratify: Stratify[] + taskRuns: OcrJobRun['taskRuns'] +}) { + if (finishedAt === null) { + return 'in_progress' + } + const currentNode = stratify.find((node) => node.id === dotId) + + let taskError = error + + if (currentNode) { + currentNode.parentIds.forEach((id) => { + const parentTaskRun = taskRuns.find((tr) => tr.taskSpec.dotId === id) + + if (parentTaskRun?.error !== null && parentTaskRun?.error === taskError) { + taskError = 'not_run' + } + }) + } + + if (taskError === 'not_run') { + return 'not_run' + } + + if (taskError !== null) { + return 'errored' + } + return 'completed' +} + +const addTaskStatus = (stratify: Stratify[]) => ( + taskRun: OcrJobRun['taskRuns'][0], + _index: number, + taskRuns: OcrJobRun['taskRuns'], +): PipelineTaskRun => { + return { + ...taskRun, + status: getTaskStatus({ taskRun, stratify, taskRuns }), + } +} + +export const transformPipelineJobRun = (jobSpecId: string) => ( + jobRun: ApiResponse['data'], +): PipelineJobRun => { + const stratify = parseDot( + `digraph {${jobRun.attributes.pipelineSpec.DotDagSource}}`, + ) + + return { + ...jobRun.attributes, + id: jobRun.id, + jobId: jobSpecId, + status: getOcrJobStatus(jobRun.attributes), + taskRuns: jobRun.attributes.taskRuns.map(addTaskStatus(stratify)), + type: 'Off-chain reporting job run', + } +} + +export const transformDirectRequestJobRun = (jobSpecId: string) => ( + jobRun: ApiResponse['data'], +): DirectRequestJobRun => ({ + ...jobRun.attributes, + id: jobRun.id, + jobId: jobSpecId, + type: 'Direct request job run', +}) diff --git a/operator_ui/src/pages/Jobs/utils.ts b/operator_ui/src/pages/Jobs/utils.ts index f47086424ad..eaac2a09434 100644 --- a/operator_ui/src/pages/Jobs/utils.ts +++ b/operator_ui/src/pages/Jobs/utils.ts @@ -1,4 +1,5 @@ import TOML from '@iarna/toml' +import { PipelineTaskError, RunStatus } from 'core/store/models' export enum JobSpecFormats { JSON = 'json', @@ -61,3 +62,24 @@ export function stringifyJobSpec({ return '' } + +export function getOcrJobStatus({ + finishedAt, + errors, +}: { + finishedAt: string | null + errors: PipelineTaskError[] +}) { + if (finishedAt === null) { + return RunStatus.IN_PROGRESS + } + if (errors[0] !== null) { + return RunStatus.ERRORED + } + return RunStatus.COMPLETED +} + +// `isNaN` actually accepts strings and we don't want to `parseInt` or `parseFloat` +// as it doesn't have the behaviour we want. +export const isOcrJob = (jobSpecId: string): boolean => + !isNaN((jobSpecId as unknown) as number) diff --git a/operator_ui/src/pages/JobsIndex/DirectRequestRow.tsx b/operator_ui/src/pages/JobsIndex/DirectRequestRow.tsx index 38b0315f8db..204d0fe7e29 100644 --- a/operator_ui/src/pages/JobsIndex/DirectRequestRow.tsx +++ b/operator_ui/src/pages/JobsIndex/DirectRequestRow.tsx @@ -1,7 +1,7 @@ import React from 'react' import { TableCell, TableRow, Typography } from '@material-ui/core' import { TimeAgo } from '@chainlink/styleguide' -import { useHistory } from 'react-router-dom' +import Link from 'components/Link' import { formatInitiators } from 'utils/jobSpecInitiators' import { DirectRequest } from './JobsIndex' import { @@ -17,6 +17,16 @@ const styles = (theme: Theme) => paddingTop: theme.spacing.unit * 2, paddingBottom: theme.spacing.unit * 2, }, + link: { + '&::before': { + content: "''", + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + }, + }, }) interface Props extends WithStyles { @@ -25,28 +35,24 @@ interface Props extends WithStyles { export const DirectRequestRow = withStyles(styles)( ({ job, classes }: Props) => { - const history = useHistory() - return ( - history.push(`/jobs/${job.id}`)} - > + - {job.attributes.name || job.id} - {job.attributes.name && ( - <> -
- - {job.id} - - - )} + + {job.attributes.name || job.id} + {job.attributes.name && ( + <> +
+ + {job.id} + + + )} +
diff --git a/operator_ui/src/pages/JobsIndex/OcrJobRow.tsx b/operator_ui/src/pages/JobsIndex/OcrJobRow.tsx index a37f1ec6f4d..6b546a4aecf 100644 --- a/operator_ui/src/pages/JobsIndex/OcrJobRow.tsx +++ b/operator_ui/src/pages/JobsIndex/OcrJobRow.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { useHistory } from 'react-router-dom' import { TableCell, TableRow, Typography } from '@material-ui/core' import { TimeAgo } from '@chainlink/styleguide' +import Link from 'components/Link' import { OffChainReporting } from './JobsIndex' import { createStyles, @@ -16,6 +16,16 @@ const styles = (theme: Theme) => paddingTop: theme.spacing.unit * 2, paddingBottom: theme.spacing.unit * 2, }, + link: { + '&::before': { + content: "''", + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + }, + }, }) interface Props extends WithStyles { @@ -23,27 +33,24 @@ interface Props extends WithStyles { } export const OcrJobRow = withStyles(styles)(({ job, classes }: Props) => { - const history = useHistory() return ( - history.push(`/jobs/${job.id}`)} - > + - {job.attributes.offChainReportingOracleSpec.name || job.id} - {job.attributes.offChainReportingOracleSpec.name && ( - <> -
- - {job.id} - - - )} + + {job.attributes.name || job.id} + {job.attributes.name && ( + <> +
+ + {job.id} + + + )} +
diff --git a/operator_ui/src/pages/Keys/AccountAddresses.test.tsx b/operator_ui/src/pages/Keys/AccountAddresses.test.tsx new file mode 100644 index 00000000000..f106a6f22ac --- /dev/null +++ b/operator_ui/src/pages/Keys/AccountAddresses.test.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import { syncFetch } from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' +import { mountWithProviders } from 'test-helpers/mountWithTheme' +import { accountBalances } from 'factories/accountBalance' +import { ACCOUNT_BALANCES_ENDPOINT } from 'api/v2/user/balances' +import { AccountAddresses } from './AccountAddresses' + +describe('pages/Keys/OcrKeys', () => { + describe('Off-Chain Reporting keys', () => { + it('renders the list of keys', async () => { + const address1 = { + ethBalance: '1', + linkBalance: '2', + address: '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f', + isFunding: true, + } + + const address2 = { + ethBalance: '158', + linkBalance: '2913', + address: '0xABCDd2D5E04012C9Ed24C0e513C9bfAa4A2dD77f', + isFunding: false, + } + global.fetch.getOnce( + globPath(ACCOUNT_BALANCES_ENDPOINT), + accountBalances([address1, address2]), + ) + + const wrapper = mountWithProviders() + await syncFetch(wrapper) + expect(wrapper.text()).toContain('just now') + expect(wrapper.find('tbody').children().length).toEqual(2) + + expect(wrapper.text()).toContain(address1.address) + expect(wrapper.text()).toContain(address1.ethBalance) + expect(wrapper.text()).toContain(address1.linkBalance) + expect(wrapper.text()).toContain('Emergency funding') + + expect(wrapper.text()).toContain(address2.address) + expect(wrapper.text()).toContain(address2.ethBalance) + expect(wrapper.text()).toContain(address2.linkBalance) + expect(wrapper.text()).toContain('Regular') + }) + }) +}) diff --git a/operator_ui/src/pages/Keys/AccountAddresses.tsx b/operator_ui/src/pages/Keys/AccountAddresses.tsx new file mode 100644 index 00000000000..4cba720433f --- /dev/null +++ b/operator_ui/src/pages/Keys/AccountAddresses.tsx @@ -0,0 +1,152 @@ +import React from 'react' +import Grid from '@material-ui/core/Grid' +import { v2 } from 'api' +import * as jsonapi from '@chainlink/json-api-client' +import * as presenters from 'core/store/presenters' +import { useErrorHandler } from 'hooks/useErrorHandler' +import { useLoadingPlaceholder } from 'hooks/useLoadingPlaceholder' +import Table from '@material-ui/core/Table' +import TableBody from '@material-ui/core/TableBody' +import TableCell from '@material-ui/core/TableCell' +import TableHead from '@material-ui/core/TableHead' +import TableRow from '@material-ui/core/TableRow' +import Card from '@material-ui/core/Card' +import CardContent from '@material-ui/core/CardContent' +import CardHeader from '@material-ui/core/CardHeader' +import Typography from '@material-ui/core/Typography' +import { TimeAgo } from '@chainlink/styleguide' +import { + createStyles, + withStyles, + WithStyles, + Theme, +} from '@material-ui/core/styles' +import { Copy } from './Copy' + +const styles = (theme: Theme) => + createStyles({ + card: { + padding: theme.spacing.unit, + marginBottom: theme.spacing.unit * 3, + }, + }) + +export const AccountAddresses = withStyles(styles)( + ({ classes }: WithStyles) => { + const [accountBalances, setAccountBalances] = React.useState< + jsonapi.ApiResponse['data'] + >() + const { error, ErrorComponent, setError } = useErrorHandler() + const { isLoading, LoadingPlaceholder } = useLoadingPlaceholder( + !error && !accountBalances, + ) + + const handleFetchIndex = React.useCallback(() => { + v2.user.balances + .getAccountBalances() + .then(({ data }) => { + setAccountBalances(data) + }) + .catch(setError) + }, [setError]) + + React.useEffect(() => { + handleFetchIndex() + }, [handleFetchIndex]) + + return ( + + + + + + + + + + + + + Address + + + + + Type + + + + + Link balance + + + + + ETH balance + + + + + Created + + + + + + {isLoading && ( + + + + + + )} + + {accountBalances?.length === 0 && ( + + + No entries to show. + + + )} + {accountBalances?.map((balance) => ( + + + + {balance.attributes.address}{' '} + + + + + + {balance.attributes.isFunding + ? 'Emergency funding' + : 'Regular'} + + + + + {balance.attributes.linkBalance} + + + + + {balance.attributes.ethBalance} + + + + + + {balance.attributes.createdAt || ''} + + + + + ))} + +
+
+
+
+ ) + }, +) diff --git a/operator_ui/src/pages/Keys/Index.test.tsx b/operator_ui/src/pages/Keys/Index.test.tsx index a8556453ba6..0dfcc7dff48 100644 --- a/operator_ui/src/pages/Keys/Index.test.tsx +++ b/operator_ui/src/pages/Keys/Index.test.tsx @@ -1,11 +1,13 @@ import React from 'react' import { jsonApiOcrKeys, OcrKeyBundle } from 'factories/jsonApiOcrKeys' import { jsonApiP2PKeys, P2PKeyBundle } from 'factories/jsonApiP2PKeys' +import { accountBalances } from 'factories/accountBalance' import { syncFetch } from 'test-helpers/syncFetch' import globPath from 'test-helpers/globPath' import { mountWithProviders } from 'test-helpers/mountWithTheme' import { partialAsFull } from '@chainlink/ts-helpers' +import { ACCOUNT_BALANCES_ENDPOINT } from 'api/v2/user/balances' import { INDEX_ENDPOINT as OCR_INDEX_ENDPOINT } from 'api/v2/ocrKeys' import { INDEX_ENDPOINT as P2P_INDEX_ENDPOINT } from 'api/v2/p2pKeys' @@ -30,10 +32,14 @@ describe('pages/Keys/Index', () => { globPath(P2P_INDEX_ENDPOINT), jsonApiP2PKeys([expectedP2P]), ) + global.fetch.getOnce( + globPath(ACCOUNT_BALANCES_ENDPOINT), + accountBalances([]), + ) const wrapper = mountWithProviders() await syncFetch(wrapper) - expect(wrapper.find('tbody').children().length).toEqual(2) + expect(wrapper.find('tbody').children().length).toEqual(3) expect(wrapper.text()).toContain(expectedOcr.id) expect(wrapper.text()).toContain(expectedP2P.peerId) diff --git a/operator_ui/src/pages/Keys/Index.tsx b/operator_ui/src/pages/Keys/Index.tsx index 3a5cda34692..f0179245c5b 100644 --- a/operator_ui/src/pages/Keys/Index.tsx +++ b/operator_ui/src/pages/Keys/Index.tsx @@ -1,22 +1,20 @@ import React from 'react' import Grid from '@material-ui/core/Grid' -import { Title } from 'components/Title' import Content from 'components/Content' import { OcrKeys } from './OcrKeys' import { P2PKeys } from './P2PKeys' +import { AccountAddresses } from './AccountAddresses' export const KeysIndex = () => { React.useEffect(() => { - document.title = 'Keys' + document.title = 'Keys and account addresses' }, []) return ( - - Keys - + ) diff --git a/operator_ui/src/theme.ts b/operator_ui/src/theme.ts new file mode 100644 index 00000000000..6bdfeee83cd --- /dev/null +++ b/operator_ui/src/theme.ts @@ -0,0 +1,4 @@ +import { theme as mainTheme } from '@chainlink/styleguide' +import { createMuiTheme } from '@material-ui/core/styles' + +export const theme = createMuiTheme(mainTheme) diff --git a/operator_ui/support/factories/accountBalance.js b/operator_ui/support/factories/accountBalance.js deleted file mode 100644 index 09360a6fc21..00000000000 --- a/operator_ui/support/factories/accountBalance.js +++ /dev/null @@ -1,15 +0,0 @@ -import { decamelizeKeys } from 'humps' - -export default (ethBalance, linkBalance) => { - return decamelizeKeys({ - data: { - id: '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f', - type: 'eTHKeys', - attributes: { - address: '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f', - ethBalance, - linkBalance, - }, - }, - }) -} diff --git a/operator_ui/support/factories/accountBalance.ts b/operator_ui/support/factories/accountBalance.ts new file mode 100644 index 00000000000..6f345841b5d --- /dev/null +++ b/operator_ui/support/factories/accountBalance.ts @@ -0,0 +1,20 @@ +import * as presenters from 'core/store/presenters' + +export const accountBalances = ( + accountBalances: Partial[], +) => { + return { + data: accountBalances.map((balance) => ({ + id: balance.address ?? '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f', + type: 'eTHKeys', + attributes: { + address: + balance.address ?? '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f', + ethBalance: balance.ethBalance ?? '0', + linkBalance: balance.linkBalance ?? '0', + isFunding: balance.isFunding ?? false, + createdAt: balance.createdAt ?? new Date().toISOString(), + }, + })), + } +} diff --git a/operator_ui/support/factories/jobRunV2.ts b/operator_ui/support/factories/jobRunV2.ts index 02ff5290ad9..c31f4a1756b 100644 --- a/operator_ui/support/factories/jobRunV2.ts +++ b/operator_ui/support/factories/jobRunV2.ts @@ -2,11 +2,18 @@ import { partialAsFull } from '@chainlink/ts-helpers' import { OcrJobRun } from 'core/store/models' export function jobRunV2( - config: Partial = {}, + config: Partial = {}, ): OcrJobRun { return partialAsFull({ outputs: config.outputs || [null], errors: config.errors || [], + pipelineSpec: { + ID: 1, + DotDagSource: + config?.dotDagSource || + ' fetch [type=http method=POST url="http://localhost:8001" requestData="{\\"hi\\": \\"hello\\"}"];\n parse [type=jsonparse path="data,result"];\n multiply [type=multiply times=100];\n fetch -\u003e parse -\u003e multiply;\n', + CreatedAt: '2020-11-19T14:01:24.989522Z', + }, taskRuns: config.taskRuns || [ { createdAt: '2020-11-19T14:01:24.989522Z', diff --git a/operator_ui/support/factories/jobSpecV2.ts b/operator_ui/support/factories/jobSpecV2.ts index ddc268e8066..d4dfdadeb18 100644 --- a/operator_ui/support/factories/jobSpecV2.ts +++ b/operator_ui/support/factories/jobSpecV2.ts @@ -4,7 +4,12 @@ import { generateUuid } from '../test-helpers/generateUuid' export function jobSpecV2( config: Partial< - OcrJobSpec['offChainReportingOracleSpec'] & { id?: string } + OcrJobSpec['offChainReportingOracleSpec'] & { + name?: string + id?: string + } & { + dotDagSource?: string + } > = {}, ): OcrJobSpec { const offChainReportingOracleSpec = partialAsFull< @@ -26,13 +31,15 @@ export function jobSpecV2( updatedAt: config.updatedAt || new Date(1600775300410).toISOString(), createdAt: config.createdAt || new Date(1600775300410).toISOString(), }) - return { + name: config.name || 'V2 job', offChainReportingOracleSpec, errors: [], pipelineSpec: { dotDagSource: - ' fetch [type=http method=POST url="http://localhost:8001" requestData="{\\"hi\\": \\"hello\\"}"];\n parse [type=jsonparse path="data,result"];\n multiply [type=multiply times=100];\n fetch -\u003e parse -\u003e multiply;\n', + typeof config.dotDagSource === 'string' + ? config.dotDagSource + : ' fetch [type=http method=POST url="http://localhost:8001" requestData="{\\"hi\\": \\"hello\\"}"];\n parse [type=jsonparse path="data,result"];\n multiply [type=multiply times=100];\n fetch -\u003e parse -\u003e multiply;\n', }, } } diff --git a/operator_ui/support/factories/jsonApiOcrJobRun.ts b/operator_ui/support/factories/jsonApiOcrJobRun.ts new file mode 100644 index 00000000000..d8306ad5a9c --- /dev/null +++ b/operator_ui/support/factories/jsonApiOcrJobRun.ts @@ -0,0 +1,21 @@ +import { ApiResponse } from '@chainlink/json-api-client' +import { OcrJobRun } from 'core/store/models' +import { jobRunV2 } from './jobRunV2' + +function getRandomInt(max: number) { + return Math.floor(Math.random() * Math.floor(max)) +} + +export const jsonApiOcrJobRun = ( + config: Partial = {}, +) => { + const id = config.id || getRandomInt(1_000_000).toString() + + return { + data: { + type: 'run', + id, + attributes: jobRunV2(config), + }, + } as ApiResponse +} diff --git a/operator_ui/support/factories/jsonApiOcrJobSpec.ts b/operator_ui/support/factories/jsonApiOcrJobSpec.ts index 11a500a8145..f96da47aa42 100644 --- a/operator_ui/support/factories/jsonApiOcrJobSpec.ts +++ b/operator_ui/support/factories/jsonApiOcrJobSpec.ts @@ -8,7 +8,9 @@ function getRandomInt(max: number) { export const jsonApiOcrJobSpec = ( config: Partial< - OcrJobSpec['offChainReportingOracleSpec'] & { id?: string } + OcrJobSpec['offChainReportingOracleSpec'] & { id?: string } & { + dotDagSource?: string + } > = {}, ) => { const id = config.id || getRandomInt(1_000_000).toString() diff --git a/package.json b/package.json index e83d645b00e..2d2d9917c39 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "scripts": { "lint": "eslint --ext js,jsx,ts,tsx .", + "lint:evm": "eslint --ext js,jsx,ts,tsx evm-contracts/ evm-test-helpers/", "prettier:check": "yarn prettier '**/*' --check --ignore-unknown", "prettier:run": "yarn prettier '**/*' --check --ignore-unknown --write", "setup": "wsrun -me -t setup", @@ -37,7 +38,7 @@ "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^4.0.0", "patch-package": "^6.2.1", - "postinstall-postinstall": "^2.0.0", + "postinstall-postinstall": "^2.1.0", "prettier": "^2.1.2", "typescript": "^3.7.4", "wsrun": "^5.2.0" diff --git a/tools/bin/ldflags b/tools/bin/ldflags index 9f0e705d1fc..3b334ce5211 100755 --- a/tools/bin/ldflags +++ b/tools/bin/ldflags @@ -5,4 +5,4 @@ cd "$(dirname "$0")" COMMIT_SHA=${COMMIT_SHA:-$(git rev-parse HEAD)} VERSION=${VERSION:-$(cat "../../VERSION")} -echo "-X github.com/smartcontractkit/chainlink/core/store.Version=$VERSION -X github.com/smartcontractkit/chainlink/core/store.Sha=$COMMIT_SHA" +echo "-X github.com/smartcontractkit/chainlink/core/static.Version=$VERSION -X github.com/smartcontractkit/chainlink/core/static.Sha=$COMMIT_SHA" diff --git a/tools/ci-ts/test-helpers/chainlinkClient.test.ts b/tools/ci-ts/test-helpers/chainlinkClient.test.ts index 2fd25f42d7f..ab194ea18a1 100644 --- a/tools/ci-ts/test-helpers/chainlinkClient.test.ts +++ b/tools/ci-ts/test-helpers/chainlinkClient.test.ts @@ -65,7 +65,7 @@ describe('ChainlinkClient', () => { expect(execa.sync).toHaveBeenCalledTimes(1) expect(execa.sync).toHaveBeenCalledWith( 'chainlink', - ['-j', 'jobs', 'list'], + ['-j', 'job_specs', 'list'], ENV, ) }) @@ -90,7 +90,7 @@ describe('ChainlinkClient', () => { expect(execa.sync).toHaveBeenCalledTimes(1) expect(execa.sync).toHaveBeenCalledWith( 'chainlink', - ['-j', 'jobs', 'create', jobString], + ['-j', 'job_specs', 'create', jobString], ENV, ) }) @@ -103,7 +103,7 @@ describe('ChainlinkClient', () => { expect(execa.sync).toHaveBeenCalledTimes(1) expect(execa.sync).toHaveBeenCalledWith( 'chainlink', - ['-j', 'jobs', 'archive', jobID], + ['-j', 'job_specs', 'archive', jobID], ENV, ) }) diff --git a/tools/ci-ts/test-helpers/chainlinkClient.ts b/tools/ci-ts/test-helpers/chainlinkClient.ts index 2d18f6d0abb..acb8edc2448 100644 --- a/tools/ci-ts/test-helpers/chainlinkClient.ts +++ b/tools/ci-ts/test-helpers/chainlinkClient.ts @@ -51,7 +51,7 @@ export default class ChainlinkClient { } public getJobs(): JobSpec[] { - return this.execute('jobs list') as JobSpec[] + return this.execute('job_specs list') as JobSpec[] } public getJobRuns(): JobRun[] { @@ -59,11 +59,11 @@ export default class ChainlinkClient { } public createJob(jobSpec: string): JobSpec { - return this.execute(`jobs create ${jobSpec}`) as JobSpec + return this.execute(`job_specs create ${jobSpec}`) as JobSpec } public archiveJob(jobId: string): void { - this.execute(`jobs archive ${jobId}`) + this.execute(`job_specs archive ${jobId}`) } public getAdminInfo(): KeyInfo[] { diff --git a/tools/ci/push_chainlink b/tools/ci/push_chainlink index f60c12bcef8..993ecbb4e59 100755 --- a/tools/ci/push_chainlink +++ b/tools/ci/push_chainlink @@ -33,10 +33,12 @@ tag_and_push() { ( set -x docker tag smartcontract/chainlink:circleci smartcontract/chainlink:${DOCKER_TAG} + docker tag smartcontract/chainlink:circleci-nonroot "smartcontract/chainlink:${DOCKER_TAG}-nonroot" ) ( set -x docker tag smartcontract/chainlink:circleci "${AWS_ECR_URL}/chainlink:${DOCKER_TAG}" + docker tag smartcontract/chainlink:circleci-nonroot "${AWS_ECR_URL}/chainlink:${DOCKER_TAG}-nonroot" ) make dockerpush } diff --git a/yarn.lock b/yarn.lock index 548369e3e19..8a38628ab50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3462,6 +3462,221 @@ resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== +"@types/d3-array@*": + version "2.8.0" + resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-2.8.0.tgz#4b70ccb0c6d2ef28dac1e7e653b3ecd1f4b1d1ee" + integrity sha512-Q0ubcGHAmCRPh90/hoYB4eKWhxYKUxphwSeQrlz2tiabQ8S9zqhaE2CZJtCaLH2cjqKcjr52WPvmOA7ha0O4ZA== + +"@types/d3-axis@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-2.0.0.tgz#a3e7534d3c399c20ba42ec3093dd2a385659366e" + integrity sha512-gUdlEwGBLl3tXGiBnBNmNzph9W3bCfa4tBgWZD60Z1eDQKTY4zyCAcZ3LksignGfKawYatmDYcBdjJ5h/54sqA== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "2.1.0" + resolved "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-2.1.0.tgz#c51ad1ab93887b23be7637d2100540f1df0dac00" + integrity sha512-rLQqxQeXWF4ArXi81GlV8HBNwJw9EDpz0jcWvvzv548EDE4tXrayBTOHYi/8Q4FZ/Df8PGXFzxpAVQmJMjOtvQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-2.0.0.tgz#8d7085e2828418f2c5087e512f276559499bacfd" + integrity sha512-3nHsLY7lImpZlM/hrPeDqqW2a+lRXXoHsG54QSurDGihZAIE/doQlohs0evoHrWOJqXyn4A4xbSVEtXnMEZZiw== + +"@types/d3-color@*": + version "2.0.1" + resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.1.tgz#570ea7f8b853461301804efa52bd790a640a26db" + integrity sha512-u7LTCL7RnaavFSmob2rIAJLNwu50i6gFwY9cHFr80BrQURYQBRkJ+Yv47nA3Fm7FeRhdWTiVTeqvSeOuMAOzBQ== + +"@types/d3-contour@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-2.0.0.tgz#6e079f281b29a8df3fcbd3ec193f2cf1d0b4a584" + integrity sha512-PS9UO6zBQqwHXsocbpdzZFONgK1oRUgWtjjh/iz2vM06KaXLInLiKZ9e3OLBRerc1cU2uJYpO+8zOnb6frvCGQ== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "5.3.0" + resolved "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-5.3.0.tgz#416169bb5c67a510c87b55d092a404fcab49def3" + integrity sha512-gJYcGxLu0xDZPccbUe32OUpeaNtd1Lz0NYJtko6ZLMyG2euF4pBzrsQXms67LHZCDFzzszw+dMhSL/QAML3bXw== + +"@types/d3-dispatch@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-2.0.0.tgz#1f8803041b73b81f2c751e026b7bb63dd5f24ce0" + integrity sha512-Sh0KW6z/d7uxssD7K4s4uCSzlEG/+SP+U47q098NVdOfFvUKNTvKAIV4XqjxsUuhE/854ARAREHOxkr9gQOCyg== + +"@types/d3-drag@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-2.0.0.tgz#ef66acc422576fbe10b8bd66af45a9fb8525199a" + integrity sha512-VaUJPjbMnDn02tcRqsHLRAX5VjcRIzCjBfeXTLGe6QjMn5JccB5Cz4ztMRXMJfkbC45ovgJFWuj6DHvWMX1thA== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-2.0.0.tgz#c9fb8b2f0f7168d21a6bbd29492141bfd1b8db16" + integrity sha512-wYqy7T8tQ/DmocwxmlPujllLI5fg3lb6/FrVVWkLUD+NsRV+kcE4nbRZg10G9yjJ8pK2ZXqu+VP5jQbN13uNRQ== + +"@types/d3-ease@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-2.0.0.tgz#798cbd9908d26cfe9f1a295a3a75164da9a3666e" + integrity sha512-6aZrTyX5LG+ptofVHf+gTsThLRY1nhLotJjgY4drYqk1OkJMu2UvuoZRlPw2fffjRHeYepue3/fxTufqKKmvsA== + +"@types/d3-fetch@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-2.0.0.tgz#580846256ed0011b36a08ebb36924e0dff70e27e" + integrity sha512-WnLepGtxepFfXRdPI8I5FTgNiHn9p4vMTTqaNCzJJfAswXx0rOY2jjeolzEU063em3iJmGZ+U79InnEeFOrCRw== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "2.1.0" + resolved "https://registry.npmjs.org/@types/d3-force/-/d3-force-2.1.0.tgz#6a2210f04d02a0862c6b069de91bad904143e7b5" + integrity sha512-LGDtC2YADu8OBniq9EBx/MOsXsMcJbEkmfSpXuz6oVdRamB+3CLCiq5EKFPEILGZQckkilGFq1ZTJ7kc289k+Q== + +"@types/d3-format@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-format/-/d3-format-2.0.0.tgz#607d261cb268f0a027f100575491031539a40ee6" + integrity sha512-uagdkftxnGkO4pZw5jEYOM5ZnZOEsh7z8j11Qxk85UkB2RzfUUxRl7R9VvvJZHwKn8l+x+rpS77Nusq7FkFmIg== + +"@types/d3-geo@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-2.0.0.tgz#6f179512343c2d30e06acde190abfacf44b2d264" + integrity sha512-DHHgYXW36lnAEQMYU2udKVOxxljHrn2EdOINeSC9jWCAXwOnGn7A19B8sNsHqgpu4F7O2bSD7//cqBXD3W0Deg== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz#92079d9dbcec1dfe2736fb050a8bf916e5850a1c" + integrity sha512-YxdskUvwzqggpnSnDQj4KVkicgjpkgXn/g/9M9iGsiToLS3nG6Ytjo1FoYhYVAAElV/fJBGVL3cQ9Hb7tcv+lw== + +"@types/d3-interpolate@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-2.0.0.tgz#325029216dc722c1c68c33ccda759f1209d35823" + integrity sha512-Wt1v2zTlEN8dSx8hhx6MoOhWQgTkz0Ukj7owAEIOF2QtI0e219paFX9rf/SLOr/UExWb1TcUzatU8zWwFby6gg== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.0.tgz#dcc7f5ecadf52b0c0c39f6c1def3733195e4b199" + integrity sha512-tXcR/9OtDdeCIsyl6eTNHC3XOAOdyc6ceF3QGBXOd9jTcK+ex/ecr00p9L9362e/op3UEPpxrToi1FHrtTSj7Q== + +"@types/d3-path@^1": + version "1.0.9" + resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c" + integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ== + +"@types/d3-polygon@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-2.0.0.tgz#8b1df0a1358016e62c4961b01e8dc8e5ab4c64e5" + integrity sha512-fISnMd8ePED1G4aa4V974Jmt+ajHSgPoxMa2D0ULxMybpx0Vw4WEzhQEaMIrL3hM8HVRcKTx669I+dTy/4PhAw== + +"@types/d3-quadtree@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-2.0.0.tgz#b17e953dc061e083966075bba0d3a9a259812150" + integrity sha512-YZuJuGBnijD0H+98xMJD4oZXgv/umPXy5deu3IimYTPGH3Kr8Th6iQUff0/6S80oNBD7KtOuIHwHUCymUiRoeQ== + +"@types/d3-random@*": + version "2.2.0" + resolved "https://registry.npmjs.org/@types/d3-random/-/d3-random-2.2.0.tgz#fc44cabb966917459490b758f31f5359adeabe5b" + integrity sha512-Hjfj9m68NmYZzushzEG7etPvKH/nj9b9s9+qtkNG3/dbRBjQZQg1XS6nRuHJcCASTjxXlyXZnKu2gDxyQIIu9A== + +"@types/d3-scale-chromatic@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#8d4a6f07cbbf2a9f2a4bec9c9476c27ed76a96ea" + integrity sha512-Y62+2clOwZoKua84Ha0xU77w7lePiaBoTjXugT4l8Rd5LAk+Mn/ZDtrgs087a+B5uJ3jYUHHtKw5nuEzp0WBHw== + +"@types/d3-scale@*": + version "3.2.1" + resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.2.1.tgz#b37578c8c7edb6f040e46e7757783aafd2d50e4e" + integrity sha512-j+FryQSVk3GHLqjOX/RsHwGHg4XByJ0xIO1ASBTgzhE9o1tgeV4kEWLOzMzJRembKalflk5F03lEkM+4V6LDrQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-2.0.0.tgz#59df94a8e47ed1050a337d4ffb4d4d213aa590a8" + integrity sha512-EF0lWZ4tg7oDFg4YQFlbOU3936e3a9UmoQ2IXlBy1+cv2c2Pv7knhKUzGlH5Hq2sF/KeDTH1amiRPey2rrLMQA== + +"@types/d3-shape@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-2.0.0.tgz#61aa065726f3c2641aedc59c3603475ab11aeb2f" + integrity sha512-NLzD02m5PiD1KLEDjLN+MtqEcFYn4ZL9+Rqc9ZwARK1cpKZXd91zBETbe6wpBB6Ia0D0VZbpmbW3+BsGPGnCpA== + dependencies: + "@types/d3-path" "^1" + +"@types/d3-time-format@*": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-3.0.0.tgz#913e984362a59792dc8d8b122dd17625991eade2" + integrity sha512-UpLg1mn/8PLyjr+J/JwdQJM/GzysMvv2CS8y+WYAL5K0+wbvXv/pPSLEfdNaprCZsGcXTxPsFMy8QtkYv9ueew== + +"@types/d3-time@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.0.0.tgz#831dd093db91f16b83ba980e194bb8e4bcef44d6" + integrity sha512-Abz8bTzy8UWDeYs9pCa3D37i29EWDjNTjemdk0ei1ApYVNqulYlGUKip/jLOpogkPSsPz/GvZCYiC7MFlEk0iQ== + +"@types/d3-timer@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-2.0.0.tgz#9901bb02af38798764674df17d66b07329705632" + integrity sha512-l6stHr1VD1BWlW6u3pxrjLtJfpPZq9I3XmKIQtq7zHM/s6fwEtI1Yn6Sr5/jQTrUDCC5jkS6gWqlFGCDArDqNg== + +"@types/d3-transition@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-2.0.0.tgz#6f073f0b567c13b7a3dcd1d54214c89f48c5a873" + integrity sha512-UJDzI98utcZQUJt3uIit/Ho0/eBIANzrWJrTmi4+TaKIyWL2iCu7ShP0o4QajCskhyjOA7C8+4CE3b1YirTzEQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-2.0.0.tgz#ef8b87464e8ebc7c66b70f6383d1ae841e78e7fc" + integrity sha512-daL0PJm4yT0ISTGa7p2lHX0kvv9FO/IR1ooWbHR/7H4jpbaKiLux5FslyS/OvISPiJ5SXb4sOqYhO6fMB6hKRw== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^6.2.0": + version "6.2.0" + resolved "https://registry.npmjs.org/@types/d3/-/d3-6.2.0.tgz#db3c70ad6479cf79b15840a3d15338a9754384e7" + integrity sha512-XhQ6sCTu+CrFLqJMsg/uRPZQrt5FlCPjPE/wvsSBYoaOZ9C1chdJSS9+2oR8+Xtk6DKGewa7/UP5icJRwAryEA== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + "@types/debug@^4.1.5": version "4.1.5" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" @@ -3528,6 +3743,11 @@ dependencies: ganache-core "*" +"@types/geojson@*": + version "7946.0.7" + resolved "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" + integrity sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ== + "@types/glob@*", "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -3740,10 +3960,10 @@ dependencies: "@types/react" "*" -"@types/react-redux@^7.1.8": - version "7.1.8" - resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.8.tgz#3631feb559f7858d6ad9eea1d6ef41fa64fe7205" - integrity sha512-kpplH7Wg2SYU00sZVT98WBN0ou6QKrYcShRaW+5Vpe5l7bluKWJbWmAL+ieiso07OQzpcP5i1PeY3690640ZWg== +"@types/react-redux@^7.1.11": + version "7.1.11" + resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.11.tgz#a18e8ab3651e8e8cc94798934927937c66021217" + integrity sha512-OjaFlmqy0CRbYKBoaWF84dub3impqnLJUrz4u8PRjDzaa4n1A2cVmjMV81shwXyAD5x767efhA8STFGJz/r1Zg== dependencies: "@types/hoist-non-react-statics" "^3.3.0" "@types/react" "*" @@ -4452,21 +4672,20 @@ aes-js@^3.1.1: resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== -airbnb-prop-types@^2.15.0: - version "2.15.0" - resolved "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef" - integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA== +airbnb-prop-types@^2.16.0: + version "2.16.0" + resolved "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2" + integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg== dependencies: - array.prototype.find "^2.1.0" - function.prototype.name "^1.1.1" - has "^1.0.3" - is-regex "^1.0.4" - object-is "^1.0.1" + array.prototype.find "^2.1.1" + function.prototype.name "^1.1.2" + is-regex "^1.1.0" + object-is "^1.1.2" object.assign "^4.1.0" - object.entries "^1.1.0" + object.entries "^1.1.2" prop-types "^15.7.2" prop-types-exact "^1.2.0" - react-is "^16.9.0" + react-is "^16.13.1" ajv-errors@^1.0.0: version "1.0.1" @@ -4852,13 +5071,13 @@ array-unique@^0.3.2: resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.find@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz#630f2eaf70a39e608ac3573e45cf8ccd0ede9ad7" - integrity sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg== +array.prototype.find@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" + integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== dependencies: define-properties "^1.1.3" - es-abstract "^1.13.0" + es-abstract "^1.17.4" array.prototype.flat@^1.2.3: version "1.2.3" @@ -6291,6 +6510,14 @@ cachedown@1.0.0: abstract-leveldown "^2.4.1" lru-cache "^3.2.0" +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -6986,6 +7213,11 @@ command-line-usage@^6.1.0: table-layout "^1.0.0" typical "^5.2.0" +commander@2, commander@^2.15.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@2.15.1: version "2.15.1" resolved "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" @@ -7001,11 +7233,6 @@ commander@3.0.2: resolved "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^2.15.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: - version "2.20.3" - resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - commander@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz#b67622721785993182e807f4883633e6401ba53c" @@ -7166,7 +7393,7 @@ cookie-signature@1.0.6: resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.0, cookie@^0.4.0: +cookie@0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== @@ -7176,6 +7403,11 @@ cookie@^0.3.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= +cookie@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookiejar@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" @@ -7497,6 +7729,256 @@ cypress@^3.4.1: url "0.11.0" yauzl "2.10.0" +d3-array@2, d3-array@>=2.5, d3-array@^2.3.0, d3-array@^2.8.0: + version "2.8.0" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-2.8.0.tgz#f76e10ad47f1f4f75f33db5fc322eb9ffde5ef23" + integrity sha512-6V272gsOeg7+9pTW1jSYOR1QE37g95I3my1hBmY+vOUNHRrk9yt4OTz/gK7PMkVAVDrYYq4mq3grTiZ8iJdNIw== + +d3-axis@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-2.0.0.tgz#40aebb65626ffe6d95e9441fbf9194274b328a8b" + integrity sha512-9nzB0uePtb+u9+dWir+HTuEAKJOEUYJoEwbJPsZ1B4K3iZUgzJcSENQ05Nj7S4CIfbZZ8/jQGoUzGKFznBhiiQ== + +d3-brush@2: + version "2.1.0" + resolved "https://registry.npmjs.org/d3-brush/-/d3-brush-2.1.0.tgz#adadfbb104e8937af142e9a6e2028326f0471065" + integrity sha512-cHLLAFatBATyIKqZOkk/mDHUbzne2B3ZwxkzMHvFTCZCmLaXDpZRihQSn8UNXTkGD/3lb/W2sQz0etAftmHMJQ== + dependencies: + d3-dispatch "1 - 2" + d3-drag "2" + d3-interpolate "1 - 2" + d3-selection "2" + d3-transition "2" + +d3-chord@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-chord/-/d3-chord-2.0.0.tgz#32491b5665391180560f738e5c1ccd1e3c47ebae" + integrity sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig== + dependencies: + d3-path "1 - 2" + +"d3-color@1 - 2", d3-color@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" + integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== + +d3-contour@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-contour/-/d3-contour-2.0.0.tgz#80ee834988563e3bea9d99ddde72c0f8c089ea40" + integrity sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA== + dependencies: + d3-array "2" + +d3-dag@^0.4.7: + version "0.4.7" + resolved "https://registry.npmjs.org/d3-dag/-/d3-dag-0.4.7.tgz#7057e16ce009e5d366490b1f20b8b6ed6fa1947f" + integrity sha512-/rEb4GV+tFgsHLJd6170/WxGrcQEesNlxA1t2a9Wj47Ob/FPzT+BFudtuvdlnEpGmnnrXBJOd/KsFaROuQ5R/w== + dependencies: + d3-array "^2.8.0" + fastpriorityqueue "^0.6.3" + javascript-lp-solver "0.4.24" + quadprog "^1.6.1" + +d3-delaunay@5: + version "5.3.0" + resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d" + integrity sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w== + dependencies: + delaunator "4" + +"d3-dispatch@1 - 2", d3-dispatch@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz#8a18e16f76dd3fcaef42163c97b926aa9b55e7cf" + integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA== + +d3-drag@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz#9eaf046ce9ed1c25c88661911c1d5a4d8eb7ea6d" + integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w== + dependencies: + d3-dispatch "1 - 2" + d3-selection "2" + +"d3-dsv@1 - 2", d3-dsv@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz#b37b194b6df42da513a120d913ad1be22b5fe7c5" + integrity sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w== + dependencies: + commander "2" + iconv-lite "0.4" + rw "1" + +"d3-ease@1 - 2", d3-ease@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz#fd1762bfca00dae4bacea504b1d628ff290ac563" + integrity sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ== + +d3-fetch@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-fetch/-/d3-fetch-2.0.0.tgz#ecd7ef2128d9847a3b41b548fec80918d645c064" + integrity sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw== + dependencies: + d3-dsv "1 - 2" + +d3-force@2: + version "2.1.1" + resolved "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz#f20ccbf1e6c9e80add1926f09b51f686a8bc0937" + integrity sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew== + dependencies: + d3-dispatch "1 - 2" + d3-quadtree "1 - 2" + d3-timer "1 - 2" + +"d3-format@1 - 2", d3-format@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" + integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== + +d3-geo@2: + version "2.0.1" + resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.1.tgz#2437fdfed3fe3aba2812bd8f30609cac83a7ee39" + integrity sha512-M6yzGbFRfxzNrVhxDJXzJqSLQ90q1cCyb3EWFZ1LF4eWOBYxFypw7I/NFVBNXKNqxv1bqLathhYvdJ6DC+th3A== + dependencies: + d3-array ">=2.5" + +d3-hierarchy@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz#dab88a58ca3e7a1bc6cab390e89667fcc6d20218" + integrity sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw== + +"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@2: + version "2.0.1" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" + integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== + dependencies: + d3-color "1 - 2" + +"d3-path@1 - 2", d3-path@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8" + integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== + +d3-polygon@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz#13608ef042fbec625ba1598327564f03c0396d8e" + integrity sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ== + +"d3-quadtree@1 - 2", d3-quadtree@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz#edbad045cef88701f6fee3aee8e93fb332d30f9d" + integrity sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw== + +d3-random@2: + version "2.2.2" + resolved "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz#5eebd209ef4e45a2b362b019c1fb21c2c98cbb6e" + integrity sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw== + +d3-scale-chromatic@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#c13f3af86685ff91323dc2f0ebd2dabbd72d8bab" + integrity sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA== + dependencies: + d3-color "1 - 2" + d3-interpolate "1 - 2" + +d3-scale@3: + version "3.2.3" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.3.tgz#be380f57f1f61d4ff2e6cbb65a40593a51649cfd" + integrity sha512-8E37oWEmEzj57bHcnjPVOBS3n4jqakOeuv1EDdQSiSrYnMCBdMd3nc4HtKk7uia8DUHcY/CGuJ42xxgtEYrX0g== + dependencies: + d3-array "^2.3.0" + d3-format "1 - 2" + d3-interpolate "1.2.0 - 2" + d3-time "1 - 2" + d3-time-format "2 - 3" + +d3-selection@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz#94a11638ea2141b7565f883780dabc7ef6a61066" + integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA== + +d3-shape@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-2.0.0.tgz#2331b62fa784a2a1daac47a7233cfd69301381fd" + integrity sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw== + dependencies: + d3-path "1 - 2" + +"d3-time-format@2 - 3", d3-time-format@3: + version "3.0.0" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" + integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== + dependencies: + d3-time "1 - 2" + +"d3-time@1 - 2", d3-time@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-2.0.0.tgz#ad7c127d17c67bd57a4c61f3eaecb81108b1e0ab" + integrity sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q== + +"d3-timer@1 - 2", d3-timer@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz#055edb1d170cfe31ab2da8968deee940b56623e6" + integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA== + +d3-transition@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-2.0.0.tgz#366ef70c22ef88d1e34105f507516991a291c94c" + integrity sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog== + dependencies: + d3-color "1 - 2" + d3-dispatch "1 - 2" + d3-ease "1 - 2" + d3-interpolate "1 - 2" + d3-timer "1 - 2" + +d3-zoom@2: + version "2.0.0" + resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz#f04d0afd05518becce879d04709c47ecd93fba54" + integrity sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw== + dependencies: + d3-dispatch "1 - 2" + d3-drag "2" + d3-interpolate "1 - 2" + d3-selection "2" + d3-transition "2" + +d3@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/d3/-/d3-6.2.0.tgz#f19b0ecb16ca4ad2171ce8b37c63247e71c6f01d" + integrity sha512-aH+kx55J8vRBh4K4k9GN4EbNO3QnZsXy4XBfrnr4fL2gQuszUAPQU3fV2oObO2iSpreRH/bG/wfvO+hDu2+e9w== + dependencies: + d3-array "2" + d3-axis "2" + d3-brush "2" + d3-chord "2" + d3-color "2" + d3-contour "2" + d3-delaunay "5" + d3-dispatch "2" + d3-drag "2" + d3-dsv "2" + d3-ease "2" + d3-fetch "2" + d3-force "2" + d3-format "2" + d3-geo "2" + d3-hierarchy "2" + d3-interpolate "2" + d3-path "2" + d3-polygon "2" + d3-quadtree "2" + d3-random "2" + d3-scale "3" + d3-scale-chromatic "2" + d3-selection "2" + d3-shape "2" + d3-time "2" + d3-time-format "3" + d3-timer "2" + d3-transition "2" + d3-zoom "2" + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -7782,6 +8264,11 @@ del@^4.1.1: pify "^4.0.1" rimraf "^2.6.3" +delaunator@4: + version "4.0.1" + resolved "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957" + integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -8254,40 +8741,40 @@ envinfo@^7.7.3: resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== -enzyme-adapter-react-16@^1.15.2: - version "1.15.2" - resolved "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501" - integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q== +enzyme-adapter-react-16@^1.15.5: + version "1.15.5" + resolved "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz#7a6f0093d3edd2f7025b36e7fbf290695473ee04" + integrity sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw== dependencies: - enzyme-adapter-utils "^1.13.0" - enzyme-shallow-equal "^1.0.1" + enzyme-adapter-utils "^1.13.1" + enzyme-shallow-equal "^1.0.4" has "^1.0.3" object.assign "^4.1.0" object.values "^1.1.1" prop-types "^15.7.2" - react-is "^16.12.0" + react-is "^16.13.1" react-test-renderer "^16.0.0-0" semver "^5.7.0" -enzyme-adapter-utils@^1.13.0: - version "1.13.0" - resolved "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78" - integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ== +enzyme-adapter-utils@^1.13.1: + version "1.13.1" + resolved "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.1.tgz#59c1b734b0927543e3d8dc477299ec957feb312d" + integrity sha512-5A9MXXgmh/Tkvee3bL/9RCAAgleHqFnsurTYCbymecO4ohvtNO5zqIhHxV370t7nJAwaCfkgtffarKpC0GPt0g== dependencies: - airbnb-prop-types "^2.15.0" + airbnb-prop-types "^2.16.0" function.prototype.name "^1.1.2" object.assign "^4.1.0" object.fromentries "^2.0.2" prop-types "^15.7.2" semver "^5.7.1" -enzyme-shallow-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e" - integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ== +enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e" + integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q== dependencies: has "^1.0.3" - object-is "^1.0.2" + object-is "^1.1.2" enzyme@^3.11.0: version "3.11.0" @@ -8331,22 +8818,6 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.13.0: - version "1.16.3" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.3.tgz#52490d978f96ff9f89ec15b5cf244304a5bca161" - integrity sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-inspect "^1.7.0" - object-keys "^1.1.1" - string.prototype.trimleft "^2.1.0" - string.prototype.trimright "^2.1.0" - es-abstract@^1.17.0: version "1.17.5" resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" @@ -8398,6 +8869,41 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-abstract@^1.17.4: + version "1.17.7" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -9637,6 +10143,11 @@ fast-url-parser@1.1.3: dependencies: punycode "^1.3.2" +fastpriorityqueue@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/fastpriorityqueue/-/fastpriorityqueue-0.6.3.tgz#619ce78f86a839530a69165d5d676fbde920f4e2" + integrity sha512-l4Whw9/MDkl/0XuqZEzGE/sw9T7dIxuUnxqq4ZJDLt8AE45j8wkx4/nBRZm50aQ9kN71NB9mwQzglLsvQGROsw== + fastq@^1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" @@ -10129,7 +10640,7 @@ function-bind@^1.1.1, function-bind@~1.1.1: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.1, function.prototype.name@^1.1.2: +function.prototype.name@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== @@ -10283,6 +10794,15 @@ get-func-name@^2.0.0: resolved "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-intrinsic@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" @@ -10592,6 +11112,21 @@ graceful-fs@^4.2.0, graceful-fs@^4.2.4: resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= +graphlib-dot@^0.6.4: + version "0.6.4" + resolved "https://registry.npmjs.org/graphlib-dot/-/graphlib-dot-0.6.4.tgz#5423e33bd46618eb5afd56a99a2f996822c7dae7" + integrity sha512-rdhDTu0mBlloTpFMfkQq+e3y4yL22OqP5MhQbkw6QUURqa+4YLgv3XZy2fA64wdEcJNZ+waI76URemVgdFtzng== + dependencies: + graphlib "^2.1.8" + lodash "^4.17.15" + +graphlib@^2.1.8: + version "2.1.8" + resolved "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + growly@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -10705,7 +11240,7 @@ has-symbol-support-x@^1.4.1: resolved "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== -has-symbols@^1.0.0, has-symbols@^1.0.1: +has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== @@ -10820,9 +11355,9 @@ heap@0.2.6: integrity sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw= highlight.js@^9.12.0, highlight.js@^9.15.8: - version "9.18.1" - resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c" - integrity sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg== + version "9.18.5" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" + integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== highlightjs-solidity@^1.0.18: version "1.0.18" @@ -11073,7 +11608,7 @@ hyphenate-style-name@^1.0.2: resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -11386,6 +11921,11 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + is-ci@1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" @@ -11565,6 +12105,11 @@ is-negated-glob@^1.0.0: resolved "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" @@ -11657,6 +12202,13 @@ is-regex@^1.0.4, is-regex@^1.0.5, is-regex@~1.0.5: dependencies: has "^1.0.3" +is-regex@^1.1.0, is-regex@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + is-relative@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" @@ -11875,6 +12427,11 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +javascript-lp-solver@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/javascript-lp-solver/-/javascript-lp-solver-0.4.24.tgz#3bb5f8aa051f0bf04747e39130133a0f738f635c" + integrity sha512-5edoDKnMrt/u3M6GnZKDDIPxOyFOg+WrwDv8mjNiMC2DePhy2H9/FFQgf4ggywaXT1utvkxusJcjQUER72cZmA== + javascript-stringify@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.0.1.tgz#6ef358035310e35d667c675ed63d3eb7c1aa19e5" @@ -14326,12 +14883,25 @@ object-inspect@^1.7.0, object-inspect@~1.7.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== +object-inspect@^1.8.0: + version "1.9.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + object-is@^1.0.1, object-is@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-is@^1.1.2: + version "1.1.4" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz#63d6c83c00a43f4cbc9434eb9757c8a5b8565068" + integrity sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -14353,15 +14923,15 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== +object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.1: + version "4.1.2" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" object.defaults@^1.0.0, object.defaults@^1.1.0: version "1.1.0" @@ -14373,7 +14943,7 @@ object.defaults@^1.0.0, object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.0, object.entries@^1.1.1: +object.entries@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== @@ -14383,6 +14953,16 @@ object.entries@^1.1.0, object.entries@^1.1.1: function-bind "^1.1.1" has "^1.0.3" +object.entries@^1.1.2: + version "1.1.3" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" + integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + object.fromentries@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" @@ -15163,10 +15743,10 @@ postcss@^7.0.32: source-map "^0.6.1" supports-color "^6.1.0" -postinstall-postinstall@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.0.0.tgz#7ba6711b4420575c4f561638836a81faad47f43f" - integrity sha512-3f6qWexsHiT4WKtZc5DRb0FPLilHtARi5KpY4fqban/DJNn8/YhZH8U7dVKVz51WbOxEnR31gV+qYQhvEdHtdQ== +postinstall-postinstall@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" + integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== prebuild-install@^5.3.0, prebuild-install@^5.3.3: version "5.3.3" @@ -15513,6 +16093,11 @@ qs@~6.5.2: resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +quadprog@^1.6.1: + version "1.6.1" + resolved "https://registry.npmjs.org/quadprog/-/quadprog-1.6.1.tgz#1cd3b13700de9553ef939a6fa73d0d55ddb2f082" + integrity sha512-fN5Jkcjlln/b3pJkseDKREf89JkKIyu6cKIVXisgL6ocKPQ0yTp9n6NZUAq3otEPPw78WZMG9K0o9WsfKyMWJw== + query-string@^5.0.1: version "5.1.1" resolved "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -15684,10 +16269,10 @@ react-inspector@^2.3.1: is-dom "^1.0.9" prop-types "^15.6.1" -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.6.3, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: - version "16.13.0" - resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527" - integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA== +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.6.3, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-is@^17.0.1: version "17.0.1" @@ -16616,6 +17201,11 @@ rustbn.js@~0.2.0: resolved "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== +rw@1: + version "1.3.3" + resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= + rxjs@^5.0.0-beta.11: version "5.5.12" resolved "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" @@ -16698,7 +17288,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: +schema-utils@^2.6.5, schema-utils@^2.7.0: version "2.7.0" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== @@ -17591,6 +18181,14 @@ string.prototype.trim@^1.2.1, string.prototype.trim@~1.2.1: es-abstract "^1.17.0-next.1" function-bind "^1.1.1" +string.prototype.trimend@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" + integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + string.prototype.trimleft@^2.1.0, string.prototype.trimleft@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" @@ -17607,6 +18205,14 @@ string.prototype.trimright@^2.1.0, string.prototype.trimright@^2.1.1: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimstart@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" + integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -17710,13 +18316,13 @@ strip-json-comments@^3.0.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== -style-loader@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" - integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg== +style-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" + integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== dependencies: loader-utils "^2.0.0" - schema-utils "^2.6.6" + schema-utils "^3.0.0" super-split@^1.1.0: version "1.1.0"