Skip to content

Commit

Permalink
test: darkpool: SettleAtomicMatch: Add native asset tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Mar 9, 2025
1 parent 8851926 commit 264b78e
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/Darkpool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ contract Darkpool {
constructor(
uint256 protocolFeeRate_,
address protocolFeeRecipient_,
IWETH9 weth_,
IHasher hasher_,
IVerifier verifier_,
IPermit2 permit2_
Expand All @@ -90,6 +91,7 @@ contract Darkpool {
hasher = hasher_;
verifier = verifier_;
permit2 = permit2_;
weth = weth_;
merkleTree.initialize();
}

Expand Down
8 changes: 4 additions & 4 deletions src/libraries/darkpool/ExternalTransfers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import { IWETH9 } from "renegade/libraries/interfaces/IWETH9.sol";
import { ISignatureTransfer } from "permit2/interfaces/ISignatureTransfer.sol";
import { IERC20 } from "forge-std/interfaces/IERC20.sol";

// @title TransferExecutor
// @notice This library implements the logic for executing external transfers
// @notice External transfers are either deposits or withdrawals into/from the darkpool
/// @title TransferExecutor
/// @notice This library implements the logic for executing external transfers
/// @notice External transfers are either deposits or withdrawals into/from the darkpool
library TransferExecutor {
using TypesLib for DepositWitness;

Expand Down Expand Up @@ -133,7 +133,7 @@ library TransferExecutor {
function executeSimpleDeposit(SimpleTransfer memory transfer, IWETH9 wrapper) internal {
// Handle native token deposits by wrapping the transaction value
if (DarkpoolConstants.isNativeToken(transfer.mint)) {
require(msg.value == transfer.amount, "Invalid ETH deposit amount");
require(msg.value == transfer.amount, "msg.value does not match deposit amount");
wrapper.deposit{ value: transfer.amount }();
return;
}
Expand Down
9 changes: 8 additions & 1 deletion test/darkpool/DarkpoolTestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.0;

import { BN254 } from "solidity-bn254/BN254.sol";
import { ERC20Mock } from "oz-contracts/mocks/token/ERC20Mock.sol";
import { WethMock } from "../test-contracts/WethMock.sol";
import { IPermit2 } from "permit2/interfaces/IPermit2.sol";
import { DeployPermit2 } from "permit2-test/utils/DeployPermit2.sol";
import { Test } from "forge-std/Test.sol";
Expand Down Expand Up @@ -30,6 +31,7 @@ contract DarkpoolTestBase is CalldataUtils {
IPermit2 public permit2;
ERC20Mock public quoteToken;
ERC20Mock public baseToken;
WethMock public weth;

address public protocolFeeAddr;

Expand All @@ -38,6 +40,7 @@ contract DarkpoolTestBase is CalldataUtils {
bytes constant INVALID_SIGNATURE_REVERT_STRING = "Invalid signature";
bytes constant INVALID_PROTOCOL_FEE_REVERT_STRING = "Invalid protocol fee rate";
bytes constant INVALID_ETH_VALUE_REVERT_STRING = "Invalid ETH value, should be zero unless selling native token";
bytes constant INVALID_ETH_DEPOSIT_AMOUNT_REVERT_STRING = "msg.value does not match deposit amount";

function setUp() public {
// Deploy a Permit2 instance for testing
Expand All @@ -47,12 +50,16 @@ contract DarkpoolTestBase is CalldataUtils {
// Deploy mock tokens for testing
quoteToken = new ERC20Mock();
baseToken = new ERC20Mock();
weth = new WethMock();

// Capitalize the weth contract
vm.deal(address(weth), 100 ether);

// Deploy the darkpool implementation contracts
hasher = IHasher(HuffDeployer.deploy("libraries/poseidon2/poseidonHasher"));
IVerifier verifier = new TestVerifier();
protocolFeeAddr = vm.randomAddress();
darkpool = new Darkpool(TEST_PROTOCOL_FEE, protocolFeeAddr, hasher, verifier, permit2);
darkpool = new Darkpool(TEST_PROTOCOL_FEE, protocolFeeAddr, weth, hasher, verifier, permit2);
}

// ---------------------------
Expand Down
136 changes: 135 additions & 1 deletion test/darkpool/SettleAtomicMatch.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import {
ExternalMatchResult,
FeeTake
} from "renegade/libraries/darkpool/Types.sol";
import { DarkpoolConstants } from "renegade/libraries/darkpool/Constants.sol";
import {
ValidMatchSettleAtomicStatement, ValidWalletUpdateStatement
} from "renegade/libraries/darkpool/PublicInputs.sol";
import { PlonkProof } from "renegade/libraries/verifier/Types.sol";
import { console2 } from "forge-std/console2.sol";

contract SettleAtomicMatchTest is DarkpoolTestBase {
using TypesLib for FeeTake;
Expand Down Expand Up @@ -285,6 +285,111 @@ contract SettleAtomicMatchTest is DarkpoolTestBase {
assertEq(darkpoolFinalBaseBalance, darkpoolInitialBaseBalance + BASE_AMT);
}

/// @notice Test settling an atomic match with a native token, buy side
function test_settleAtomicMatch_nativeToken_buySide() public {
Vm.Wallet memory externalParty = randomEthereumWallet();

// Setup tokens
quoteToken.mint(externalParty.addr, QUOTE_AMT);
weth.mint(address(darkpool), BASE_AMT);

uint256 userInitialQuoteBalance = quoteToken.balanceOf(externalParty.addr);
uint256 userInitialNativeBalance = externalParty.addr.balance;
uint256 darkpoolInitialQuoteBalance = quoteToken.balanceOf(address(darkpool));
uint256 darkpoolInitialWethBalance = weth.balanceOf(address(darkpool));

// Setup the match
ExternalMatchResult memory matchResult = ExternalMatchResult({
quoteMint: address(quoteToken),
baseMint: DarkpoolConstants.NATIVE_TOKEN_ADDRESS,
quoteAmount: QUOTE_AMT,
baseAmount: BASE_AMT,
direction: ExternalMatchDirection.InternalPartySell
});

// Setup calldata
BN254.ScalarField merkleRoot = darkpool.getMerkleRoot();
(
PartyMatchPayload memory internalPartyPayload,
ValidMatchSettleAtomicStatement memory statement,
MatchAtomicProofs memory proofs,
MatchAtomicLinkingProofs memory linkingProofs
) = settleAtomicMatchCalldataWithMatchResult(merkleRoot, matchResult);

// Process the match
vm.startBroadcast(externalParty.addr);
quoteToken.approve(address(darkpool), QUOTE_AMT);
darkpool.processAtomicMatchSettle(internalPartyPayload, statement, proofs, linkingProofs);
vm.stopBroadcast();

// Check the token flows
FeeTake memory fees = statement.externalPartyFees;
uint256 totalFee = fees.total();
uint256 expectedBaseAmt = BASE_AMT - totalFee;

uint256 userFinalQuoteBalance = quoteToken.balanceOf(externalParty.addr);
uint256 userFinalNativeBalance = externalParty.addr.balance;
uint256 darkpoolFinalQuoteBalance = quoteToken.balanceOf(address(darkpool));
uint256 darkpoolFinalWethBalance = weth.balanceOf(address(darkpool));

assertEq(userFinalQuoteBalance, userInitialQuoteBalance - QUOTE_AMT);
assertEq(userFinalNativeBalance, userInitialNativeBalance + expectedBaseAmt);
assertEq(darkpoolFinalQuoteBalance, darkpoolInitialQuoteBalance + QUOTE_AMT);
assertEq(darkpoolFinalWethBalance, darkpoolInitialWethBalance - BASE_AMT);
}

/// @notice Test settling an atomic match with a native token, sell side
function test_settleAtomicMatch_nativeToken_sellSide() public {
Vm.Wallet memory externalParty = randomEthereumWallet();

// Setup tokens
vm.deal(externalParty.addr, BASE_AMT);
quoteToken.mint(address(darkpool), QUOTE_AMT);

uint256 userInitialQuoteBalance = quoteToken.balanceOf(externalParty.addr);
uint256 userInitialNativeBalance = externalParty.addr.balance;
uint256 darkpoolInitialQuoteBalance = quoteToken.balanceOf(address(darkpool));
uint256 darkpoolInitialWethBalance = weth.balanceOf(address(darkpool));

// Setup the match
ExternalMatchResult memory matchResult = ExternalMatchResult({
quoteMint: address(quoteToken),
baseMint: DarkpoolConstants.NATIVE_TOKEN_ADDRESS,
quoteAmount: QUOTE_AMT,
baseAmount: BASE_AMT,
direction: ExternalMatchDirection.InternalPartyBuy
});

// Setup calldata
BN254.ScalarField merkleRoot = darkpool.getMerkleRoot();
(
PartyMatchPayload memory internalPartyPayload,
ValidMatchSettleAtomicStatement memory statement,
MatchAtomicProofs memory proofs,
MatchAtomicLinkingProofs memory linkingProofs
) = settleAtomicMatchCalldataWithMatchResult(merkleRoot, matchResult);

// Process the match
vm.startBroadcast(externalParty.addr);
darkpool.processAtomicMatchSettle{ value: BASE_AMT }(internalPartyPayload, statement, proofs, linkingProofs);
vm.stopBroadcast();

// Check the token flows
FeeTake memory fees = statement.externalPartyFees;
uint256 totalFee = fees.total();
uint256 expectedQuoteAmt = QUOTE_AMT - totalFee;

uint256 userFinalQuoteBalance = quoteToken.balanceOf(externalParty.addr);
uint256 userFinalNativeBalance = externalParty.addr.balance;
uint256 darkpoolFinalQuoteBalance = quoteToken.balanceOf(address(darkpool));
uint256 darkpoolFinalWethBalance = weth.balanceOf(address(darkpool));

assertEq(userFinalQuoteBalance, userInitialQuoteBalance + expectedQuoteAmt);
assertEq(userFinalNativeBalance, userInitialNativeBalance - BASE_AMT);
assertEq(darkpoolFinalQuoteBalance, darkpoolInitialQuoteBalance - QUOTE_AMT);
assertEq(darkpoolFinalWethBalance, darkpoolInitialWethBalance + BASE_AMT);
}

// --- Invalid Match Tests --- //

/// @notice Test settling an atomic match wherein the fees exceed the receive amount
Expand Down Expand Up @@ -409,4 +514,33 @@ contract SettleAtomicMatchTest is DarkpoolTestBase {
vm.expectRevert(INVALID_PROTOCOL_FEE_REVERT_STRING);
darkpool.processAtomicMatchSettle(internalPartyPayload, statement, proofs, linkingProofs);
}

/// @notice Test settling an atomic match with insufficient tx value
function test_settleAtomicMatch_insufficientTxValue() public {
Vm.Wallet memory externalParty = randomEthereumWallet();
vm.deal(externalParty.addr, 100);

ExternalMatchResult memory matchResult = ExternalMatchResult({
quoteMint: address(quoteToken),
baseMint: DarkpoolConstants.NATIVE_TOKEN_ADDRESS,
quoteAmount: 100,
baseAmount: 100,
direction: ExternalMatchDirection.InternalPartyBuy
});

// Setup calldata
BN254.ScalarField merkleRoot = darkpool.getMerkleRoot();
(
PartyMatchPayload memory internalPartyPayload,
ValidMatchSettleAtomicStatement memory statement,
MatchAtomicProofs memory proofs,
MatchAtomicLinkingProofs memory linkingProofs
) = settleAtomicMatchCalldataWithMatchResult(merkleRoot, matchResult);

// Should fail
vm.startBroadcast(externalParty.addr);
vm.expectRevert(INVALID_ETH_DEPOSIT_AMOUNT_REVERT_STRING);
darkpool.processAtomicMatchSettle{ value: 1 wei }(internalPartyPayload, statement, proofs, linkingProofs);
vm.stopBroadcast();
}
}
20 changes: 20 additions & 0 deletions test/test-contracts/WethMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { IWETH9 } from "renegade/libraries/interfaces/IWETH9.sol";
import { ERC20Mock } from "oz-contracts/mocks/token/ERC20Mock.sol";

/// @title WethMock
/// @notice A mock implementation of the IWETH9 interface
contract WethMock is IWETH9, ERC20Mock {
/// @notice Deposit ETH into the contract
function deposit() external payable {
_mint(msg.sender, msg.value);
}

/// @notice Withdraw ETH from the contract
function withdrawTo(address to, uint256 amount) external {
_burn(msg.sender, amount);
payable(to).transfer(amount);
}
}

0 comments on commit 264b78e

Please sign in to comment.