mirror of
https://github.com/logos-storage/logos-storage-contracts-eth.git
synced 2026-01-07 15:53:07 +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/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
- run: npm run lint
|
||||||
- run: npm run format:check
|
- run: npm run format:check
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@ -30,11 +31,6 @@ jobs:
|
|||||||
node-version: 22
|
node-version: 22
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm test
|
- run: npm test
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: fuzzing/corpus
|
|
||||||
key: fuzzing
|
|
||||||
- run: npm run fuzz
|
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
runs-on: ubuntu-latest
|
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";
|
import {Requests} from "../../contracts/Requests.sol";
|
||||||
|
|
||||||
contract MarketplaceHarness is Marketplace {
|
contract MarketplaceHarness is Marketplace {
|
||||||
constructor(MarketplaceConfig memory config, IERC20 token, IGroth16Verifier verifier)
|
|
||||||
Marketplace(config, token, verifier)
|
|
||||||
{}
|
|
||||||
|
|
||||||
function publicPeriodEnd(Period period) public view returns (uint64) {
|
function publicPeriodEnd(Period period) public view returns (uint64) {
|
||||||
return _periodEnd(period);
|
return _periodEnd(period);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
|
const { loadZkeyHash } = require("../verifier/verifier.js")
|
||||||
|
|
||||||
const BASE_PATH = __dirname + "/networks"
|
const BASE_PATH = __dirname + "/networks"
|
||||||
|
|
||||||
@ -23,6 +24,17 @@ const DEFAULT_CONFIGURATION = {
|
|||||||
requestDurationLimit: 60*60*24*30 // 30 days
|
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) {
|
function loadConfiguration(name) {
|
||||||
const path = `${BASE_PATH}/${name}/configuration.js`
|
const path = `${BASE_PATH}/${name}/configuration.js`
|
||||||
if (fs.existsSync(path)) {
|
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;
|
pragma solidity 0.8.28;
|
||||||
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
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/math/Math.sol";
|
||||||
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||||
import "./Configuration.sol";
|
import "./Configuration.sol";
|
||||||
@ -12,7 +13,7 @@ import "./StateRetrieval.sol";
|
|||||||
import "./Endian.sol";
|
import "./Endian.sol";
|
||||||
import "./Groth16.sol";
|
import "./Groth16.sol";
|
||||||
|
|
||||||
contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
contract Marketplace is Initializable, SlotReservations, Proofs, StateRetrieval, Endian {
|
||||||
error Marketplace_RepairRewardPercentageTooHigh();
|
error Marketplace_RepairRewardPercentageTooHigh();
|
||||||
error Marketplace_SlashPercentageTooHigh();
|
error Marketplace_SlashPercentageTooHigh();
|
||||||
error Marketplace_MaximumSlashingTooHigh();
|
error Marketplace_MaximumSlashingTooHigh();
|
||||||
@ -46,7 +47,7 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
|||||||
using Requests for Request;
|
using Requests for Request;
|
||||||
using AskHelpers for Ask;
|
using AskHelpers for Ask;
|
||||||
|
|
||||||
IERC20 private immutable _token;
|
IERC20 private _token;
|
||||||
MarketplaceConfig private _config;
|
MarketplaceConfig private _config;
|
||||||
|
|
||||||
mapping(RequestId => Request) private _requests;
|
mapping(RequestId => Request) private _requests;
|
||||||
@ -96,11 +97,20 @@ contract Marketplace is SlotReservations, Proofs, StateRetrieval, Endian {
|
|||||||
uint64 slotIndex;
|
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,
|
MarketplaceConfig memory config,
|
||||||
IERC20 token_,
|
IERC20 token_,
|
||||||
IGroth16Verifier verifier
|
IGroth16Verifier verifier
|
||||||
) SlotReservations(config.reservations) Proofs(config.proofs, verifier) {
|
) public initializer {
|
||||||
|
_initializeSlotReservations(config.reservations);
|
||||||
|
_initializeProofs(config.proofs, verifier);
|
||||||
_token = token_;
|
_token = token_;
|
||||||
|
|
||||||
if (config.collateral.repairRewardPercentage > 100)
|
if (config.collateral.repairRewardPercentage > 100)
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity 0.8.28;
|
pragma solidity 0.8.28;
|
||||||
|
|
||||||
contract Periods {
|
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||||
|
|
||||||
|
contract Periods is Initializable {
|
||||||
error Periods_InvalidSecondsPerPeriod();
|
error Periods_InvalidSecondsPerPeriod();
|
||||||
|
|
||||||
type Period is uint64;
|
type Period is uint64;
|
||||||
|
|
||||||
uint64 internal immutable _secondsPerPeriod;
|
uint64 internal _secondsPerPeriod;
|
||||||
|
|
||||||
constructor(uint64 secondsPerPeriod) {
|
function _initializePeriods(uint64 secondsPerPeriod) internal onlyInitializing {
|
||||||
if (secondsPerPeriod == 0) {
|
if (secondsPerPeriod == 0) {
|
||||||
revert Periods_InvalidSecondsPerPeriod();
|
revert Periods_InvalidSecondsPerPeriod();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity 0.8.28;
|
pragma solidity 0.8.28;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||||
|
|
||||||
import "./Configuration.sol";
|
import "./Configuration.sol";
|
||||||
import "./Requests.sol";
|
import "./Requests.sol";
|
||||||
import "./Periods.sol";
|
import "./Periods.sol";
|
||||||
@ -26,15 +28,17 @@ abstract contract Proofs is Periods {
|
|||||||
/**
|
/**
|
||||||
* Creation of the contract requires at least 256 mined blocks!
|
* Creation of the contract requires at least 256 mined blocks!
|
||||||
* @param config Proving configuration
|
* @param config Proving configuration
|
||||||
|
* @param verifier Proof verifier
|
||||||
*/
|
*/
|
||||||
constructor(
|
function _initializeProofs(
|
||||||
ProofConfig memory config,
|
ProofConfig memory config,
|
||||||
IGroth16Verifier verifier
|
IGroth16Verifier verifier
|
||||||
) Periods(config.period) {
|
) internal onlyInitializing {
|
||||||
if (block.number <= 256) {
|
if (block.number <= 256) {
|
||||||
revert Proofs_InsufficientBlockHeight();
|
revert Proofs_InsufficientBlockHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initializePeriods(config.period);
|
||||||
_config = config;
|
_config = config;
|
||||||
_verifier = verifier;
|
_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
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity 0.8.28;
|
pragma solidity 0.8.28;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
|
||||||
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
||||||
|
|
||||||
import "./Requests.sol";
|
import "./Requests.sol";
|
||||||
import "./Configuration.sol";
|
import "./Configuration.sol";
|
||||||
|
|
||||||
abstract contract SlotReservations {
|
abstract contract SlotReservations is Initializable {
|
||||||
using EnumerableSet for EnumerableSet.AddressSet;
|
using EnumerableSet for EnumerableSet.AddressSet;
|
||||||
error SlotReservations_ReservationNotAllowed();
|
error SlotReservations_ReservationNotAllowed();
|
||||||
|
|
||||||
mapping(SlotId => EnumerableSet.AddressSet) internal _reservations;
|
mapping(SlotId => EnumerableSet.AddressSet) internal _reservations;
|
||||||
SlotReservationsConfig private _config;
|
SlotReservationsConfig private _config;
|
||||||
|
|
||||||
constructor(SlotReservationsConfig memory config) {
|
function _initializeSlotReservations(SlotReservationsConfig memory config) internal onlyInitializing {
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,14 +5,6 @@ import "./Marketplace.sol";
|
|||||||
|
|
||||||
// exposes internal functions of Marketplace for testing
|
// exposes internal functions of Marketplace for testing
|
||||||
contract TestMarketplace is Marketplace {
|
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 {
|
function forciblyFreeSlot(SlotId slotId) public {
|
||||||
_forciblyFreeSlot(slotId);
|
_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.
|
// private to internal, which may cause problems in the Marketplace contract.
|
||||||
ProofConfig private _proofConfig;
|
ProofConfig private _proofConfig;
|
||||||
|
|
||||||
constructor(
|
function initialize (
|
||||||
ProofConfig memory config,
|
ProofConfig memory config,
|
||||||
IGroth16Verifier verifier
|
IGroth16Verifier verifier
|
||||||
) Proofs(config, verifier) {
|
) public initializer {
|
||||||
_proofConfig = config;
|
_proofConfig = config;
|
||||||
|
_initializeProofs(config, verifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
function slotState(SlotId slotId) public view override returns (SlotState) {
|
function slotState(SlotId slotId) public view override returns (SlotState) {
|
||||||
|
|||||||
@ -8,9 +8,11 @@ contract TestSlotReservations is SlotReservations {
|
|||||||
|
|
||||||
mapping(SlotId => SlotState) private _states;
|
mapping(SlotId => SlotState) private _states;
|
||||||
|
|
||||||
// solhint-disable-next-line no-empty-blocks
|
function initialize (
|
||||||
constructor(SlotReservationsConfig memory config) SlotReservations(config) {}
|
SlotReservationsConfig memory config
|
||||||
|
) public initializer {
|
||||||
|
_initializeSlotReservations(config);
|
||||||
|
}
|
||||||
function contains(SlotId slotId, address host) public view returns (bool) {
|
function contains(SlotId slotId, address host) public view returns (bool) {
|
||||||
return _reservations[slotId].contains(host);
|
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 { buildModule } = require('@nomicfoundation/hardhat-ignition/modules')
|
||||||
const { loadZkeyHash } = require("../../verifier/verifier.js")
|
const { getDefaultConfig } = require("../../configuration/configuration.js")
|
||||||
const { loadConfiguration } = require("../../configuration/configuration.js")
|
|
||||||
const TokenModule = require("./token.js")
|
const TokenModule = require("./token.js")
|
||||||
const VerifierModule = require("./verifier.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)
|
* Module that deploy the Marketplace logic
|
||||||
const { verifier } = m.useModule(VerifierModule)
|
*/
|
||||||
const configuration = m.getParameter("configuration", getDefaultConfig())
|
const marketplaceLogicModule = buildModule("MarketplaceLogic", (m) => {
|
||||||
|
const marketplace = m.contract("Marketplace", [])
|
||||||
const marketplace = m.contract(
|
|
||||||
"Marketplace",
|
|
||||||
[configuration, token, verifier],
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
|
|
||||||
let testMarketplace
|
let testMarketplace
|
||||||
const config = hre.network.config
|
const config = hre.network.config
|
||||||
|
|
||||||
if (config && config.tags && config.tags.includes("local")) {
|
if (config && config.tags && config.tags.includes("local")) {
|
||||||
const { testVerifier } = m.useModule(VerifierModule)
|
testMarketplace = m.contract("TestMarketplace", [])
|
||||||
|
|
||||||
testMarketplace = m.contract(
|
|
||||||
"TestMarketplace",
|
|
||||||
[configuration, token, testVerifier],
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
marketplace,
|
marketplace,
|
||||||
testMarketplace,
|
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) => {
|
module.exports = buildModule("Periods", (m) => {
|
||||||
const secondsPerPeriod = m.getParameter("secondsPerPeriod", 0)
|
const secondsPerPeriod = m.getParameter("secondsPerPeriod", 0)
|
||||||
|
const periods = m.contract("TestPeriods", [])
|
||||||
|
|
||||||
const periods = m.contract("Periods", [secondsPerPeriod], {})
|
m.call(periods, "initialize", [secondsPerPeriod]);
|
||||||
|
|
||||||
return { periods }
|
return { periods }
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,7 +5,8 @@ module.exports = buildModule("Proofs", (m) => {
|
|||||||
const { verifier } = m.useModule(VerifierModule)
|
const { verifier } = m.useModule(VerifierModule)
|
||||||
const configuration = m.getParameter("configuration", null)
|
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 }
|
return { testProofs }
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,12 +2,9 @@ const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules")
|
|||||||
|
|
||||||
module.exports = buildModule("SlotReservations", (m) => {
|
module.exports = buildModule("SlotReservations", (m) => {
|
||||||
const configuration = m.getParameter("configuration", null)
|
const configuration = m.getParameter("configuration", null)
|
||||||
|
const testSlotReservations = m.contract("TestSlotReservations", [])
|
||||||
|
|
||||||
const testSlotReservations = m.contract(
|
m.call(testSlotReservations, "initialize", [configuration]);
|
||||||
"TestSlotReservations",
|
|
||||||
[configuration],
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
|
|
||||||
return { testSlotReservations }
|
return { testSlotReservations }
|
||||||
})
|
})
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -8,7 +8,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
|
"@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",
|
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||||
"@openzeppelin/contracts": "^5.3.0",
|
"@openzeppelin/contracts": "^5.3.0",
|
||||||
"@stdlib/stats-binomial-test": "^0.2.2",
|
"@stdlib/stats-binomial-test": "^0.2.2",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "codex-contracts-eth",
|
"name": "codex-contracts-eth",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run lint && hardhat test",
|
"test": "hardhat test",
|
||||||
"fuzz": "hardhat compile && fuzzing/fuzz.sh",
|
"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\"",
|
"start": "concurrently --names \"hardhat,deployment\" --prefix \"[{time} {name}]\" \"hardhat node\" \"sleep 2 && npm run mine && npm run deploy -- --network localhost\"",
|
||||||
"compile": "hardhat compile",
|
"compile": "hardhat compile",
|
||||||
@ -18,10 +18,10 @@
|
|||||||
"gas:report": "REPORT_GAS=true hardhat test"
|
"gas:report": "REPORT_GAS=true hardhat test"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openzeppelin/contracts": "^5.3.0",
|
|
||||||
"@nomicfoundation/hardhat-chai-matchers": "^2.0.8",
|
"@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",
|
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||||
|
"@openzeppelin/contracts": "^5.3.0",
|
||||||
"@stdlib/stats-binomial-test": "^0.2.2",
|
"@stdlib/stats-binomial-test": "^0.2.2",
|
||||||
"chai": "^4.5.0",
|
"chai": "^4.5.0",
|
||||||
"ethers": "6.14.4",
|
"ethers": "6.14.4",
|
||||||
|
|||||||
@ -41,6 +41,7 @@ const {
|
|||||||
} = require("./evm")
|
} = require("./evm")
|
||||||
const { getBytes } = require("ethers")
|
const { getBytes } = require("ethers")
|
||||||
const MarketplaceModule = require("../ignition/modules/marketplace")
|
const MarketplaceModule = require("../ignition/modules/marketplace")
|
||||||
|
const MarketplaceUpgradeModule = require("../ignition/modules/marketplace-test-upgrade")
|
||||||
const { assertDeploymentRejectedWithCustomError } = require("./helpers")
|
const { assertDeploymentRejectedWithCustomError } = require("./helpers")
|
||||||
|
|
||||||
const ACCOUNT_STARTING_BALANCE = 1_000_000_000_000_000n
|
const ACCOUNT_STARTING_BALANCE = 1_000_000_000_000_000n
|
||||||
@ -62,7 +63,7 @@ describe("Marketplace constructor", function () {
|
|||||||
|
|
||||||
const promise = ignition.deploy(MarketplaceModule, {
|
const promise = ignition.deploy(MarketplaceModule, {
|
||||||
parameters: {
|
parameters: {
|
||||||
Marketplace: {
|
TestProxy: {
|
||||||
configuration: config,
|
configuration: config,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -86,11 +87,9 @@ describe("Marketplace constructor", function () {
|
|||||||
config.collateral.slashPercentage = 1
|
config.collateral.slashPercentage = 1
|
||||||
config.collateral.maxNumberOfSlashes = 101
|
config.collateral.maxNumberOfSlashes = 101
|
||||||
|
|
||||||
const expectedError = "Marketplace_MaximumSlashingTooHigh"
|
|
||||||
|
|
||||||
const promise = ignition.deploy(MarketplaceModule, {
|
const promise = ignition.deploy(MarketplaceModule, {
|
||||||
parameters: {
|
parameters: {
|
||||||
Marketplace: {
|
TestProxy: {
|
||||||
configuration: config,
|
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 () {
|
describe("Marketplace", function () {
|
||||||
const proof = exampleProof()
|
const proof = exampleProof()
|
||||||
const config = exampleConfiguration()
|
const config = exampleConfiguration()
|
||||||
@ -143,7 +244,8 @@ describe("Marketplace", function () {
|
|||||||
MarketplaceModule,
|
MarketplaceModule,
|
||||||
{
|
{
|
||||||
parameters: {
|
parameters: {
|
||||||
Marketplace: {
|
TestProxy: {
|
||||||
|
// TestMarketplace initialization is done in the TestProxy module
|
||||||
configuration: config,
|
configuration: config,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user