<!-- canonical: https://0xsimao.com/findings/crestal-network-approving-blueprint-spend-erc20 -->

# Anyone who is approving `BlueprintV5` contract to spend ERC20 can get drained because `Payment::payWithERC20`

Crit/High · Sherlock · AI agent · 11th March 2025

Finding H-1 of the Crestal Network competition.

- Protocol: https://audits.sherlock.xyz/contests/755
- Codebase: https://github.com/0xsimao/2025-03-crestal-network/tree/27a3c28155702b3a68f29347efedffb048010e33
- Source: https://github.com/sherlock-audit/2025-03-crestal-network-judging/issues/260

---

### Summary

`payWithERC20` is supposed to be used inside `BlueprintV5` contract to handle payment. But this function also can be used to drain anyone who is interact with `BlueprintV5` and using it to approve payment token when creating an agent.

### Root Cause

[Payment.sol#L25-L32](https://github.com/sherlock-audit/2025-03-crestal-network/blob/main/crestal-omni-contracts/src/Payment.sol#L25-L32)

```Solidity
@>    function payWithERC20(address erc20TokenAddress, uint256 amount, address fromAddress, address toAddress) public {
        // check from and to address
        require(fromAddress != toAddress, "Cannot transfer to self address");
        require(toAddress != address(0), "Invalid to address");
        require(amount > 0, "Amount must be greater than 0");
        IERC20 token = IERC20(erc20TokenAddress);
        token.safeTransferFrom(fromAddress, toAddress, amount);
    }
```

the root cause simply because this function is public function, meaning anyone can call this and supply valid token address, then fill `fromAddress` with any address that still have allowance/approving the payment token to be spend by `BlueprintV5` contract

### Internal Pre-conditions

1. admin enable usdc or any erc20 token as payment by calling `Blueprint::addPaymentAddress`

### External Pre-conditions

1. victim approve the spending of usdc or any erc20 token set in last step for `BlueprintV5` contract address proxy
2. the amount approved should be greater than the amount used for creating agent with token cost
3. victim call the function to create agent (optional)

### Attack Path

1. attacker call `payWithERC20` supplying the parameter with usdc address, victim address and sufficient amount to be sent into attacker address

### Impact

user/victim who interacted would lose their funds drained by attacker

### PoC

_No response_

### Mitigation

make the `Payment::payWithERC20` internal
