feat: contract address management (#405)
Co-authored-by: Eric Mastro <github@egonat.me>
This commit is contained in:
parent
ff6cc01857
commit
4d028c6cb3
|
@ -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),
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 6e66abbfcd9be6cbd0434dc5a80f1419c66a914e
|
||||
Subproject commit 30affa0da85985f6dc90b62f6293de46a9e26130
|
Loading…
Reference in New Issue