21 Commits

Author SHA1 Message Date
c4db98ae00 feat: add support for private key authentication in environment configuration 2026-05-29 17:48:49 -03:00
arthur
9cc62efb8a fix_lint (#11)
Co-authored-by: Arthur Abeilice <afa7789@gmail.com>
Reviewed-on: https://git.p2pix.co/doiim/p2pix-smart-contracts/pulls/11
Co-authored-by: arthur <abeilice@kosmos.org>
Co-committed-by: arthur <abeilice@kosmos.org>
2026-05-29 20:09:12 +00:00
1addaae1c7 fix: conditionally register live networks only when ALCHEMY_API_KEY is set 2026-05-29 15:43:39 -03:00
aa96fd89da feat: add deployment configurations for Goerli, Polygon Mumbai, RSK Testnet, and Sepolia networks
refactor: update Hardhat config for cleaner network setup
chore: remove outdated MockToken documentation
fix: correct DEFAULT_SUPPLY initialization in MockToken module
2026-05-29 18:15:21 +00:00
96f66f9cec chore: update config files
Ignore deploy artifacts, auto-generated docs, and reformat solcover skipFiles
2026-05-29 14:09:04 -03:00
c5766dc226 solhint 2026-05-28 15:53:48 -03:00
76783c2d6b chore: hardhat config 2026-05-28 15:45:39 -03:00
36e1dd30ce fix: actions 2026-05-28 15:20:24 -03:00
d731791b49 feat: add lint and test workflows for GitHub Actions 2026-05-22 18:55:49 -03:00
131f53a731 feat: add P2PIXProd Ignition module for deploys without MockToken 2026-05-22 00:06:41 -03:00
13d1d16084 feat: migrate deployment to Hardhat Ignition
Replace the imperative deploy scripts with Hardhat Ignition modules:
- ignition/modules/{MockToken,Reputation,P2PIX}.ts orchestrate the full
  deployment graph; P2PIX.ts wires MockToken + Reputation and deploys
  P2PIX via its constructor
- ignition/parameters/localhost.json holds per-network values
  (defaultBlocks, validSigners, MockToken supply)
- swap hardhat-toolbox for the individual plugins that Ignition needs;
  add hardhat-verify (v2) and bump hardhat/hardhat-tracer accordingly
- delete scripts/1-deploy-mockToken.ts and scripts/2-deploy-p2pix.ts
- add deploy:{localhost,goerli,sepolia,mumbai} npm scripts
- include ignition/**/* in tsconfig.json
- gitignore ignition/deployments/chain-31337/ (ephemeral local state)

This branch carries the deployment-tooling migration only — the contract
is still the original constructor-based P2PIX. Proxy / UUPS deploy
support will land alongside the upgradeable contract change.
2026-05-22 00:06:41 -03:00
eaf45a0288 chore(gitignore): exclude generated mock docs 2026-05-04 22:12:53 -03:00
4e2fd86d21 Revert "chore(scripts): use wagmi binary and stop clobbering submodule .env"
This reverts commit 26765bee60.
2026-05-04 22:12:33 -03:00
26765bee60 chore(scripts): use wagmi binary and stop clobbering submodule .env 2026-05-04 22:03:57 -03:00
6adf8778cb feat: add localhost network for e2e against Anvil
- Declare 'localhost' network in hardhat.config.ts pointing to
  http://127.0.0.1:8545 (chainId 31337) so deploy scripts work
  against an external Anvil/Hardhat node.
- Make MNEMONIC and ALCHEMY_API_KEY optional: fall back to the
  Hardhat/Anvil deterministic mnemonic when MNEMONIC is missing,
  and only require ALCHEMY_API_KEY when targeting public networks.
- Reset deploys/localhost.json to a starter state (signers populated,
  contract addresses empty) so the frontend e2e setup writes fresh
  addresses on every run.
2026-05-04 09:46:33 -03:00
hueso
4bf8841a89 OZ v5.5.0 2026-03-27 14:23:12 -03:00
hueso
0151910b0d bump OZ version to 5.5.0-rc.1 2026-03-27 14:23:12 -03:00
hueso
7a2aec7e71 restore trustedForwarders as an OZ override 2026-03-27 14:23:12 -03:00
hueso
5737ab1623 import ECDSA, ERC2771, ERC20, MerkleProofLib from @openzeppelin 2026-03-27 14:23:12 -03:00
hueso
183db96fda WIP: use openzeppelin contracts 2026-03-27 14:23:12 -03:00
filipesoccol
7f6efc4cb6 Merge pull request #10 from jeffmant/dev
Refactoring and code improvements | by @hueso
2025-10-03 16:24:56 -03:00
52 changed files with 1943 additions and 1760 deletions

View File

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

View File

@@ -1,21 +0,0 @@
# 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*

View File

@@ -1,21 +0,0 @@
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: "_"

32
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
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

35
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
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,3 +28,11 @@ coverage.json
npm-debug.log*
yarn-debug.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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
"plugins": ["prettier"],
"rules": {
"code-complexity": ["error", 8],
"avoid-low-level-calls": "off",
"compiler-version": ["error", ">=0.8.4"],
"const-name-snakecase": "off",
"constructor-syntax": "error",
@@ -10,7 +11,14 @@
"error",
{ "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],
"no-inline-assembly": "off",
"not-rely-on-time": "off",
"prettier/prettier": [
"error",
@@ -18,6 +26,7 @@
"endOfLine": "auto"
}
],
"reason-string": ["warn", { "maxLength": 64 }]
"reason-string": ["warn", { "maxLength": 64 }],
"use-natspec": "off"
}
}

