Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/dvp/V1/DeliveryVersusPaymentV1.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
/**
* ██████╗░██╗░░░██╗██████╗░███████╗░█████╗░░██████╗██╗░░░██╗░░░██╗░░██╗██╗░░░██╗███████╗
* ██╔══██╗██║░░░██║██╔══██╗██╔════╝██╔══██╗██╔════╝╚██╗░██╔╝░░░╚██╗██╔╝╚██╗░██╔╝╚════██║
* ██║░░██║╚██╗░██╔╝██████╔╝█████╗░░███████║╚█████╗░░╚████╔╝░░░░░╚███╔╝░░╚████╔╝░░░███╔═╝
* ██║░░██║░╚████╔╝░██╔═══╝░██╔══╝░░██╔══██║░╚═══██╗░░╚██╔╝░░░░░░██╔██╗░░░╚██╔╝░░██╔══╝░░
* ██████╔╝░░╚██╔╝░░██║░░░░░███████╗██║░░██║██████╔╝░░░██║░░░██╗██╔╝╚██╗░░░██║░░░███████╗
* ╚═════╝░░░░╚═╝░░░╚═╝░░░░░╚══════╝╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░╚══════╝
*/
import {Address} from "@openzeppelin/contracts-v5-2-0/utils/Address.sol";
import {ERC165Checker} from "@openzeppelin/contracts-v5-2-0/utils/introspection/ERC165Checker.sol";
import {IDeliveryVersusPaymentV1} from "./IDeliveryVersusPaymentV1.sol";
import {IERC20} from "@openzeppelin/contracts-v5-2-0/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts-v5-2-0/token/ERC721/IERC721.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts-v5-2-0/utils/ReentrancyGuardTransient.sol";
import {SafeERC20} from "@openzeppelin/contracts-v5-2-0/token/ERC20/utils/SafeERC20.sol";
/**
* @title DeliveryVersusPaymentV1
* @dev Delivery Versus Payment implementation for ERC-20, ERC-721 and Ether transfers.
* Created by https://pv0.one. UI implemented at https://dvpeasy.xyz.
*
* Workflow Summary:
*
* 1) Create a Settlement
* A settlement is collection of intended value transfers (Flows) between parties, along with a free text
* reference, a deadline (cutoff date) and an auto-settlement flag indicating if settlement should be immediately
* processed after final approval received. ERC-20, ERC-721 and Ether transfers are supported.
* For example a settlement could include the following 3 flows, be set to expire in 1 week, and be auto-settled
* when all "from" parties (sender addresses) have approved:
* ------------------------------------------------
* From -> To AmountOrId Token isNFT
* ------------------------------------------------
* Alice -> Bob 1 ETH false
* Bob -> Charlie 400 TokenA false
* Charlie -> Alice 500(id) TokenB true
* If a token claims to be an NFT and is not, the creation will revert.
* If a token claims to be an ERC20, but doesn't implement decimals(), the creation will revert.
*
* 2) Approve a Settlement
* Each party who is a "from" address in one or more flows needs to approve the settlement before it can proceed.
* They do this by calling approveSettlements() and including their necessary total ETH deposit if their flows involve
* sending ETH. ERC-20 and ERC-721 tokens are not deposited upfront, they only need transfer approval before execution.
* If a settlement is marked as isAutoSettled:
* - the settlement will be executed automatically after all approvals are in place, the gas cost being borne
* by the last approver.
* - if settlement approval succeeds, but auto-execution fails, the entire transaction is not reverted. The approval
* remains on-chain, only the settlement execution is reverted.
*
* 3) Execute a Settlement
* Anyone can call executeSettlement() before the cutoff date, if all approvals are in place. At execution time the
* contract makes the transfers in an atomic, all or nothing, manner. If any Flow transfer fails the entire settlement
* is reverted.
*
* 4) Changes
* If a party changes their mind before the settlement is fully executed — and before the cutoff date — they can revoke
* their approval by calling revokeApprovals(). This returns any deposited ETH back to them and removes their approval.
* Once expired a settlement can no longer be executed, any ETH deposited can be withdrawn by each party using withdrawETH().
*
* NB There are many unbounded loops in this contract, by design. There is no limit on the number of flows in a settlement,
* nor on how many settlements can be batch processed (for functions that receive an array of settlementIds). The current
* chain's block gas limit acts as a cap. In every case it is the caller's responsibility to ensure that the gas requirement
* can be met.
*/
contract DeliveryVersusPaymentV1 is IDeliveryVersusPaymentV1, ReentrancyGuardTransient {
using SafeERC20 for IERC20;
using ERC165Checker for address;
event ETHReceived(address indexed party, uint256 amount);
event ETHWithdrawn(address indexed party, uint256 amount);
event SettlementApproved(uint256 indexed settlementId, address indexed party);
event SettlementCreated(uint256 indexed settlementId, address indexed creator);
event SettlementExecuted(uint256 indexed settlementId, address indexed executor);
event SettlementExecutionFailedReason(
uint256 indexed settlementId, address indexed executor, bool autoExecuted, string reason
);
event SettlementExecutionFailedPanic(
uint256 indexed settlementId, address indexed executor, bool autoExecuted, uint256 errorCode
);
event SettlementExecutionFailedOther(
uint256 indexed settlementId, address indexed executor, bool autoExecuted, bytes lowLevelData
);
event SettlementApprovalRevoked(uint256 indexed settlementId, address indexed party);
// Custom Errors
error ApprovalAlreadyGranted();
error ApprovalNotGranted();
error CallerNotInvolved();
error CallerMustBeDvpContract();
error CannotSendEtherDirectly();
error CutoffDateNotPassed();
error CutoffDatePassed();
error IncorrectETHAmount();
error InvalidAmountOrId();
error InvalidERC20Token();
error InvalidERC721Token();
error InvalidFromAddress();
error InvalidToAddress();
error NoETHToWithdraw();
error NoFlowsProvided();
error SettlementAlreadyExecuted();
error SettlementDoesNotExist();
error SettlementNotApproved();
/**
* @dev TokenStatus contains an assessment of how ready a party is to settle, for a particular token.
*/
struct TokenStatus {
address tokenAddress;
bool isNFT;
/// @dev Amount or ID required for the settlement
uint256 amountOrIdRequired;
/// @dev Amount or ID already approved for DVP by the party
uint256 amountOrIdApprovedForDvp;
/// @dev Total amount or ID held by the party
uint256 amountOrIdHeldByParty;
}
/**
* @dev A Settlement is a collection of Flows together with a free text reference, a cutoff date,
* an auto-settlement flag, and mappings for approvals and a record of ETH deposits.
* A settlement is uniquely identified by its settlementId.
*/
struct Settlement {
string settlementReference;
Flow[] flows;
mapping(address => bool) approvals;
mapping(address => uint256) ethDeposits;
uint128 cutoffDate;
bool isSettled;
bool isAutoSettled;
}
mapping(uint256 => Settlement) private settlements;
/// @dev Last settlement id used
uint256 public settlementIdCounter;
/// @dev selector for decimals() function in ERC-20 tokens
bytes4 private constant SELECTOR_ERC20_DECIMALS = bytes4(keccak256("decimals()"));
//------------------------------------------------------------------------------
// Public
//------------------------------------------------------------------------------
/**
* @dev Checks if all parties have approved the settlement.
* @param settlementId The id of the settlement to check.
*/
function isSettlementApproved(uint256 settlementId) public view returns (bool) {
Settlement storage settlement = settlements[settlementId];
uint256 lengthFlows = settlement.flows.length;
if (lengthFlows == 0) revert SettlementDoesNotExist();
for (uint256 i = 0; i < lengthFlows; i++) {
address party = settlement.flows[i].from;
if (!settlement.approvals[party]) {
return false;
}
}
return true;
}
//------------------------------------------------------------------------------
// External
//------------------------------------------------------------------------------
/**
* @dev Approves multiple settlements and sends exact required ETH deposits. To find out how much ETH to send with
* an approval, call function getSettlementPartyStatus() first. If a settlement is marked as auto-settled,
* and this is the final approval, then the settlement will also be executed.
* NB:
* 1) It is the caller's responsibility to ensure that, if they are final approver in an auto-settled settlement,
* then any gas requirement can be met.
* 2) If approval succeeds, but settlement fails, the entire transaction is NOT reverted. Approvals remain and
* the settlement can be executed later.
* @param settlementIds The ids of the settlements to approve.
*/
function approveSettlements(uint256[] calldata settlementIds) external payable nonReentrant {
uint256 totalEthRequired;
uint256 lengthSettlements = settlementIds.length;
for (uint256 i = 0; i < lengthSettlements; i++) {
uint256 settlementId = settlementIds[i];
Settlement storage settlement = settlements[settlementId];
uint256 lengthFlows = settlement.flows.length;
if (lengthFlows == 0) revert SettlementDoesNotExist();
if (settlement.isSettled) revert SettlementAlreadyExecuted();
if (block.timestamp > settlement.cutoffDate) revert CutoffDatePassed();
if (settlement.approvals[msg.sender]) revert ApprovalAlreadyGranted();
uint256 ethAmountRequired = 0;
bool isInvolved = false;
for (uint256 j = 0; j < lengthFlows; j++) {
Flow storage flow = settlement.flows[j];
if (flow.from == msg.sender) {
isInvolved = true;
if (flow.token == address(0)) {
ethAmountRequired += flow.amountOrId;
}
}
}
if (!isInvolved) revert CallerNotInvolved();
totalEthRequired += ethAmountRequired;
settlement.approvals[msg.sender] = true;
if (ethAmountRequired > 0) {
settlement.ethDeposits[msg.sender] = ethAmountRequired;
}
emit SettlementApproved(settlementId, msg.sender);
}
if (msg.value != totalEthRequired) revert IncorrectETHAmount();
if (msg.value > 0) {
emit ETHReceived(msg.sender, msg.value);
}
// For any settlement: if we're last approver, and auto settlement is enabled, then execute that settlement
for (uint256 i = 0; i < lengthSettlements; i++) {
uint256 settlementId = settlementIds[i];
Settlement storage settlement = settlements[settlementId];
if (settlement.isAutoSettled && isSettlementApproved(settlementId)) {
// Failed auto-execution will not revert the entire transaction, only that settlement's execution will fail.
// Other settlements will still be processed, and the earlier approval will remain. Note that try{} only
// supports external/public calls.
try this.executeSettlementInner(msg.sender, settlementId) {
// Success
}
catch Error(string memory reason) {
// Revert with reason string
emit SettlementExecutionFailedReason(settlementId, msg.sender, true, reason);
} catch Panic(uint256 errorCode) {
// Revert due to serious error (eg division by zero)
emit SettlementExecutionFailedPanic(settlementId, msg.sender, true, errorCode);
} catch (bytes memory lowLevelData) {
// Revert in every other case (eg custom error)
emit SettlementExecutionFailedOther(settlementId, msg.sender, true, lowLevelData);
}
}
}
}
/**
* @dev Creates a new settlement with the specified flows and cutoff date.
* Reverts if cutoff date is in the past, no flows are provided, or a claimed NFT token is not
* and NFT. There is intentionally no limit on the number of flows, the current chain's block
* gas limit acts as a cap for the max flows.
* @param flows The flows to include in the settlement.
* @param settlementReference A free text reference for the settlement.
* @param cutoffDate The deadline for approvals and execution.
* @param isAutoSettled If true, the settlement will be executed automatically after all approvals are in place.
*/
function createSettlement(
Flow[] calldata flows,
string calldata settlementReference,
uint128 cutoffDate,
bool isAutoSettled
) external returns (uint256 id) {
if (block.timestamp > cutoffDate) revert CutoffDatePassed();
uint256 lengthFlows = flows.length;
if (lengthFlows == 0) revert NoFlowsProvided();
// Validate flows
for (uint256 i = 0; i < lengthFlows; i++) {
Flow calldata flow = flows[i];
// Validate addresses
if (flow.from == address(0)) revert InvalidFromAddress();
if (flow.to == address(0)) revert InvalidToAddress();
// Validate tokens
if (flow.isNFT) {
if (!_isERC721(flow.token)) revert InvalidERC721Token();
} else if (flow.token != address(0)) {
if (!_isERC20(flow.token)) revert InvalidERC20Token();
}
// Validate amounts
// For ERC20/ETH, zero amounts not expected
// For NFTs, tokenID 0 is valid per ERC721 standard
if (!flow.isNFT && flow.amountOrId == 0) revert InvalidAmountOrId();
}
// Store new settlement
id = ++settlementIdCounter;
Settlement storage settlement = settlements[id];
settlement.settlementReference = settlementReference;
settlement.cutoffDate = cutoffDate;
settlement.isAutoSettled = isAutoSettled;
settlement.flows = flows; // needs "via IR" compilation
emit SettlementCreated(id, msg.sender);
}
/**
* @dev Executes the settlement if all approvals are in place.
* @param settlementId The id of the settlement to execute.
*/
function executeSettlement(uint256 settlementId) external nonReentrant {
try this.executeSettlementInner(msg.sender, settlementId) {
// Success — settlement executed
}
catch Error(string memory reason) {
emit SettlementExecutionFailedReason(settlementId, msg.sender, false, reason);
revert(reason); // Still revert for manual execution
} catch Panic(uint256 errorCode) {
emit SettlementExecutionFailedPanic(settlementId, msg.sender, false, errorCode);
// Re-throw panic
assembly {
invalid()
}
} catch (bytes memory lowLevelData) {
emit SettlementExecutionFailedOther(settlementId, msg.sender, false, lowLevelData);
// Re-throw the original error
assembly {
revert(add(lowLevelData, 0x20), mload(lowLevelData))
}
}
}
/**
* @dev Execute a single settlement. This is an external function, so that it can be used
* inside a try/catch, but it behaves like an internal function, in that the caller must
* be the self contract or the call with revert.
*/
function executeSettlementInner(address originalCaller, uint256 settlementId) external {
// this function can only be called by the DVP contract itself
if (msg.sender != address(this)) {
revert CallerMustBeDvpContract();
}
Settlement storage settlement = settlements[settlementId];
if (settlement.flows.length == 0) revert SettlementDoesNotExist();
if (block.timestamp > settlement.cutoffDate) revert CutoffDatePassed();
if (settlement.isSettled) revert SettlementAlreadyExecuted();
if (!isSettlementApproved(settlementId)) revert SettlementNotApproved();
uint256 lengthFlows = settlement.flows.length;
for (uint256 i = 0; i < lengthFlows; i++) {
Flow storage flow = settlement.flows[i];
if (flow.token == address(0)) {
// ETH Transfer
// Note: settlement.ethDeposits[flow.from] must == flow.amount, otherwise approval would not have been
// possible and isSettlementApproved() would have failed. So no need to check for insufficient balance.
uint256 amount = flow.amountOrId;
settlement.ethDeposits[flow.from] -= amount;
// sendValue reverts if unsuccessful
Address.sendValue(payable(flow.to), amount);
} else {
// ERC-721 or ERC-20 transfer in the same way (must cast to IERC20 for OZ safeTransfer for ERC-20)
IERC20 eitherToken = IERC20(flow.token);
// safeTransfer reverts if unsuccessful for ERC-20 and ERC-721
eitherToken.safeTransferFrom(flow.from, flow.to, flow.amountOrId);
}
}
settlement.isSettled = true;
emit SettlementExecuted(settlementId, originalCaller);
}
/**
* @dev Retrieves settlement details.
* @param settlementId The id of the settlement to retrieve.
*/
function getSettlement(uint256 settlementId)
external
view
returns (
string memory settlementReference,
uint256 cutoffDate,
Flow[] memory flows,
bool isSettled,
bool isAutoSettled
)
{
Settlement storage settlement = settlements[settlementId];
if (settlement.flows.length == 0) revert SettlementDoesNotExist();
return (
settlement.settlementReference,
settlement.cutoffDate,
settlement.flows,
settlement.isSettled,
settlement.isAutoSettled
);
}
/**
* @dev Provides a view of how ready a party is for a settlement to be executed:
* 1) If they have approved the settlement
* 2) How much ETH is required from them and how much they've deposited
* 3) For each distinct ERC-20 token they must send, how much they need, how much they've allowed DVP to spend, and...
* 4) ...how much they currently hold of that token
* 5) For each distinct ERC-721 token they must send, which token they need, if they've allowed DVP to spend it yet, and...
* 6) ...if the currenly hold that token
* NB: This function is not intended to be called inside a state-changing transaction, this view is for external callers
* only, so gas consumption is not critical.
* It is caller's responsibility to check party is involved in the settlement (check flows in getSettlement).
* Reverts if settlement does not exist.
* @param settlementId The id of the settlement to check.
* @param party The party to check.
*/
function getSettlementPartyStatus(uint256 settlementId, address party)
external
view
returns (bool isApproved, uint256 etherRequired, uint256 etherDeposited, TokenStatus[] memory tokenStatuses)
{
Settlement storage settlement = settlements[settlementId];
if (settlement.flows.length == 0) revert SettlementDoesNotExist();
isApproved = settlement.approvals[party];
(etherRequired, etherDeposited) = _getPartyEthStats(settlement, party);
tokenStatuses = _getTokenStatuses(settlement, party);
return (isApproved, etherRequired, etherDeposited, tokenStatuses);
}
/**
* @dev Check if a token is ERC-721
* @param token The address of the token to check.
* @return True if the token is ERC-721, false otherwise.
*/
function isERC721(address token) external view returns (bool) {
return _isERC721(token);
}
/**
* @dev Check if token is potentially ERC-20. This is a heuristic, not a guarantee.
* ERC-20 tokens that do not return a valid value from `decimals()` will be misclassified.
* @param token The address of the token to check.
* @return True if the token is potentially ERC-20, false otherwise.
*/
function isERC20(address token) external view returns (bool) {
return _isERC20(token);
}
/**
* @dev Revokes approvals for multiple settlements and refunds ETH deposits.
* @param settlementIds The ids of the settlements to revoke approvals for.
*/
function revokeApprovals(uint256[] calldata settlementIds) external nonReentrant {
uint256 totalRefund = 0;
uint256 lengthSettlements = settlementIds.length;
for (uint256 i = 0; i < lengthSettlements; i++) {
uint256 settlementId = settlementIds[i];
Settlement storage settlement = settlements[settlementId];
if (settlement.flows.length == 0) revert SettlementDoesNotExist();
if (settlement.isSettled) revert SettlementAlreadyExecuted();
if (!settlement.approvals[msg.sender]) revert ApprovalNotGranted();
uint256 ethAmountToRefund = settlement.ethDeposits[msg.sender];
if (ethAmountToRefund > 0) {
settlement.ethDeposits[msg.sender] = 0;
totalRefund += ethAmountToRefund;
emit ETHWithdrawn(msg.sender, ethAmountToRefund);
}
settlement.approvals[msg.sender] = false;
emit SettlementApprovalRevoked(settlementId, msg.sender);
}
// sendValue reverts if unsuccessful
if (totalRefund > 0) {
Address.sendValue(payable(msg.sender), totalRefund);
}
}
/**
* @dev Withdraws ETH deposits after the cutoff date if the settlement wasn't executed.
* @param settlementIds The ids of the settlements to withdraw ETH from.
*/
function withdrawETH(uint256[] calldata settlementIds) external nonReentrant {
uint256 totalAmount = 0;
for (uint256 i = 0; i < settlementIds.length; i++) {
uint256 settlementId = settlementIds[i];
Settlement storage settlement = settlements[settlementId];
if (settlement.flows.length == 0) revert SettlementDoesNotExist();
if (block.timestamp <= settlement.cutoffDate) revert CutoffDateNotPassed();
if (settlement.isSettled) revert SettlementAlreadyExecuted();
uint256 amount = settlement.ethDeposits[msg.sender];
if (amount > 0) {
settlement.ethDeposits[msg.sender] = 0;
totalAmount += amount;
emit ETHWithdrawn(msg.sender, amount);
}
}
if (totalAmount == 0) revert NoETHToWithdraw();
// sendValue reverts if unsuccessful
Address.sendValue(payable(msg.sender), totalAmount);
emit ETHWithdrawn(msg.sender, totalAmount);
}
/**
* @dev Make explicit the intention to revert transaction if contract is directly sent Ether.
*/
receive() external payable {
revert CannotSendEtherDirectly();
}
//------------------------------------------------------------------------------
// Internal
//------------------------------------------------------------------------------
/**
* @dev Internal helper to get ETH required and deposited for a party.
* @param settlement The settlement to check.
* @param party The party to check.
* @return etherRequired The total ETH required from the party.
* @return etherDeposited The total ETH deposited by the party.
*/
function _getPartyEthStats(Settlement storage settlement, address party)
internal
view
returns (uint256 etherRequired, uint256 etherDeposited)
{
uint256 lengthFlows = settlement.flows.length;
for (uint256 i = 0; i < lengthFlows; i++) {
Flow storage flow = settlement.flows[i];
if (flow.from == party && flow.token == address(0)) {
etherRequired += flow.amountOrId;
}
}
etherDeposited = settlement.ethDeposits[party];
}
/**
* @dev Internal helper to get TokenStatus for all tokens for a party.
* NB: This function is not intended to be called inside a state-changing transaction, this view is for external callers
* only, so gas consumption is not critical.
* @param settlement The settlement to check.
* @param party The party to check.
* @return tokenStatuses An array of TokenStatus, one for each token in the settlement.
*/
function _getTokenStatuses(Settlement storage settlement, address party)
internal
view
returns (TokenStatus[] memory)
{
uint256 lengthFlows = settlement.flows.length;
TokenStatus[] memory tokenStatuses = new TokenStatus[](lengthFlows);
uint256 index = 0;
for (uint256 i = 0; i < lengthFlows; i++) {
Flow storage f = settlement.flows[i];
if (f.from != party || f.token == address(0)) continue;
if (f.isNFT) {
IERC721 nft = IERC721(f.token);
tokenStatuses[index++] = TokenStatus({
tokenAddress: f.token,
isNFT: true,
amountOrIdRequired: f.amountOrId,
amountOrIdApprovedForDvp: nft.getApproved(f.amountOrId) == address(this)
|| nft.isApprovedForAll(party, address(this))
? f.amountOrId
: 0,
amountOrIdHeldByParty: nft.ownerOf(f.amountOrId) == party ? f.amountOrId : 0
});
} else {
uint256 allowed = IERC20(f.token).allowance(party, address(this));
uint256 balance = IERC20(f.token).balanceOf(party);
tokenStatuses[index++] = TokenStatus({
tokenAddress: f.token,
isNFT: false,
amountOrIdRequired: f.amountOrId,
amountOrIdApprovedForDvp: allowed,
amountOrIdHeldByParty: balance
});
}
}
// Trim the array to fit actual entries
assembly {
mstore(tokenStatuses, index)
}
return tokenStatuses;
}
/**
* @dev Internal version of {isERC721}
*/
function _isERC721(address token) internal view returns (bool) {
return token.supportsInterface(type(IERC721).interfaceId);
}
/**
* @dev Internal version of {isERC20}
*/
function _isERC20(address token) internal view returns (bool) {
(bool success, bytes memory result) = token.staticcall(abi.encodeWithSelector(SELECTOR_ERC20_DECIMALS));
return success && result.length == 32;
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly ("memory-safe") {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert Errors.FailedCall();
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165Checker.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Library used to query support of an interface declared via {IERC165}.
*
* Note that these functions return the actual result of the query: they do not
* `revert` if an interface is not supported. It is up to the caller to decide
* what to do in these cases.
*/
library ERC165Checker {
// As per the ERC-165 spec, no interface should ever match 0xffffffff
bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff;
/**
* @dev Returns true if `account` supports the {IERC165} interface.
*/
function supportsERC165(address account) internal view returns (bool) {
// Any contract that implements ERC-165 must explicitly indicate support of
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
return
supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
!supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);
}
/**
* @dev Returns true if `account` supports the interface defined by
* `interfaceId`. Support for {IERC165} itself is queried automatically.
*
* See {IERC165-supportsInterface}.
*/
function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
// query support of both ERC-165 as per the spec and support of _interfaceId
return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
}
/**
* @dev Returns a boolean array where each value corresponds to the
* interfaces passed in and whether they're supported or not. This allows
* you to batch check interfaces for a contract where your expectation
* is that some interfaces may not be supported.
*
* See {IERC165-supportsInterface}.
*/
function getSupportedInterfaces(
address account,
bytes4[] memory interfaceIds
) internal view returns (bool[] memory) {
// an array of booleans corresponding to interfaceIds and whether they're supported or not
bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
// query support of ERC-165 itself
if (supportsERC165(account)) {
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
}
}
return interfaceIdsSupported;
}
/**
* @dev Returns true if `account` supports all the interfaces defined in
* `interfaceIds`. Support for {IERC165} itself is queried automatically.
*
* Batch-querying can lead to gas savings by skipping repeated checks for
* {IERC165} support.
*
* See {IERC165-supportsInterface}.
*/
function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
// query support of ERC-165 itself
if (!supportsERC165(account)) {
return false;
}
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
return false;
}
}
// all interfaces supported
return true;
}
/**
* @notice Query if a contract implements an interface, does not check ERC-165 support
* @param account The address of the contract to query for support of an interface
* @param interfaceId The interface identifier, as specified in ERC-165
* @return true if the contract at account indicates support of the interface with
* identifier interfaceId, false otherwise
* @dev Assumes that account contains a contract that supports ERC-165, otherwise
* the behavior of this method is undefined. This precondition can be checked
* with {supportsERC165}.
*
* Some precompiled contracts will falsely indicate support for a given interface, so caution
* should be exercised when using this function.
*
* Interface identification is specified in ERC-165.
*/
function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
// prepare call
bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));
// perform static call
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
returnSize := returndatasize()
returnValue := mload(0x00)
}
return success && returnSize >= 0x20 && returnValue > 0;
}
}
"
},
"src/dvp/V1/IDeliveryVersusPaymentV1.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
/**
* ██████╗░██╗░░░██╗██████╗░███████╗░█████╗░░██████╗██╗░░░██╗░░░██╗░░██╗██╗░░░██╗███████╗
* ██╔══██╗██║░░░██║██╔══██╗██╔════╝██╔══██╗██╔════╝╚██╗░██╔╝░░░╚██╗██╔╝╚██╗░██╔╝╚════██║
* ██║░░██║╚██╗░██╔╝██████╔╝█████╗░░███████║╚█████╗░░╚████╔╝░░░░░╚███╔╝░░╚████╔╝░░░███╔═╝
* ██║░░██║░╚████╔╝░██╔═══╝░██╔══╝░░██╔══██║░╚═══██╗░░╚██╔╝░░░░░░██╔██╗░░░╚██╔╝░░██╔══╝░░
* ██████╔╝░░╚██╔╝░░██║░░░░░███████╗██║░░██║██████╔╝░░░██║░░░██╗██╔╝╚██╗░░░██║░░░███████╗
* ╚═════╝░░░░╚═╝░░░╚═╝░░░░░╚══════╝╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░╚══════╝
*/
/**
* @title IDeliveryVersusPaymentV1
* @dev Interface sufficient for DVP Helper contract to interact with a DVP contract.
* Created by https://pv0.one. UI implemented at https://dvpeasy.xyz.
*/
interface IDeliveryVersusPaymentV1 {
/// @dev A Flow is a single transfer from one address to another
struct Flow {
/// @dev address of ERC-20, ERC-721 or zero address for ETH
address token;
/// @dev flag of token is NFT
bool isNFT;
/// @dev party from address
address from;
/// @dev party to address
address to;
/// @dev Stores amount for ERC-20, ETH or tokenId for ERC-721
uint256 amountOrId;
}
/**
* @dev Returns the last settlement id used.
*/
function settlementIdCounter() external view returns (uint256);
/**
* @dev Retrieves settlement details.
* @param settlementId The settlement ID.
* @return settlementReference A free-text reference.
* @return cutoffDate The settlement's cutoff date.
* @return flows An array of flows contained in the settlement.
* @return isSettled True if the settlement has been executed.
* @return isAutoSettled True if the settlement is set for auto-settlement.
*/
function getSettlement(uint256 settlementId)
external
view
returns (
string memory settlementReference,
uint256 cutoffDate,
Flow[] memory flows,
bool isSettled,
bool isAutoSettled
);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuardTransient.sol)
pragma solidity ^0.8.24;
import {TransientSlot} from "./TransientSlot.sol";
/**
* @dev Variant of {ReentrancyGuard} that uses transient storage.
*
* NOTE: This variant only works on networks where EIP-1153 is available.
*
* _Available since v5.1._
*/
abstract contract ReentrancyGuardTransient {
using TransientSlot for *;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant REENTRANCY_GUARD_STORAGE =
0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_reentrancyGuardEntered()) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
REENTRANCY_GUARD_STORAGE.asBoolean().tstore(true);
}
function _nonReentrantAfter() private {
REENTRANCY_GUARD_STORAGE.asBoolean().tstore(false);
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return REENTRANCY_GUARD_STORAGE.asBoolean().tload();
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Errors.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/TransientSlot.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/TransientSlot.sol)
// This file was procedurally generated from scripts/generate/templates/TransientSlot.js.
pragma solidity ^0.8.24;
/**
* @dev Library for reading and writing value-types to specific transient storage slots.
*
* Transient slots are often used to store temporary values that are removed after the current transaction.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* * Example reading and writing values using transient storage:
* ```solidity
* contract Lock {
* using TransientSlot for *;
*
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
*
* modifier locked() {
* require(!_LOCK_SLOT.asBoolean().tload());
*
* _LOCK_SLOT.asBoolean().tstore(true);
* _;
* _LOCK_SLOT.asBoolean().tstore(false);
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library TransientSlot {
/**
* @dev UDVT that represent a slot holding a address.
*/
type AddressSlot is bytes32;
/**
* @dev Cast an arbitrary slot to a AddressSlot.
*/
function asAddress(bytes32 slot) internal pure returns (AddressSlot) {
return AddressSlot.wra
Submitted on: 2025-10-23 19:55:16
Comments
Log in to comment.
No comments yet.