feat: contract address management (#405)

Co-authored-by: Eric Mastro <github@egonat.me>
This commit is contained in:
Adam Uhlíř 2023-05-03 09:24:25 +02:00 committed by GitHub
parent ff6cc01857
commit 4d028c6cb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 144 additions and 94 deletions

View File

@ -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),

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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())

View File

@ -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()

View File

@ -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)

View File

@ -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