Enhanced integration & optimized testing

This commit is contained in:
PedroCailleret
2023-02-14 18:40:02 -03:00
parent 4c8016080d
commit 8310e013f6
40 changed files with 1516 additions and 649 deletions

View File

@@ -2,18 +2,6 @@
pragma solidity 0.8.9;
library DataTypes {
// struct Deposit {
// /// @dev Remaining tokens available.
// uint256 remaining;
// /// @dev The PIX account for the seller receive transactions.
// string pixTarget;
// address seller;
// /// @dev ERC20 stable token address.
// address token;
// /// @dev Could be invalidated by the seller.
// bool valid;
// }
struct Lock {
uint256 sellerKey;
uint256 counter;
@@ -33,4 +21,12 @@ library DataTypes {
address relayerAddress;
address token;
}
// prettier-ignore
enum LockStatus {
Inexistent, // 0 := Uninitialized Lock.
Active, // 1 := Valid Lock.
Expired, // 2 := Expired Lock.
Released // 3 := Already released Lock.
}
}

View File

@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
/// @title Multicall.
/// @notice Contract that batches view function calls and aggregates their results.
/// @author Adapted from Makerdao's Multicall2 (https://github.com/makerdao/multicall/blob/master/src/Multicall2.sol).
contract Multicall {
/// @dev 0x
error CallFailed(string reason);
struct Call {
address target;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
//prettier-ignore
constructor(/* */) payable {/* */}
function mtc1(Call[] calldata calls)
external
returns (uint256, bytes[] memory)
{
uint256 bn = block.number;
uint256 len = calls.length;
bytes[] memory res = new bytes[](len);
uint256 j;
while (j < len) {
(bool success, bytes memory ret) = calls[j]
.target
.call(calls[j].callData);
if (!success) {
if (ret.length < 0x44) revert CallFailed("");
assembly {
ret := add(ret, 0x04)
}
revert CallFailed({
reason: abi.decode(ret, (string))
});
}
res[j] = ret;
++j;
}
return (bn, res);
}
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 */
);
uint256 len = calls.length;
Result[] memory res = new Result[](len);
uint256 i;
for (i; i < len; ) {
(bool success, bytes memory ret) = calls[i]
.target
.call(calls[i].callData);
res[i] = Result(success, ret);
++i;
}
return (bn, bh, res);
}
}

View File

@@ -114,46 +114,4 @@ library SafeTransferLib {
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// We'll write our calldata to this slot below, but restore it later.
let memPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(
0,
0x095ea7b300000000000000000000000000000000000000000000000000000000
)
mstore(4, to) // Append the "to" argument.
mstore(36, amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(
and(
eq(mload(0), 1),
gt(returndatasize(), 31)
),
iszero(returndatasize())
),
// We use 68 because that's the total length of our calldata (4 + 32 * 2)
// Counterintuitively, this call() must be positioned after the or() in the
// surrounding and() because and() evaluates its arguments from right to left.
call(gas(), token, 0, 0, 68, 0, 32)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, memPointer) // Restore the memPointer.
}
require(success, "APPROVE_FAILED");
}
}

View File