View File

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

View File

@@ -55,25 +55,23 @@
| Testnet | Token Address | P2pix Address | Reputation Address | Multicall Address |
| ------- | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ |
| Goerli | 0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00 | 0x2414817FF64A114d91eCFA16a834d3fCf69103d4 | 0x2CFD9354Ec7614fEf036EFd6A730dA1d5fC2762A | 0x8FE009992d96A86c7f0Bccdaf1eC3471E302a8a6 |
| Mumbai | 0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29 | 0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00 | 0x570445E3eF413bCDb5De79ed27B1c3840683e385 | 0x718B2C4DE4F9654E1349F610ff561249bfe1c418 |
| Sepolia | 0x3eBE212377D847828eBb9D2a100f5c5EA1EF3A72 | 0xb7cDAE58C6e715Cfd795BB142041E43b9CB02497 | 0xFd1194A56995Ef7B62730F4061408e79d88E5207 | 0x2f7f9848A803E67d79C0C8aa42b663dCF6E1B5ed |
<!-- All contracts deployed by 0x8dC06F985C131166570825F52447E8c88d64aE20 -->
<!-- https://goerli.etherscan.io/address/0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00#code -->
<!-- https://goerli.etherscan.io/address/0x2414817FF64A114d91eCFA16a834d3fCf69103d4#code -->
<!-- https://goerli.etherscan.io/address/0x2CFD9354Ec7614fEf036EFd6A730dA1d5fC2762A#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 -->
<!-- https://sepolia.etherscan.io/address/0x3eBE212377D847828eBb9D2a100f5c5EA1EF3A72#code -->
<!-- https://sepolia.etherscan.io/address/0xb7cDAE58C6e715Cfd795BB142041E43b9CB02497#code -->
<!-- https://sepolia.etherscan.io/address/0xFd1194A56995Ef7B62730F4061408e79d88E5207#code -->
<!-- https://sepolia.etherscan.io/address/0x2f7f9848A803E67d79C0C8aa42b663dCF6E1B5ed#code -->
## Usage
### Pre Requisites
Before installing, create a `.env` file and set a BIP-39 compatible mnemonic and other env criteria as in `.env.example`.
Before installing, create a `.env` file and set the authentication method 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
@@ -125,36 +123,71 @@ To grab deployment addresses you can just grab from deploys folder:
import localhostDeploys from "p2pix-smart-contracts/deploys/localhost.json";
```
## Deploying to local environment
## Deployment (Hardhat Ignition)
On the first teminal, use the following command and import some wallets to your Metamask, then connect to the network pointed:
Deployments are orchestrated by
[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
yarn hardhat node
```
On the second teminal, run the following commands:
On the second terminal:
```sh
yarn deploy1:localhost
yarn deploy2:localhost
yarn deploy:mock --network localhost --parameters ignition/parameters/localhost.json
```
**_NOTE_:** The second script transfers 2M tokens to the first wallet of the node.
To use the P2Pix smart contract first transfer some of the tokens to other wallets.
Addresses for every deployed contract are written to
`ignition/deployments/chain-31337/deployed_addresses.json` (this path is
gitignored — local-only state).
## Deploying to testnets
Deploy to Ethereum's Goerli testnet:
### Testnets
```sh
yarn deploy1:goerli
yarn deploy2:goerli
yarn deploy:mock --network sepolia
```
Deploy to Polygon's Mumbai testnet:
Requires the matching `ignition/parameters/sepolia.json` and the relevant
API keys in `.env`.
```sh
yarn deploy1:mumbai
yarn deploy2:mumbai
```
### Production (real tokens)
Use the `P2PIX` module when the target network already has the ERC20
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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,87 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
pragma solidity ^0.8.20;
/// @author OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Context.sol)
import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
/// @dev Provides information about the current execution context, including the
/// 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 _msgData()
internal
view
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;
abstract contract ERC2771 is ERC2771Context(address(0)) {
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();
}
function isTrustedForwarder(
address forwarder
) public view override returns (bool) {
return trustedForwarders[forwarder];
}
}

