Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions src/IntuitionFeeProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,8 @@ contract IntuitionFeeProxy {

// ============ Proxy Functions (Payable) ============

/// @notice Create atoms with fee collection and deposit to receiver
/// @dev Receiver must have approved this proxy on MultiVault for DEPOSIT
/// @param receiver Address to receive the shares (the real user)
/// @notice Create atoms with fee collection and deposit to the caller
/// @param receiver Address to receive the shares, which must match msg.sender
/// @param data Array of atom data (IPFS URIs as bytes)
/// @param assets Array of deposit amounts for each atom (on top of creation cost)
/// @param curveId Bonding curve ID for deposits (1 = linear, 2 = progressive)
Expand All @@ -211,6 +210,7 @@ contract IntuitionFeeProxy {
if (data.length != assets.length) {
revert Errors.IntuitionFeeProxy_WrongArrayLengths();
}
_validateReceiver(receiver);

uint256 count = data.length;
uint256 atomCost = ethMultiVault.getAtomCost();
Expand Down Expand Up @@ -254,9 +254,8 @@ contract IntuitionFeeProxy {
return atomIds;
}

/// @notice Create triples with fee collection and deposit to receiver
/// @dev Receiver must have approved this proxy on MultiVault for DEPOSIT
/// @param receiver Address to receive the shares (the real user)
/// @notice Create triples with fee collection and deposit to the caller
/// @param receiver Address to receive the shares, which must match msg.sender
/// @param subjectIds Array of subject atom IDs
/// @param predicateIds Array of predicate atom IDs
/// @param objectIds Array of object atom IDs
Expand All @@ -276,6 +275,7 @@ contract IntuitionFeeProxy {
objectIds.length != assets.length) {
revert Errors.IntuitionFeeProxy_WrongArrayLengths();
}
_validateReceiver(receiver);

uint256 count = subjectIds.length;
uint256 tripleCost = ethMultiVault.getTripleCost();
Expand Down Expand Up @@ -326,7 +326,7 @@ contract IntuitionFeeProxy {

/// @notice Deposit with fee collection - SAME SIGNATURE AS MULTIVAULT
/// @dev Fee is calculated from msg.value using inverse formula
/// @param receiver Address to receive shares
/// @param receiver Address to receive shares, which must match msg.sender
/// @param termId Vault ID (atom or triple)
/// @param curveId Bonding curve ID
/// @param minShares Minimum shares expected
Expand All @@ -337,6 +337,8 @@ contract IntuitionFeeProxy {
uint256 curveId,
uint256 minShares
) external payable returns (uint256 shares) {
_validateReceiver(receiver);

// Must send more than just the fixed fee
if (msg.value <= depositFixedFee) {
revert Errors.IntuitionFeeProxy_InsufficientValue();
Expand Down Expand Up @@ -371,7 +373,7 @@ contract IntuitionFeeProxy {
}

/// @notice Batch deposit with fee collection
/// @param receiver Address to receive shares
/// @param receiver Address to receive shares, which must match msg.sender
/// @param termIds Array of vault IDs
/// @param curveIds Array of curve IDs
/// @param assets Array of deposit amounts
Expand All @@ -389,6 +391,7 @@ contract IntuitionFeeProxy {
assets.length != minShares.length) {
revert Errors.IntuitionFeeProxy_WrongArrayLengths();
}
_validateReceiver(receiver);

uint256 totalDeposit = _sumArray(assets);
// Fee: fixed fee per deposit + percentage of total
Expand Down Expand Up @@ -472,6 +475,14 @@ contract IntuitionFeeProxy {

// ============ Internal Functions ============

/// @notice Ensure proxy deposits cannot be redirected to another receiver
/// @param receiver Address requested as the MultiVault receiver
function _validateReceiver(address receiver) internal view {
if (receiver != msg.sender) {
revert Errors.IntuitionFeeProxy_ReceiverMismatch();
}
}

/// @notice Transfer collected fees to recipient
/// @param amount Amount to transfer
function _transferFee(uint256 amount) internal {
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ library Errors {
/// @notice Zero address provided where not allowed
error IntuitionFeeProxy_ZeroAddress();

/// @notice Receiver address does not match the transaction sender
error IntuitionFeeProxy_ReceiverMismatch();

/// @notice Fee percentage exceeds maximum allowed (100%)
error IntuitionFeeProxy_FeePercentageTooHigh();
}
65 changes: 65 additions & 0 deletions test/IntuitionFeeProxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,23 @@ describe("IntuitionFeeProxy", function () {
proxy.connect(user).createAtoms(user.address, data, assets, curveId, { value: ethers.parseEther("0.01") })
).to.be.revertedWithCustomError(proxy, "IntuitionFeeProxy_InsufficientValue");
});

it("Should revert when createAtoms receiver is not the caller", async function () {
const { proxy, mockMultiVault, user, nonAdmin } = await loadFixture(deployFixture);

const data = [ethers.toUtf8Bytes("ipfs://atom1")];
const assets = [ethers.parseEther("0.01")];
const curveId = 1n;

const atomCost = await mockMultiVault.getAtomCost();
const totalDeposit = ethers.parseEther("0.01");
const fee = await proxy.calculateDepositFee(1n, totalDeposit);
const totalRequired = atomCost + totalDeposit + fee;

await expect(
proxy.connect(user).createAtoms(nonAdmin.address, data, assets, curveId, { value: totalRequired })
).to.be.revertedWithCustomError(proxy, "IntuitionFeeProxy_ReceiverMismatch");
});
});

describe("Proxy Functions - createTriples", function () {
Expand Down Expand Up @@ -308,6 +325,25 @@ describe("IntuitionFeeProxy", function () {
proxy.connect(user).createTriples(user.address, subjectIds, predicateIds, objectIds, assets, curveId, { value: ethers.parseEther("10") })
).to.be.revertedWithCustomError(proxy, "IntuitionFeeProxy_WrongArrayLengths");
});

it("Should revert when createTriples receiver is not the caller", async function () {
const { proxy, mockMultiVault, user, nonAdmin } = await loadFixture(deployFixture);

const subjectIds = [ethers.zeroPadValue("0x01", 32)];
const predicateIds = [ethers.zeroPadValue("0x02", 32)];
const objectIds = [ethers.zeroPadValue("0x03", 32)];
const assets = [ethers.parseEther("0.01")];
const curveId = 1n;

const tripleCost = await mockMultiVault.getTripleCost();
const totalDeposit = ethers.parseEther("0.01");
const fee = await proxy.calculateDepositFee(1n, totalDeposit);
const totalRequired = tripleCost + totalDeposit + fee;

await expect(
proxy.connect(user).createTriples(nonAdmin.address, subjectIds, predicateIds, objectIds, assets, curveId, { value: totalRequired })
).to.be.revertedWithCustomError(proxy, "IntuitionFeeProxy_ReceiverMismatch");
});
});

describe("Proxy Functions - deposit", function () {
Expand Down Expand Up @@ -359,6 +395,18 @@ describe("IntuitionFeeProxy", function () {
expect(await proxy.getMultiVaultAmountFromValue(DEPOSIT_FEE)).to.equal(0n);
expect(await proxy.getMultiVaultAmountFromValue(ethers.parseEther("0.05"))).to.equal(0n);
});

it("Should revert when deposit receiver is not the caller", async function () {
const { proxy, user, nonAdmin } = await loadFixture(deployFixture);

const desiredDepositAmount = ethers.parseEther("1");
const totalToSend = await proxy.getTotalDepositCost(desiredDepositAmount);
const termId = ethers.zeroPadValue("0x01", 32);

await expect(
proxy.connect(user).deposit(nonAdmin.address, termId, 1n, 0n, { value: totalToSend })
).to.be.revertedWithCustomError(proxy, "IntuitionFeeProxy_ReceiverMismatch");
});
});

describe("Proxy Functions - depositBatch", function () {
Expand Down Expand Up @@ -396,6 +444,23 @@ describe("IntuitionFeeProxy", function () {
proxy.connect(user).depositBatch(user.address, termIds, curveIds, assets, minShares, { value: ethers.parseEther("20") })
).to.be.revertedWithCustomError(proxy, "IntuitionFeeProxy_WrongArrayLengths");
});

it("Should revert when depositBatch receiver is not the caller", async function () {
const { proxy, user, nonAdmin } = await loadFixture(deployFixture);

const termIds = [ethers.zeroPadValue("0x01", 32), ethers.zeroPadValue("0x02", 32)];
const curveIds = [1n, 1n];
const assets = [ethers.parseEther("1"), ethers.parseEther("1")];
const minShares = [0n, 0n];

const totalDeposit = ethers.parseEther("2");
const fee = await proxy.calculateDepositFee(2n, totalDeposit);
const totalRequired = totalDeposit + fee;

await expect(
proxy.connect(user).depositBatch(nonAdmin.address, termIds, curveIds, assets, minShares, { value: totalRequired })
).to.be.revertedWithCustomError(proxy, "IntuitionFeeProxy_ReceiverMismatch");
});
});

describe("View Functions (Passthrough)", function () {
Expand Down