1 Commits

Author SHA1 Message Date
hueso
4e496ec5eb Partial hardhat -> viem migration for tests.
Notably all calls to p2pix.callStatic.getStr() were replaced by viem.stringToHex() due to bug in ethers (https://github.com/ethers-io/ethers.js/issues/4965)
Typescript updated to v5.
Full migration to viem might not be possible currently due to hard-chai-matchers incompatibility:
> The hardhat-chai-matchers plugin is designed to work with hardhat-ethers. Attempting to use it in conjunction with hardhat-viem results in compatibility issues.
https://hardhat.org/hardhat-chai-matchers/docs/overview
2025-04-13 17:21:14 -03:00
63 changed files with 8455 additions and 5213 deletions

2
.commitlintrc.yaml Normal file
View File

@@ -0,0 +1,2 @@
extends:
- "@commitlint/config-conventional"

3
.czrc Normal file
View File

@@ -0,0 +1,3 @@
{
"path": "cz-conventional-changelog"
}

View File

@@ -1,5 +1,4 @@
MNEMONIC="{INSERT_12_WORD_MNEMONIC}" MNEMONIC="{INSERT_12_WORD_MNEMONIC}"
PRIVATE_KEY="{INSERT_0x_PRIVATE_KEY}"
INFURA_API_KEY="{INSERT_API_KEY}" INFURA_API_KEY="{INSERT_API_KEY}"
ALCHEMY_API_KEY="{INSERT_API_KEY}" ALCHEMY_API_KEY="{INSERT_API_KEY}"

21
.eslintignore Normal file
View File

@@ -0,0 +1,21 @@
# directories
.yarn/
**/.coverage_artifacts
**/.coverage_cache
**/.coverage_contracts
**/artifacts
**/build
**/cache
**/coverage
**/dist
**/node_modules
**/types
# files
*.env
*.log
.pnp.*
coverage.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*

21
.eslintrc.yaml Normal file
View File

@@ -0,0 +1,21 @@
extends:
- "eslint:recommended"
- "plugin:@typescript-eslint/eslint-recommended"
- "plugin:@typescript-eslint/recommended"
- "prettier"
parser: "@typescript-eslint/parser"
parserOptions:
project: "./tsconfig.json"
plugins:
- "@typescript-eslint"
root: true
rules:
"@typescript-eslint/no-floating-promises":
- error
- ignoreIIFE: true
ignoreVoid: true
"@typescript-eslint/no-inferrable-types": "off"
"@typescript-eslint/no-unused-vars":
- error
- argsIgnorePattern: "_"
varsIgnorePattern: "_"

View File

@@ -1,32 +0,0 @@
name: Lint
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
concurrency:
group: lint-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Solidity + TypeScript + Prettier
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Run lint
run: yarn lint

View File

@@ -1,35 +0,0 @@
name: Test
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Hardhat tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Compile contracts
run: yarn compile
- name: Run tests
run: yarn test

8
.gitignore vendored
View File

@@ -28,11 +28,3 @@ coverage.json
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
docs/lib/mock/*.md
.dagrobin/
# Hardhat Ignition local/ephemeral deployments
ignition/deployments/chain-31337/
# Per-developer local deploy state (deterministic per mnemonic)
deploys/localhost.json

1
.husky/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_

4
.husky/commit-msg Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn dlx commitlint --edit $1

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn dlx lint-staged

View File

@@ -11,12 +11,6 @@
**/node_modules **/node_modules
**/types **/types
# deploy artifacts
deploys/old/
# auto-generated docs
docs/
# files # files
*.env *.env
*.log *.log
@@ -25,3 +19,5 @@ coverage.json
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
contracts/p2pix.sol

View File

@@ -1,8 +1,10 @@
plugins:
- prettier-plugin-solidity
arrowParens: avoid arrowParens: avoid
bracketSpacing: true bracketSpacing: true
endOfLine: auto endOfLine: auto
importOrder: ["<THIRD_PARTY_MODULES>", "^[./]"]
importOrderParserPlugins: ["typescript"]
importOrderSeparation: true
importOrderSortSpecifiers: true
printWidth: 62 printWidth: 62
singleQuote: false singleQuote: false
tabWidth: 2 tabWidth: 2

View File

@@ -14,9 +14,5 @@ module.exports = {
providerOptions: { providerOptions: {
mnemonic: process.env.MNEMONIC, mnemonic: process.env.MNEMONIC,
}, },
skipFiles: [ skipFiles: ["test", 'core/BaseUtils.sol', 'core/OwnerSettings.sol'],
"test",
"core/BaseUtils.sol",
"core/OwnerSettings.sol",
],
}; };

View File

@@ -3,7 +3,6 @@
"plugins": ["prettier"], "plugins": ["prettier"],
"rules": { "rules": {
"code-complexity": ["error", 8], "code-complexity": ["error", 8],
"avoid-low-level-calls": "off",
"compiler-version": ["error", ">=0.8.4"], "compiler-version": ["error", ">=0.8.4"],
"const-name-snakecase": "off", "const-name-snakecase": "off",
"constructor-syntax": "error", "constructor-syntax": "error",
@@ -11,14 +10,7 @@
"error", "error",
{ "ignoreConstructors": true } { "ignoreConstructors": true }
], ],
"function-max-lines": "off",
"gas-calldata-parameters": "off",
"gas-indexed-events": "off",
"gas-strict-inequalities": "off",
"gas-struct-packing": "off",
"interface-starts-with-i": "off",
"max-line-length": ["error", 120], "max-line-length": ["error", 120],
"no-inline-assembly": "off",
"not-rely-on-time": "off", "not-rely-on-time": "off",
"prettier/prettier": [ "prettier/prettier": [
"error", "error",
@@ -26,7 +18,6 @@
"endOfLine": "auto" "endOfLine": "auto"
} }
], ],
"reason-string": ["warn", { "maxLength": 64 }], "reason-string": ["warn", { "maxLength": 64 }]
"use-natspec": "off"
} }
} }

View File

@@ -1,6 +1,3 @@
# directories # directories
**/artifacts **/artifacts
**/cache
**/node_modules **/node_modules
deploys/
docs/

File diff suppressed because one or more lines are too long

786
.yarn/releases/yarn-3.2.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
compressionLevel: mixed
enableGlobalCache: false
nodeLinker: node-modules nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.9.2.cjs plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.2.1.cjs

View File

