Finished pending tasks
This commit is contained in:
@@ -23,8 +23,11 @@ library DataTypes {
|
||||
/// @dev If not paid at this block will be expired.
|
||||
uint256 expirationBlock;
|
||||
/// @dev Where the tokens are sent the when order gets validated.
|
||||
address targetAddress;
|
||||
/// @dev Relayer address that facilitated this transaction.
|
||||
address buyerAddress;
|
||||
/// @dev Relayer's target address that receives `relayerPremium` funds.
|
||||
address relayerTarget;
|
||||
/// @dev Relayer address (msg.sender) that facilitated this transaction.
|
||||
/// @dev Reputation points accruer.
|
||||
address relayerAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
pragma solidity 0.8.9;
|
||||
|
||||
interface EventAndErrors {
|
||||
// bytes32 constant DEPOSIT_ADDED_SIGNATURE =
|
||||
|
||||
/// ███ Events ████████████████████████████████████████████████████████████
|
||||
|
||||
@@ -26,17 +27,15 @@ interface EventAndErrors {
|
||||
uint256 depositID,
|
||||
uint256 amount
|
||||
);
|
||||
event LockReleased(
|
||||
address indexed buyer,
|
||||
bytes32 lockId
|
||||
);
|
||||
event LockReturned(
|
||||
address indexed buyer,
|
||||
bytes32 lockId
|
||||
);
|
||||
event FundsWithdrawn(
|
||||
address owner,
|
||||
uint256 amount
|
||||
event LockReleased(address indexed buyer, bytes32 lockId);
|
||||
event LockReturned(address indexed buyer, bytes32 lockId);
|
||||
event FundsWithdrawn(address owner, uint256 amount);
|
||||
event ReputationUpdated(address reputation);
|
||||
event LockBlocksUpdated(uint256 blocks);
|
||||
event ValidSignersUpdated(address[] signers);
|
||||
event AllowedERC20Updated(
|
||||
address indexed token,
|
||||
bool indexed state
|
||||
);
|
||||
|
||||
/// ███ Errors ████████████████████████████████████████████████████████████
|
||||
@@ -45,6 +44,7 @@ interface EventAndErrors {
|
||||
/// @dev 0xc44bd765
|
||||
error DepositAlreadyExists();
|
||||
/// @dev Only seller could call this function.
|
||||
/// @dev `msg.sender` and the seller differ.
|
||||
/// @dev 0x85d1f726
|
||||
error OnlySeller();
|
||||
/// @dev Lock not expired or already released.
|
||||
@@ -69,4 +69,20 @@ interface EventAndErrors {
|
||||
/// @dev Signer is not a valid signer.
|
||||
/// @dev 0x815e1d64
|
||||
error InvalidSigner();
|
||||
/// @dev Address doesn't exist in a MerkleTree.
|
||||
/// @dev Address not allowed as relayer.
|
||||
/// @dev 0x3b8474be
|
||||
error AddressDenied();
|
||||
/// @dev Arrays' length don't match.
|
||||
/// @dev 0xff633a38
|
||||
error LengthMismatch();
|
||||
/// @dev No tokens array provided as argument.
|
||||
/// @dev 0xdf957883
|
||||
error NoTokens();
|
||||
/// @dev Token address not allowed to be deposited.
|
||||
/// @dev 0x1578328e
|
||||
error TokenDenied();
|
||||
/// @dev Wished amount to be locked exceeds the limit allowed.
|
||||
/// @dev 0x1c18f846
|
||||
error AmountNotAllowed();
|
||||
}
|
||||
|
||||
45
contracts/Reputation.sol
Normal file
45
contracts/Reputation.sol
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.9;
|
||||
|
||||
import { IReputation } from "./lib/interfaces/IReputation.sol";
|
||||
import { Owned } from "./lib/auth/Owned.sol";
|
||||
import { FixedPointMathLib as WADMath } from "./lib/utils/FixedPointMathLib.sol";
|
||||
|
||||
|
||||
contract Reputation is
|
||||
IReputation,
|
||||
Owned(msg.sender)
|
||||
{
|
||||
|
||||
using WADMath for uint256;
|
||||
|
||||
/// @dev Asymptote numerator constant value for the `limiter` fx.
|
||||
uint256 constant public maxLimit = 1e6;
|
||||
/// @dev Denominator's constant operand for the `limiter` fx.
|
||||
uint256 constant public magicValue = 2.5e11;
|
||||
|
||||
constructor(/* */) {/* */}
|
||||
|
||||
function limiter(uint256 _userCredit)
|
||||
external
|
||||
pure
|
||||
override(IReputation)
|
||||
returns(uint256 _spendLimit)
|
||||
{
|
||||
// _spendLimit = 1 + ( ( maxLimit * _userCredit ) / sqrt( magicValue * ( _userCredit * _userCredit ) ) );
|
||||
// return _spendLimit;
|
||||
|
||||
|
||||
unchecked {
|
||||
uint256 numeratorWad =
|
||||
maxLimit.mulWadDown(_userCredit);
|
||||
uint256 userCreditSquaredWad =
|
||||
_userCredit.mulWadDown(_userCredit);
|
||||
uint256 denominatorSqrtWad =
|
||||
(userCreditSquaredWad.mulWadDown(magicValue)).sqrt();
|
||||
|
||||
_spendLimit = (1 + (numeratorWad).divWadDown(denominatorSqrtWad));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
contracts/lib/interfaces/IReputation.sol
Normal file
12
contracts/lib/interfaces/IReputation.sol
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.8.4;
|
||||
|
||||
|
||||
interface IReputation {
|
||||
|
||||
function limiter(uint256 _userCredit)
|
||||
external
|
||||
pure
|
||||
returns(uint256 _spendLimit);
|
||||
|
||||
}
|
||||
115
contracts/lib/utils/FixedPointMathLib.sol
Normal file
115
contracts/lib/utils/FixedPointMathLib.sol
Normal file
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.8.4;
|
||||
|
||||
/// @notice Arithmetic library with operations for fixed-point numbers.
|
||||
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
|
||||
library FixedPointMathLib {
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
SIMPLIFIED FIXED POINT OPERATIONS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
/// @dev The scalar of ETH and most ERC20s.
|
||||
uint256 internal constant WAD = 1e18;
|
||||
|
||||
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
|
||||
// Equivalent to (x * y) / WAD rounded down.
|
||||
return mulDivDown(x, y, WAD);
|
||||
}
|
||||
|
||||
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
|
||||
// Equivalent to (x * WAD) / y rounded down.
|
||||
return mulDivDown(x, WAD, y);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
LOW LEVEL FIXED POINT OPERATIONS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function mulDivDown(
|
||||
uint256 x,
|
||||
uint256 y,
|
||||
uint256 denominator
|
||||
) internal pure returns (uint256 z) {
|
||||
assembly {
|
||||
// Store x * y in z for now.
|
||||
z := mul(x, y)
|
||||
|
||||
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
|
||||
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
|
||||
revert(0, 0)
|
||||
}
|
||||
|
||||
// Divide z by the denominator.
|
||||
z := div(z, denominator)
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
GENERAL NUMBER UTILITIES
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function sqrt(uint256 x) internal pure returns (uint256 z) {
|
||||
assembly {
|
||||
let y := x // We start y at x, which will help us make our initial estimate.
|
||||
|
||||
z := 181 // The "correct" value is 1, but this saves a multiplication later.
|
||||
|
||||
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
|
||||
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
|
||||
|
||||
// We check y >= 2^(k + 8) but shift right by k bits
|
||||
// each branch to ensure that if x >= 256, then y >= 256.
|
||||
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
|
||||
y := shr(128, y)
|
||||
z := shl(64, z)
|
||||
}
|
||||
if iszero(lt(y, 0x1000000000000000000)) {
|
||||
y := shr(64, y)
|
||||
z := shl(32, z)
|
||||
}
|
||||
if iszero(lt(y, 0x10000000000)) {
|
||||
y := shr(32, y)
|
||||
z := shl(16, z)
|
||||
}
|
||||
if iszero(lt(y, 0x1000000)) {
|
||||
y := shr(16, y)
|
||||
z := shl(8, z)
|
||||
}
|
||||
|
||||
// Goal was to get z*z*y within a small factor of x. More iterations could
|
||||
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
|
||||
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
|
||||
// That's not possible if x < 256 but we can just verify those cases exhaustively.
|
||||
|
||||
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
|
||||
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
|
||||
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
|
||||
|
||||
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
|
||||
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
|
||||
|
||||
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
|
||||
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
|
||||
|
||||
// There is no overflow risk here since y < 2^136 after the first branch above.
|
||||
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
|
||||
|
||||
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
|
||||
z := shr(1, add(z, div(x, z)))
|
||||
z := shr(1, add(z, div(x, z)))
|
||||
z := shr(1, add(z, div(x, z)))
|
||||
z := shr(1, add(z, div(x, z)))
|
||||
z := shr(1, add(z, div(x, z)))
|
||||
z := shr(1, add(z, div(x, z)))
|
||||
z := shr(1, add(z, div(x, z)))
|
||||
|
||||
// If x+1 is a perfect square, the Babylonian method cycles between
|
||||
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
|
||||
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
|
||||
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
|
||||
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
|
||||
z := sub(z, lt(div(x, z), z))
|
||||
}
|
||||
}
|
||||
}
|
||||
43
contracts/lib/utils/MerkleProofLib.sol
Normal file
43
contracts/lib/utils/MerkleProofLib.sol
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.9;
|
||||
|
||||
/// ______ __
|
||||
/// ______ __
|
||||
/// .-----.|__ |.-----.|__|.--.--.
|
||||
/// | _ || __|| _ || ||_ _|
|
||||
/// | __||______|| __||__||__.__|
|
||||
/// |__| |__|
|
||||
/// |__| |__|
|
||||
///
|
||||
|
||||
import { Owned } from "./lib/auth/Owned.sol";
|
||||
import { Counters } from "./lib/utils/Counters.sol";
|
||||
import { ERC20, SafeTransferLib } from "./lib/utils/SafeTransferLib.sol";
|
||||
import { IReputation } from "./lib/interfaces/IReputation.sol";
|
||||
import { MerkleProofLib as Merkle } from "./lib/utils/MerkleProofLib.sol";
|
||||
import { ReentrancyGuard } from "./lib/utils/ReentrancyGuard.sol";
|
||||
import { EventAndErrors } from "./EventAndErrors.sol";
|
||||
import { DataTypes as DT } from "./DataTypes.sol";
|
||||
|
||||
contract P2PIX is
|
||||
EventAndErrors,
|
||||
contract P2PIX is
|
||||
EventAndErrors,
|
||||
Owned(msg.sender),
|
||||
ReentrancyGuard
|
||||
ReentrancyGuard
|
||||
{
|
||||
// solhint-disable use-forbidden-name
|
||||
// solhint-disable no-inline-assembly
|
||||
@@ -29,12 +31,14 @@ contract P2PIX is
|
||||
|
||||
/// ███ Storage ████████████████████████████████████████████████████████████
|
||||
|
||||
IReputation public reputation;
|
||||
Counters.Counter public depositCount;
|
||||
|
||||
|
||||
/// @dev Default blocks that lock will hold tokens.
|
||||
uint256 public defaultLockBlocks;
|
||||
|
||||
|
||||
/// @dev Stores an relayer's last computed credit.
|
||||
mapping(uint256 => uint256) public userRecord;
|
||||
/// @dev List of valid Bacen signature addresses
|
||||
mapping(uint256 => bool) public validBacenSigners;
|
||||
/// @dev Seller list of deposits
|
||||
@@ -43,58 +47,66 @@ contract P2PIX is
|
||||
mapping(bytes32 => DT.Lock) public mapLocks;
|
||||
/// @dev List of Pix transactions already signed.
|
||||
mapping(bytes32 => bool) private usedTransactions;
|
||||
/// @dev Seller casted to key => Seller's allowlist merkleroot.
|
||||
mapping(uint256 => bytes32) public sellerAllowList;
|
||||
/// @dev Tokens allowed to serve as the underlying amount of a deposit.
|
||||
mapping(ERC20 => bool) public allowedERC20s;
|
||||
|
||||
/// ███ Constructor ████████████████████████████████████████████████████████
|
||||
|
||||
constructor(
|
||||
uint256 defaultBlocks,
|
||||
address[] memory validSigners
|
||||
address[] memory validSigners,
|
||||
IReputation _reputation,
|
||||
address[] memory tokens,
|
||||
bool[] memory tokenStates
|
||||
) payable {
|
||||
assembly {
|
||||
sstore(defaultLockBlocks.slot, defaultBlocks)
|
||||
}
|
||||
unchecked {
|
||||
uint256 i;
|
||||
uint256 len = validSigners.length;
|
||||
for (i; i < len; ) {
|
||||
uint256 key = _castAddrToKey(validSigners[i]);
|
||||
validBacenSigners[key] = true;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
setDefaultLockBlocks(defaultBlocks);
|
||||
setReputation(_reputation);
|
||||
setValidSigners(validSigners);
|
||||
tokenSettings(tokens, tokenStates);
|
||||
}
|
||||
|
||||
/// ███ Public FX ██████████████████████████████████████████████████████████
|
||||
|
||||
/// @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.
|
||||
/// @dev Seller needs to send his tokens to the P2PIX smart contract.
|
||||
/// @param _pixTarget Pix key destination provided by the offer's seller.
|
||||
/// @return depositID The `uint256` return value provided
|
||||
/// as the deposit identifier.
|
||||
/// @param allowlistRoot Optional allow list merkleRoot update `bytes32` value.
|
||||
/// @return depositID The `uint256` return value provided
|
||||
/// as the deposit identifier.
|
||||
/// @dev Function sighash: 0xbfe07da6.
|
||||
function deposit(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
string calldata _pixTarget
|
||||
)
|
||||
public
|
||||
returns (uint256 depositID)
|
||||
string calldata _pixTarget,
|
||||
bytes32 allowlistRoot
|
||||
)
|
||||
public
|
||||
returns (
|
||||
uint256 depositID
|
||||
)
|
||||
{
|
||||
(depositID) = _encodeDepositID();
|
||||
ERC20 t = ERC20(_token);
|
||||
|
||||
DT.Deposit memory d =
|
||||
DT.Deposit({
|
||||
remaining: _amount,
|
||||
pixTarget: _pixTarget,
|
||||
seller: msg.sender,
|
||||
token: _token,
|
||||
valid: true
|
||||
});
|
||||
|
||||
if (!allowedERC20s[t]) revert TokenDenied();
|
||||
|
||||
(depositID) = _encodeDepositID();
|
||||
|
||||
DT.Deposit memory d = DT.Deposit({
|
||||
remaining: _amount,
|
||||
pixTarget: _pixTarget,
|
||||
seller: msg.sender,
|
||||
token: _token,
|
||||
valid: true
|
||||
});
|
||||
|
||||
setReentrancyGuard();
|
||||
|
||||
if (allowlistRoot != 0) {
|
||||
setRoot(msg.sender, allowlistRoot);
|
||||
}
|
||||
|
||||
mapDeposits[depositID] = d;
|
||||
depositCount.increment();
|
||||
|
||||
@@ -104,7 +116,7 @@ contract P2PIX is
|
||||
address(this),
|
||||
_amount
|
||||
);
|
||||
|
||||
|
||||
clearReentrancyGuard();
|
||||
|
||||
emit DepositAdded(
|
||||
@@ -115,14 +127,12 @@ contract P2PIX is
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Enables seller to invalidate future
|
||||
/// @notice Enables seller to invalidate future
|
||||
/// locks made to his/her token offering order.
|
||||
/// @dev This function does not affect any ongoing active locks.
|
||||
/// @dev Function sighash: 0x72fada5c.
|
||||
|
||||
function cancelDeposit(
|
||||
uint256 depositID
|
||||
) public {
|
||||
|
||||
function cancelDeposit(uint256 depositID) public {
|
||||
_onlySeller(depositID);
|
||||
mapDeposits[depositID].valid = false;
|
||||
emit DepositClosed(
|
||||
@@ -131,93 +141,128 @@ contract P2PIX is
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Public method designed to lock an remaining amount of
|
||||
/// the deposit order of a seller.
|
||||
/// @dev This method can be performed by either an order's seller,
|
||||
/// relayer, or buyer.
|
||||
/// @dev There can only exist a lock per each `_amount` partitioned
|
||||
/// @notice Public method designed to lock an remaining amount of
|
||||
/// the deposit order of a seller.
|
||||
/// @dev This method can be performed either by:
|
||||
/// - An user allowed via the seller's allowlist;
|
||||
/// - An user with enough userRecord to lock the wished amount;
|
||||
/// @dev There can only exist a lock per each `_amount` partitioned
|
||||
/// from the total `remaining` value.
|
||||
/// @dev Locks can only be performed in valid orders.
|
||||
/// @param _targetAddress The address of the buyer of a `_depositID`.
|
||||
/// @param _relayerAddress The relayer's address.
|
||||
/// @param _buyerAddress The address of the buyer of a `_depositID`.
|
||||
/// @param _relayerTarget Target address entitled to the `relayerPremim`.
|
||||
/// @param _relayerPremium The refund/premium owed to a relayer.
|
||||
/// @param expiredLocks An array of `bytes32` identifiers to be
|
||||
/// provided so to unexpire locks using this transaction gas push.
|
||||
/// @param _amount The deposit's remaining amount wished to be locked.
|
||||
/// @param merkleProof This value should be:
|
||||
/// - Provided as a pass if the `msg.sender` is in the seller's allowlist;
|
||||
/// - Left empty otherwise;
|
||||
/// @param expiredLocks An array of `bytes32` identifiers to be
|
||||
/// provided so to unexpire locks using this transaction gas push.
|
||||
/// @return lockID The `bytes32` value returned as the lock identifier.
|
||||
/// @dev Function sighash: 0x03aaf306.
|
||||
function lock(
|
||||
uint256 _depositID,
|
||||
address _targetAddress,
|
||||
address _relayerAddress,
|
||||
address _buyerAddress,
|
||||
address _relayerTarget,
|
||||
uint256 _relayerPremium,
|
||||
uint256 _amount,
|
||||
bytes32[] calldata merkleProof,
|
||||
bytes32[] calldata expiredLocks
|
||||
)
|
||||
public
|
||||
nonReentrant
|
||||
returns (bytes32 lockID)
|
||||
{
|
||||
) public nonReentrant returns (bytes32 lockID) {
|
||||
unlockExpired(expiredLocks);
|
||||
DT.Deposit storage d =
|
||||
mapDeposits[_depositID];
|
||||
|
||||
if(!d.valid)
|
||||
revert InvalidDeposit();
|
||||
if(d.remaining < _amount)
|
||||
revert NotEnoughTokens();
|
||||
(lockID) =
|
||||
_encodeLockID(
|
||||
_depositID,
|
||||
_amount,
|
||||
_targetAddress
|
||||
);
|
||||
|
||||
DT.Lock memory l =
|
||||
DT.Lock
|
||||
({
|
||||
depositID: _depositID,
|
||||
relayerPremium: _relayerPremium,
|
||||
amount: _amount,
|
||||
expirationBlock: (block.number + defaultLockBlocks),
|
||||
targetAddress: _targetAddress,
|
||||
relayerAddress: _relayerAddress
|
||||
});
|
||||
|
||||
mapLocks[lockID] = l;
|
||||
d.remaining -= _amount;
|
||||
|
||||
emit LockAdded(
|
||||
_targetAddress,
|
||||
lockID,
|
||||
DT.Deposit storage d = mapDeposits[_depositID];
|
||||
|
||||
if (!d.valid) revert InvalidDeposit();
|
||||
if (d.remaining < _amount) revert NotEnoughTokens();
|
||||
|
||||
(lockID) = _encodeLockID(
|
||||
_depositID,
|
||||
_amount
|
||||
_amount,
|
||||
_buyerAddress
|
||||
);
|
||||
|
||||
DT.Lock memory l = DT.Lock({
|
||||
depositID: _depositID,
|
||||
relayerPremium: _relayerPremium,
|
||||
amount: _amount,
|
||||
expirationBlock: (block.number +
|
||||
defaultLockBlocks),
|
||||
buyerAddress: _buyerAddress,
|
||||
relayerTarget: _relayerTarget,
|
||||
relayerAddress: msg.sender
|
||||
});
|
||||
|
||||
if (merkleProof.length != 0) {
|
||||
merkleVerify(
|
||||
merkleProof,
|
||||
sellerAllowList[_castAddrToKey(d.seller)],
|
||||
msg.sender
|
||||
);
|
||||
|
||||
mapLocks[lockID] = l;
|
||||
d.remaining -= _amount;
|
||||
|
||||
emit LockAdded(
|
||||
_buyerAddress,
|
||||
lockID,
|
||||
l.depositID,
|
||||
_amount
|
||||
);
|
||||
|
||||
// Halt execution and output `lockID`.
|
||||
return lockID;
|
||||
}
|
||||
else {
|
||||
uint256 userCredit = userRecord[
|
||||
_castAddrToKey(msg.sender)
|
||||
];
|
||||
uint256 spendLimit;
|
||||
(spendLimit) = _limiter(userCredit);
|
||||
|
||||
if (l.amount > spendLimit)
|
||||
revert AmountNotAllowed();
|
||||
|
||||
mapLocks[lockID] = l;
|
||||
d.remaining -= _amount;
|
||||
|
||||
emit LockAdded(
|
||||
_buyerAddress,
|
||||
lockID,
|
||||
l.depositID,
|
||||
_amount
|
||||
);
|
||||
|
||||
// Halt execution and output `lockID`.
|
||||
return lockID;
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Lock release method that liquidate lock
|
||||
// orders and distributes relayer fees.
|
||||
/// @dev This method can be called by either an
|
||||
/// order's seller, relayer, or buyer.
|
||||
/// @notice Lock release method that liquidate lock
|
||||
/// orders and distributes relayer fees.
|
||||
/// @dev This method can be called by any public actor
|
||||
/// as long the signature provided is valid.
|
||||
/// @dev `relayerPremium` gets splitted equaly
|
||||
/// if `relayerTarget` addresses differ.
|
||||
/// @dev If the `msg.sender` of this method and `l.relayerAddress` are the same,
|
||||
/// `msg.sender` accrues both l.amount and l.relayerPremium as userRecord credit.
|
||||
/// In case of they differing:
|
||||
/// - `lock` caller gets accrued with `l.amount` as userRecord credit;
|
||||
/// - `release` caller gets accrued with `l.relayerPremium` as userRecord credit;
|
||||
/// @param _relayerTarget Target address entitled to the `relayerPremim`.
|
||||
/// @dev Function sighash: 0x4e1389ed.
|
||||
function release(
|
||||
bytes32 lockID,
|
||||
address _relayerTarget,
|
||||
uint256 pixTimestamp,
|
||||
bytes32 r,
|
||||
bytes32 s,
|
||||
uint8 v
|
||||
)
|
||||
public
|
||||
nonReentrant
|
||||
{
|
||||
/// @todo Prevent a PIX non-related to the app from
|
||||
/// getting targeted, due to both sharing the same destination.
|
||||
) public nonReentrant {
|
||||
DT.Lock storage l = mapLocks[lockID];
|
||||
|
||||
if(
|
||||
l.expirationBlock <= block.number ||
|
||||
l.amount <= 0
|
||||
) revert
|
||||
AlreadyReleased();
|
||||
if (
|
||||
l.expirationBlock <= block.number || l.amount <= 0
|
||||
) revert AlreadyReleased();
|
||||
|
||||
DT.Deposit storage d = mapDeposits[l.depositID];
|
||||
bytes32 message = keccak256(
|
||||
@@ -234,21 +279,14 @@ contract P2PIX is
|
||||
)
|
||||
);
|
||||
|
||||
if(
|
||||
usedTransactions[message]
|
||||
== true
|
||||
) revert
|
||||
TxAlreadyUsed();
|
||||
if (usedTransactions[message] == true)
|
||||
revert TxAlreadyUsed();
|
||||
|
||||
uint256 signer = _castAddrToKey(
|
||||
ecrecover(
|
||||
messageDigest,
|
||||
v,
|
||||
r,
|
||||
s
|
||||
));
|
||||
uint256 signer = _castAddrToKey(
|
||||
ecrecover(messageDigest, v, r, s)
|
||||
);
|
||||
|
||||
if(!validBacenSigners[signer])
|
||||
if (!validBacenSigners[signer])
|
||||
revert InvalidSigner();
|
||||
|
||||
ERC20 t = ERC20(d.token);
|
||||
@@ -260,53 +298,79 @@ contract P2PIX is
|
||||
l.expirationBlock = 0;
|
||||
usedTransactions[message] = true;
|
||||
|
||||
SafeTransferLib.safeTransfer(
|
||||
t,
|
||||
l.targetAddress,
|
||||
if (msg.sender != l.relayerAddress) {
|
||||
userRecord[_castAddrToKey(msg.sender)] += l
|
||||
.relayerPremium;
|
||||
userRecord[_castAddrToKey(l.relayerAddress)] += l
|
||||
.amount;
|
||||
} else {
|
||||
userRecord[_castAddrToKey(msg.sender)] += (l
|
||||
.relayerPremium + l.amount);
|
||||
}
|
||||
|
||||
SafeTransferLib.safeTransfer(
|
||||
t,
|
||||
l.buyerAddress,
|
||||
totalAmount
|
||||
);
|
||||
|
||||
// Method doesn't check for zero address.
|
||||
if (l.relayerPremium != 0) {
|
||||
SafeTransferLib.safeTransfer(
|
||||
t,
|
||||
l.relayerAddress,
|
||||
l.relayerPremium
|
||||
);
|
||||
if (_relayerTarget != l.relayerTarget) {
|
||||
SafeTransferLib.safeTransfer(
|
||||
t,
|
||||
l.relayerTarget,
|
||||
(l.relayerPremium >> 1)
|
||||
);
|
||||
SafeTransferLib.safeTransfer(
|
||||
t,
|
||||
_relayerTarget,
|
||||
(l.relayerPremium >> 1)
|
||||
);
|
||||
} else {
|
||||
SafeTransferLib.safeTransfer(
|
||||
t,
|
||||
_relayerTarget,
|
||||
l.relayerPremium
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
emit LockReleased(
|
||||
l.targetAddress,
|
||||
lockID
|
||||
);
|
||||
emit LockReleased(l.buyerAddress, lockID);
|
||||
}
|
||||
|
||||
|
||||
/// @notice Unlocks expired locks.
|
||||
/// @dev Triggered in the callgraph by both `lock` and `withdraw` functions.
|
||||
/// @dev This method can also have any public actor as its `tx.origin`.
|
||||
/// @dev For each successfull unexpired lock recovered,
|
||||
/// `userRecord[_castAddrToKey(l.relayerAddress)]` is decreased by half of its value.
|
||||
/// @dev Function sighash: 0x8e2749d6.
|
||||
function unlockExpired(
|
||||
bytes32[] calldata lockIDs
|
||||
) public {
|
||||
function unlockExpired(bytes32[] calldata lockIDs)
|
||||
public
|
||||
{
|
||||
uint256 i;
|
||||
uint256 locksSize =
|
||||
lockIDs.length;
|
||||
|
||||
for (i; i < locksSize;)
|
||||
{
|
||||
uint256 locksSize = lockIDs.length;
|
||||
|
||||
for (i; i < locksSize; ) {
|
||||
DT.Lock storage l = mapLocks[lockIDs[i]];
|
||||
|
||||
_notExpired(l);
|
||||
|
||||
mapDeposits[l.depositID].remaining
|
||||
+= l.amount;
|
||||
mapDeposits[l.depositID].remaining += l.amount;
|
||||
l.amount = 0;
|
||||
|
||||
emit LockReturned(
|
||||
l.targetAddress,
|
||||
lockIDs[i]
|
||||
);
|
||||
|
||||
|
||||
uint256 userKey =
|
||||
_castAddrToKey(l.relayerAddress);
|
||||
uint256 _newUserRecord =
|
||||
(userRecord[userKey] >> 1);
|
||||
|
||||
if (_newUserRecord <= 100) {
|
||||
userRecord[userKey] = 100;
|
||||
} else {
|
||||
userRecord[userKey] = _newUserRecord;
|
||||
}
|
||||
emit LockReturned(l.buyerAddress, lockIDs[i]);
|
||||
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
@@ -315,39 +379,29 @@ contract P2PIX is
|
||||
assembly {
|
||||
if lt(i, locksSize) {
|
||||
// LoopOverflow()
|
||||
mstore(
|
||||
0x00,
|
||||
0xdfb035c9
|
||||
)
|
||||
revert(
|
||||
0x1c,
|
||||
0x04
|
||||
)
|
||||
mstore(0x00, 0xdfb035c9)
|
||||
revert(0x1c, 0x04)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Seller's expired deposit fund sweeper.
|
||||
/// @dev A seller may use this method to recover
|
||||
/// @dev A seller may use this method to recover
|
||||
/// tokens from expired deposits.
|
||||
/// @dev Function sighash: 0x36317972.
|
||||
function withdraw(
|
||||
uint256 depositID,
|
||||
bytes32[] calldata expiredLocks
|
||||
)
|
||||
public
|
||||
nonReentrant
|
||||
{
|
||||
) public nonReentrant {
|
||||
_onlySeller(depositID);
|
||||
unlockExpired(expiredLocks);
|
||||
|
||||
DT.Deposit storage d =
|
||||
mapDeposits[depositID];
|
||||
|
||||
if (d.valid == true) {
|
||||
cancelDeposit(depositID);
|
||||
|
||||
DT.Deposit storage d = mapDeposits[depositID];
|
||||
|
||||
if (d.valid == true) {
|
||||
cancelDeposit(depositID);
|
||||
}
|
||||
|
||||
|
||||
ERC20 token = ERC20(d.token);
|
||||
|
||||
// Withdraw remaining tokens from mapDeposit[depositID]
|
||||
@@ -355,71 +409,149 @@ contract P2PIX is
|
||||
d.remaining = 0;
|
||||
|
||||
// safeTransfer tokens to seller
|
||||
SafeTransferLib.safeTransfer(
|
||||
token,
|
||||
d.seller,
|
||||
amount
|
||||
);
|
||||
SafeTransferLib.safeTransfer(token, d.seller, amount);
|
||||
|
||||
emit DepositWithdrawn(
|
||||
msg.sender,
|
||||
depositID,
|
||||
amount
|
||||
);
|
||||
emit DepositWithdrawn(msg.sender, depositID, amount);
|
||||
}
|
||||
|
||||
function setRoot(address addr, bytes32 merkleroot)
|
||||
public
|
||||
{
|
||||
if (addr == msg.sender) {
|
||||
sellerAllowList[
|
||||
_castAddrToKey(addr)
|
||||
] = merkleroot;
|
||||
} else revert OnlySeller();
|
||||
}
|
||||
|
||||
/// ███ Owner Only █████████████████████████████████████████████████████████
|
||||
|
||||
/// @dev Contract's balance withdraw method.
|
||||
/// @dev Contract's underlying balance withdraw method.
|
||||
/// @dev Function sighash: 0x5fd8c710.
|
||||
function withdrawBalance() external onlyOwner {
|
||||
uint256 balance =
|
||||
address(this).balance;
|
||||
SafeTransferLib.safeTransferETH(
|
||||
msg.sender,
|
||||
balance
|
||||
);
|
||||
emit FundsWithdrawn(
|
||||
msg.sender,
|
||||
balance
|
||||
);
|
||||
uint256 balance = address(this).balance;
|
||||
SafeTransferLib.safeTransferETH(msg.sender, balance);
|
||||
emit FundsWithdrawn(msg.sender, balance);
|
||||
}
|
||||
|
||||
function setReputation(IReputation _reputation)
|
||||
public
|
||||
onlyOwner
|
||||
{
|
||||
assembly {
|
||||
sstore(reputation.slot, _reputation)
|
||||
}
|
||||
emit ReputationUpdated(address(_reputation));
|
||||
}
|
||||
|
||||
function setDefaultLockBlocks(uint256 _blocks)
|
||||
public
|
||||
onlyOwner
|
||||
{
|
||||
assembly {
|
||||
sstore(defaultLockBlocks.slot, _blocks)
|
||||
}
|
||||
emit LockBlocksUpdated(_blocks);
|
||||
}
|
||||
|
||||
function setValidSigners(address[] memory _validSigners)
|
||||
public
|
||||
onlyOwner
|
||||
{
|
||||
unchecked {
|
||||
uint256 i;
|
||||
uint256 len = _validSigners.length;
|
||||
for (i; i < len; ) {
|
||||
uint256 key = _castAddrToKey(
|
||||
_validSigners[i]
|
||||
);
|
||||
validBacenSigners[key] = true;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
emit ValidSignersUpdated(_validSigners);
|
||||
}
|
||||
|
||||
function tokenSettings(
|
||||
address[] memory _tokens,
|
||||
bool[] memory _states
|
||||
) public onlyOwner {
|
||||
/* Yul Impl */
|
||||
assembly {
|
||||
// first 32 bytes eq to array's length
|
||||
let tLen := mload(_tokens)
|
||||
if iszero(tLen) {
|
||||
mstore(0x00, 0xdf957883)
|
||||
revert(0x1c, 0x04)
|
||||
}
|
||||
if iszero(eq(tLen, mload(_states))) {
|
||||
mstore(0x00, 0xff633a38)
|
||||
revert(0x1c, 0x04)
|
||||
}
|
||||
let tLoc := add(_tokens, 0x20)
|
||||
let sLoc := add(_states, 0x20)
|
||||
for {
|
||||
let end := add(tLoc, mul(tLen, 0x20))
|
||||
} iszero(eq(tLoc, end)) {
|
||||
tLoc := add(tLoc, 0x20)
|
||||
sLoc := add(sLoc, 0x20)
|
||||
} {
|
||||
mstore(0x00, mload(tLoc))
|
||||
mstore(0x20, allowedERC20s.slot)
|
||||
let mapSlot := keccak256(0x00, 0x40)
|
||||
sstore(mapSlot, mload(sLoc))
|
||||
log3(
|
||||
0,
|
||||
0,
|
||||
0x5d6e86e5341d57a92c49934296c51542a25015c9b1782a1c2722a940131c3d9a,
|
||||
mload(tLoc),
|
||||
mload(sLoc)
|
||||
)
|
||||
}
|
||||
}
|
||||
/* Solidity Impl */
|
||||
// uint256 tLen = _tokens.length;
|
||||
// uint256 sLen = _states.length;
|
||||
|
||||
// if (tLen != sLen)
|
||||
// revert LengthMismatch();
|
||||
// if (tLen == 0)
|
||||
// revert NoTokens();
|
||||
|
||||
// uint256 i;
|
||||
// for (i; i > tLen;) {
|
||||
// allowedERC20s[ERC20(_tokens[i])] = _states[i];
|
||||
// emit AllowedERC20Updated(_tokens[i], _states[i]);
|
||||
// unchecked {
|
||||
// ++i;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/// ███ Helper FX ██████████████████████████████████████████████████████████
|
||||
|
||||
|
||||
// solhint-disable-next-line no-empty-blocks
|
||||
receive() external payable {}
|
||||
|
||||
/// @notice Access control private view method that
|
||||
/// @notice Access control private view method that
|
||||
/// performs auth check on an deposit's seller.
|
||||
/// @dev Function sighash: 0x4125a4d9.
|
||||
function _onlySeller(uint256 _depositID)
|
||||
private
|
||||
view
|
||||
{
|
||||
if (
|
||||
mapDeposits[_depositID].seller
|
||||
!= msg.sender
|
||||
) revert
|
||||
OnlySeller();
|
||||
function _onlySeller(uint256 _depositID) private view {
|
||||
if (mapDeposits[_depositID].seller != msg.sender)
|
||||
revert OnlySeller();
|
||||
}
|
||||
|
||||
/// @notice Private view auxiliar logic that reverts
|
||||
/// @notice Private view auxiliar logic that reverts
|
||||
/// on a not expired lock passed as argument of the function.
|
||||
/// @dev Called exclusively by the `unlockExpired` method.
|
||||
/// @dev Function sighash: 0x74e2a0bb.
|
||||
function _notExpired(DT.Lock storage _l)
|
||||
private
|
||||
view
|
||||
{
|
||||
function _notExpired(DT.Lock storage _l) private view {
|
||||
// Custom Error Solidity Impl
|
||||
if
|
||||
(
|
||||
_l.expirationBlock >= block.number ||
|
||||
if (
|
||||
_l.expirationBlock >= block.number ||
|
||||
_l.amount <= 0
|
||||
) revert
|
||||
NotExpired();
|
||||
/*
|
||||
) revert NotExpired();
|
||||
/*
|
||||
// Custom Error Yul Impl
|
||||
assembly {
|
||||
if iszero(iszero(
|
||||
@@ -448,55 +580,96 @@ contract P2PIX is
|
||||
/// @notice Internal view auxiliar logic that returns a new valid `_depositID`.
|
||||
/// @dev It reverts on an already valid counter (`uint256`) value.
|
||||
/// @dev Function sighash: 0xdb51d697.
|
||||
function _encodeDepositID()
|
||||
internal
|
||||
view
|
||||
returns (uint256 _depositID)
|
||||
function _encodeDepositID()
|
||||
internal
|
||||
view
|
||||
returns (uint256 _depositID)
|
||||
{
|
||||
(_depositID) = depositCount.current();
|
||||
if (
|
||||
mapDeposits[_depositID].valid
|
||||
== true
|
||||
) revert
|
||||
DepositAlreadyExists();
|
||||
if (mapDeposits[_depositID].valid == true)
|
||||
revert DepositAlreadyExists();
|
||||
}
|
||||
|
||||
/// @notice Private view auxiliar logic that encodes/returns
|
||||
/// @notice Private view auxiliar logic that encodes/returns
|
||||
/// the `bytes32` identifier of an lock.
|
||||
/// @dev reverts on a not expired lock with the same ID passed
|
||||
/// @dev reverts on a not expired lock with the same ID passed
|
||||
/// as argument of the function.
|
||||
/// @dev Called exclusively by the `lock` method.
|
||||
/// @dev Function sighash: 0x3fc5fb52.
|
||||
function _encodeLockID(
|
||||
uint256 _depositID,
|
||||
uint256 _amount,
|
||||
address _targetAddress)
|
||||
private
|
||||
view
|
||||
returns (bytes32 _lockID)
|
||||
{
|
||||
uint256 _depositID,
|
||||
uint256 _amount,
|
||||
address _buyerAddress
|
||||
) private view returns (bytes32 _lockID) {
|
||||
_lockID = keccak256(
|
||||
abi.encodePacked(_depositID, _amount, _targetAddress)
|
||||
abi.encodePacked(
|
||||
_depositID,
|
||||
_amount,
|
||||
_buyerAddress
|
||||
)
|
||||
);
|
||||
if (
|
||||
mapLocks[_lockID].expirationBlock
|
||||
>= block.number
|
||||
) revert
|
||||
NotExpired();
|
||||
if (mapLocks[_lockID].expirationBlock >= block.number)
|
||||
revert NotExpired();
|
||||
}
|
||||
|
||||
/// @notice Public method that handles `address`
|
||||
function merkleVerify(
|
||||
bytes32[] calldata _merkleProof,
|
||||
bytes32 root,
|
||||
address _addr
|
||||
) private pure {
|
||||
if (
|
||||
!Merkle.verify(
|
||||
_merkleProof,
|
||||
root,
|
||||
bytes32(uint256(uint160(_addr)))
|
||||
)
|
||||
) revert AddressDenied();
|
||||
}
|
||||
|
||||
function _limiter(uint256 _userCredit)
|
||||
internal
|
||||
view
|
||||
returns (uint256 _spendLimit)
|
||||
{
|
||||
// enconde the fx sighash and args
|
||||
bytes memory encodedParams = abi.encodeWithSelector(
|
||||
IReputation.limiter.selector,
|
||||
_userCredit
|
||||
);
|
||||
// cast the uninitialized return values to memory
|
||||
bool success;
|
||||
uint256 returnSize;
|
||||
uint256 returnValue;
|
||||
// perform staticcall from the stack w yul
|
||||
assembly {
|
||||
success := staticcall(
|
||||
// gas
|
||||
30000,
|
||||
// address
|
||||
sload(reputation.slot),
|
||||
// argsOffset
|
||||
add(encodedParams, 0x20),
|
||||
// argsSize
|
||||
mload(encodedParams),
|
||||
// retOffset
|
||||
0x00,
|
||||
// retSize
|
||||
0x20
|
||||
)
|
||||
returnSize := returndatasize()
|
||||
returnValue := mload(0x00)
|
||||
_spendLimit := returnValue
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Public method that handles `address`
|
||||
/// to `uint256` safe type casting.
|
||||
/// @dev Function sighash: 0x4b2ae980.
|
||||
function _castAddrToKey(address _addr)
|
||||
public
|
||||
pure
|
||||
returns (uint256 _key)
|
||||
function _castAddrToKey(address _addr)
|
||||
public
|
||||
pure
|
||||
returns (uint256 _key)
|
||||
{
|
||||
_key = uint256(
|
||||
uint160(
|
||||
address(
|
||||
_addr
|
||||
))) << 12;
|
||||
_key = uint256(uint160(address(_addr))) << 12;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user