diff --git a/codex/codex.nim b/codex/codex.nim index f90ab69e..9926811c 100644 --- a/codex/codex.nim +++ b/codex/codex.nim @@ -33,6 +33,7 @@ import ./erasure import ./discovery import ./contracts import ./contracts/clock +import ./contracts/deployment import ./utils/addrutils import ./namespaces @@ -50,11 +51,54 @@ type CodexPrivateKey* = libp2p.PrivateKey # alias +proc bootstrapInteractions(config: CodexConf, repo: RepoStore): Future[Contracts] {.async.} = + + if not config.persistence and not config.validator: + if config.ethAccount.isSome: + warn "Ethereum account was set, but neither persistence nor validator is enabled" + return + + without account =? config.ethAccount: + if config.persistence: + error "Persistence enabled, but no Ethereum account was set" + if config.validator: + error "Validator enabled, but no Ethereum account was set" + quit QuitFailure + + let provider = JsonRpcProvider.new(config.ethProvider) + let signer = provider.getSigner(account) + + let deploy = Deployment.new(provider, config) + without marketplaceAddress =? await deploy.address(Marketplace): + error "No Marketplace address was specified or there is no known address for the current network" + quit QuitFailure + + let marketplace = Marketplace.new(marketplaceAddress, signer) + let market = OnChainMarket.new(marketplace) + let clock = OnChainClock.new(provider) + + var client: ?ClientInteractions + var host: ?HostInteractions + var validator: ?ValidatorInteractions + if config.persistence: + let purchasing = Purchasing.new(market, clock) + let proving = Proving.new(market, clock) + let sales = Sales.new(market, clock, proving, repo) + client = some ClientInteractions.new(clock, purchasing) + host = some HostInteractions.new(clock, sales, proving) + if config.validator: + let validation = Validation.new(clock, market, config.validatorMaxSlots) + validator = some ValidatorInteractions.new(clock, validation) + + return (client, host, validator) + proc start*(s: CodexServer) {.async.} = notice "Starting codex node" await s.repoStore.start() s.restServer.start() + + s.codexNode.contracts = await bootstrapInteractions(s.config, s.repoStore) await s.codexNode.start() s.maintenance.start() @@ -97,57 +141,6 @@ proc stop*(s: CodexServer) {.async.} = s.runHandle.complete() -proc new(_: type Contracts, - config: CodexConf, - repo: RepoStore): Contracts = - - if not config.persistence and not config.validator: - if config.ethAccount.isSome: - warn "Ethereum account was set, but neither persistence nor validator is enabled" - return - - without account =? config.ethAccount: - if config.persistence: - error "Persistence enabled, but no Ethereum account was set" - if config.validator: - error "Validator enabled, but no Ethereum account was set" - quit QuitFailure - - var deploy: Deployment - try: - if deployFile =? config.ethDeployment: - deploy = Deployment.init(deployFile) - else: - deploy = Deployment.init() - except IOError as e: - error "Unable to read deployment json" - quit QuitFailure - - without marketplaceAddress =? deploy.address(Marketplace): - error "Marketplace contract address not found in deployment file" - quit QuitFailure - - let provider = JsonRpcProvider.new(config.ethProvider) - let signer = provider.getSigner(account) - let marketplace = Marketplace.new(marketplaceAddress, signer) - let market = OnChainMarket.new(marketplace) - let clock = OnChainClock.new(provider) - - var client: ?ClientInteractions - var host: ?HostInteractions - var validator: ?ValidatorInteractions - if config.persistence: - let purchasing = Purchasing.new(market, clock) - let proving = Proving.new(market, clock) - let sales = Sales.new(market, clock, proving, repo) - client = some ClientInteractions.new(clock, purchasing) - host = some HostInteractions.new(clock, sales, proving) - if config.validator: - let validation = Validation.new(clock, market, config.validatorMaxSlots) - validator = some ValidatorInteractions.new(clock, validation) - - (client, host, validator) - proc new*(T: type CodexServer, config: CodexConf, privateKey: CodexPrivateKey): T = let @@ -219,8 +212,7 @@ proc new*(T: type CodexServer, config: CodexConf, privateKey: CodexPrivateKey): engine = BlockExcEngine.new(repoStore, wallet, network, blockDiscovery, peerStore, pendingBlocks) store = NetworkStore.new(engine, repoStore) erasure = Erasure.new(store, leoEncoderProvider, leoDecoderProvider) - contracts = Contracts.new(config, repoStore) - codexNode = CodexNodeRef.new(switch, store, engine, erasure, discovery, contracts) + codexNode = CodexNodeRef.new(switch, store, engine, erasure, discovery) restServer = RestServerRef.new( codexNode.initRestApi(config), initTAddress(config.apiBindAddress , config.apiPort), diff --git a/codex/conf.nim b/codex/conf.nim index f7e75787..0fb365bb 100644 --- a/codex/conf.nim +++ b/codex/conf.nim @@ -219,11 +219,11 @@ type name: "eth-account" .}: Option[EthAddress] - ethDeployment* {. - desc: "The json file describing the contract deployment" - defaultValue: string.none - name: "eth-deployment" - .}: Option[string] + marketplaceAddress* {. + desc: "Address of deployed Marketplace contract" + defaultValue: EthAddress.none + name: "marketplace-address" + .}: Option[EthAddress] validator* {. desc: "Enables validator, requires an Ethereum node" diff --git a/codex/contracts.nim b/codex/contracts.nim index a9b99ac3..ecf298f4 100644 --- a/codex/contracts.nim +++ b/codex/contracts.nim @@ -1,11 +1,9 @@ import contracts/requests import contracts/marketplace -import contracts/deployment import contracts/market import contracts/interactions export requests export marketplace -export deployment export market export interactions diff --git a/codex/contracts/deployment.nim b/codex/contracts/deployment.nim index 408a7503..c5526064 100644 --- a/codex/contracts/deployment.nim +++ b/codex/contracts/deployment.nim @@ -1,26 +1,40 @@ import std/json import std/os +import std/tables import pkg/ethers import pkg/questionable +import pkg/chronicles -type Deployment* = object - json: JsonNode +import ../conf +import ./marketplace -const defaultFile = "vendor" / "codex-contracts-eth" / "deployment-localhost.json" +type Deployment* = ref object + provider: Provider + config: CodexConf -## Reads deployment information from a json file. It expects a file that has -## been exported with Hardhat deploy. -## See also: -## https://github.com/wighawag/hardhat-deploy/tree/master#6-hardhat-export -proc init*(_: type Deployment, file: string = defaultFile): Deployment = - Deployment(json: parseFile(file)) +const knownAddresses = { + # Hardhat localhost network + "31337": { + "Marketplace": Address.init("0x59b670e9fA9D0A427751Af201D676719a970857b") + }.toTable +}.toTable -proc address*(deployment: Deployment, Contract: typedesc): ?Address = - if deployment.json == nil: +proc getKnownAddress(T: type, chainId: UInt256): ?Address = + let id = chainId.toString(10) + notice "Looking for well-known contract address with ChainID ", chainId=id + + if not (id in knownAddresses): return none Address - try: - let address = deployment.json["contracts"][$Contract]["address"].getStr() - Address.init(address) - except KeyError: - none Address + return knownAddresses[id].getOrDefault($T, Address.none) + +proc new*(_: type Deployment, provider: Provider, config: CodexConf): Deployment = + Deployment(provider: provider, config: config) + +proc address*(deployment: Deployment, contract: type): Future[?Address] {.async.} = + when contract is Marketplace: + if address =? deployment.config.marketplaceAddress: + return some address + + let chainId = await deployment.provider.getChainId() + return contract.getKnownAddress(chainId) diff --git a/tests/contracts/deployment.nim b/tests/contracts/deployment.nim new file mode 100644 index 00000000..7689a302 --- /dev/null +++ b/tests/contracts/deployment.nim @@ -0,0 +1,16 @@ +import std/os +import pkg/ethers +import pkg/codex/contracts/marketplace + +const hardhatMarketAddress = Address.init("0x59b670e9fA9D0A427751Af201D676719a970857b").get() +const marketAddressEnvName = "CODEX_MARKET_ADDRESS" + +proc address*(_: type Marketplace): Address = + if existsEnv(marketAddressEnvName): + without address =? Address.init(getEnv(marketAddressEnvName)): + raiseAssert "Invalid env. variable marketplace contract address" + + return address + + hardhatMarketAddress + diff --git a/tests/contracts/testContracts.nim b/tests/contracts/testContracts.nim index a6d41d2c..8792bb6b 100644 --- a/tests/contracts/testContracts.nim +++ b/tests/contracts/testContracts.nim @@ -6,6 +6,7 @@ import codex/contracts import ../ethertest import ./examples import ./time +import ./deployment ethersuite "Marketplace contracts": let proof = exampleProof() @@ -25,8 +26,7 @@ ethersuite "Marketplace contracts": client = provider.getSigner(accounts[0]) host = provider.getSigner(accounts[1]) - let deployment = Deployment.init() - marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner()) + marketplace = Marketplace.new(Marketplace.address, provider.getSigner()) let tokenAddress = await marketplace.token() token = Erc20Token.new(tokenAddress, provider.getSigner()) diff --git a/tests/contracts/testDeployment.nim b/tests/contracts/testDeployment.nim index 9a9cdd4c..0a2695a2 100644 --- a/tests/contracts/testDeployment.nim +++ b/tests/contracts/testDeployment.nim @@ -1,17 +1,47 @@ import std/os -import pkg/codex/contracts -import pkg/codex/stores -import ../ethertest +import pkg/asynctest +import pkg/ethers +import codex/contracts/deployment +import codex/conf +import codex/contracts + + +type MockProvider = ref object of Provider + chainId*: UInt256 + +method getChainId*(provider: MockProvider): Future[UInt256] {.async.} = + return provider.chainId + +proc configFactory(): CodexConf = + CodexConf(cmd: noCommand, nat: ValidIpAddress.init("127.0.0.1"), discoveryIp: ValidIpAddress.init(IPv4_any()), metricsAddress: ValidIpAddress.init("127.0.0.1")) + +proc configFactory(marketplace: Option[EthAddress]): CodexConf = + CodexConf(cmd: noCommand, nat: ValidIpAddress.init("127.0.0.1"), discoveryIp: ValidIpAddress.init(IPv4_any()), metricsAddress: ValidIpAddress.init("127.0.0.1"), marketplaceAddress: marketplace) suite "Deployment": - let deploymentFile = "vendor" / "codex-contracts-eth" / "deployment-localhost.json" - test "can be instantiated with a deployment file": - discard Deployment.init(deploymentFile) + let provider = MockProvider() - test "contract address can be retreived from deployment json": - let deployment = Deployment.init(deploymentFile) - check deployment.address(Marketplace).isSome + test "uses conf value as priority": + let deployment = Deployment.new(provider, configFactory(EthAddress.init("0x59b670e9fA9D0A427751Af201D676719a970aaaa"))) + provider.chainId = 1.u256 + + let address = await deployment.address(Marketplace) + check address.isSome + check $(!address) == "0x59b670e9fa9d0a427751af201d676719a970aaaa" + + test "uses chainId hardcoded values as fallback": + let deployment = Deployment.new(provider, configFactory()) + provider.chainId = 31337.u256 + + let address = await deployment.address(Marketplace) + check address.isSome + check $(!address) == "0x59b670e9fa9d0a427751af201d676719a970857b" + + test "return none for unknown networks": + let deployment = Deployment.new(provider, configFactory()) + provider.chainId = 1.u256 + + let address = await deployment.address(Marketplace) + check address.isNone - test "can be instantiated without a deployment file": - discard Deployment.init() diff --git a/tests/contracts/testMarket.nim b/tests/contracts/testMarket.nim index fab1623d..a8b7676c 100644 --- a/tests/contracts/testMarket.nim +++ b/tests/contracts/testMarket.nim @@ -5,6 +5,7 @@ import codex/contracts import ../ethertest import ./examples import ./time +import ./deployment ethersuite "On-Chain Market": let proof = exampleProof() @@ -16,8 +17,7 @@ ethersuite "On-Chain Market": var periodicity: Periodicity setup: - let deployment = Deployment.init() - marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner()) + marketplace = Marketplace.new(Marketplace.address, provider.getSigner()) let config = await marketplace.config() market = OnChainMarket.new(marketplace) diff --git a/tests/integration/testproofs.nim b/tests/integration/testproofs.nim index f19eff22..4f907ad3 100644 --- a/tests/integration/testproofs.nim +++ b/tests/integration/testproofs.nim @@ -3,6 +3,7 @@ import codex/contracts/marketplace import codex/contracts/deployment import codex/periods import ../contracts/time +import ../contracts/deployment import ../codex/helpers/eventually import ./twonodes @@ -14,8 +15,7 @@ twonodessuite "Proving integration test", debug1=false, debug2=false: var period: uint64 setup: - let deployment = Deployment.init() - marketplace = Marketplace.new(!deployment.address(Marketplace), provider) + marketplace = Marketplace.new(Marketplace.address, provider) period = (await marketplace.config()).proofs.period.truncate(uint64) # Our Hardhat configuration does use automine, which means that time tracked by `provider.currentTime()` is not diff --git a/vendor/codex-contracts-eth b/vendor/codex-contracts-eth index 6e66abbf..30affa0d 160000 --- a/vendor/codex-contracts-eth +++ b/vendor/codex-contracts-eth @@ -1 +1 @@ -Subproject commit 6e66abbfcd9be6cbd0434dc5a80f1419c66a914e +Subproject commit 30affa0da85985f6dc90b62f6293de46a9e26130