View File

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

View File

@@ -1,250 +0,0 @@
// 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

@@ -1,95 +0,0 @@
// 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

@@ -1,58 +0,0 @@
// 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
//solhint-disable-next-line no-empty-blocks
constructor(/* */) payable {/* */}
function mtc1(Call[] calldata calls)
external
returns (uint256, bytes[] memory)
{
function mtc1(
Call[] calldata calls
) external returns (uint256, bytes[] memory) {
uint256 bn = block.number;
uint256 len = calls.length;
bytes[] memory res = new bytes[](len);
@@ -49,21 +49,14 @@ contract Multicall {
return (bn, res);
}
function mtc2(Call[] calldata calls)
external
returns (
uint256,
bytes32,
Result[] memory
)
{
function mtc2(
Call[] calldata calls
) external returns (uint256, bytes32, Result[] memory) {
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 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).
bytes32 bh = blockhash(
bn /* - 1 */
);
bytes32 bh = blockhash(bn /* - 1 */);
uint256 len = calls.length;
Result[] memory res = new Result[](len);
uint256 i;

View File

@@ -1,34 +0,0 @@
// 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
pragma solidity >=0.8.4;
import { ERC20 } from "../tokens/ERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/// @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)

View File

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

View File

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

View File

@@ -1,224 +0,0 @@
# 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

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

55
eslint.config.js Normal file
View File

@@ -0,0 +1,55 @@
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,54 +9,74 @@ import "solidity-docgen";
dotenvConfig({ path: resolve(__dirname, "./.env") });
const mnemonic: string | undefined = process.env.MNEMONIC;
if (!mnemonic) {
throw new Error("Please set your MNEMONIC in a .env file");
}
// Default mnemonic used by Hardhat / Anvil for deterministic accounts.
// Lets `--network localhost` (and the default in-process `hardhat` net)
// run without a .env. Public networks still require a real MNEMONIC.
const DEFAULT_MNEMONIC =
"test test test test test test test test test test test junk";
const mnemonic: string =
process.env.MNEMONIC ?? DEFAULT_MNEMONIC;
const alchemyApiKey: string | undefined =
process.env.ALCHEMY_API_KEY;
if (!alchemyApiKey) {
throw new Error(
"Please set your ALCHEMY_API_KEY in a .env file",
);
}
const privateKey: string | undefined =
process.env.PRIVATE_KEY;
const chainIds = {
// "{INSERT_NAME}": {INSERT_ID},
hardhat: 31337,
localhost: 31337,
mainnet: 1,
"eth-sepolia": 11155111,
"polygon-mumbai": 80001,
rootstock:30,
"rootstock-testnet":31,
"polygon-mainnet": 137,
"arb-mainnet": 42161,
rootstock: 30,
"rootstock-testnet": 31,
};
function getChainConfig(
chain: keyof typeof chainIds,
): NetworkUserConfig {
let jsonRpcUrl = "https://" + chain + ".g.alchemy.com/v2/" + alchemyApiKey;
if (!alchemyApiKey) {
throw new Error(
`Please set ALCHEMY_API_KEY in a .env file before targeting ${chain}`,
);
}
const jsonRpcUrl =
"https://" + chain + ".g.alchemy.com/v2/" + alchemyApiKey;
return {
// Comment out for default hardhat account settings
accounts: {
count: 10,
mnemonic,
path: "m/44'/60'/0'/0",
},
// gasPrice: 8000000000,
accounts: privateKey
? [privateKey]
: {
count: 10,
mnemonic,
path: "m/44'/60'/0'/0",
},
chainId: chainIds[chain],
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 = {
defaultNetwork: "hardhat",
etherscan: {
apiKey: {
mainnet: process.env.ETHERSCAN_API_KEY || "",
rinkeby: process.env.ETHERSCAN_API_KEY || "",
goerli: process.env.ETHERSCAN_API_KEY || "",
polygonMumbai: process.env.POLYGONSCAN_API_KEY || "",
sepolia: process.env.ETHERSCAN_API_KEY || "",
polygon: process.env.POLYGONSCAN_API_KEY || "",
arbitrumOne: process.env.ARBISCAN_API_KEY || "",
},
},
gasReporter: {
@@ -82,12 +102,16 @@ const config: HardhatUserConfig = {
},
chainId: chainIds.hardhat,
},
// network: getChainConfig("{INSERT_NAME}"),
mainnet: getChainConfig("mainnet"),
sepolia: getChainConfig("eth-sepolia"),
mumbai: getChainConfig("polygon-mumbai"),
rootstock: getChainConfig("rootstock"),
rsktestnet: getChainConfig("rootstock-testnet"),
// External Anvil / Hardhat node (e.g. `anvil --port 8545`). Used by
// the frontend e2e suite to deploy a fresh chain per CI run.
localhost: {
url: "http://127.0.0.1:8545",
chainId: chainIds.localhost,
accounts: {
mnemonic,
},
},
...liveNetworks,
},
paths: {
artifacts: "./artifacts",
@@ -123,7 +147,7 @@ const config: HardhatUserConfig = {
},
docgen: {
pages: "files",
}
},
};
export default config;

View File

@@ -0,0 +1,14 @@
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 };
});

31
ignition/modules/P2PIX.ts Normal file
View File

@@ -0,0 +1,31 @@
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

@@ -0,0 +1,32 @@
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

@@ -0,0 +1,8 @@
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

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

View File

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

View File

@@ -13,16 +13,12 @@
"compile": "hardhat compile",
"typechain": "hardhat typechain",
"test": "hardhat test",
"deploy1:localhost": "hardhat run scripts/1-deploy-mockToken.ts --network localhost",
"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",
"deploy": "hardhat ignition deploy ignition/modules/P2PIX.ts",
"deploy:mock": "hardhat ignition deploy ignition/modules/P2PIXWithMock.ts",
"coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain",
"lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check",
"lint:sol": "solhint --config ./.solhint.json --max-warnings 0 \"contracts/**/*.sol\"",
"lint:ts": "eslint --config ./.eslintrc.yaml --ignore-path ./.eslintignore --ext .js,.ts .",
"lint:ts": "eslint .",
"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}\""
},
@@ -38,6 +34,7 @@
"@nomicfoundation/hardhat-verify": "^2.1.0",
"@nomicfoundation/hardhat-viem": "^2.1.0",
"@nomicfoundation/ignition-core": "^0.15.13",
"@openzeppelin/contracts": "5.5.0",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.20",
@@ -55,11 +52,16 @@
"lodash": "^4.17.21",
"merkletreejs": "^0.5.2",
"mocha": "^10.8.2",
"prettier": "^3.0.0",
"prettier-plugin-solidity": "^1.0.0",
"solhint": "^6.2.1",
"solhint-plugin-prettier": "^0.1.0",
"solidity-coverage": "^0.8.16",
"solidity-docgen": "^0.6.0-beta.36",
"ts-node": "^10.9.2",
"typechain": "^8.3.2",
"typescript": "^5.8.3",
"typescript-eslint": "^8.60.0",
"viem": "^2.33.1"
},
"files": [

View File

@@ -1,49 +0,0 @@
import "@nomicfoundation/hardhat-ethers";
import * as fs from "fs";
import { ethers, network } from "hardhat";
import { Deploys } from "../test/utils/interfaces";
let deploysJson: Deploys;
const supply: BigInt = ethers.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}`);
let erc20 = await ethers.deployContract("MockToken", [supply]);
erc20 = await erc20.waitForDeployment();
deploysJson.token = await erc20.getAddress();
console.log("🚀 Mock Token Deployed:", await erc20.getAddress());
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);
});

View File

@@ -1,75 +0,0 @@
import "@nomicfoundation/hardhat-ethers";
import * as fs from "fs";
import { ethers, network } 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}`);
let reputation = await ethers.deployContract("Reputation");
let multicall = await ethers.deployContract("Multicall");
let p2pix = await ethers.deployContract("P2PIX", [
10,
deploysJson.signers,
reputation.target,
[deploysJson.token],
[true],
]);
reputation = await reputation.waitForDeployment();
multicall = await multicall.waitForDeployment();
p2pix = await p2pix.waitForDeployment();
deploysJson.p2pix = await p2pix.getAddress();
console.log("🚀 P2PIX Deployed:", await p2pix.getAddress());
console.log("🌠 Reputation Deployed:", await reputation.getAddress());
console.log("🛰 Multicall Deployed:", await multicall.getAddress());
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,12 +20,22 @@ const main = async () => {
}
const [deployer] = await ethers.getSigners();
console.log(`Signing transactions with ${deployer.address}`);
console.log(
`Signing transactions with ${deployer.address}`,
);
const iface = new ethers.utils.Interface(P2PIX__factory.abi);
const calldata = iface.encodeFunctionData("setDefaultLockBlocks", ["10000"]);
const tx = await deployer.sendTransaction({to:deploysJson.p2pix, data: calldata});
const iface = new ethers.utils.Interface(
P2PIX__factory.abi,
);
const calldata = iface.encodeFunctionData(
"setDefaultLockBlocks",
["10000"],
);
const tx = await deployer.sendTransaction({
to: deploysJson.p2pix,
data: calldata,
});
const done = await tx.wait();
console.log(done.transactionHash);
};

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -4,15 +4,13 @@ import { ethers } from "hardhat";
import keccak256 from "keccak256";
import { MerkleTree } from "merkletreejs";
import {
MockToken,
Multicall,
P2PIX,
P2PIX__factory,
Reputation,
} from "../../src/types";
import { MockToken, P2PIX__factory } from "../../src/types";
import { Call, RepFixture, P2PixAndReputation, DepositArgs, LockArgs, ReleaseArgs } from "./interfaces";
import {
Call,
RepFixture,
P2PixAndReputation,
} from "./interfaces";
// exported constants
export const getSignerAddrs = (
@@ -22,18 +20,16 @@ export const getSignerAddrs = (
return addrs.slice(0, amount).map(({ address }) => address);
};
export const getBnFrom = (nums: number[]): BigInt[] => {
export const getBnFrom = (nums: number[]): bigint[] => {
const bns = nums.map(num => BigInt(num));
return bns;
};
export const getLockData = (
addr: string,
locks: BigInt[][],
locks: bigint[][],
): Call[] => {
const iface = new ethers.Interface(
P2PIX__factory.abi,
);
const iface = new ethers.Interface(P2PIX__factory.abi);
return locks.map(lock => ({
target: addr,
callData: iface.encodeFunctionData("getLocksStatus", [
@@ -71,8 +67,9 @@ export const curve = (x: number): number => {
// exported async functions
export async function repFixture(): Promise<RepFixture> {
const reputation = await ethers.deployContract("Reputation");
return { reputation: await reputation.waitForDeployment() };
const reputation =
await ethers.deployContract("Reputation");
return { reputation: await reputation.waitForDeployment() };
}
export async function p2pixFixture(): Promise<P2PixAndReputation> {
@@ -81,11 +78,12 @@ export async function p2pixFixture(): Promise<P2PixAndReputation> {
await ethers.getSigners(),
);
const reputation = await ethers.deployContract("Reputation");
const reputation =
await ethers.deployContract("Reputation");
const erc20 = await ethers.deployContract("MockToken", [
ethers.parseEther("20000000") // 20M
]) as MockToken;
const erc20 = (await ethers.deployContract("MockToken", [
ethers.parseEther("20000000"), // 20M
])) as MockToken;
const p2pix = await ethers.deployContract("P2PIX", [
10,

View File

@@ -5,7 +5,6 @@ import {
Reputation,
} from "../../src/types";
// exported interfaces
export interface Deploys {
signers: string[];
@@ -17,29 +16,29 @@ export interface DepositArgs {
pixTarget: string;
allowlistRoot: string;
token: string;
amount: BigInt;
amount: bigint;
valid: boolean;
}
}
export interface LockArgs {
seller: string;
token: string;
amount: BigInt;
amount: bigint;
merkleProof: string[];
expiredLocks: BigInt[];
expiredLocks: bigint[];
}
export interface ReleaseArgs {
lockID: BigInt;
lockID: bigint;
pixTimestamp: string;
signature: string;
}
export interface Lock {
counter: BigInt;
expirationBlock: BigInt;
counter: bigint;
expirationBlock: bigint;
pixTarget: string;
amount: BigInt;
amount: bigint;
token: string;
buyerAddress: string;
seller: string;
@@ -72,4 +71,4 @@ export interface MtcFixture {
export type P2PixAndReputation = P2pixFixture &
RepFixture &
MtcFixture;
MtcFixture;

View File

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

867
yarn.lock

File diff suppressed because it is too large Load Diff