@@ -55,23 +55,25 @@
| Testnet | Token Address | P2pix Address | Reputation Address | Multicall Address | | Testnet | Token Address | P2pix Address | Reputation Address | Multicall Address |
| ------- | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | | ------- | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ |
| Sepolia | 0x3eBE212377D847828eBb9D2a100f5c5EA1EF3A72 | 0xb7cDAE58C6e715Cfd795BB142041E43b9CB02497 | 0xFd1194A56995Ef7B62730F4061408e79d88E5207 | 0x2f7f9848A803E67d79C0C8aa42b663dCF6E1B5ed | | Goerli | 0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00 | 0x2414817FF64A114d91eCFA16a834d3fCf69103d4 | 0x2CFD9354Ec7614fEf036EFd6A730dA1d5fC2762A | 0x8FE009992d96A86c7f0Bccdaf1eC3471E302a8a6 |
| Mumbai | 0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29 | 0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00 | 0x570445E3eF413bCDb5De79ed27B1c3840683e385 | 0x718B2C4DE4F9654E1349F610ff561249bfe1c418 |
<!-- All contracts deployed by 0x8dC06F985C131166570825F52447E8c88d64aE20 --> <!-- All contracts deployed by 0x8dC06F985C131166570825F52447E8c88d64aE20 -->
<!-- https://sepolia.etherscan.io/address/0x3eBE212377D847828eBb9D2a100f5c5EA1EF3A72#code --> <!-- https://goerli.etherscan.io/address/0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00#code -->
<!-- https://sepolia.etherscan.io/address/0xb7cDAE58C6e715Cfd795BB142041E43b9CB02497#code --> <!-- https://goerli.etherscan.io/address/0x2414817FF64A114d91eCFA16a834d3fCf69103d4#code -->
<!-- https://sepolia.etherscan.io/address/0xFd1194A56995Ef7B62730F4061408e79d88E5207#code --> <!-- https://goerli.etherscan.io/address/0x2CFD9354Ec7614fEf036EFd6A730dA1d5fC2762A#code -->
<!-- https://sepolia.etherscan.io/address/0x2f7f9848A803E67d79C0C8aa42b663dCF6E1B5ed#code --> <!-- https://goerli.etherscan.io/address/0x8FE009992d96A86c7f0Bccdaf1eC3471E302a8a6#code -->
<!-- https://mumbai.polygonscan.com/address/0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29#code -->
<!-- https://mumbai.polygonscan.com/address/0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00#code -->
<!-- https://mumbai.polygonscan.com/address/0x570445e3ef413bcdb5de79ed27b1c3840683e385#code -->
<!-- https://mumbai.polygonscan.com/address/0x718B2C4DE4F9654E1349F610ff561249bfe1c418#code -->
## Usage ## Usage
### Pre Requisites ### Pre Requisites
Before installing, create a `.env` file and set the authentication method and other env criteria as in `.env.example`. Before installing, create a `.env` file and set a BIP-39 compatible mnemonic and other env criteria as in `.env.example`.
You can authenticate with either:
- `PRIVATE_KEY` — a raw hex private key (e.g. `0x123...`). Takes priority if both are set.
- `MNEMONIC` — a BIP-39 compatible 12-word mnemonic phrase. Used as fallback when `PRIVATE_KEY` is not set.
### Install ### Install
@@ -123,71 +125,36 @@ To grab deployment addresses you can just grab from deploys folder:
import localhostDeploys from "p2pix-smart-contracts/deploys/localhost.json"; import localhostDeploys from "p2pix-smart-contracts/deploys/localhost.json";
``` ```
## Deployment (Hardhat Ignition) ## Deploying to local environment
Deployments are orchestrated by On the first teminal, use the following command and import some wallets to your Metamask, then connect to the network pointed:
[Hardhat Ignition](https://hardhat.org/ignition). Each module declares its
contracts; Ignition handles ordering, idempotency, and per-network state
under `ignition/deployments/`.
### Modules
- `ignition/modules/MockToken.ts` — ERC20 mock used in dev / testnets.
- `ignition/modules/Reputation.ts` — Reputation + Multicall.
- `ignition/modules/P2PIX.ts` — production deploy (no MockToken). Token
addresses passed via parameters (`tokens` + `allowed`).
- `ignition/modules/P2PIXWithMock.ts` — dev/test deploy. Wires Reputation +
MockToken and deploys P2PIX.
### Per-network parameters
Network-specific values (e.g. `defaultBlocks`, `validSigners`, MockToken
`supply`) live in `ignition/parameters/<network>.json`. Only
`localhost.json` is checked in; copy it to e.g. `sepolia.json` and set the
network values before running the deploy script.
### Local environment
On the first terminal:
```sh ```sh
yarn hardhat node yarn hardhat node
``` ```
On the second terminal: On the second teminal, run the following commands:
```sh ```sh
yarn deploy:mock --network localhost --parameters ignition/parameters/localhost.json yarn deploy1:localhost
yarn deploy2:localhost
``` ```
Addresses for every deployed contract are written to **_NOTE_:** The second script transfers 2M tokens to the first wallet of the node.
`ignition/deployments/chain-31337/deployed_addresses.json` (this path is To use the P2Pix smart contract first transfer some of the tokens to other wallets.
gitignored — local-only state).
### Testnets ## Deploying to testnets
Deploy to Ethereum's Goerli testnet:
```sh ```sh
yarn deploy:mock --network sepolia yarn deploy1:goerli
yarn deploy2:goerli
``` ```
Requires the matching `ignition/parameters/sepolia.json` and the relevant Deploy to Polygon's Mumbai testnet:
API keys in `.env`.
### Production (real tokens) ```sh
yarn deploy1:mumbai
Use the `P2PIX` module when the target network already has the ERC20 yarn deploy2:mumbai
tokens you want P2PIX to support — no MockToken is deployed. ```
1. Copy `ignition/parameters/prod.example.json` to
`ignition/parameters/<network>.json` and set:
- `defaultBlocks`, `validSigners`
- `tokens` — array of ERC20 addresses on the target network
- `allowed` — array of booleans, same length as `tokens`
2. Run:
```sh
yarn deploy --network <network> --parameters ignition/parameters/<network>.json
```
The deployed Reputation/Multicall/P2PIX addresses are written to
`ignition/deployments/chain-<id>/deployed_addresses.json`.

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
import { IReputation } from "./lib/interfaces/IReputation.sol"; import { IReputation } from "./lib/interfaces/IReputation.sol";

View File

@@ -1,12 +1,11 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
import { ERC20, OwnerSettings } from "contracts/core/OwnerSettings.sol"; import { ERC20, OwnerSettings } from "contracts/core/OwnerSettings.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { ECDSA } from "contracts/lib/utils/ECDSA.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import { MerkleProofLib as Merkle } from "contracts/lib/utils/MerkleProofLib.sol";
import { MerkleProof as Merkle } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import { ReentrancyGuard } from "contracts/lib/utils/ReentrancyGuard.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
abstract contract BaseUtils is abstract contract BaseUtils is
OwnerSettings, OwnerSettings,
@@ -44,9 +43,10 @@ abstract contract BaseUtils is
if ( if (
!validBacenSigners( !validBacenSigners(
_castAddrToKey( _castAddrToKey(
ECDSA.recover( ECDSA.recoverCalldata(
MessageHashUtils ECDSA.toEthSignedMessageHash(
.toEthSignedMessageHash(_message), _message
),
_signature _signature
) )
) )

View File

@@ -1,26 +1,22 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
abstract contract Constants { abstract contract Constants {
/// ███ Constants ██████████████████████████████████████████████████████████ /// ███ Constants ██████████████████████████████████████████████████████████
uint256 internal constant _ROOT_UPDATED_EVENT_SIGNATURE = uint256 constant _ROOT_UPDATED_EVENT_SIGNATURE =
0x0b294da292f26e55fd442b5c0164fbb9013036ff00c5cfdde0efd01c1baaf632; 0x0b294da292f26e55fd442b5c0164fbb9013036ff00c5cfdde0efd01c1baaf632;
uint256 uint256 constant _ALLOWED_ERC20_UPDATED_EVENT_SIGNATURE =
internal constant _ALLOWED_ERC20_UPDATED_EVENT_SIGNATURE =
0x5d6e86e5341d57a92c49934296c51542a25015c9b1782a1c2722a940131c3d9a; 0x5d6e86e5341d57a92c49934296c51542a25015c9b1782a1c2722a940131c3d9a;
uint256 uint256 constant _TRUSTED_FORWARDER_UPDATED_EVENT_SIGNATURE =
internal constant _TRUSTED_FORWARDER_UPDATED_EVENT_SIGNATURE =
0xbee55516e29d3969d3cb8eb01351eb3c52d06f9e2435bd5a8bfe3647e185df92; 0xbee55516e29d3969d3cb8eb01351eb3c52d06f9e2435bd5a8bfe3647e185df92;
/// @dev Seller casted to key => Seller's allowlist merkleroot. /// @dev Seller casted to key => Seller's allowlist merkleroot.
/// mapping(uint256 => bytes32) public sellerAllowList; /// mapping(uint256 => bytes32) public sellerAllowList;
uint256 internal constant _SELLER_ALLOWLIST_SLOT_SEED = uint256 constant _SELLER_ALLOWLIST_SLOT_SEED = 0x74dfee70;
0x74dfee70;
/// @dev Tokens allowed to serve as the underlying amount of a deposit. /// @dev Tokens allowed to serve as the underlying amount of a deposit.
/// mapping(ERC20 => bool) public allowedERC20s; /// mapping(ERC20 => bool) public allowedERC20s;
uint256 internal constant _ALLOWED_ERC20_SLOT_SEED = uint256 constant _ALLOWED_ERC20_SLOT_SEED = 0xcbc9d1c4;
0xcbc9d1c4;
/// @dev `balance` max. value = 10**26. /// @dev `balance` max. value = 10**26.
/// @dev `pixTarget` keys are restricted to 160 bits. /// @dev `pixTarget` keys are restricted to 160 bits.
@@ -36,21 +32,16 @@ abstract contract Constants {
/// mstore(0x0c, _SELLER_BALANCE_SLOT_SEED) /// mstore(0x0c, _SELLER_BALANCE_SLOT_SEED)
/// mstore(0x00, seller) /// mstore(0x00, seller)
/// let value := sload(keccak256(0x0c, 0x34)). /// let value := sload(keccak256(0x0c, 0x34)).
uint256 internal constant _SELLER_BALANCE_SLOT_SEED = uint256 constant _SELLER_BALANCE_SLOT_SEED = 0x739094b1;
0x739094b1;
/// @dev The bitmask of `sellerBalance` entry. /// @dev The bitmask of `sellerBalance` entry.
uint256 internal constant BITMASK_SB_ENTRY = uint256 constant BITMASK_SB_ENTRY = (1 << 94) - 1;
(1 << 94) - 1;
/// @dev The bit position of `valid` in `sellerBalance`. /// @dev The bit position of `valid` in `sellerBalance`.
uint256 internal constant BITPOS_VALID = 95; uint256 constant BITPOS_VALID = 95;
/// @dev The scalar of BRZ token. /// @dev The scalar of BRZ token.
uint256 internal constant WAD = 1e18; uint256 constant WAD = 1e18;
uint256 internal constant MAXBALANCE_UPPERBOUND = uint256 constant MAXBALANCE_UPPERBOUND = 1e8 ether;
1e8 ether; uint256 constant REPUTATION_LOWERBOUND = 1e2 ether;
uint256 internal constant REPUTATION_LOWERBOUND = uint256 constant LOCKAMOUNT_UPPERBOUND = 1e6 ether;
1e2 ether;
uint256 internal constant LOCKAMOUNT_UPPERBOUND =
1e6 ether;
} }

View File

@@ -1,9 +1,10 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20 } from "contracts/lib/tokens/ERC20.sol";
library DataTypes { library DataTypes {
struct Lock { struct Lock {
uint256 counter; uint256 counter;
uint256 expirationBlock; uint256 expirationBlock;

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20 } from "contracts/lib/tokens/ERC20.sol";
// prettier-ignore // prettier-ignore
interface EventAndErrors { interface EventAndErrors {

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
import { ERC2771 } from "contracts/lib/metatx/ERC2771Context.sol"; import { ERC2771Context as ERC2771 } from "contracts/lib/metatx/ERC2771Context.sol";
import { ERC20, SafeTransferLib } from "contracts/lib/utils/SafeTransferLib.sol"; import { ERC20, SafeTransferLib } from "contracts/lib/utils/SafeTransferLib.sol";
import { IReputation } from "contracts/lib/interfaces/IReputation.sol"; import { IReputation } from "contracts/lib/interfaces/IReputation.sol";
import { EventAndErrors } from "contracts/core/EventAndErrors.sol"; import { EventAndErrors } from "contracts/core/EventAndErrors.sol";

View File

@@ -1,15 +1,87 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; pragma solidity >=0.8.4;
import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; /// @author OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Context.sol)
abstract contract ERC2771 is ERC2771Context(address(0)) { /// @dev Provides information about the current execution context, including the
mapping(address => bool) public trustedForwarders; /// sender of the transaction and its data. While these are generally available
/// via msg.sender and msg.data, they should not be accessed in such a direct
/// manner, since when dealing with meta-transactions the account sending and
/// paying for execution may not be the actual sender (as far as an application
/// is concerned).
///
/// This contract is only required for intermediate, library-like contracts.
abstract contract Context {
function _msgSender()
internal
view
virtual
returns (address)
{
return msg.sender;
}
function isTrustedForwarder( function _msgData()
address forwarder internal
) public view override returns (bool) { view
return trustedForwarders[forwarder]; virtual
returns (bytes calldata)
{
return msg.data;
}
}
/// @author Modified from OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol)
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Context.sol
/// @dev Context variant with ERC2771 support.
abstract contract ERC2771Context is Context {
// address private immutable _trustedForwarder;
mapping(address => bool) public trustedForwarders;
/// @custom:oz-upgrades-unsafe-allow constructor
// constructor(address trustedForwarder) {
// _trustedForwarder = trustedForwarder;
// }
function _msgSender()
internal
view
virtual
override
returns (address sender)
{
if (trustedForwarders[msg.sender]) {
// The assembly code is more direct than the Solidity version using `abi.decode`.
/// @solidity memory-safe-assembly
assembly {
sender := shr(
96,
calldataload(sub(calldatasize(), 20))
)
}
} else {
return super._msgSender();
}
}
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
return trustedForwarders[forwarder];
}
function _msgData()
internal
view
virtual
override
returns (bytes calldata)
{
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return super._msgData();
}
} }
} }

View File

@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20 } from "../tokens/ERC20.sol";
contract MockToken is ERC20 { contract MockToken is ERC20 {
constructor(uint256 supply) ERC20("MockBRL", "MBRL") { constructor(uint256 supply) ERC20("MockBRL", "MBRL", 18) {
_mint(msg.sender, supply); _mint(msg.sender, supply);
} }

View File

@@ -0,0 +1,250 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(
address indexed from,
address indexed to,
uint256 amount
);
event Approval(
address indexed owner,
address indexed spender,
uint256 amount
);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256))
public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(
address spender,
uint256 amount
) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(
address to,
uint256 amount
) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max)
allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(
deadline >= block.timestamp,
"PERMIT_DEADLINE_EXPIRED"
);
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(
recoveredAddress != address(0) &&
recoveredAddress == owner,
"INVALID_SIGNER"
);
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR()
public
view
virtual
returns (bytes32)
{
return
block.chainid == INITIAL_CHAIN_ID
? INITIAL_DOMAIN_SEPARATOR
: computeDomainSeparator();
}
function computeDomainSeparator()
internal
view
virtual
returns (bytes32)
{
return
keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(
address to,
uint256 amount
) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(
address from,
uint256 amount
) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
/// @notice Gas optimized ECDSA wrapper.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol)
library ECDSA {
/// @dev The signature is invalid.
error InvalidSignature();
/// @dev The number which `s` must not exceed in order for
/// the signature to be non-malleable.
bytes32 private constant _MALLEABILITY_THRESHOLD =
0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0;
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the `signature`.
///
/// This function does NOT accept EIP-2098 short form signatures.
/// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098
/// short form signatures instead.
function recoverCalldata(
bytes32 hash,
bytes calldata signature
) internal view returns (address result) {
/// @solidity memory-safe-assembly
assembly {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// Directly copy `r` and `s` from the calldata.
calldatacopy(0x40, signature.offset, 0x40)
// Store the `hash` in the scratch space.
mstore(0x00, hash)
// Compute `v` and store it in the scratch space.
mstore(
0x20,
byte(
0,
calldataload(add(signature.offset, 0x40))
)
)
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
and(
// If the signature is exactly 65 bytes in length.
eq(signature.length, 65),
// If `s` in lower half order, such that the signature is not malleable.
lt(
mload(0x60),
add(_MALLEABILITY_THRESHOLD, 1)
)
), // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x00, // Start of output.
0x20 // Size of output.
)
)
result := mload(0x00)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if iszero(returndatasize()) {
// Store the function selector of `InvalidSignature()`.
mstore(0x00, 0x8baa579f)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the zero slot.
mstore(0x60, 0)
// Restore the free memory pointer.
mstore(0x40, m)
}
}
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(
bytes32 hash
) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
// Store into scratch space for keccak256.
mstore(0x20, hash)
mstore(
0x00,
"\x00\x00\x00\x00\x19Ethereum Signed Message:\n32"
)
// 0x40 - 0x04 = 0x3c
result := keccak256(0x04, 0x3c)
}
}
}

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree.
/// @author Solady
/// (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solmate
/// (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from OpenZeppelin
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
library MerkleProofLib {
/// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
function verify(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
if proof.length {
// Left shift by 5 is equivalent to multiplying by 0x20.
let end := add(
proof.offset,
shl(5, proof.length)
)
// Initialize `offset` to the offset of `proof` in the calldata.
let offset := proof.offset
// Iterate over proof elements to compute root hash.
for {
} 1 {
} {
// Slot of `leaf` in scratch space.
// If the condition is true: 0x20, otherwise: 0x00.
let scratch := shl(
5,
gt(leaf, calldataload(offset))
)
// Store elements to hash contiguously in scratch space.
// Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
mstore(scratch, leaf)
mstore(
xor(scratch, 0x20),
calldataload(offset)
)
// Reuse `leaf` to store the hash to reduce stack operations.
leaf := keccak256(0x00, 0x40)
offset := add(offset, 0x20)
if iszero(lt(offset, end)) {
break
}
}
}
isValid := eq(leaf, root)
}
}
}

View File

@@ -19,12 +19,12 @@ contract Multicall {
} }
//prettier-ignore //prettier-ignore
//solhint-disable-next-line no-empty-blocks
constructor(/* */) payable {/* */} constructor(/* */) payable {/* */}
function mtc1( function mtc1(Call[] calldata calls)
Call[] calldata calls external
) external returns (uint256, bytes[] memory) { returns (uint256, bytes[] memory)
{
uint256 bn = block.number; uint256 bn = block.number;
uint256 len = calls.length; uint256 len = calls.length;
bytes[] memory res = new bytes[](len); bytes[] memory res = new bytes[](len);
@@ -49,14 +49,21 @@ contract Multicall {
return (bn, res); return (bn, res);
} }
function mtc2( function mtc2(Call[] calldata calls)
Call[] calldata calls external
) external returns (uint256, bytes32, Result[] memory) { returns (
uint256,
bytes32,
Result[] memory
)
{
uint256 bn = block.number; uint256 bn = block.number;
// µ 0 s [0] ≡ P(IHp , µs [0], 0) ∴ P is the hash of a block of a particular number, up to a maximum age. // µ 0 s [0] ≡ P(IHp , µs [0], 0) ∴ P is the hash of a block of a particular number, up to a maximum age.
// 0 is left on the stack if the looked for `block.number` is >= to the current `block.number` or more than 256 // 0 is left on the stack if the looked for `block.number` is >= to the current `block.number` or more than 256
// blocks behind the current block (Yellow Paper, p. 33, https://ethereum.github.io/yellowpaper/paper.pdf). // blocks behind the current block (Yellow Paper, p. 33, https://ethereum.github.io/yellowpaper/paper.pdf).
bytes32 bh = blockhash(bn /* - 1 */); bytes32 bh = blockhash(
bn /* - 1 */
);
uint256 len = calls.length; uint256 len = calls.length;
Result[] memory res = new Result[](len); Result[] memory res = new Result[](len);
uint256 i; uint256 i;

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
/// @notice Reentrancy protection for smart contracts.
/// @author z0r0z.eth
/// @author Modified from Seaport
/// (https://github.com/ProjectOpenSea/seaport/blob/main/contracts/lib/ReentrancyGuard.sol)
/// @author Modified from Solmate
/// (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
error Reentrancy();
uint256 private guard = 1;
modifier nonReentrant() virtual {
setReentrancyGuard();
_;
clearReentrancyGuard();
}
/// @dev Check guard sentinel value and set it.
function setReentrancyGuard() internal virtual {
if (guard == 2) revert Reentrancy();
guard = 2;
}
/// @dev Unset sentinel value.
function clearReentrancyGuard() internal virtual {
guard = 1;
}
}

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity >=0.8.4; pragma solidity >=0.8.4;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ERC20 } from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)

View File

@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19; pragma solidity 0.8.19;
/// ______ __ /// ______ __
/// .-----.|__ |.-----.|__|.--.--. /// .-----.|__ |.-----.|__|.--.--.
@@ -12,6 +12,7 @@ import { OwnerSettings, ERC20, SafeTransferLib } from "contracts/core/OwnerSetti
import { BaseUtils } from "contracts/core/BaseUtils.sol"; import { BaseUtils } from "contracts/core/BaseUtils.sol";
import { DataTypes as DT } from "contracts/core/DataTypes.sol"; import { DataTypes as DT } from "contracts/core/DataTypes.sol";
contract P2PIX is BaseUtils { contract P2PIX is BaseUtils {
// solhint-disable use-forbidden-name // solhint-disable use-forbidden-name
// solhint-disable no-inline-assembly // solhint-disable no-inline-assembly
@@ -34,7 +35,6 @@ contract P2PIX is BaseUtils {
ERC20[] memory tokens, ERC20[] memory tokens,
bool[] memory tokenStates bool[] memory tokenStates
) )
payable
OwnerSettings( OwnerSettings(
defaultBlocks, defaultBlocks,
validSigners, validSigners,
@@ -42,9 +42,7 @@ contract P2PIX is BaseUtils {
tokens, tokens,
tokenStates tokenStates
) )
{ payable {/* */}
/* */
}
/// @notice Creates a deposit order based on a seller's /// @notice Creates a deposit order based on a seller's
/// offer of an amount of ERC20 tokens. /// offer of an amount of ERC20 tokens.
@@ -60,13 +58,10 @@ contract P2PIX is BaseUtils {
uint96 amount, uint96 amount,
bool valid bool valid
) public nonReentrant { ) public nonReentrant {
if (bytes(pixTarget).length == 0)
revert EmptyPixTarget(); if (bytes(pixTarget).length == 0) revert EmptyPixTarget();
if (!allowedERC20s(token)) revert TokenDenied(); if (!allowedERC20s(token)) revert TokenDenied();
uint256 _sellerBalance = __sellerBalance( uint256 _sellerBalance = __sellerBalance(msg.sender, token);
msg.sender,
token
);
uint256 currBal = _sellerBalance & BITMASK_SB_ENTRY; uint256 currBal = _sellerBalance & BITMASK_SB_ENTRY;
uint256 _newBal = uint256(currBal + amount); uint256 _newBal = uint256(currBal + amount);
@@ -102,10 +97,7 @@ contract P2PIX is BaseUtils {
/// @notice This function does not affect any ongoing active locks. /// @notice This function does not affect any ongoing active locks.
/// @dev Function sighash: 0x6d82d9e0 /// @dev Function sighash: 0x6d82d9e0
function setValidState(ERC20 token, bool state) public { function setValidState(ERC20 token, bool state) public {
uint256 _sellerBalance = __sellerBalance( uint256 _sellerBalance = __sellerBalance(msg.sender, token);
msg.sender,
token
);
if (_sellerBalance != 0) { if (_sellerBalance != 0) {
uint256 _valid = _castBool(state); uint256 _valid = _castBool(state);
@@ -156,8 +148,9 @@ contract P2PIX is BaseUtils {
lockID = ++lockCounter; lockID = ++lockCounter;
} }
if (mapLocks[lockID].expirationBlock >= block.number) if (
revert NotExpired(); mapLocks[lockID].expirationBlock >= block.number
) revert NotExpired();
bytes32 _pixTarget = getPixTarget(seller, token); bytes32 _pixTarget = getPixTarget(seller, token);
@@ -165,19 +158,12 @@ contract P2PIX is BaseUtils {
// otherwise, the trustedForwarder must be previously added // otherwise, the trustedForwarder must be previously added
// to a seller whitelist. // to a seller whitelist.
if (merkleProof.length != 0) { if (merkleProof.length != 0) {
_merkleVerify( _merkleVerify( merkleProof, sellerAllowList(seller), _msgSender());
merkleProof,
sellerAllowList(seller), } else if ( amount > REPUTATION_LOWERBOUND && msg.sender == _msgSender() ) {
_msgSender()
); uint256 spendLimit; uint256 userCredit =
} else if ( userRecord[_castAddrToKey(_msgSender())];
amount > REPUTATION_LOWERBOUND &&
msg.sender == _msgSender()
) {
uint256 spendLimit;
uint256 userCredit = userRecord[
_castAddrToKey(_msgSender())
];
(spendLimit) = _limiter(userCredit / WAD); (spendLimit) = _limiter(userCredit / WAD);
if ( if (
amount > (spendLimit * WAD) || amount > (spendLimit * WAD) ||
@@ -242,18 +228,11 @@ contract P2PIX is BaseUtils {
if (_msgSender() == msg.sender) { if (_msgSender() == msg.sender) {
if (msg.sender != l.buyerAddress) { if (msg.sender != l.buyerAddress) {
userRecord[ userRecord[_castAddrToKey(msg.sender)] += (lockAmount >> 1);
_castAddrToKey(msg.sender) userRecord[_castAddrToKey(l.buyerAddress)] += (lockAmount >> 1);
] += (lockAmount >> 1);
userRecord[
_castAddrToKey(l.buyerAddress)
] += (lockAmount >> 1);
} else { } else {
userRecord[ userRecord[_castAddrToKey(msg.sender)] += lockAmount;
_castAddrToKey(msg.sender) }}
] += lockAmount;
}
}
SafeTransferLib.safeTransfer( SafeTransferLib.safeTransfer(
t, t,
@@ -270,9 +249,9 @@ contract P2PIX is BaseUtils {
/// @notice For each successfull unexpired lock recovered, /// @notice For each successfull unexpired lock recovered,
/// `userRecord[_castAddrToKey(l.relayerAddress)]` is decreased by half of its value. /// `userRecord[_castAddrToKey(l.relayerAddress)]` is decreased by half of its value.
/// @dev Function sighash: 0xb0983d39 /// @dev Function sighash: 0xb0983d39
function unlockExpired( function unlockExpired(uint256[] calldata lockIDs)
uint256[] calldata lockIDs public
) public { {
uint256 i; uint256 i;
uint256 locksSize = lockIDs.length; uint256 locksSize = lockIDs.length;
@@ -281,21 +260,19 @@ contract P2PIX is BaseUtils {
_notExpired(l); _notExpired(l);
uint256 _sellerBalance = __sellerBalance( uint256 _sellerBalance =
l.seller, __sellerBalance(l.seller, l.token) & BITMASK_SB_ENTRY;
l.token
) & BITMASK_SB_ENTRY;
if ( if ((_sellerBalance + l.amount) > MAXBALANCE_UPPERBOUND)
(_sellerBalance + l.amount) > revert MaxBalExceeded();
MAXBALANCE_UPPERBOUND
) revert MaxBalExceeded();
_addSellerBalance(l.seller, l.token, l.amount); _addSellerBalance(l.seller, l.token, l.amount);
l.amount = 0; l.amount = 0;
uint256 userKey = _castAddrToKey(l.buyerAddress); uint256 userKey = _castAddrToKey(
l.buyerAddress
);
uint256 _newUserRecord = (userRecord[userKey] >> uint256 _newUserRecord = (userRecord[userKey] >>
1); 1);
@@ -335,8 +312,7 @@ contract P2PIX is BaseUtils {
setValidState(token, false); setValidState(token, false);
_decBal( _decBal(
(__sellerBalance(msg.sender, token) & (__sellerBalance(msg.sender, token) & BITMASK_SB_ENTRY),
BITMASK_SB_ENTRY),
amount, amount,
token, token,
msg.sender msg.sender
@@ -349,13 +325,16 @@ contract P2PIX is BaseUtils {
amount amount
); );
emit DepositWithdrawn(msg.sender, token, amount); emit DepositWithdrawn(
msg.sender,
token,
amount
);
} }
function setRoot( function setRoot(address addr, bytes32 merkleroot)
address addr, public
bytes32 merkleroot {
) public {
assembly ("memory-safe") { assembly ("memory-safe") {
// if (addr != msg.sender) // if (addr != msg.sender)
if iszero(eq(addr, caller())) { if iszero(eq(addr, caller())) {
@@ -429,13 +408,14 @@ contract P2PIX is BaseUtils {
} }
// we can directly dec from packed uint entry value // we can directly dec from packed uint entry value
_decSellerBalance(_k, _t, _amount); _decSellerBalance(_k,_t, _amount);
} }
function getBalance( function getBalance(address seller, ERC20 token)
address seller, public
ERC20 token view
) public view returns (uint256 bal) { returns (uint256 bal)
{
assembly ("memory-safe") { assembly ("memory-safe") {
for { for {
/* */ /* */
@@ -454,10 +434,11 @@ contract P2PIX is BaseUtils {
} }
} }
function getValid( function getValid(address seller, ERC20 token)
address seller, public
ERC20 token view
) public view returns (bool valid) { returns (bool valid)
{
assembly ("memory-safe") { assembly ("memory-safe") {
for { for {
/* */ /* */
@@ -471,9 +452,7 @@ contract P2PIX is BaseUtils {
BITMASK_SB_ENTRY, BITMASK_SB_ENTRY,
shr( shr(
BITPOS_VALID, BITPOS_VALID,
sload( sload(add(keccak256(0x0c, 0x34), 0x01))
add(keccak256(0x0c, 0x34), 0x01)
)
) )
) )
break break
@@ -481,10 +460,11 @@ contract P2PIX is BaseUtils {
} }
} }
function getPixTarget( function getPixTarget(address seller, ERC20 token)
address seller, public
ERC20 token view
) public view returns (bytes32 pixTarget) { returns (bytes32 pixTarget)
{
assembly ("memory-safe") { assembly ("memory-safe") {
for { for {
/* */ /* */
@@ -500,10 +480,7 @@ contract P2PIX is BaseUtils {
} }
} }
function getPixTargetString( function getPixTargetString(address seller, ERC20 token) public view returns (string memory pixTarget) {
address seller,
ERC20 token
) public view returns (string memory pixTarget) {
bytes32 _pixEnc = getPixTarget(seller, token); bytes32 _pixEnc = getPixTarget(seller, token);
pixTarget = string(abi.encodePacked(_pixEnc)); pixTarget = string(abi.encodePacked(_pixEnc));
} }
@@ -529,9 +506,7 @@ contract P2PIX is BaseUtils {
/// @notice External getter that returns the status of a lockIDs array. /// @notice External getter that returns the status of a lockIDs array.
/// @notice Call will not revert if provided with an empty array as parameter. /// @notice Call will not revert if provided with an empty array as parameter.
/// @dev Function sighash: 0x49ef8448 /// @dev Function sighash: 0x49ef8448
function getLocksStatus( function getLocksStatus(uint256[] memory ids)
uint256[] memory ids
)
external external
view view
returns (uint256[] memory, DT.LockStatus[] memory) returns (uint256[] memory, DT.LockStatus[] memory)

View File

@@ -3,6 +3,6 @@
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8" "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
], ],
"p2pix": "", "p2pix": "0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29",
"token": "" "token": "0xD38D6367f452D097ccBfDe4490b7de570B6A72Db"
} }

View File

@@ -1,9 +0,0 @@
{
"signers": [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"0x8963E134E6d22Ee9A26ac62a99964aB391ead816"
],
"token": "0xfE841c74250e57640390f46d914C88d22C51e82e",
"p2pix": "0x57Dcba05980761169508886eEdc6f5E7EC0411Dc"
}

8
deploys/rsktestnet.json Normal file
View File

@@ -0,0 +1,8 @@
{
"signers": [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
],
"token": "0xfE841c74250e57640390f46d914C88d22C51e82e",
"p2pix": "0x98ba35eb14b38D6Aa709338283af3e922476dE34"
}

224
docs/lib/mock/MockToken.md Normal file
View File

@@ -0,0 +1,224 @@
# MockToken
## Methods
### DOMAIN_SEPARATOR
```solidity
function DOMAIN_SEPARATOR() external view returns (bytes32)
```
#### Returns
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | bytes32 | undefined |
### allowance
```solidity
function allowance(address, address) external view returns (uint256)
```
#### Parameters
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | address | undefined |
| \_1 | address | undefined |
#### Returns
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | uint256 | undefined |
### approve
```solidity
function approve(address spender, uint256 amount) external nonpayable returns (bool)
```
#### Parameters
| Name | Type | Description |
| ------- | ------- | ----------- |
| spender | address | undefined |
| amount | uint256 | undefined |
#### Returns
| Name | Type | Description |
| ---- | ---- | ----------- |
| \_0 | bool | undefined |
### balanceOf
```solidity
function balanceOf(address) external view returns (uint256)
```
#### Parameters
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | address | undefined |
#### Returns
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | uint256 | undefined |
### decimals
```solidity
function decimals() external view returns (uint8)
```
#### Returns
| Name | Type | Description |
| ---- | ----- | ----------- |
| \_0 | uint8 | undefined |
### name
```solidity
function name() external view returns (string)
```
#### Returns
| Name | Type | Description |
| ---- | ------ | ----------- |
| \_0 | string | undefined |
### nonces
```solidity
function nonces(address) external view returns (uint256)
```
#### Parameters
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | address | undefined |
#### Returns
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | uint256 | undefined |
### permit
```solidity
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external nonpayable
```
#### Parameters
| Name | Type | Description |
| -------- | ------- | ----------- |
| owner | address | undefined |
| spender | address | undefined |
| value | uint256 | undefined |
| deadline | uint256 | undefined |
| v | uint8 | undefined |
| r | bytes32 | undefined |
| s | bytes32 | undefined |
### symbol
```solidity
function symbol() external view returns (string)
```
#### Returns
| Name | Type | Description |
| ---- | ------ | ----------- |
| \_0 | string | undefined |
### totalSupply
```solidity
function totalSupply() external view returns (uint256)
```
#### Returns
| Name | Type | Description |
| ---- | ------- | ----------- |
| \_0 | uint256 | undefined |
### transfer
```solidity
function transfer(address to, uint256 amount) external nonpayable returns (bool)
```
#### Parameters
| Name | Type | Description |
| ------ | ------- | ----------- |
| to | address | undefined |
| amount | uint256 | undefined |
#### Returns
| Name | Type | Description |
| ---- | ---- | ----------- |
| \_0 | bool | undefined |
### transferFrom
```solidity
function transferFrom(address from, address to, uint256 amount) external nonpayable returns (bool)
```
#### Parameters
| Name | Type | Description |
| ------ | ------- | ----------- |
| from | address | undefined |
| to | address | undefined |
| amount | uint256 | undefined |
#### Returns
| Name | Type | Description |
| ---- | ---- | ----------- |
| \_0 | bool | undefined |
## Events
### Approval
```solidity
event Approval(address indexed owner, address indexed spender, uint256 amount)
```
#### Parameters
| Name | Type | Description |
| ----------------- | ------- | ----------- |
| owner `indexed` | address | undefined |
| spender `indexed` | address | undefined |
| amount | uint256 | undefined |
### Transfer
```solidity
event Transfer(address indexed from, address indexed to, uint256 amount)
```
#### Parameters
| Name | Type | Description |
| -------------- | ------- | ----------- |
| from `indexed` | address | undefined |
| to `indexed` | address | undefined |
| amount | uint256 | undefined |

View File

@@ -0,0 +1,16 @@
# Solidity API
## MockToken
### constructor
```solidity
constructor(uint256 supply) public
```
### mint
```solidity
function mint(address[] to, uint256 value) public virtual
```

View File

@@ -1,55 +0,0 @@
const tseslint = require("typescript-eslint");
const js = require("@eslint/js");
const eslintConfigPrettier = require("eslint-config-prettier");
module.exports = tseslint.config(
{
ignores: [
".yarn/",
"**/.coverage_artifacts",
"**/.coverage_cache",
"**/.coverage_contracts",
"**/artifacts",
"**/build",
"**/cache",
"**/coverage",
"**/dist",
"**/node_modules",
"**/types",
"deploys/old/",
"docs/",
"*.env",
"*.log",
".pnp.*",
"coverage.json",
"npm-debug.log*",
"yarn-debug.log*",
"yarn-error.log*",
".solcover.js",
"eslint.config.js",
],
},
js.configs.recommended,
...tseslint.configs.recommended,
eslintConfigPrettier,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: __dirname,
},
},
rules: {
"@typescript-eslint/no-floating-promises": [
"error",
{ ignoreIIFE: true, ignoreVoid: true },
],
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "_", varsIgnorePattern: "_" },
],
"@typescript-eslint/no-unused-expressions": "off",
},
},
);

View File

@@ -9,74 +9,82 @@ import "solidity-docgen";
dotenvConfig({ path: resolve(__dirname, "./.env") }); dotenvConfig({ path: resolve(__dirname, "./.env") });
// Default mnemonic used by Hardhat / Anvil for deterministic accounts. const mnemonic: string | undefined = process.env.MNEMONIC;
// Lets `--network localhost` (and the default in-process `hardhat` net) if (!mnemonic) {
// run without a .env. Public networks still require a real MNEMONIC. throw new Error("Please set your MNEMONIC in a .env file");
const DEFAULT_MNEMONIC = }
"test test test test test test test test test test test junk";
const infuraApiKey: string | undefined =
process.env.INFURA_API_KEY;
if (!infuraApiKey) {
throw new Error(
"Please set your INFURA_API_KEY in a .env file",
);
}
const mnemonic: string =
process.env.MNEMONIC ?? DEFAULT_MNEMONIC;
const alchemyApiKey: string | undefined = const alchemyApiKey: string | undefined =
process.env.ALCHEMY_API_KEY; process.env.ALCHEMY_API_KEY;
const privateKey: string | undefined = if (!alchemyApiKey) {
process.env.PRIVATE_KEY; throw new Error(
"Please set your ALCHEMY_API_KEY in a .env file",
);
}
const chainIds = { const chainIds = {
// "{INSERT_NAME}": {INSERT_ID}, // "{INSERT_NAME}": {INSERT_ID},
hardhat: 31337, hardhat: 31337,
localhost: 31337,
mainnet: 1, mainnet: 1,
"eth-sepolia": 11155111, sepolia: 11155111,
"polygon-mainnet": 137, goerli: 5,
"arb-mainnet": 42161, "polygon-mumbai": 80001,
rootstock: 30, rootstock:30,
"rootstock-testnet": 31, rsktestnet:31,
}; };
function getChainConfig( function getChainConfig(
chain: keyof typeof chainIds, chain: keyof typeof chainIds,
): NetworkUserConfig { ): NetworkUserConfig {
if (!alchemyApiKey) { let jsonRpcUrl: string;
throw new Error( switch (chain) {
`Please set ALCHEMY_API_KEY in a .env file before targeting ${chain}`, case "polygon-mumbai":
); jsonRpcUrl =
"https://polygon-mumbai.g.alchemy.com/v2/" +
alchemyApiKey;
break;
case "rsktestnet":
jsonRpcUrl = "https://public-node.testnet.rsk.co/";
break;
case "rootstock":
jsonRpcUrl = "https://public-node.rsk.co/";
break;
case "sepolia":
jsonRpcUrl = "https://rpc.sepolia.online";
break
default:
jsonRpcUrl =
"https://" + chain + ".infura.io/v3/" + infuraApiKey;
} }
const jsonRpcUrl =
"https://" + chain + ".g.alchemy.com/v2/" + alchemyApiKey;
return { return {
accounts: privateKey // Comment out for default hardhat account settings
? [privateKey] accounts: {
: {
count: 10, count: 10,
mnemonic, mnemonic,
path: "m/44'/60'/0'/0", path: "m/44'/60'/0'/0",
}, },
// gasPrice: 8000000000,
chainId: chainIds[chain], chainId: chainIds[chain],
url: jsonRpcUrl, url: jsonRpcUrl,
}; };
} }
const liveNetworks: Record<string, NetworkUserConfig> =
alchemyApiKey
? {
mainnet: getChainConfig("mainnet"),
sepolia: getChainConfig("eth-sepolia"),
polygon: getChainConfig("polygon-mainnet"),
arbitrum: getChainConfig("arb-mainnet"),
rootstock: getChainConfig("rootstock"),
rsktestnet: getChainConfig("rootstock-testnet"),
}
: {};
const config: HardhatUserConfig = { const config: HardhatUserConfig = {
defaultNetwork: "hardhat", defaultNetwork: "hardhat",
etherscan: { etherscan: {
apiKey: { apiKey: {
mainnet: process.env.ETHERSCAN_API_KEY || "", mainnet: process.env.ETHERSCAN_API_KEY || "",
sepolia: process.env.ETHERSCAN_API_KEY || "", rinkeby: process.env.ETHERSCAN_API_KEY || "",
polygon: process.env.POLYGONSCAN_API_KEY || "", goerli: process.env.ETHERSCAN_API_KEY || "",
arbitrumOne: process.env.ARBISCAN_API_KEY || "", polygonMumbai: process.env.POLYGONSCAN_API_KEY || "",
}, },
}, },
gasReporter: { gasReporter: {
@@ -84,7 +92,6 @@ const config: HardhatUserConfig = {
process.env.REPORT_GAS && process.env.REPORT_GAS &&
process.env.REPORT_GAS != "false" process.env.REPORT_GAS != "false"
), ),
offline: true,
showTimeSpent: true, showTimeSpent: true,
showMethodSig: true, showMethodSig: true,
token: "ETH", token: "ETH",
@@ -102,16 +109,13 @@ const config: HardhatUserConfig = {
}, },
chainId: chainIds.hardhat, chainId: chainIds.hardhat,
}, },
// External Anvil / Hardhat node (e.g. `anvil --port 8545`). Used by // network: getChainConfig("{INSERT_NAME}"),
// the frontend e2e suite to deploy a fresh chain per CI run. mainnet: getChainConfig("mainnet"),
localhost: { goerli: getChainConfig("goerli"),
url: "http://127.0.0.1:8545", sepolia: getChainConfig("sepolia"),
chainId: chainIds.localhost, "polygon-mumbai": getChainConfig("polygon-mumbai"),
accounts: { rootstock: getChainConfig("rootstock"),
mnemonic, rsktestnet: getChainConfig("rsktestnet"),
},
},
...liveNetworks,
}, },
paths: { paths: {
artifacts: "./artifacts", artifacts: "./artifacts",
@@ -120,10 +124,10 @@ const config: HardhatUserConfig = {
tests: "./test", tests: "./test",
}, },
solidity: { solidity: {
version: "0.8.28", version: "0.8.19",
settings: { settings: {
viaIR: true, viaIR: true,
evmVersion: "cancun", evmVersion: "paris",
optimizer: { optimizer: {
enabled: true, enabled: true,
runs: 20_000, runs: 20_000,
@@ -143,11 +147,11 @@ const config: HardhatUserConfig = {
}, },
typechain: { typechain: {
outDir: "src/types", outDir: "src/types",
target: "ethers-v6", target: "ethers-v5",
}, },
docgen: { docgen: {
pages: "files", pages: "files",
}, }
}; };
export default config; export default config;

View File

@@ -1,14 +0,0 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { ethers } from "ethers";
const DEFAULT_SUPPLY = ethers
.parseEther("20000000")
.toString();
export default buildModule("MockToken", m => {
const supply = m.getParameter("supply", DEFAULT_SUPPLY);
const token = m.contract("MockToken", [supply]);
return { token };
});

View File

@@ -1,31 +0,0 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import ReputationModule from "./Reputation";
export default buildModule("P2PIX", m => {
const { reputation, multicall } = m.useModule(
ReputationModule,
);
const defaultBlocks = m.getParameter("defaultBlocks", 10);
const validSigners = m.getParameter<string[]>(
"validSigners",
[],
);
const tokens = m.getParameter<string[]>("tokens");
const allowed = m.getParameter<boolean[]>("allowed");
const p2pix = m.contract("P2PIX", [
defaultBlocks,
validSigners,
reputation,
tokens,
allowed,
]);
return {
p2pix,
reputation,
multicall,
};
});

View File

@@ -1,32 +0,0 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import MockTokenModule from "./MockToken";
import ReputationModule from "./Reputation";
export default buildModule("P2PIXWithMock", m => {
const { token } = m.useModule(MockTokenModule);
const { reputation, multicall } = m.useModule(
ReputationModule,
);
const defaultBlocks = m.getParameter("defaultBlocks", 10);
const validSigners = m.getParameter<string[]>(
"validSigners",
[],
);
const p2pix = m.contract("P2PIX", [
defaultBlocks,
validSigners,
reputation,
[token],
[true],
]);
return {
p2pix,
reputation,
multicall,
token,
};
});

View File

@@ -1,8 +0,0 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("Reputation", m => {
const reputation = m.contract("Reputation", []);
const multicall = m.contract("Multicall", []);
return { reputation, multicall };
});

View File

@@ -1,9 +0,0 @@
{
"P2PIXWithMock": {
"defaultBlocks": 10,
"validSigners": []
},
"MockToken": {
"supply": "20000000000000000000000000"
}
}

View File

@@ -1,8 +0,0 @@
{
"P2PIX": {
"defaultBlocks": 10,
"validSigners": [],
"tokens": ["0x0000000000000000000000000000000000000000"],
"allowed": [true]
}
}

View File

@@ -9,65 +9,86 @@
"url": "https://github.com/doiim/p2pix-smart-contracts/issues" "url": "https://github.com/doiim/p2pix-smart-contracts/issues"
}, },
"scripts": { "scripts": {
"clean": "hardhat clean", "clean": "shx rm -rf ./artifacts ./cache ./coverage ./src/types ./coverage.json && yarn typechain",
"compile": "hardhat compile", "commit": "git-cz",
"typechain": "hardhat typechain", "compile": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile",
"typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain",
"test": "hardhat test", "test": "hardhat test",
"deploy": "hardhat ignition deploy ignition/modules/P2PIX.ts", "deploy1:localhost": "hardhat run scripts/1-deploy-mockToken.ts --network localhost",
"deploy:mock": "hardhat ignition deploy ignition/modules/P2PIXWithMock.ts", "deploy2:localhost": "hardhat run scripts/2-deploy-p2pix.ts --network localhost",
"deploy1:goerli": "hardhat run scripts/1-deploy-mockToken.ts --network goerli",
"deploy2:goerli": "hardhat run scripts/2-deploy-p2pix.ts --network goerli",
"deploy1:mumbai": "hardhat run scripts/1-deploy-mockToken.ts --network polygon-mumbai",
"deploy2:mumbai": "hardhat run scripts/2-deploy-p2pix.ts --network polygon-mumbai",
"coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain", "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain",
"lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check", "lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check",
"lint:sol": "solhint --config ./.solhint.json --max-warnings 0 \"contracts/**/*.sol\"", "lint:sol": "solhint --config ./.solhint.json --max-warnings 0 \"contracts/**/*.sol\"",
"lint:ts": "eslint .", "lint:ts": "eslint --config ./.eslintrc.yaml --ignore-path ./.eslintignore --ext .js,.ts .",
"_postinstall": "husky install",
"postpublish": "pinst --enable",
"prepublishOnly": "pinst --disable",
"prettier": "prettier --config ./.prettierrc.yaml --write \"**/*.{js,json,md,sol,ts,yaml,yml}\"", "prettier": "prettier --config ./.prettierrc.yaml --write \"**/*.{js,json,md,sol,ts,yaml,yml}\"",
"prettier:check": "prettier --check --config ./.prettierrc.yaml \"**/*.{js,json,md,sol,ts,yaml,yml}\"" "prettier:check": "prettier --check --config ./.prettierrc.yaml \"**/*.{js,json,md,sol,ts,yaml,yml}\""
}, },
"devDependencies": { "devDependencies": {
"@ethersproject/abi": "^5.8.0", "@commitlint/cli": "^17.2.0",
"@ethersproject/providers": "^5.8.0", "@commitlint/config-conventional": "^17.2.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.1.0", "@ethersproject/abi": "^5.7.0",
"@nomicfoundation/hardhat-ethers": "^3.1.0", "@ethersproject/abstract-signer": "^5.7.0",
"@nomicfoundation/hardhat-ignition": "^0.15.13", "@ethersproject/bignumber": "^5.7.0",
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.14", "@ethersproject/bytes": "^5.7.0",
"@nomicfoundation/hardhat-network-helpers": "^1.1.0", "@ethersproject/providers": "^5.7.2",
"@nomicfoundation/hardhat-toolbox": "^6.1.0", "@nomicfoundation/hardhat-chai-matchers": "^1.0.4",
"@nomicfoundation/hardhat-verify": "^2.1.0", "@nomicfoundation/hardhat-network-helpers": "1.0.6",
"@nomicfoundation/hardhat-viem": "^2.1.0", "@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomicfoundation/ignition-core": "^0.15.13", "@nomicfoundation/hardhat-viem": "^2.0.6",
"@openzeppelin/contracts": "5.5.0", "@nomiclabs/hardhat-ethers": "^2.2.3",
"@typechain/ethers-v6": "^0.5.1", "@nomiclabs/hardhat-etherscan": "^3.1.2",
"@typechain/hardhat": "^9.1.0", "@trivago/prettier-plugin-sort-imports": "^3.4.0",
"@types/chai": "^4.3.20", "@typechain/ethers-v5": "^10.1.1",
"@types/mocha": "^10.0.10", "@typechain/hardhat": "^6.1.4",
"@types/node": "^24.1.0", "@types/chai": "^4.3.3",
"chai": "4.5.0", "@types/fs-extra": "^9.0.13",
"dotenv": "^16.6.1", "@types/mocha": "^9.1.1",
"eslint": "^9.32.0", "@types/node": "^18.11.9",
"eslint-config-prettier": "^10.1.8", "@typescript-eslint/eslint-plugin": "^5.42.0",
"ethers": "^6.15.0", "@typescript-eslint/parser": "^5.42.0",
"hardhat": "^2.26.1", "chai": "^4.3.6",
"hardhat-contract-sizer": "^2.10.0", "chalk": "4.x",
"hardhat-gas-reporter": "^2.3.0", "commitizen": "^4.2.5",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"dotenv": "^16.0.3",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"ethers": "^5.7.2",
"fs-extra": "^10.1.0",
"hardhat": "^2.12.2",
"hardhat-contract-sizer": "^2.8.0",
"hardhat-gas-reporter": "^1.0.9",
"husky": "^8.0.1",
"keccak256": "^1.0.6", "keccak256": "^1.0.6",
"lint-staged": "^13.0.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merkletreejs": "^0.5.2", "merkletreejs": "^0.2.32",
"mocha": "^10.8.2", "mocha": "^10.1.0",
"prettier": "^3.0.0", "prettier": "^2.7.1",
"prettier-plugin-solidity": "^1.0.0", "prettier-plugin-solidity": "^1.0.0-rc.1",
"solhint": "^6.2.1", "shx": "^0.3.4",
"solhint-plugin-prettier": "^0.1.0", "solhint": "^3.3.7",
"solidity-coverage": "^0.8.16", "solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.8.2",
"solidity-docgen": "^0.6.0-beta.36", "solidity-docgen": "^0.6.0-beta.36",
"ts-node": "^10.9.2", "ts-generator": "^0.1.1",
"typechain": "^8.3.2", "ts-node": "^10.9.1",
"typescript": "^5.8.3", "typechain": "^8.1.1",
"typescript-eslint": "^8.60.0", "typescript": "^5",
"viem": "^2.33.1" "viem": "^2.23.14"
}, },
"files": [ "files": [
"/contracts" "/contracts"
], ],
"packageManager": "yarn@4.9.2", "packageManager": "yarn@3.2.1",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
} }

View File

@@ -0,0 +1,56 @@
import "@nomiclabs/hardhat-ethers";
import "@nomiclabs/hardhat-etherscan";
import { BigNumber } from "ethers";
import * as fs from "fs";
import { ethers, network } from "hardhat";
import hre from "hardhat";
import { Deploys } from "../test/utils/interfaces";
let deploysJson: Deploys;
const supply: BigNumber = ethers.utils.parseEther("20000000");
const main = async () => {
try {
const data = fs.readFileSync(
`./deploys/${network.name}.json`,
{ encoding: "utf-8" },
);
deploysJson = JSON.parse(data);
} catch (err) {
console.log("Error loading Master address: ", err);
process.exit(1);
}
const [deployer] = await ethers.getSigners();
console.log(`Deploying contracts with ${deployer.address}`);
const ERC20Factory = await ethers.getContractFactory(
"MockToken",
);
const erc20 = await ERC20Factory.deploy(supply);
await erc20.deployed();
deploysJson.token = erc20.address;
console.log("🚀 Mock Token Deployed:", erc20.address);
await erc20.deployTransaction.wait(6);
fs.writeFileSync(
`./deploys/${network.name}.json`,
JSON.stringify(deploysJson, undefined, 2),
);
/* UNCOMMENT WHEN DEPLOYING TO MAINNET/PUBLIC TESTNETS */
// verify
// await hre.run("verify:verify", {
// address: erc20.address,
// constructorArguments: [supply],
// });
};
main()
.then(() => process.exit(0))
.catch(error => {
console.log(error);
process.exit(1);
});

85
scripts/2-deploy-p2pix.ts Normal file
View File

@@ -0,0 +1,85 @@
import "@nomiclabs/hardhat-ethers";
import "@nomiclabs/hardhat-etherscan";
import * as fs from "fs";
import { ethers, network } from "hardhat";
import hre from "hardhat";
import { Deploys } from "../test/utils/interfaces";
let deploysJson: Deploys;
const main = async () => {
try {
const data = fs.readFileSync(
`./deploys/${network.name}.json`,
{ encoding: "utf-8" },
);
deploysJson = JSON.parse(data);
} catch (err) {
console.log("Error loading Master address: ", err);
process.exit(1);
}
const [deployer] = await ethers.getSigners();
console.log(`Deploying contracts with ${deployer.address}`);
const Reputation = await ethers.getContractFactory(
"Reputation",
);
const Multicall = await ethers.getContractFactory(
"Multicall",
);
const reputation = await Reputation.deploy();
await reputation.deployed();
const mutlicall = await Multicall.deploy();
await mutlicall.deployed();
const P2PIX = await ethers.getContractFactory("P2PIX");
const p2pix = await P2PIX.deploy(
10,
deploysJson.signers,
reputation.address,
[deploysJson.token],
[true],
);
await p2pix.deployed();
deploysJson.p2pix = p2pix.address;
console.log("🚀 P2PIX Deployed:", p2pix.address);
console.log("🌠 Reputation Deployed:", reputation.address);
console.log("🛰 Multicall Deployed:", mutlicall.address);
await p2pix.deployTransaction.wait(6);
fs.writeFileSync(
`./deploys/${network.name}.json`,
JSON.stringify(deploysJson, undefined, 2),
);
/* UNCOMMENT WHEN DEPLOYING TO MAINNET/PUBLIC TESTNETS */
//verify
// await hre.run("verify:verify", {
// address: p2pix.address,
// constructorArguments: [
// 10,
// deploysJson.signers,
// reputation.address,
// [deploysJson.token],
// [true],
// ],
// });
// await hre.run("verify:verify", {
// address: reputation.address,
// constructorArguments: [],
// });
// await hre.run("verify:verify", {
// address: mutlicall.address,
// constructorArguments: [],
// });
};
main()
.then(() => process.exit(0))
.catch(error => {
console.log(error);
process.exit(1);
});

View File

@@ -20,22 +20,12 @@ const main = async () => {
} }
const [deployer] = await ethers.getSigners(); const [deployer] = await ethers.getSigners();
console.log( console.log(`Signing transactions with ${deployer.address}`);
`Signing transactions with ${deployer.address}`,
);
const iface = new ethers.utils.Interface( const iface = new ethers.utils.Interface(P2PIX__factory.abi);
P2PIX__factory.abi, const calldata = iface.encodeFunctionData("setDefaultLockBlocks", ["10000"]);
);
const calldata = iface.encodeFunctionData(
"setDefaultLockBlocks",
["10000"],
);
const tx = await deployer.sendTransaction({ const tx = await deployer.sendTransaction({to:deploysJson.p2pix, data: calldata});
to: deploysJson.p2pix,
data: calldata,
});
const done = await tx.wait(); const done = await tx.wait();
console.log(done.transactionHash); console.log(done.transactionHash);
}; };

View File

@@ -1,5 +1,6 @@
import "@nomicfoundation/hardhat-chai-matchers"; import "@nomicfoundation/hardhat-chai-matchers";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai"; import { expect } from "chai";
import { ethers, network } from "hardhat"; import { ethers, network } from "hardhat";
@@ -7,11 +8,14 @@ import { Reputation } from "../src/types";
import { curve, repFixture } from "./utils/fixtures"; import { curve, repFixture } from "./utils/fixtures";
describe("Reputation", () => { describe("Reputation", () => {
// contract deployer/admin
let owner: SignerWithAddress;
// Reputation Interface instance; // Reputation Interface instance;
let reputation: Reputation; let reputation: Reputation;
before("Set signers and reset network", async () => { before("Set signers and reset network", async () => {
await ethers.getSigners(); // eslint-disable-next-line @typescript-eslint/no-explicit-any
[owner] = await (ethers as any).getSigners();
await network.provider.send("hardhat_reset"); await network.provider.send("hardhat_reset");
}); });
@@ -56,7 +60,7 @@ describe("Reputation", () => {
}, },
{ {
x: Number.MAX_SAFE_INTEGER, x: Number.MAX_SAFE_INTEGER,
expected: curve(Number.MAX_SAFE_INTEGER), shouldRevert: "overflow",
}, },
{ {
x: Number.POSITIVE_INFINITY, x: Number.POSITIVE_INFINITY,

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-useless-escape */
describe("_", () => { describe("_", () => {
console.log( console.log(
"/// ______ __\r\n/// .-----.|__ |.-----.|__|.--.--.\r\n/// | _ || __|| _ || ||_ _|\r\n/// | __||______|| __||__||__.__|\r\n/// |__| |__|\r\n///", "/// ______ __\r\n/// .-----.|__ |.-----.|__|.--.--.\r\n/// | _ || __|| _ || ||_ _|\r\n/// | __||______|| __||__||__.__|\r\n/// |__| |__|\r\n///",

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,19 @@
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { Signer } from "ethers"; import { BigNumber, Signer } from "ethers";
import { ethers } from "hardhat"; import { ethers } from "hardhat";
import keccak256 from "keccak256"; import keccak256 from "keccak256";
import { MerkleTree } from "merkletreejs"; import { MerkleTree } from "merkletreejs";
import { MockToken, P2PIX__factory } from "../../src/types";
import { import {
Call, MockToken,
RepFixture, Multicall,
P2PixAndReputation, P2PIX,
} from "./interfaces"; P2PIX__factory,
Reputation,
} from "../../src/types";
import { Call, RepFixture, P2PixAndReputation, DepositArgs, LockArgs, ReleaseArgs } from "./interfaces";
// exported constants // exported constants
export const getSignerAddrs = ( export const getSignerAddrs = (
@@ -20,16 +23,18 @@ export const getSignerAddrs = (
return addrs.slice(0, amount).map(({ address }) => address); return addrs.slice(0, amount).map(({ address }) => address);
}; };
export const getBnFrom = (nums: number[]): bigint[] => { export const getBnFrom = (nums: number[]): BigNumber[] => {
const bns = nums.map(num => BigInt(num)); const bns = nums.map(num => ethers.BigNumber.from(num));
return bns; return bns;
}; };
export const getLockData = ( export const getLockData = (
addr: string, addr: string,
locks: bigint[][], locks: BigNumber[][],
): Call[] => { ): Call[] => {
const iface = new ethers.Interface(P2PIX__factory.abi); const iface = new ethers.utils.Interface(
P2PIX__factory.abi,
);
return locks.map(lock => ({ return locks.map(lock => ({
target: addr, target: addr,
callData: iface.encodeFunctionData("getLocksStatus", [ callData: iface.encodeFunctionData("getLocksStatus", [
@@ -67,9 +72,13 @@ export const curve = (x: number): number => {
// exported async functions // exported async functions
export async function repFixture(): Promise<RepFixture> { export async function repFixture(): Promise<RepFixture> {
const Reputation = await ethers.getContractFactory(
"Reputation",
);
const reputation = const reputation =
await ethers.deployContract("Reputation"); (await Reputation.deploy()) as Reputation;
return { reputation: await reputation.waitForDeployment() };
return { reputation };
} }
export async function p2pixFixture(): Promise<P2PixAndReputation> { export async function p2pixFixture(): Promise<P2PixAndReputation> {
@@ -78,22 +87,30 @@ export async function p2pixFixture(): Promise<P2PixAndReputation> {
await ethers.getSigners(), await ethers.getSigners(),
); );
const Reputation = await ethers.getContractFactory(
"Reputation",
);
const reputation = const reputation =
await ethers.deployContract("Reputation"); (await Reputation.deploy()) as Reputation;
const erc20 = (await ethers.deployContract("MockToken", [ const ERC20 = await ethers.getContractFactory("MockToken");
ethers.parseEther("20000000"), // 20M const erc20 = (await ERC20.deploy(
])) as MockToken; ethers.utils.parseEther("20000000"), // 20M
)) as MockToken;
const p2pix = await ethers.deployContract("P2PIX", [ const P2PIX = await ethers.getContractFactory("P2PIX");
const p2pix = (await P2PIX.deploy(
10, 10,
validSigners, validSigners,
reputation.target, reputation.address,
[erc20.target], [erc20.address],
[true], [true],
]); )) as P2PIX;
const multicall = await ethers.deployContract("Multicall"); const Multicall = await ethers.getContractFactory(
"Multicall",
);
const multicall = (await Multicall.deploy()) as Multicall;
const signers = await ethers.getSigners(); const signers = await ethers.getSigners();
const whitelisted = signers.slice(0, 2); const whitelisted = signers.slice(0, 2);
@@ -109,10 +126,10 @@ export async function p2pixFixture(): Promise<P2PixAndReputation> {
); );
return { return {
multicall: await multicall.waitForDeployment(), multicall,
reputation: await reputation.waitForDeployment(), reputation,
erc20: await erc20.waitForDeployment(), erc20,
p2pix: await p2pix.waitForDeployment(), p2pix,
merkleRoot, merkleRoot,
proof, proof,
}; };

View File

@@ -1,3 +1,5 @@
import { BigNumber } from "ethers";
import { import {
MockToken, MockToken,
Multicall, Multicall,
@@ -5,6 +7,7 @@ import {
Reputation, Reputation,
} from "../../src/types"; } from "../../src/types";
// exported interfaces // exported interfaces
export interface Deploys { export interface Deploys {
signers: string[]; signers: string[];
@@ -16,29 +19,29 @@ export interface DepositArgs {
pixTarget: string; pixTarget: string;
allowlistRoot: string; allowlistRoot: string;
token: string; token: string;
amount: bigint; amount: BigNumber;
valid: boolean; valid: boolean;
} }
export interface LockArgs { export interface LockArgs {
seller: string; seller: string;
token: string; token: string;
amount: bigint; amount: BigNumber;
merkleProof: string[]; merkleProof: string[];
expiredLocks: bigint[]; expiredLocks: BigNumber[];
} }
export interface ReleaseArgs { export interface ReleaseArgs {
lockID: bigint; lockID: BigNumber;
pixTimestamp: string; pixTimestamp: string;
signature: string; signature: string;
} }
export interface Lock { export interface Lock {
counter: bigint; counter: BigNumber;
expirationBlock: bigint; expirationBlock: BigNumber;
pixTarget: string; pixTarget: string;
amount: bigint; amount: BigNumber;
token: string; token: string;
buyerAddress: string; buyerAddress: string;
seller: string; seller: string;

View File

@@ -18,10 +18,5 @@
}, },
"exclude": ["node_modules"], "exclude": ["node_modules"],
"files": ["./hardhat.config.ts"], "files": ["./hardhat.config.ts"],
"include": [ "include": ["src/**/*", "test/**/*", "scripts/**/*"]
"src/**/*",
"test/**/*",
"scripts/**/*",
"ignition/**/*"
]
} }

7837
yarn.lock

File diff suppressed because it is too large Load Diff