diff --git a/contracts/coordinator/CHANGELOG.json b/contracts/coordinator/CHANGELOG.json index 1d4ce3877d..7b0001c8a4 100644 --- a/contracts/coordinator/CHANGELOG.json +++ b/contracts/coordinator/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "0.1.0", + "changes": [ + { + "note": "Added Coordinator Registry", + "pr": 1675 + } + ] + }, { "version": "0.0.1", "changes": [ diff --git a/contracts/coordinator/compiler.json b/contracts/coordinator/compiler.json index a300da2350..a5f8264110 100644 --- a/contracts/coordinator/compiler.json +++ b/contracts/coordinator/compiler.json @@ -17,5 +17,10 @@ } } }, - "contracts": ["src/Coordinator.sol", "test/TestLibs.sol", "test/TestMixins.sol"] + "contracts": [ + "src/Coordinator.sol", + "src/registry/CoordinatorRegistry.sol", + "test/TestLibs.sol", + "test/TestMixins.sol" + ] } diff --git a/contracts/coordinator/contracts/src/registry/CoordinatorRegistry.sol b/contracts/coordinator/contracts/src/registry/CoordinatorRegistry.sol new file mode 100644 index 0000000000..040cd03b34 --- /dev/null +++ b/contracts/coordinator/contracts/src/registry/CoordinatorRegistry.sol @@ -0,0 +1,33 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.5.3; +pragma experimental ABIEncoderV2; + +import "./MixinCoordinatorRegistryCore.sol"; + + +// solhint-disable no-empty-blocks +contract CoordinatorRegistry is + MixinCoordinatorRegistryCore +{ + constructor () + public + MixinCoordinatorRegistryCore() + {} +} diff --git a/contracts/coordinator/contracts/src/registry/MixinCoordinatorRegistryCore.sol b/contracts/coordinator/contracts/src/registry/MixinCoordinatorRegistryCore.sol new file mode 100644 index 0000000000..6a6dcd0089 --- /dev/null +++ b/contracts/coordinator/contracts/src/registry/MixinCoordinatorRegistryCore.sol @@ -0,0 +1,45 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.5.3; +pragma experimental ABIEncoderV2; + +import "./interfaces/ICoordinatorRegistryCore.sol"; + + +// solhint-disable no-empty-blocks +contract MixinCoordinatorRegistryCore is + ICoordinatorRegistryCore +{ + // mapping from `coordinatorOperator` -> `coordinatorEndpoint` + mapping (address => string) internal coordinatorEndpoints; + + /// @dev Called by a Coordinator operator to set the endpoint of their Coordinator. + /// @param coordinatorEndpoint endpoint of the Coordinator. + function setCoordinatorEndpoint(string calldata coordinatorEndpoint) external { + address coordinatorOperator = msg.sender; + coordinatorEndpoints[coordinatorOperator] = coordinatorEndpoint; + emit CoordinatorEndpointSet(coordinatorOperator, coordinatorEndpoint); + } + + /// @dev Gets the endpoint for a Coordinator. + /// @param coordinatorOperator operator of the Coordinator endpoint. + function getCoordinatorEndpoint(address coordinatorOperator) external view returns (string memory coordinatorEndpoint) { + return coordinatorEndpoints[coordinatorOperator]; + } +} diff --git a/contracts/coordinator/contracts/src/registry/interfaces/ICoordinatorRegistryCore.sol b/contracts/coordinator/contracts/src/registry/interfaces/ICoordinatorRegistryCore.sol new file mode 100644 index 0000000000..f080d7c51b --- /dev/null +++ b/contracts/coordinator/contracts/src/registry/interfaces/ICoordinatorRegistryCore.sol @@ -0,0 +1,39 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.5.3; +pragma experimental ABIEncoderV2; + + +// solhint-disable no-empty-blocks +contract ICoordinatorRegistryCore +{ + /// @dev Emitted when a Coordinator endpoint is set. + event CoordinatorEndpointSet( + address coordinatorOperator, + string coordinatorEndpoint + ); + + /// @dev Called by a Coordinator operator to set the endpoint of their Coordinator. + /// @param coordinatorEndpoint endpoint of the Coordinator. + function setCoordinatorEndpoint(string calldata coordinatorEndpoint) external; + + /// @dev Gets the endpoint for a Coordinator. + /// @param coordinatorOperator operator of the Coordinator endpoint. + function getCoordinatorEndpoint(address coordinatorOperator) external view returns (string memory coordinatorEndpoint); +} diff --git a/contracts/coordinator/package.json b/contracts/coordinator/package.json index fa4c2beea6..b78e923e62 100644 --- a/contracts/coordinator/package.json +++ b/contracts/coordinator/package.json @@ -33,7 +33,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(Coordinator|TestLibs|TestMixins).json", + "abis": "./generated-artifacts/@(Coordinator|CoordinatorRegistry|TestLibs|TestMixins).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/coordinator/src/artifacts.ts b/contracts/coordinator/src/artifacts.ts index 2e4450f27f..e32e231879 100644 --- a/contracts/coordinator/src/artifacts.ts +++ b/contracts/coordinator/src/artifacts.ts @@ -6,10 +6,12 @@ import { ContractArtifact } from 'ethereum-types'; import * as Coordinator from '../generated-artifacts/Coordinator.json'; +import * as CoordinatorRegistry from '../generated-artifacts/CoordinatorRegistry.json'; import * as TestLibs from '../generated-artifacts/TestLibs.json'; import * as TestMixins from '../generated-artifacts/TestMixins.json'; export const artifacts = { Coordinator: Coordinator as ContractArtifact, + CoordinatorRegistry: CoordinatorRegistry as ContractArtifact, TestLibs: TestLibs as ContractArtifact, TestMixins: TestMixins as ContractArtifact, }; diff --git a/contracts/coordinator/src/wrappers.ts b/contracts/coordinator/src/wrappers.ts index e6d6dc8753..d8e6cc9857 100644 --- a/contracts/coordinator/src/wrappers.ts +++ b/contracts/coordinator/src/wrappers.ts @@ -4,5 +4,6 @@ * ----------------------------------------------------------------------------- */ export * from '../generated-wrappers/coordinator'; +export * from '../generated-wrappers/coordinator_registry'; export * from '../generated-wrappers/test_libs'; export * from '../generated-wrappers/test_mixins'; diff --git a/contracts/coordinator/test/coordinator_registry.ts b/contracts/coordinator/test/coordinator_registry.ts new file mode 100644 index 0000000000..c9404cc939 --- /dev/null +++ b/contracts/coordinator/test/coordinator_registry.ts @@ -0,0 +1,81 @@ +import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange'; +import { chaiSetup, provider, web3Wrapper } from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; + +import { CoordinatorRegistryCoordinatorEndpointSetEventArgs } from '../src'; + +import { CoordinatorRegistryWrapper } from './utils/coordinator_registry_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +web3Wrapper.abiDecoder.addABI(exchangeArtifacts.Exchange.compilerOutput.abi); +// tslint:disable:no-unnecessary-type-assertion +describe('Coordinator Registry tests', () => { + let coordinatorOperator: string; + const coordinatorEndpoint = 'http://sometec.0x.org'; + const nilCoordinatorEndpoint = ''; + let coordinatorRegistryWrapper: CoordinatorRegistryWrapper; + // tests + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + // setup accounts (skip owner) + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + [, coordinatorOperator] = accounts; + // deploy coordinator registry + coordinatorRegistryWrapper = new CoordinatorRegistryWrapper(provider); + await coordinatorRegistryWrapper.deployCoordinatorRegistryAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('core', () => { + it('Should successfully set a Coordinator endpoint', async () => { + await coordinatorRegistryWrapper.setCoordinatorEndpointAsync(coordinatorOperator, coordinatorEndpoint); + const recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync( + coordinatorOperator, + ); + expect(recordedCoordinatorEndpoint).to.be.equal(coordinatorEndpoint); + }); + it('Should successfully unset a Coordinator endpoint', async () => { + // set Coordinator endpoint + await coordinatorRegistryWrapper.setCoordinatorEndpointAsync(coordinatorOperator, coordinatorEndpoint); + let recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync( + coordinatorOperator, + ); + expect(recordedCoordinatorEndpoint).to.be.equal(coordinatorEndpoint); + // unset Coordinator endpoint + await coordinatorRegistryWrapper.setCoordinatorEndpointAsync(coordinatorOperator, nilCoordinatorEndpoint); + recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync( + coordinatorOperator, + ); + expect(recordedCoordinatorEndpoint).to.be.equal(nilCoordinatorEndpoint); + }); + it('Should emit an event when setting Coordinator endpoint', async () => { + // set Coordinator endpoint + const txReceipt = await coordinatorRegistryWrapper.setCoordinatorEndpointAsync( + coordinatorOperator, + coordinatorEndpoint, + ); + const recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync( + coordinatorOperator, + ); + expect(recordedCoordinatorEndpoint).to.be.equal(coordinatorEndpoint); + // validate event + expect(txReceipt.logs.length).to.be.equal(1); + const log = txReceipt.logs[0] as LogWithDecodedArgs; + expect(log.args.coordinatorOperator).to.be.equal(coordinatorOperator); + expect(log.args.coordinatorEndpoint).to.be.equal(coordinatorEndpoint); + }); + }); +}); diff --git a/contracts/coordinator/test/utils/coordinator_registry_wrapper.ts b/contracts/coordinator/test/utils/coordinator_registry_wrapper.ts new file mode 100644 index 0000000000..70c9477929 --- /dev/null +++ b/contracts/coordinator/test/utils/coordinator_registry_wrapper.ts @@ -0,0 +1,65 @@ +import { LogDecoder, txDefaults } from '@0x/contracts-test-utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { TransactionReceiptWithDecodedLogs, ZeroExProvider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { artifacts, CoordinatorRegistryContract } from '../../src'; + +export class CoordinatorRegistryWrapper { + private readonly _web3Wrapper: Web3Wrapper; + private readonly _provider: ZeroExProvider; + private readonly _logDecoder: LogDecoder; + private _coordinatorRegistryContract?: CoordinatorRegistryContract; + /** + * Instanitates an CoordinatorRegistryWrapper + * @param provider Web3 provider to use for all JSON RPC requests + * Instance of CoordinatorRegistryWrapper + */ + constructor(provider: ZeroExProvider) { + this._web3Wrapper = new Web3Wrapper(provider); + this._provider = provider; + this._logDecoder = new LogDecoder(this._web3Wrapper, artifacts); + } + public async deployCoordinatorRegistryAsync(): Promise { + this._coordinatorRegistryContract = await CoordinatorRegistryContract.deployFrom0xArtifactAsync( + artifacts.CoordinatorRegistry, + this._provider, + txDefaults, + ); + if (_.isUndefined(this._coordinatorRegistryContract)) { + throw new Error(`Failed to deploy Coordinator Registry contract.`); + } + return this._coordinatorRegistryContract; + } + public async setCoordinatorEndpointAsync( + coordinatorOperator: string, + coordinatorEndpoint: string, + ): Promise { + this._assertCoordinatorRegistryDeployed(); + const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync( + await (this + ._coordinatorRegistryContract as CoordinatorRegistryContract).setCoordinatorEndpoint.sendTransactionAsync( + coordinatorEndpoint, + { + from: coordinatorOperator, + }, + ), + ); + return txReceipt; + } + public async getCoordinatorEndpointAsync(coordinatorOperator: string): Promise { + this._assertCoordinatorRegistryDeployed(); + const coordinatorEndpoint = await (this + ._coordinatorRegistryContract as CoordinatorRegistryContract).getCoordinatorEndpoint.callAsync( + coordinatorOperator, + ); + return coordinatorEndpoint; + } + private _assertCoordinatorRegistryDeployed(): void { + if (_.isUndefined(this._coordinatorRegistryContract)) { + throw new Error( + 'The Coordinator Registry contract was not deployed through the CoordinatorRegistryWrapper. Call `deployCoordinatorRegistryAsync` to deploy.', + ); + } + } +} diff --git a/contracts/coordinator/tsconfig.json b/contracts/coordinator/tsconfig.json index adbd5d2853..9de6c34a60 100644 --- a/contracts/coordinator/tsconfig.json +++ b/contracts/coordinator/tsconfig.json @@ -4,6 +4,7 @@ "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ "generated-artifacts/Coordinator.json", + "generated-artifacts/CoordinatorRegistry.json", "generated-artifacts/TestLibs.json", "generated-artifacts/TestMixins.json" ],