@@ -26,6 +26,7 @@ contract P2PIX is
// solhint-disable no-empty-blocks
using DT for DT.Lock;
using DT for DT.LockStatus;
/// ███ Constants ██████████████████████████████████████████████████████████
@@ -105,15 +106,11 @@ contract P2PIX is
ERC20 t = ERC20(_token);
uint256 k = _castAddrToKey(msg.sender);
if (_pixTarget == 0)
revert EmptyPixTarget();
if (!allowedERC20s[t])
revert TokenDenied();
uint256 _sellerBalance =
sellerBalance[k][t];
if (_pixTarget == 0) revert EmptyPixTarget();
if (!allowedERC20s[t]) revert TokenDenied();
uint256 _sellerBalance = sellerBalance[k][t];
uint256 currBal =
_sellerBalance & BITMASK_SB_ENTRY;
uint256 currBal = _sellerBalance & BITMASK_SB_ENTRY;
if ((currBal + _amount) > 1e8 ether)
revert MaxBalExceeded();
@@ -130,12 +127,8 @@ contract P2PIX is
amountCasted,
pixTargetCasted,
validCasted
) = _castToUint(
_amount,
_pixTarget,
_valid
);
) = _castToUint(_amount, _pixTarget, _valid);
sellerBalance[k][t] =
(currBal + amountCasted) |
(pixTargetCasted << BITPOS_PIXTARGET) |
@@ -150,11 +143,7 @@ contract P2PIX is
clearReentrancyGuard();
emit DepositAdded(
msg.sender,
_token,
_amount
);
emit DepositAdded(msg.sender, _token, _amount);
}
/// @notice Enables seller to invalidate future
@@ -162,29 +151,23 @@ contract P2PIX is
/// @dev This function does not affect any ongoing active locks.
/// @dev Function sighash: 0x72fada5c.
function setValidState(ERC20 token, bool state) public {
uint256 key =
_castAddrToKey(msg.sender);
uint256 _sellerBalance =
sellerBalance[key][token];
uint256 key = _castAddrToKey(msg.sender);
uint256 _sellerBalance = sellerBalance[key][token];
if (_sellerBalance != 0) {
uint256 _valid;
assembly { _valid := state }
assembly {
_valid := state
}
_sellerBalance =
(_sellerBalance & BITMASK_VALID) |
(_valid << BITPOS_VALID);
sellerBalance[key][token] =
_sellerBalance;
emit ValidSet(
msg.sender,
address(token),
state
);
} else
revert NotInitialized();
sellerBalance[key][token] = _sellerBalance;
emit ValidSet(msg.sender, address(token), state);
} else revert NotInitialized();
}
/// @notice Public method designed to lock an remaining amount of
@@ -219,33 +202,26 @@ contract P2PIX is
unlockExpired(expiredLocks);
ERC20 t = ERC20(_token);
if (!getValid(_seller, t))
revert InvalidDeposit();
if (!getValid(_seller, t)) revert InvalidDeposit();
uint256 bal =
getBalance(_seller, t);
if (bal < _amount)
revert NotEnoughTokens();
uint256 bal = getBalance(_seller, t);
if (bal < _amount) revert NotEnoughTokens();
uint256 k =
_castAddrToKey(_seller);
uint256 k = _castAddrToKey(_seller);
uint256 cCounter =
lockCounter + 1;
uint256 cCounter = lockCounter + 1;
if (mapLocks[cCounter].expirationBlock
>= block.number)
revert NotExpired();
if (
mapLocks[cCounter].expirationBlock >= block.number
) revert NotExpired();
DT.Lock memory l = DT.Lock(
k,
cCounter,
_relayerPremium,
_amount,
(block.number +
defaultLockBlocks),
uint160(sellerBalance[k][t]
>> BITPOS_PIXTARGET),
(block.number + defaultLockBlocks),
uint160(sellerBalance[k][t] >> BITPOS_PIXTARGET),
_buyerAddress,
_relayerTarget,
msg.sender,
@@ -259,58 +235,34 @@ contract P2PIX is
msg.sender
);
_addLock(
bal,
_amount,
cCounter,
l,
t,
k
);
_addLock(bal, _amount, cCounter, l, t, k);
lockCounter++;
// Halt execution and output `lockID`.
return cCounter;
} else {
if (l.amount <= 1e2 ether) {
_addLock(
bal,
_amount,
cCounter,
l,
t,
k
);
_addLock(bal, _amount, cCounter, l, t, k);
lockCounter++;
// Halt execution and output `lockID`.
return cCounter;
} else {
uint256 userCredit = userRecord[
_castAddrToKey(msg.sender)
];
uint256 spendLimit;
(spendLimit) =
_limiter(userCredit / WAD);
(spendLimit) = _limiter(userCredit / WAD);
if (
l.amount > (spendLimit * WAD) ||
l.amount > 1e6 ether
) revert AmountNotAllowed();
_addLock(
bal,
_amount,
cCounter,
l,
t,
k
);
_addLock(bal, _amount, cCounter, l, t, k);
lockCounter++;
@@ -374,25 +326,22 @@ contract P2PIX is
ERC20 t = ERC20(l.token);
// We cache values before zeroing them out.
uint256 lockAmount =
l.amount;
uint256 totalAmount =
(lockAmount - l.relayerPremium);
uint256 lockAmount = l.amount;
uint256 totalAmount = (lockAmount - l.relayerPremium);
l.amount = 0;
l.expirationBlock = 0;
usedTransactions[message] = true;
if (msg.sender != l.relayerAddress) {
userRecord[
_castAddrToKey(msg.sender)
] += l.relayerPremium;
userRecord[_castAddrToKey(msg.sender)] += l
.relayerPremium;
userRecord[
_castAddrToKey(l.relayerAddress)
] += lockAmount;
} else {
userRecord[_castAddrToKey(msg.sender)]
+= (l.relayerPremium + lockAmount);
userRecord[_castAddrToKey(msg.sender)] += (l
.relayerPremium + lockAmount);
}
SafeTransferLib.safeTransfer(
@@ -423,11 +372,7 @@ contract P2PIX is
}
}
emit LockReleased(
l.buyerAddress,
lockID,
lockAmount
);
emit LockReleased(l.buyerAddress, lockID, lockAmount);
}
/// @notice Unlocks expired locks.
@@ -443,42 +388,34 @@ contract P2PIX is
uint256 locksSize = lockIDs.length;
for (i; i < locksSize; ) {
DT.Lock storage l =
mapLocks[lockIDs[i]];
DT.Lock storage l = mapLocks[lockIDs[i]];
_notExpired(l);
uint256 _sellerBalance =
sellerBalance[
l.sellerKey][ERC20(l.token)
] & BITMASK_SB_ENTRY;
if (
(_sellerBalance + l.amount)
> 1e8 ether
)
uint256 _sellerBalance = sellerBalance[
l.sellerKey
][ERC20(l.token)] & BITMASK_SB_ENTRY;
if ((_sellerBalance + l.amount) > 1e8 ether)
revert MaxBalExceeded();
sellerBalance[
l.sellerKey][ERC20(l.token)
] += l.amount;
sellerBalance[l.sellerKey][ERC20(l.token)] += l
.amount;
l.amount = 0;
uint256 userKey =
_castAddrToKey(l.relayerAddress);
uint256 _newUserRecord =
(userRecord[userKey] >> 1);
uint256 userKey = _castAddrToKey(
l.relayerAddress
);
uint256 _newUserRecord = (userRecord[userKey] >>
1);
if (_newUserRecord <= 1e2 ether) {
userRecord[userKey] = 1e2 ether;
} else {
userRecord[userKey] = _newUserRecord;
}
emit LockReturned(
l.buyerAddress,
lockIDs[i]
);
emit LockReturned(l.buyerAddress, lockIDs[i]);
unchecked {
++i;
@@ -502,23 +439,16 @@ contract P2PIX is
ERC20 token,
uint256 amount,
uint256[] calldata expiredLocks
)
public
nonReentrant
{
) public nonReentrant {
unlockExpired(expiredLocks);
if (getValid(msg.sender, token)
== true
) {
if (getValid(msg.sender, token) == true) {
setValidState(token, false);
}
uint256 key =
_castAddrToKey(msg.sender);
uint256 key = _castAddrToKey(msg.sender);
_decBal(
(sellerBalance[key][token]
& BITMASK_SB_ENTRY),
(sellerBalance[key][token] & BITMASK_SB_ENTRY),
amount,
token,
key
@@ -784,17 +714,23 @@ contract P2PIX is
// sellerBalance[_castAddrToKey(seller)][token] &
// BITMASK_SB_ENTRY;
assembly {
for {/* */} iszero(0x0) {/* */} {
mstore(0x00, shl(0xC,seller))
mstore(0x20, sellerBalance.slot)
let sbkslot := keccak256(0x00, 0x40)
mstore(0x00, token)
mstore(0x20, sbkslot)
bal := and(
BITMASK_SB_ENTRY,
sload(keccak256(0x00,0x40)
)) break
}}
for {
/* */
} iszero(0x0) {
/* */
} {
mstore(0x00, shl(0xC, seller))
mstore(0x20, sellerBalance.slot)
let sbkslot := keccak256(0x00, 0x40)
mstore(0x00, token)
mstore(0x20, sbkslot)
bal := and(
BITMASK_SB_ENTRY,
sload(keccak256(0x00, 0x40))
)
break
}
}
}
function getValid(address seller, ERC20 token)
@@ -807,19 +743,26 @@ contract P2PIX is
// ][token];
// ] >> BITPOS_VALID) & BITMASK_SB_ENTRY;
assembly {
for {/* */} iszero(0x0) {/* */} {
mstore(0x00, shl(0xC,seller))
mstore(0x20, sellerBalance.slot)
let sbkslot := keccak256(0x00, 0x40)
mstore(0x00, token)
mstore(0x20, sbkslot)
valid := and(
BITMASK_SB_ENTRY,
shr(
BITPOS_VALID,
sload(keccak256(0x00,0x40)
))) break
}}
for {
/* */
} iszero(0x0) {
/* */
} {
mstore(0x00, shl(0xC, seller))
mstore(0x20, sellerBalance.slot)
let sbkslot := keccak256(0x00, 0x40)
mstore(0x00, token)
mstore(0x20, sbkslot)
valid := and(
BITMASK_SB_ENTRY,
shr(
BITPOS_VALID,
sload(keccak256(0x00, 0x40))
)
)
break
}
}
}
function getPixTarget(address seller, ERC20 token)
@@ -832,40 +775,38 @@ contract P2PIX is
// BITPOS_PIXTARGET
// );
assembly {
for {/* */} iszero(0) {/* */} {
mstore(0,shl(12,seller))
mstore(32,sellerBalance.slot)
let sbkslot := keccak256(0,64)
mstore(0,token)
mstore(32,sbkslot)
pixTarget := shr(
BITPOS_PIXTARGET,
sload(keccak256(0,64)
)) break
}}
for {
/* */
} iszero(0) {
/* */
} {
mstore(0, shl(12, seller))
mstore(32, sellerBalance.slot)
let sbkslot := keccak256(0, 64)
mstore(0, token)
mstore(32, sbkslot)
pixTarget := shr(
BITPOS_PIXTARGET,
sload(keccak256(0, 64))
)
break
}
}
}
function getBalances(
address[] memory sellers,
address[] memory sellers,
ERC20 token
)
external
view
returns(uint256[] memory)
{
) external view returns (uint256[] memory) {
uint256 j;
uint256 len =
sellers.length;
uint256[] memory balances =
new uint256[](len);
uint256 len = sellers.length;
uint256[] memory balances = new uint256[](len);
while (j < len) {
uint256 bal =
getBalance(
sellers[j],
token
);
uint256 bal = getBalance(sellers[j], token);
balances[j] = bal;
unchecked { ++j; }
unchecked {
++j;
}
}
return balances;
@@ -875,42 +816,51 @@ contract P2PIX is
/// @dev Call will not revert if provided with an empty array as parameter.
/// @dev Function sighash: 0x49ef8448
function getLocksStatus(uint256[] memory ids)
external
view
returns(
uint256[] memory,
bool[] memory
) {
if (ids.length == 0) {
uint256[] memory null1 =
new uint256[](0);
bool[] memory null2 =
new bool[](0);
return(null1, null2); }
uint256 c;
uint256 len =
ids.length;
bool[] memory status =
new bool[](len);
uint256[] memory sortedIDs =
new uint256[](len);
for(c; c < len;) {
if(
mapLocks[ids[c]].expirationBlock
< block.number ||
mapLocks[ids[c]].amount == 0x0) {
sortedIDs[c] = ids[c];
status[c] = false; ++c;
} else {
sortedIDs[c] = ids[c];
status[c] = true; ++c;
}
}
return(sortedIDs, status);
external
view
returns (uint256[] memory, DT.LockStatus[] memory)
{
if (ids.length == 0) {
uint256[] memory null1 = new uint256[](0);
DT.LockStatus[]
memory null2 = new DT.LockStatus[](0);
return (null1, null2);
}
uint256 c;
uint256 len = ids.length;
uint256[] memory sortedIDs = new uint256[](len);
DT.LockStatus[] memory status = new DT.LockStatus[](
len
);
unchecked {
for (c; c < len; ) {
if (mapLocks[ids[c]].sellerKey == 0x0) {
sortedIDs[c] = ids[c];
status[c] = type(DT.LockStatus).min;
++c;
} else if (mapLocks[ids[c]].amount == 0x0) {
sortedIDs[c] = ids[c];
status[c] = type(DT.LockStatus).max;
++c;
} else if (
mapLocks[ids[c]].expirationBlock <
block.number
) {
sortedIDs[c] = ids[c];
status[c] = DT.LockStatus.Expired;
++c;
} else {
sortedIDs[c] = ids[c];
status[c] = DT.LockStatus.Active;
++c;
}
}
}
return (sortedIDs, status);
}
/// @notice Public method that handles `address`
/// to `uint256` safe type casting.
/// @dev Function sighash: 0x4b2ae980.
@@ -921,7 +871,7 @@ contract P2PIX is
{
// _key = uint256(uint160(address(_addr))) << 12;
assembly {
_key := shl(12,_addr)
_key := shl(12, _addr)
}
}
@@ -932,7 +882,7 @@ contract P2PIX is
{
// _addr = address(uint160(uint256(_key >> 12)));
assembly {
_addr := shr(12,_key)
_addr := shr(12, _key)
}
}
}