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 ./discovery
|
||||||
import ./contracts
|
import ./contracts
|
||||||
import ./contracts/clock
|
import ./contracts/clock
|
||||||
|
import ./contracts/deployment
|
||||||
import ./utils/addrutils
|
import ./utils/addrutils
|
||||||
import ./namespaces
|
import ./namespaces
|
||||||
|
|
||||||
|
@ -50,11 +51,54 @@ type
|
||||||
|
|
||||||
CodexPrivateKey* = libp2p.PrivateKey # alias
|
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.} =
|
proc start*(s: CodexServer) {.async.} =
|
||||||
notice "Starting codex node"
|
notice "Starting codex node"
|
||||||
|
|
||||||
await s.repoStore.start()
|
await s.repoStore.start()
|
||||||
s.restServer.start()
|
s.restServer.start()
|
||||||
|
|
||||||
|
s.codexNode.contracts = await bootstrapInteractions(s.config, s.repoStore)
|
||||||
await s.codexNode.start()
|
await s.codexNode.start()
|
||||||
s.maintenance.start()
|
s.maintenance.start()
|
||||||
|
|
||||||
|
@ -97,57 +141,6 @@ proc stop*(s: CodexServer) {.async.} =
|
||||||
|
|
||||||
s.runHandle.complete()
|
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 =
|
proc new*(T: type CodexServer, config: CodexConf, privateKey: CodexPrivateKey): T =
|
||||||
|
|
||||||
let
|
let
|
||||||
|
@ -219,8 +212,7 @@ proc new*(T: type CodexServer, config: CodexConf, privateKey: CodexPrivateKey):
|
||||||
engine = BlockExcEngine.new(repoStore, wallet, network, blockDiscovery, peerStore, pendingBlocks)
|
engine = BlockExcEngine.new(repoStore, wallet, network, blockDiscovery, peerStore, pendingBlocks)
|
||||||
store = NetworkStore.new(engine, repoStore)
|
store = NetworkStore.new(engine, repoStore)
|
||||||
erasure = Erasure.new(store, leoEncoderProvider, leoDecoderProvider)
|
erasure = Erasure.new(store, leoEncoderProvider, leoDecoderProvider)
|
||||||
contracts = Contracts.new(config, repoStore)
|
codexNode = CodexNodeRef.new(switch, store, engine, erasure, discovery)
|
||||||
codexNode = CodexNodeRef.new(switch, store, engine, erasure, discovery, contracts)
|
|
||||||
restServer = RestServerRef.new(
|
restServer = RestServerRef.new(
|
||||||
codexNode.initRestApi(config),
|
codexNode.initRestApi(config),
|
||||||
initTAddress(config.apiBindAddress , config.apiPort),
|
initTAddress(config.apiBindAddress , config.apiPort),
|
||||||
|
|
|
@ -219,11 +219,11 @@ type
|
||||||
name: "eth-account"
|
name: "eth-account"
|
||||||
.}: Option[EthAddress]
|
.}: Option[EthAddress]
|
||||||
|
|
||||||
ethDeployment* {.
|
marketplaceAddress* {.
|
||||||
desc: "The json file describing the contract deployment"
|
desc: "Address of deployed Marketplace contract"
|
||||||
defaultValue: string.none
|
defaultValue: EthAddress.none
|
||||||
name: "eth-deployment"
|
name: "marketplace-address"
|
||||||
.}: Option[string]
|
.}: Option[EthAddress]
|
||||||
|
|
||||||
validator* {.
|
validator* {.
|
||||||
desc: "Enables validator, requires an Ethereum node"
|
desc: "Enables validator, requires an Ethereum node"
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import contracts/requests
|
import contracts/requests
|
||||||
import contracts/marketplace
|
import contracts/marketplace
|
||||||
import contracts/deployment
|
|
||||||
import contracts/market
|
import contracts/market
|
||||||
import contracts/interactions
|
import contracts/interactions
|
||||||
|
|
||||||
export requests
|
export requests
|
||||||
export marketplace
|
export marketplace
|
||||||
export deployment
|
|
||||||
export market
|
export market
|
||||||
export interactions
|
export interactions
|
||||||
|
|
|
@ -1,26 +1,40 @@
|
||||||
import std/json
|
import std/json
|
||||||
import std/os
|
import std/os
|
||||||
|
import std/tables
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
|
import pkg/chronicles
|
||||||
|
|
||||||
type Deployment* = object
|
import ../conf
|
||||||
json: JsonNode
|
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
|
const knownAddresses = {
|
||||||
## been exported with Hardhat deploy.
|
# Hardhat localhost network
|
||||||
## See also:
|
"31337": {
|
||||||
## https://github.com/wighawag/hardhat-deploy/tree/master#6-hardhat-export
|
"Marketplace": Address.init("0x59b670e9fA9D0A427751Af201D676719a970857b")
|
||||||
proc init*(_: type Deployment, file: string = defaultFile): Deployment =
|
}.toTable
|
||||||
Deployment(json: parseFile(file))
|
}.toTable
|
||||||
|
|
||||||
proc address*(deployment: Deployment, Contract: typedesc): ?Address =
|
proc getKnownAddress(T: type, chainId: UInt256): ?Address =
|
||||||
if deployment.json == nil:
|
let id = chainId.toString(10)
|
||||||
|
notice "Looking for well-known contract address with ChainID ", chainId=id
|
||||||
|
|
||||||
|
if not (id in knownAddresses):
|
||||||
return none Address
|
return none Address
|
||||||
|
|
||||||
try:
|
return knownAddresses[id].getOrDefault($T, Address.none)
|
||||||
let address = deployment.json["contracts"][$Contract]["address"].getStr()
|
|
||||||
Address.init(address)
|
proc new*(_: type Deployment, provider: Provider, config: CodexConf): Deployment =
|
||||||
except KeyError:
|
Deployment(provider: provider, config: config)
|
||||||
none Address
|
|
||||||
|
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 ../ethertest
|
||||||
import ./examples
|
import ./examples
|
||||||
import ./time
|
import ./time
|
||||||
|
import ./deployment
|
||||||
|
|
||||||
ethersuite "Marketplace contracts":
|
ethersuite "Marketplace contracts":
|
||||||
let proof = exampleProof()
|
let proof = exampleProof()
|
||||||
|
@ -25,8 +26,7 @@ ethersuite "Marketplace contracts":
|
||||||
client = provider.getSigner(accounts[0])
|
client = provider.getSigner(accounts[0])
|
||||||
host = provider.getSigner(accounts[1])
|
host = provider.getSigner(accounts[1])
|
||||||
|
|
||||||
let deployment = Deployment.init()
|
marketplace = Marketplace.new(Marketplace.address, provider.getSigner())
|
||||||
marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner())
|
|
||||||
|
|
||||||
let tokenAddress = await marketplace.token()
|
let tokenAddress = await marketplace.token()
|
||||||
token = Erc20Token.new(tokenAddress, provider.getSigner())
|
token = Erc20Token.new(tokenAddress, provider.getSigner())
|
||||||
|
|
|
@ -1,17 +1,47 @@
|
||||||
import std/os
|
import std/os
|
||||||
import pkg/codex/contracts
|
import pkg/asynctest
|
||||||
import pkg/codex/stores
|
import pkg/ethers
|
||||||
import ../ethertest
|
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":
|
suite "Deployment":
|
||||||
let deploymentFile = "vendor" / "codex-contracts-eth" / "deployment-localhost.json"
|
|
||||||
|
|
||||||
test "can be instantiated with a deployment file":
|
let provider = MockProvider()
|
||||||
discard Deployment.init(deploymentFile)
|
|
||||||
|
|
||||||
test "contract address can be retreived from deployment json":
|
test "uses conf value as priority":
|
||||||
let deployment = Deployment.init(deploymentFile)
|
let deployment = Deployment.new(provider, configFactory(EthAddress.init("0x59b670e9fA9D0A427751Af201D676719a970aaaa")))
|
||||||
check deployment.address(Marketplace).isSome
|
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 ../ethertest
|
||||||
import ./examples
|
import ./examples
|
||||||
import ./time
|
import ./time
|
||||||
|
import ./deployment
|
||||||
|
|
||||||
ethersuite "On-Chain Market":
|
ethersuite "On-Chain Market":
|
||||||
let proof = exampleProof()
|
let proof = exampleProof()
|
||||||
|
@ -16,8 +17,7 @@ ethersuite "On-Chain Market":
|
||||||
var periodicity: Periodicity
|
var periodicity: Periodicity
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
let deployment = Deployment.init()
|
marketplace = Marketplace.new(Marketplace.address, provider.getSigner())
|
||||||
marketplace = Marketplace.new(!deployment.address(Marketplace), provider.getSigner())
|
|
||||||
let config = await marketplace.config()
|
let config = await marketplace.config()
|
||||||
|
|
||||||
market = OnChainMarket.new(marketplace)
|
market = OnChainMarket.new(marketplace)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import codex/contracts/marketplace
|
||||||
import codex/contracts/deployment
|
import codex/contracts/deployment
|
||||||
import codex/periods
|
import codex/periods
|
||||||
import ../contracts/time
|
import ../contracts/time
|
||||||
|
import ../contracts/deployment
|
||||||
import ../codex/helpers/eventually
|
import ../codex/helpers/eventually
|
||||||
import ./twonodes
|
import ./twonodes
|
||||||
|
|
||||||
|
@ -14,8 +15,7 @@ twonodessuite "Proving integration test", debug1=false, debug2=false:
|
||||||
var period: uint64
|
var period: uint64
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
let deployment = Deployment.init()
|
marketplace = Marketplace.new(Marketplace.address, provider)
|
||||||
marketplace = Marketplace.new(!deployment.address(Marketplace), provider)
|
|
||||||
period = (await marketplace.config()).proofs.period.truncate(uint64)
|
period = (await marketplace.config()).proofs.period.truncate(uint64)
|
||||||
|
|
||||||
# Our Hardhat configuration does use automine, which means that time tracked by `provider.currentTime()` is not
|
# 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