mirror of
https://github.com/logos-storage/logos-storage-contracts-eth.git
synced 2026-01-04 14:23:10 +00:00
Merge 64174d8ce666e33d21e17acea76b2b66e0f1b6c8 into da1400ce9a65c3695d7847f7ca8e3386a5a189c8
This commit is contained in:
commit
8a7cb119ca
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -17,6 +17,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- run: npm install
|
||||
- run: npm run lint
|
||||
- run: npm run format:check
|
||||
|
||||
test:
|
||||
@ -30,11 +31,6 @@ jobs:
|
||||
node-version: 22
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: fuzzing/corpus
|
||||
key: fuzzing
|
||||
- run: npm run fuzz
|
||||
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
206
README.md
Normal file
206
README.md
Normal file
@ -0,0 +1,206 @@
|
||||
# Codex Marketplace Contracts
|
||||
|
||||
An implementation of the smart contracts that underlay the Codex
|
||||
storage network. Its goal is to facilitate storage marketplace
|
||||
for the Codex's persistance layer.
|
||||
|
||||
## Running
|
||||
|
||||
To run the tests, execute the following commands:
|
||||
|
||||
npm install
|
||||
npm test
|
||||
|
||||
You can also run fuzzing tests (using [Echidna][echidna]) on the contracts:
|
||||
|
||||
npm run fuzz
|
||||
|
||||
To start a local Ethereum node with the contracts deployed, execute:
|
||||
|
||||
npm start
|
||||
|
||||
### Running the prover
|
||||
|
||||
To run the formal verification rules using Certora, first, make sure you have Java (JDK >= 11.0) installed on your
|
||||
machine, and then install the Certora CLI
|
||||
|
||||
```
|
||||
$ pip install certora-cli
|
||||
```
|
||||
|
||||
Once that is done the `certoraRun` command can be used to send CVL specs to the prover.
|
||||
|
||||
You can run Certora's specs with the provided `npm` script:
|
||||
|
||||
npm run verify
|
||||
|
||||
## Deployment
|
||||
|
||||
To deploy the marketplace, you need to specify the network using `--network MY_NETWORK`:
|
||||
|
||||
```bash
|
||||
npm run deploy -- --network localhost
|
||||
```
|
||||
|
||||
Hardhat uses [reconciliation](https://hardhat.org/ignition/docs/advanced/reconciliation) to recover from
|
||||
errors or resume a previous deployment. In our case, we will likely redeploy a new contract every time,
|
||||
so we will need
|
||||
to [clear the previous deployment](https://hardhat.org/ignition/docs/guides/modifications#clearing-an-existing-deployment-with-reset):
|
||||
|
||||
```bash
|
||||
npm run deploy -- --network testnet --reset
|
||||
```
|
||||
|
||||
To reuse a previously deployed `Token` contract, define the environment variable `TOKEN_ADDRESS`.
|
||||
The deployment script will use `contractAt` from Hardhat Ignition to retrieve the existing contract
|
||||
instead of deploying a new one.
|
||||
|
||||
When deploying to other network then Hardhat localhost's, you have to specify the Proxy's owner address
|
||||
using the env. variable `PROXY_ADMIN_ADDRESS`. This account then can perform upgrades to the contract.
|
||||
|
||||
The deployment files are kept under version
|
||||
control [as recommended by Hardhat](https://hardhat.org/ignition/docs/advanced/versioning), except the build files,
|
||||
which are 18 MB.
|
||||
|
||||
## Smart contracts overview
|
||||
|
||||
This contract suite deploys two smart contracts:
|
||||
|
||||
1. `Marketplace` smart contract
|
||||
2. `Vault` smart contract
|
||||
|
||||
The `Marketplace` smart contract implements the storage marketplace logic. Its internal logic is divided into
|
||||
multiple abstract subcontracts that focus on specific pieces like `Periods`, `Proofs`, `SlotReservations`, and so on,
|
||||
which are all bundled at the top level of the `Marketplace` contract itself.
|
||||
|
||||
The `Vault` smart contract is a specialized contract designed to safely keep users' funds. It is utilized by the
|
||||
`Marketplace` contract to delegate all funds' safe-keeping to it. There are several mechanisms in the `Vault` contract
|
||||
that should prevent a complete "grab & run" of all the funds in case an exploit is found in the `Marketplace` smart
|
||||
contract.
|
||||
|
||||
### Upgradability
|
||||
|
||||
The `Marketplace` contract employs the contract's upgradability pattern using [
|
||||
`TransparentUpgradeableProxy`](https://docs.openzeppelin.com/contracts/5.x/api/proxy#TransparentUpgradeableProxy), which
|
||||
allows replacing the underlying implementation while preserving the contract address and its storage. The upgrade can be
|
||||
performed only by the account that is specified during the initial deployment through the `PROXY_ADMIN_ADDRESS`
|
||||
environment variable. This capability is dedicated to emergency upgrades, as described in
|
||||
our [Codex Contract Deployment, Upgrades and Security](https://github.com/codex-storage/codex-research/blob/master/design/contract-deployment.md)
|
||||
document.
|
||||
|
||||
The steps to perform an emergency upgrade are:
|
||||
|
||||
1. Create a new `Marketplace` contract that will incorporate the changes. Name it, for example, `MarketplaceV2`.
|
||||
- The original `Marketplace` and its abstract subcontracts should not be edited once deployed.
|
||||
- If you need to make changes in one of the abstract subcontracts, also create a new version copy like `PeriodsV2`.
|
||||
- **Do not modify any storage variables in the contract!** The upgrade mechanism is not designed for this.
|
||||
2. Create a new Ignition deployment script that will perform the upgrade.
|
||||
- Take inspiration from the `marketplace-test-upgrade` module, which performs the upgrade in our test suite.
|
||||
- The upgrading transaction needs to originate from the account that was specified as `PROXY_ADMIN_ADDRESS` in the
|
||||
initial deployment.
|
||||
3. Deploy the upgrade with `hardhat ignition deploy <new module> --network <deployment network>`.
|
||||
|
||||
Once the new feature upgrade is planned, the first step when drafting this new version is to reconcile all
|
||||
the upgrade's changes (if there were any) back into the `Marketplace` contract and any modified subcontract
|
||||
on the new feature branch.
|
||||
|
||||
#### Safe Multisig Upgrade
|
||||
|
||||
When the `Proxy`'s owner is set to Safe's Multisig Wallet, the upgrade process needs to be modified. The upgrade
|
||||
deployment module cannot create the `upgradeAndCall` call directly; hence, the deploy module needs only to deploy the
|
||||
upgraded implementation logic.
|
||||
|
||||
Then, the `upgradeAndCall` call needs to be created through the Safe Wallet UI using the "Transaction Builder," where it
|
||||
is recommended to input the `ProxyAdmin` ABI, which helps to easily create the `upgradeAndCall` call. The `proxy`
|
||||
parameter is the Marketplace's Proxy address, the `implementation` parameter is the address of the new upgraded
|
||||
implementation, and the `data` can be left empty (`0x`).
|
||||
|
||||
## Marketplace overview
|
||||
|
||||
The Codex storage network depends on hosts offering storage to clients of the
|
||||
network. The smart contracts in this repository handle interactions between
|
||||
client and hosts as they negotiate and fulfill a contract to store data for a
|
||||
certain amount of time.
|
||||
|
||||
When all goes well, the client and hosts perform the following steps:
|
||||
|
||||
Client Host Marketplace Contract
|
||||
| | |
|
||||
| |
|
||||
| --------------- request (1) -------------> |
|
||||
| |
|
||||
| ----- data (2) ---> | |
|
||||
| | |
|
||||
| ----- fill (3) ----> |
|
||||
| |
|
||||
| ---- proof (4) ----> |
|
||||
| |
|
||||
| ---- proof (4) ----> |
|
||||
| |
|
||||
| ---- proof (4) ----> |
|
||||
| |
|
||||
| <-- payment (5) ---- |
|
||||
|
||||
1. Client submits a request for storage, containing the size of the data that
|
||||
it wants to store and the length of time it wants to store it
|
||||
2. Client makes the data available to hosts
|
||||
3. Hosts submit storage proofs to fill slots in the contract
|
||||
4. While the storage contract is active, host prove that they are still
|
||||
storing the data by responding to frequent random challenges
|
||||
5. At the end of the contract the hosts are paid
|
||||
|
||||
For full overview
|
||||
see [Codex Marketplace specification](https://github.com/codex-storage/codex-spec/blob/master/specs/marketplace.md).
|
||||
|
||||
### Storage Contracts
|
||||
|
||||
A storage contract contains of a number of slots. Each of these slots represents
|
||||
an agreement with a storage host to store a part of the data. Hosts that want to
|
||||
offer storage can fill a slot in the contract.
|
||||
|
||||
A contract can be negotiated through requests. A request contains the size of
|
||||
the data, the length of time during which it needs to be stored, and a number of
|
||||
slots. It also contains the reward that a client is willing to pay and proof
|
||||
requirements such as how often a proof will need to be submitted by hosts. A
|
||||
random nonce is included to ensure uniqueness among similar requests.
|
||||
|
||||
When a new storage contract is created the client immediately pays the entire
|
||||
price of the contract. The payment is only released to the host upon successful
|
||||
completion of the contract.
|
||||
|
||||
### Collateral
|
||||
|
||||
To motivate a host to remain honest, it must put up some collateral before it is
|
||||
allowed to participate in storage contracts. The collateral may not be withdrawn
|
||||
as long as a host is participating in an active storage contract.
|
||||
|
||||
Should a host be misbehaving, then its collateral may be reduced by a certain
|
||||
percentage (slashed).
|
||||
|
||||
### Proofs
|
||||
|
||||
Hosts are required to submit frequent proofs while a contract is active. These
|
||||
proofs ensure with a high probability that hosts are still holding on to the
|
||||
data that they were entrusted with.
|
||||
|
||||
To ensure that hosts are not able to predict and precalculate proofs, these
|
||||
proofs are based on a random challenge. Currently we use ethereum block hashes
|
||||
to determine two things: 1) whether or not a proof is required at this point in
|
||||
time, and 2) the random challenge for the proof. Although hosts will not be able
|
||||
to predict the exact times at which proofs are required, the frequency of proofs
|
||||
averages out to a value that was set by the client in the request for storage.
|
||||
|
||||
Hosts have a small period of time in which they are expected to submit a proof.
|
||||
When that time has expired without seeing a proof, validators are able to point
|
||||
out the lack of proof. If a host misses too many proofs, it results into a
|
||||
slashing of its collateral.
|
||||
|
||||
## References
|
||||
|
||||
* [A marketplace for storage
|
||||
durability](https://github.com/codex-storage/codex-research/blob/master/design/marketplace.md)
|
||||
(design document)
|
||||
* [Timing of Storage
|
||||
Proofs](https://github.com/codex-storage/codex-research/blob/master/design/storage-proof-timing.md)
|
||||
(design document)
|
||||
* [Codex Marketplace spec](https://github.com/codex-storage/codex-spec/blob/master/specs/marketplace.md)
|
||||
170
Readme.md
170
Readme.md
@ -1,170 +0,0 @@
|
||||
Codex Contracts
|
||||
================
|
||||
|
||||
An experimental implementation of the smart contracts that underlay the Codex
|
||||
storage network. Its goal is to experiment with the rules around the bidding
|
||||
process, the storage contracts, the storage proofs and the host collateral.
|
||||
Neither completeness nor correctness are guaranteed at this moment in time.
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
To run the tests, execute the following commands:
|
||||
|
||||
npm install
|
||||
npm test
|
||||
|
||||
You can also run fuzzing tests (using [Echidna][echidna]) on the contracts:
|
||||
|
||||
npm run fuzz
|
||||
|
||||
To start a local Ethereum node with the contracts deployed, execute:
|
||||
|
||||
npm start
|
||||
|
||||
Deployment
|
||||
----------
|
||||
|
||||
To deploy the marketplace, you need to specify the network using `--network MY_NETWORK`:
|
||||
|
||||
```bash
|
||||
npm run deploy -- --network localhost
|
||||
```
|
||||
|
||||
Hardhat uses [reconciliation](https://hardhat.org/ignition/docs/advanced/reconciliation) to recover from
|
||||
errors or resume a previous deployment. In our case, we will likely redeploy a new contract every time,
|
||||
so we will need to [clear the previous deployment](https://hardhat.org/ignition/docs/guides/modifications#clearing-an-existing-deployment-with-reset):
|
||||
|
||||
```bash
|
||||
npm run deploy -- --network testnet --reset
|
||||
```
|
||||
|
||||
To reuse a previously deployed `Token` contract, define the environment variable `TOKEN_ADDRESS`.
|
||||
The deployment script will use `contractAt` from Hardhat Ignition to retrieve the existing contract
|
||||
instead of deploying a new one.
|
||||
|
||||
The deployment files are kept under version control [as recommended by Hardhat](https://hardhat.org/ignition/docs/advanced/versioning), except the build files, which are 18 MB.
|
||||
|
||||
Running the prover
|
||||
------------------
|
||||
|
||||
To run the formal verification rules using Certora, first, make sure you have Java (JDK >= 11.0) installed on your machine, and then install the Certora CLI
|
||||
|
||||
```
|
||||
$ pip install certora-cli
|
||||
```
|
||||
|
||||
Once that is done the `certoraRun` command can be used to send CVL specs to the prover.
|
||||
|
||||
You can run Certora's specs with the provided `npm` script:
|
||||
|
||||
npm run verify
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Codex storage network depends on hosts offering storage to clients of the
|
||||
network. The smart contracts in this repository handle interactions between
|
||||
client and hosts as they negotiate and fulfill a contract to store data for a
|
||||
certain amount of time.
|
||||
|
||||
When all goes well, the client and hosts perform the following steps:
|
||||
|
||||
Client Host Marketplace Contract
|
||||
| | |
|
||||
| |
|
||||
| --------------- request (1) -------------> |
|
||||
| |
|
||||
| ----- data (2) ---> | |
|
||||
| | |
|
||||
| ----- fill (3) ----> |
|
||||
| |
|
||||
| ---- proof (4) ----> |
|
||||
| |
|
||||
| ---- proof (4) ----> |
|
||||
| |
|
||||
| ---- proof (4) ----> |
|
||||
| |
|
||||
| <-- payment (5) ---- |
|
||||
|
||||
1. Client submits a request for storage, containing the size of the data that
|
||||
it wants to store and the length of time it wants to store it
|
||||
2. Client makes the data available to hosts
|
||||
3. Hosts submit storage proofs to fill slots in the contract
|
||||
4. While the storage contract is active, host prove that they are still
|
||||
storing the data by responding to frequent random challenges
|
||||
5. At the end of the contract the hosts are paid
|
||||
|
||||
Contracts
|
||||
---------
|
||||
|
||||
A storage contract contains of a number of slots. Each of these slots represents
|
||||
an agreement with a storage host to store a part of the data. Hosts that want to
|
||||
offer storage can fill a slot in the contract.
|
||||
|
||||
A contract can be negotiated through requests. A request contains the size of
|
||||
the data, the length of time during which it needs to be stored, and a number of
|
||||
slots. It also contains the reward that a client is willing to pay and proof
|
||||
requirements such as how often a proof will need to be submitted by hosts. A
|
||||
random nonce is included to ensure uniqueness among similar requests.
|
||||
|
||||
When a new storage contract is created the client immediately pays the entire
|
||||
price of the contract. The payment is only released to the host upon successful
|
||||
completion of the contract.
|
||||
|
||||
Collateral
|
||||
----------
|
||||
|
||||
To motivate a host to remain honest, it must put up some collateral before it is
|
||||
allowed to participate in storage contracts. The collateral may not be withdrawn
|
||||
as long as a host is participating in an active storage contract.
|
||||
|
||||
Should a host be misbehaving, then its collateral may be reduced by a certain
|
||||
percentage (slashed).
|
||||
|
||||
Proofs
|
||||
------
|
||||
|
||||
Hosts are required to submit frequent proofs while a contract is active. These
|
||||
proofs ensure with a high probability that hosts are still holding on to the
|
||||
data that they were entrusted with.
|
||||
|
||||
To ensure that hosts are not able to predict and precalculate proofs, these
|
||||
proofs are based on a random challenge. Currently we use ethereum block hashes
|
||||
to determine two things: 1) whether or not a proof is required at this point in
|
||||
time, and 2) the random challenge for the proof. Although hosts will not be able
|
||||
to predict the exact times at which proofs are required, the frequency of proofs
|
||||
averages out to a value that was set by the client in the request for storage.
|
||||
|
||||
Hosts have a small period of time in which they are expected to submit a proof.
|
||||
When that time has expired without seeing a proof, validators are able to point
|
||||
out the lack of proof. If a host misses too many proofs, it results into a
|
||||
slashing of its collateral.
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
* [A marketplace for storage
|
||||
durability](https://github.com/codex-storage/codex-research/blob/master/design/marketplace.md)
|
||||
(design document)
|
||||
* [Timing of Storage
|
||||
Proofs](https://github.com/codex-storage/codex-research/blob/master/design/storage-proof-timing.md)
|
||||
(design document)
|
||||
|
||||
To Do
|
||||
-----
|
||||
|
||||
* Contract repair
|
||||
|
||||
Allow another host to take over a slot in the contract when the original
|
||||
host missed too many proofs.
|
||||
|
||||
* Reward validators
|
||||
|
||||
A validator that points out missed proofs should be compensated for its
|
||||
vigilance and for the gas costs of invoking the smart contract.
|
||||
|
||||
* Analysis and optimization of gas usage
|
||||
|
||||
[echidna]: https://github.com/crytic/echidna
|
||||
@ -10,10 +10,6 @@ import {RequestId, SlotId} from "../../contracts/Requests.sol";
|
||||
import {Requests} from "../../contracts/Requests.sol";
|
||||
|
||||
contract MarketplaceHarness is Marketplace {
|
||||
constructor(MarketplaceConfig memory config, IERC20 token, IGroth16Verifier verifier)
|
||||
Marketplace(config, token, verifier)
|
||||
{}
|
||||
|
||||
function publicPeriodEnd(Period period) public view returns (uint64) {
|
||||
return _periodEnd(period);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
const fs = require("fs")
|
||||
const { loadZkeyHash } = require("../verifier/verifier.js")
|
||||
|
||||
const BASE_PATH = __dirname + "/networks"
|
||||
|
||||
@ -23,6 +24,17 @@ const DEFAULT_CONFIGURATION = {
|
||||
requestDurationLimit: 60*60*24*30 // 30 days
|
||||
}
|
||||
|
||||
function getDefaultConfig(networkName) {
|
||||
if (networkName === undefined) {
|
||||
throw new TypeError("Network name needs to be specified!")
|
||||
}
|
||||
|
||||
const zkeyHash = loadZkeyHash(networkName)
|
||||
const config = loadConfiguration(networkName)
|
||||
config.proofs.zkeyHash = zkeyHash
|
||||
return config
|
||||
}
|
||||
|
||||
function loadConfiguration(name) {
|
||||
const path = `${BASE_PATH}/${name}/configuration.js`
|
||||
if (fs.existsSync(path)) {
|
||||
@ -32,4 +44,4 @@ function loadConfiguration(name) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { loadConfiguration }
|
||||
module.exports = { loadConfiguration, getDefaultConfig }
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.28;
|
||||
|
||||
import "./TestToken.sol";
|
||||
import "./Marketplace.sol";
|
||||
import "./TestVerifier.sol";
|
||||
|
||||
contract FuzzMarketplace is Marketplace {
|
||||
constructor()
|
||||
Marketplace(
|
||||
MarketplaceConfig(
|
||||
CollateralConfig(10, 5, 10, 20),
|
||||
ProofConfig(10, 5, 64, 67, ""),
|
||||
SlotReservationsConfig(20),
|
||||
60 * 60 * 24 * 30 // 30 days
|
||||
),
|
||||
new TestToken(),
|
||||
new TestVerifier()
|
||||
)
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Properties to be tested through fuzzing
|
||||
|
||||
MarketplaceTotals private _lastSeenTotals;
|
||||
|
||||
function neverDecreaseTotals() public {
|
||||
assert(_marketplaceTotals.received >= _lastSeenTotals.received);
|
||||
assert(_marketplaceTotals.sent >= _lastSeenTotals.sent);
|
||||
_lastSeenTotals = _marketplaceTotals;
|
||||
}
|
||||
|
||||
function neverLoseFunds() public view {
|
||||
uint256 total = _marketplaceTotals.received - _marketplaceTotals.sent;
|
||||
assert(token().balanceOf(address(this)) >= total);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
import "./Configuration.sol";
|
||||
@ -12,7 +13,7 @@ import "./StateRetrieval.sol";
|
||||
import "./Endian.sol";
|
||||
import "./Groth16.sol";
|
||||
|
||||
contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
contract Marketplace is Initializable, SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
error Marketplace_RepairRewardPercentageTooHigh();
|
||||
error Marketplace_SlashPercentageTooHigh();
|
||||
error Marketplace_MaximumSlashingTooHigh();
|
||||
@ -46,7 +47,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
using Requests for Request;
|
||||
using AskHelpers for Ask;
|
||||
|
||||
IERC20 private immutable _token;
|
||||
IERC20 private _token;
|
||||
MarketplaceConfig private _config;
|
||||
|
||||
mapping(RequestId => Request) private _requests;
|
||||
@ -96,11 +97,20 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
||||
uint64 slotIndex;
|
||||
}
|
||||
|
||||
constructor(
|
||||
constructor() {
|
||||
// In case that the contract would get deployed without initialization
|
||||
// this prevents attackers to call the initializations themselves with
|
||||
// potentially malicious initialization values.
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
function initialize (
|
||||
MarketplaceConfig memory config,
|
||||
IERC20 token_,
|
||||
IGroth16Verifier verifier
|
||||
) SlotReservations(config.reservations) Proofs(config.proofs, verifier) {
|
||||
) public initializer {
|
||||
_initializeSlotReservations(config.reservations);
|
||||
_initializeProofs(config.proofs, verifier);
|
||||
_token = token_;
|
||||
|
||||
if (config.collateral.repairRewardPercentage > 100)
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
contract Periods {
|
||||
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
|
||||
contract Periods is Initializable {
|
||||
error Periods_InvalidSecondsPerPeriod();
|
||||
|
||||
type Period is uint64;
|
||||
|
||||
uint64 internal immutable _secondsPerPeriod;
|
||||
uint64 internal _secondsPerPeriod;
|
||||
|
||||
constructor(uint64 secondsPerPeriod) {
|
||||
function _initializePeriods(uint64 secondsPerPeriod) internal onlyInitializing {
|
||||
if (secondsPerPeriod == 0) {
|
||||
revert Periods_InvalidSecondsPerPeriod();
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
|
||||
import "./Configuration.sol";
|
||||
import "./Requests.sol";
|
||||
import "./Periods.sol";
|
||||
@ -26,15 +28,17 @@ abstract contract Proofs is Periods {
|
||||
/**
|
||||
* Creation of the contract requires at least 256 mined blocks!
|
||||
* @param config Proving configuration
|
||||
* @param verifier Proof verifier
|
||||
*/
|
||||
constructor(
|
||||
function _initializeProofs(
|
||||
ProofConfig memory config,
|
||||
IGroth16Verifier verifier
|
||||
) Periods(config.period) {
|
||||
) internal onlyInitializing {
|
||||
if (block.number <= 256) {
|
||||
revert Proofs_InsufficientBlockHeight();
|
||||
}
|
||||
|
||||
_initializePeriods(config.period);
|
||||
_config = config;
|
||||
_verifier = verifier;
|
||||
}
|
||||
|
||||
6
contracts/Proxies.sol
Normal file
6
contracts/Proxies.sol
Normal file
@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.28;
|
||||
|
||||
|
||||
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
||||
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
@ -1,18 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||
|
||||
import "./Requests.sol";
|
||||
import "./Configuration.sol";
|
||||
|
||||
abstract contract SlotReservations {
|
||||
abstract contract SlotReservations is Initializable {
|
||||
using EnumerableSet for EnumerableSet.AddressSet;
|
||||
error SlotReservations_ReservationNotAllowed();
|
||||
|
||||
mapping(SlotId => EnumerableSet.AddressSet) internal _reservations;
|
||||
SlotReservationsConfig private _config;
|
||||
|
||||
constructor(SlotReservationsConfig memory config) {
|
||||
function _initializeSlotReservations(SlotReservationsConfig memory config) internal onlyInitializing {
|
||||
_config = config;
|
||||
}
|
||||
|
||||
|
||||
@ -5,14 +5,6 @@ import "./Marketplace.sol";
|
||||
|
||||
// exposes internal functions of Marketplace for testing
|
||||
contract TestMarketplace is Marketplace {
|
||||
constructor(
|
||||
MarketplaceConfig memory config,
|
||||
IERC20 token,
|
||||
IGroth16Verifier verifier
|
||||
)
|
||||
Marketplace(config, token, verifier) // solhint-disable-next-line no-empty-blocks
|
||||
{}
|
||||
|
||||
function forciblyFreeSlot(SlotId slotId) public {
|
||||
_forciblyFreeSlot(slotId);
|
||||
}
|
||||
|
||||
12
contracts/TestMarketplaceUpgrade.sol
Normal file
12
contracts/TestMarketplaceUpgrade.sol
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.28;
|
||||
|
||||
import "./Marketplace.sol";
|
||||
|
||||
// exposes internal functions of Marketplace for testing
|
||||
contract TestMarketplaceUpgraded is Marketplace {
|
||||
|
||||
function newShinyMethod() public pure returns (uint256) {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
13
contracts/TestPeriods.sol
Normal file
13
contracts/TestPeriods.sol
Normal file
@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.28;
|
||||
|
||||
import "./Periods.sol";
|
||||
|
||||
contract TestPeriods is Periods {
|
||||
|
||||
function initialize (
|
||||
uint64 secondsPerPeriod
|
||||
) public initializer {
|
||||
_initializePeriods(secondsPerPeriod);
|
||||
}
|
||||
}
|
||||
@ -13,11 +13,12 @@ contract TestProofs is Proofs {
|
||||
// private to internal, which may cause problems in the Marketplace contract.
|
||||
ProofConfig private _proofConfig;
|
||||
|
||||
constructor(
|
||||
function initialize (
|
||||
ProofConfig memory config,
|
||||
IGroth16Verifier verifier
|
||||
) Proofs(config, verifier) {
|
||||
) public initializer {
|
||||
_proofConfig = config;
|
||||
_initializeProofs(config, verifier);
|
||||
}
|
||||
|
||||
function slotState(SlotId slotId) public view override returns (SlotState) {
|
||||
|
||||
@ -8,9 +8,11 @@ contract TestSlotReservations is SlotReservations {
|
||||
|
||||
mapping(SlotId => SlotState) private _states;
|
||||
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
constructor(SlotReservationsConfig memory config) SlotReservations(config) {}
|
||||
|
||||
function initialize (
|
||||
SlotReservationsConfig memory config
|
||||
) public initializer {
|
||||
_initializeSlotReservations(config);
|
||||
}
|
||||
function contains(SlotId slotId, address host) public view returns (bool) {
|
||||
return _reservations[slotId].contains(host);
|
||||
}
|
||||
|
||||
36
ignition/modules/marketplace-test-upgrade.js
Normal file
36
ignition/modules/marketplace-test-upgrade.js
Normal file
@ -0,0 +1,36 @@
|
||||
const { buildModule } = require('@nomicfoundation/hardhat-ignition/modules')
|
||||
const MarketplaceModule = require("./marketplace.js")
|
||||
|
||||
/**
|
||||
* This module upgrades the Marketplace's Proxy with a new implementation.
|
||||
* It deploys the new Marketplace contract and then calls the Proxy with
|
||||
* the `upgradeAndCall` function, which swaps the implementations.
|
||||
*/
|
||||
const upgradeModule = buildModule('UpgradeProxyImplementation', (m) => {
|
||||
const config = hre.network.config
|
||||
|
||||
if (!(config && config.tags && config.tags.includes("local"))) {
|
||||
throw new Error("Module is not meant for real deployments!")
|
||||
}
|
||||
|
||||
const proxyAdminOwner = m.getAccount(9)
|
||||
const marketplaceUpgraded = m.contract("TestMarketplaceUpgraded", [])
|
||||
const {proxyAdmin, proxy, token} = m.useModule(MarketplaceModule);
|
||||
|
||||
m.call(proxyAdmin, "upgradeAndCall", [proxy, marketplaceUpgraded, "0x"], {
|
||||
from: proxyAdminOwner,
|
||||
});
|
||||
|
||||
return { proxyAdmin, proxy, token };
|
||||
})
|
||||
|
||||
/**
|
||||
* The main module that represents the upgraded Marketplace contract.
|
||||
*/
|
||||
module.exports = buildModule('MarketplaceUpgraded', (m) => {
|
||||
const { proxy, proxyAdmin, token } = m.useModule(upgradeModule)
|
||||
|
||||
const marketplace = m.contractAt('TestMarketplaceUpgraded', proxy)
|
||||
|
||||
return { marketplace, proxy, proxyAdmin, token }
|
||||
})
|
||||
@ -1,43 +1,149 @@
|
||||
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
|
||||
const { loadZkeyHash } = require("../../verifier/verifier.js")
|
||||
const { loadConfiguration } = require("../../configuration/configuration.js")
|
||||
const { buildModule } = require('@nomicfoundation/hardhat-ignition/modules')
|
||||
const { getDefaultConfig } = require("../../configuration/configuration.js")
|
||||
const TokenModule = require("./token.js")
|
||||
const VerifierModule = require("./verifier.js")
|
||||
|
||||
function getDefaultConfig() {
|
||||
const zkeyHash = loadZkeyHash(hre.network.name)
|
||||
const config = loadConfiguration(hre.network.name)
|
||||
config.proofs.zkeyHash = zkeyHash
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = buildModule("Marketplace", (m) => {
|
||||
const { token } = m.useModule(TokenModule)
|
||||
const { verifier } = m.useModule(VerifierModule)
|
||||
const configuration = m.getParameter("configuration", getDefaultConfig())
|
||||
|
||||
const marketplace = m.contract(
|
||||
"Marketplace",
|
||||
[configuration, token, verifier],
|
||||
{},
|
||||
)
|
||||
/**
|
||||
* Module that deploy the Marketplace logic
|
||||
*/
|
||||
const marketplaceLogicModule = buildModule("MarketplaceLogic", (m) => {
|
||||
const marketplace = m.contract("Marketplace", [])
|
||||
|
||||
let testMarketplace
|
||||
const config = hre.network.config
|
||||
|
||||
if (config && config.tags && config.tags.includes("local")) {
|
||||
const { testVerifier } = m.useModule(VerifierModule)
|
||||
|
||||
testMarketplace = m.contract(
|
||||
"TestMarketplace",
|
||||
[configuration, token, testVerifier],
|
||||
{},
|
||||
)
|
||||
testMarketplace = m.contract("TestMarketplace", [])
|
||||
}
|
||||
|
||||
return {
|
||||
marketplace,
|
||||
testMarketplace,
|
||||
token,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* This module deploy Proxy with Marketplace contract used as implementation
|
||||
* and it initializes the Marketplace in the Proxy context.
|
||||
*/
|
||||
const proxyModule = buildModule('Proxy', (m) => {
|
||||
const deployer = m.getAccount(0)
|
||||
const config = hre.network.config
|
||||
|
||||
|
||||
// This address is the owner of the ProxyAdmin contract,
|
||||
// so it will be the only account that can upgrade the proxy when needed.
|
||||
let proxyAdminOwner
|
||||
|
||||
if (config && config.tags && config.tags.includes("local")) {
|
||||
// The Proxy Admin is not allowed to make "forwarded" calls through Proxy,
|
||||
// it can only upgrade it, hence this account must not be used for example in tests.
|
||||
proxyAdminOwner = process.env.PROXY_ADMIN_ADDRESS || m.getAccount(9)
|
||||
} else {
|
||||
if (!process.env.PROXY_ADMIN_ADDRESS) {
|
||||
throw new Error("In non-Hardhat network you need to specify PROXY_ADMIN_ADDRESS env. variable")
|
||||
}
|
||||
|
||||
proxyAdminOwner = process.env.PROXY_ADMIN_ADDRESS
|
||||
}
|
||||
|
||||
const { marketplace } = m.useModule(marketplaceLogicModule)
|
||||
const { token } = m.useModule(TokenModule)
|
||||
const { verifier } = m.useModule(VerifierModule)
|
||||
const configuration = m.getParameter("configuration", getDefaultConfig(hre.network.name))
|
||||
const encodedMarketplaceInitializerCall = m.encodeFunctionCall(
|
||||
marketplace,
|
||||
"initialize",
|
||||
[configuration, token, verifier]
|
||||
);
|
||||
|
||||
// The TransparentUpgradeableProxy contract creates the ProxyAdmin within its constructor.
|
||||
const proxy = m.contract(
|
||||
'TransparentUpgradeableProxy',
|
||||
[
|
||||
marketplace,
|
||||
proxyAdminOwner,
|
||||
encodedMarketplaceInitializerCall,
|
||||
],
|
||||
{ from: deployer },
|
||||
)
|
||||
|
||||
|
||||
// We need to get the address of the ProxyAdmin contract that was created by the TransparentUpgradeableProxy
|
||||
// so that we can use it to upgrade the proxy later.
|
||||
const proxyAdminAddress = m.readEventArgument(
|
||||
proxy,
|
||||
'AdminChanged',
|
||||
'newAdmin'
|
||||
)
|
||||
|
||||
// Here we use m.contractAt(...) to create a contract instance for the ProxyAdmin that we can interact with later to upgrade the proxy.
|
||||
const proxyAdmin = m.contractAt('ProxyAdmin', proxyAdminAddress)
|
||||
|
||||
|
||||
return { proxyAdmin, proxy, token }
|
||||
})
|
||||
|
||||
/**
|
||||
* This module deploy Proxy with TestMarketplace contract used for testing purposes.
|
||||
*/
|
||||
const testProxyModule = buildModule('TestProxy', (m) => {
|
||||
const deployer = m.getAccount(0)
|
||||
const config = hre.network.config
|
||||
|
||||
// We allow testing contract only in local/Hardhat network
|
||||
if (!(config && config.tags && config.tags.includes("local"))) {
|
||||
return { testProxy: undefined }
|
||||
}
|
||||
|
||||
|
||||
let proxyAdminOwner = process.env.PROXY_ADMIN_ADDRESS || m.getAccount(9)
|
||||
const { testMarketplace } = m.useModule(marketplaceLogicModule)
|
||||
const { token } = m.useModule(TokenModule)
|
||||
const { testVerifier } = m.useModule(VerifierModule)
|
||||
const configuration = m.getParameter("configuration", getDefaultConfig(hre.network.name))
|
||||
const encodedMarketplaceInitializerCall = m.encodeFunctionCall(
|
||||
testMarketplace,
|
||||
"initialize",
|
||||
[configuration, token, testVerifier]
|
||||
);
|
||||
|
||||
const testProxy = m.contract(
|
||||
'TransparentUpgradeableProxy',
|
||||
[
|
||||
testMarketplace,
|
||||
proxyAdminOwner,
|
||||
encodedMarketplaceInitializerCall,
|
||||
],
|
||||
{ from: deployer },
|
||||
)
|
||||
|
||||
return { testProxy }
|
||||
})
|
||||
|
||||
/**
|
||||
* The main module that represents Marketplace contract.
|
||||
* Underneath there is the deployed Proxy contract with Markeplace's logic used as proxy's implementation
|
||||
* and initilized proxy's context with Marketplace's configuration.
|
||||
*/
|
||||
module.exports = buildModule('Marketplace', (m) => {
|
||||
const { proxy, proxyAdmin, token } = m.useModule(proxyModule)
|
||||
const config = hre.network.config
|
||||
|
||||
// We use the Proxy contract as it would be Marketplace contract
|
||||
const marketplace = m.contractAt('Marketplace', proxy)
|
||||
|
||||
// We allow testing contract only in local/Hardhat network
|
||||
if (config && config.tags && config.tags.includes("local")) {
|
||||
const { testProxy } = m.useModule(testProxyModule)
|
||||
const testMarketplace = m.contractAt('TestMarketplace', testProxy)
|
||||
|
||||
return { marketplace, proxy, proxyAdmin, testMarketplace, token }
|
||||
}
|
||||
|
||||
// Return the contract instance, along with the original proxy and proxyAdmin contracts
|
||||
// so that they can be used by other modules, or in tests and scripts.
|
||||
return { marketplace, proxy, proxyAdmin, token }
|
||||
})
|
||||
|
||||
@ -2,8 +2,9 @@ const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
|
||||
|
||||
module.exports = buildModule("Periods", (m) => {
|
||||
const secondsPerPeriod = m.getParameter("secondsPerPeriod", 0)
|
||||
const periods = m.contract("TestPeriods", [])
|
||||
|
||||
const periods = m.contract("Periods", [secondsPerPeriod], {})
|
||||
m.call(periods, "initialize", [secondsPerPeriod]);
|
||||
|
||||
return { periods }
|
||||
})
|
||||
|
||||
@ -5,7 +5,8 @@ module.exports = buildModule("Proofs", (m) => {
|
||||
const { verifier } = m.useModule(VerifierModule)
|
||||
const configuration = m.getParameter("configuration", null)
|
||||
|
||||
const testProofs = m.contract("TestProofs", [configuration, verifier], {})
|
||||
const testProofs = m.contract("TestProofs", [])
|
||||
m.call(testProofs, "initialize", [configuration, verifier])
|
||||
|
||||
return { testProofs }
|
||||
})
|
||||
|
||||
@ -2,12 +2,9 @@ const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
|
||||
|
||||
module.exports = buildModule("SlotReservations", (m) => {
|
||||
const configuration = m.getParameter("configuration", null)
|
||||
const testSlotReservations = m.contract("TestSlotReservations", [])
|
||||
|
||||
const testSlotReservations = m.contract(
|
||||
"TestSlotReservations",
|
||||
[configuration],
|
||||
{},
|
||||
)
|
||||
m.call(testSlotReservations, "initialize", [configuration]);
|
||||
|
||||
return { testSlotReservations }
|
||||
})
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -8,7 +8,7 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
|
||||
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.11",
|
||||
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.12",
|
||||
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||
"@openzeppelin/contracts": "^5.3.0",
|
||||
"@stdlib/stats-binomial-test": "^0.2.2",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "codex-contracts-eth",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "npm run lint && hardhat test",
|
||||
"test": "hardhat test",
|
||||
"fuzz": "hardhat compile && fuzzing/fuzz.sh",
|
||||
"start": "concurrently --names \"hardhat,deployment\" --prefix \"[{time} {name}]\" \"hardhat node\" \"sleep 2 && npm run mine && npm run deploy -- --network localhost\"",
|
||||
"compile": "hardhat compile",
|
||||
@ -18,10 +18,10 @@
|
||||
"gas:report": "REPORT_GAS=true hardhat test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openzeppelin/contracts": "^5.3.0",
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
|
||||
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.11",
|
||||
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.12",
|
||||
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||
"@openzeppelin/contracts": "^5.3.0",
|
||||
"@stdlib/stats-binomial-test": "^0.2.2",
|
||||
"chai": "^4.5.0",
|
||||
"ethers": "6.14.4",
|
||||
|
||||
@ -41,6 +41,7 @@ const {
|
||||
} = require("./evm")
|
||||
const { getBytes } = require("ethers")
|
||||
const MarketplaceModule = require("../ignition/modules/marketplace")
|
||||
const MarketplaceUpgradeModule = require("../ignition/modules/marketplace-test-upgrade")
|
||||
const { assertDeploymentRejectedWithCustomError } = require("./helpers")
|
||||
|
||||
const ACCOUNT_STARTING_BALANCE = 1_000_000_000_000_000n
|
||||
@ -62,7 +63,7 @@ describe("Marketplace constructor", function () {
|
||||
|
||||
const promise = ignition.deploy(MarketplaceModule, {
|
||||
parameters: {
|
||||
Marketplace: {
|
||||
TestProxy: {
|
||||
configuration: config,
|
||||
},
|
||||
},
|
||||
@ -86,11 +87,9 @@ describe("Marketplace constructor", function () {
|
||||
config.collateral.slashPercentage = 1
|
||||
config.collateral.maxNumberOfSlashes = 101
|
||||
|
||||
const expectedError = "Marketplace_MaximumSlashingTooHigh"
|
||||
|
||||
const promise = ignition.deploy(MarketplaceModule, {
|
||||
parameters: {
|
||||
Marketplace: {
|
||||
TestProxy: {
|
||||
configuration: config,
|
||||
},
|
||||
},
|
||||
@ -103,6 +102,108 @@ describe("Marketplace constructor", function () {
|
||||
})
|
||||
})
|
||||
|
||||
// Currently, Hardhat Ignition does not track deployments in the Hardhat network,
|
||||
// so when Hardhat runs tests on its network, Ignition will deploy new contracts
|
||||
// every time it is deploying something. This prevents proper testing of the upgradability
|
||||
// because a new Proxy will be deployed on the "upgrade deployment," and it won't reuse the
|
||||
// original one.
|
||||
// This can be tested manually by running `hardhat node` and then `hardhat test --network localhost`.
|
||||
// But even then, the test "should share the same storage" is having issues, which I suspect
|
||||
// are related to different network usage.
|
||||
// Blocked until this issue is resolved: https://github.com/NomicFoundation/hardhat/issues/6927
|
||||
describe.skip("Marketplace upgrades", function () {
|
||||
const config = exampleConfiguration()
|
||||
let originalMarketplace, proxy, token, request, client
|
||||
enableRequestAssertions()
|
||||
|
||||
beforeEach(async function () {
|
||||
await snapshot()
|
||||
await ensureMinimumBlockHeight(256)
|
||||
const {
|
||||
marketplace,
|
||||
proxy: deployedProxy,
|
||||
token: token_,
|
||||
} = await ignition.deploy(MarketplaceModule, {
|
||||
deploymentId: "test",
|
||||
parameters: {
|
||||
TestProxy: {
|
||||
configuration: config,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
proxy = deployedProxy
|
||||
originalMarketplace = marketplace
|
||||
token = token_
|
||||
|
||||
await ensureMinimumBlockHeight(256)
|
||||
;[client] = await ethers.getSigners()
|
||||
|
||||
request = await exampleRequest()
|
||||
request.client = client.address
|
||||
|
||||
token = token.connect(client)
|
||||
originalMarketplace = marketplace.connect(client)
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
await revert()
|
||||
})
|
||||
|
||||
it("should get upgraded", async () => {
|
||||
expect(() => originalMarketplace.newShinyMethod()).to.throw(TypeError)
|
||||
|
||||
const { marketplace: upgradedMarketplace, proxy: upgradedProxy } =
|
||||
await ignition.deploy(MarketplaceUpgradeModule, {
|
||||
deploymentId: "test",
|
||||
parameters: {
|
||||
TestProxy: {
|
||||
configuration: config,
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(await upgradedMarketplace.newShinyMethod()).to.equal(42)
|
||||
expect(await originalMarketplace.getAddress()).to.equal(
|
||||
await upgradedMarketplace.getAddress(),
|
||||
)
|
||||
expect(await originalMarketplace.configuration()).to.deep.equal(
|
||||
await upgradedMarketplace.configuration(),
|
||||
)
|
||||
})
|
||||
|
||||
it("should share the same storage", async () => {
|
||||
// Old implementation not supporting the shiny method
|
||||
expect(() => originalMarketplace.newShinyMethod()).to.throw(TypeError)
|
||||
|
||||
// We create a Request on the old implementation
|
||||
await token.approve(
|
||||
await originalMarketplace.getAddress(),
|
||||
maxPrice(request),
|
||||
)
|
||||
const now = await currentTime()
|
||||
await setNextBlockTimestamp(now)
|
||||
const expectedExpiry = now + request.expiry
|
||||
await expect(originalMarketplace.requestStorage(request))
|
||||
.to.emit(originalMarketplace, "StorageRequested")
|
||||
.withArgs(requestId(request), askToArray(request.ask), expectedExpiry)
|
||||
|
||||
// Assert that the data is there
|
||||
expect(
|
||||
await originalMarketplace.getRequest(requestId(request)),
|
||||
).to.be.request(request)
|
||||
|
||||
// Upgrade Marketplace
|
||||
const { marketplace: upgradedMarketplace, token: _token } =
|
||||
await ignition.deploy(MarketplaceUpgradeModule)
|
||||
expect(await upgradedMarketplace.newShinyMethod()).to.equal(42)
|
||||
|
||||
// Assert that the data is there
|
||||
expect(
|
||||
await upgradedMarketplace.getRequest(requestId(request)),
|
||||
).to.be.request(request)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Marketplace", function () {
|
||||
const proof = exampleProof()
|
||||
const config = exampleConfiguration()
|
||||
@ -143,7 +244,8 @@ describe("Marketplace", function () {
|
||||
MarketplaceModule,
|
||||
{
|
||||
parameters: {
|
||||
Marketplace: {
|
||||
TestProxy: {
|
||||
// TestMarketplace initialization is done in the TestProxy module
|
||||
configuration: config,
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user