Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts_prod/P2PLending.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
import "@openzeppelin/contracts/access/Ownable.sol";\r
import "@openzeppelin/contracts/utils/math/Math.sol";\r
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";\r
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";\r
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";\r
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";\r
import "./interface/IERC20_Decimals.sol";\r
import "./interface/IP2PLending.sol";\r
\r
contract P2PLending is IP2PLending, Ownable, ERC1155Supply, ReentrancyGuard\r
{\r
using SafeERC20 for IERC20;\r
\r
bool public stopped = false;\r
uint96 constant public CLAIM_PERIOD = 2 days;\r
uint8 constant public RATIOS_DECIMALS = 4;\r
uint96 constant public WITHDRAW_COOLDOWN = 30 seconds;\r
uint public LOT_SIZE = 10; // 10 USDC in wei after constructor\r
uint32 public MIN_LOTS_AMOUNT = 10;\r
uint32 public MIN_DURATION = 7; // days\r
uint32 public MAX_DURATION = 365; // days\r
uint32 public MIN_FILL_DURATION = 1; // days \r
uint32 public MIN_APY = 100; // 100 = 1%\r
uint32 public MAX_APY = 100000; // 1000% \r
uint32 public TARGET_RELATIVE_VALUE = 13000; // 130%\r
uint32 public LIQUIDATION_RELATIVE_VALUE = 11500; // 115%\r
uint32 public PROTOCOL_FEE = 150; // 1.5%\r
address public FEES_COLLECTOR; \r
IPriceData public PRICE_ORACLE;\r
\r
address immutable public COLLATERAL;\r
uint8 immutable private COLLATERAL_DECIMALS;\r
IERC20 immutable public USDC; \r
\r
mapping (uint => LoanConditions) private loan_conditions;\r
mapping (uint => LoanState) private loan_state;\r
uint private next_loan_id = 1;\r
mapping (address => mapping(uint => uint96)) private _withdraw_cooldown;\r
\r
constructor(address _price_oracle_address, address _COLLATERAL, address _USDC, address _FEES_COLLECTOR, string memory _erc1155_uri) Ownable(_msgSender()) ERC1155(_erc1155_uri)\r
{\r
PRICE_ORACLE = IPriceData(_price_oracle_address);\r
COLLATERAL = _COLLATERAL;\r
COLLATERAL_DECIMALS = IERC20_Decimals(COLLATERAL).decimals();\r
USDC = IERC20(_USDC);\r
LOT_SIZE = LOT_SIZE*10**IERC20_Decimals(_USDC).decimals();\r
FEES_COLLECTOR = _FEES_COLLECTOR;\r
}\r
\r
modifier validate_loan(uint loan_id) \r
{\r
require(loan_id > 0 && loan_id < next_loan_id, "Invalid Loan ID!");\r
_;\r
}\r
\r
function time_now() private view returns(uint96)\r
{\r
return uint96(block.timestamp);\r
}\r
\r
function emit_state_updated(uint loan_id) private \r
{\r
emit StateUpdated(loan_id, loan_state[loan_id].borrower, loan_state[loan_id].deadline, loan_state[loan_id].collateral_balance, loan_state[loan_id].USDC_balance, loan_state[loan_id].claim_deadline);\r
}\r
\r
function usdc_to_collateral(uint usdc_amount, IPriceData price_oracle, bytes calldata offchain_price_data) internal returns(uint)\r
{\r
return price_oracle.useQuoteAmountToCollateralAmount(COLLATERAL, COLLATERAL_DECIMALS, usdc_amount, offchain_price_data);\r
}\r
\r
function getDebt(uint loan_id) public view returns(uint)\r
{\r
LoanState storage state = loan_state[loan_id];\r
if(state.deadline == 0 || state.collateral_balance == 0)\r
{\r
return 0;\r
}\r
LoanConditions storage conditions = loan_conditions[loan_id];\r
uint USDC_required = totalSupply(loan_id)*conditions.lot_size;\r
uint since_start = state.deadline > time_now() ? time_now() - (state.deadline - conditions.duration) : conditions.duration - 1;\r
since_start = (since_start/(1 days) + 1)*(1 days);\r
return USDC_required + since_start*conditions.apy*USDC_required/(10**RATIOS_DECIMALS)/(365 days);\r
}\r
\r
function getLoanStatus(uint loan_id) public view returns(LoanStatus)\r
{\r
LoanConditions storage conditions = loan_conditions[loan_id];\r
LoanState storage state = loan_state[loan_id];\r
if(conditions.lots_required == 0)\r
{\r
return LoanStatus.UNDEFINED;\r
}\r
else if((state.borrower == address(0) && totalSupply(loan_id) == 0) \r
|| (state.borrower != address(0) && state.collateral_balance == 0))\r
{\r
return LoanStatus.FINISHED;\r
}\r
else if(state.deadline == 0)\r
{\r
if(state.claim_deadline != 0 && state.claim_deadline < time_now())\r
{\r
return LoanStatus.EXPIRED;\r
}\r
else if(conditions.lots_required > totalSupply(loan_id))\r
{\r
return LoanStatus.FINANCING;\r
}\r
else\r
{\r
return LoanStatus.FULLY_FUNDED;\r
}\r
}\r
else if(state.collateral_balance > 0)\r
{\r
if(totalSupply(loan_id) > 0)\r
{\r
return LoanStatus.ACTIVE;\r
}\r
else \r
{\r
return LoanStatus.FINISHED;\r
}\r
}\r
else\r
{\r
return LoanStatus.FINISHED;\r
}\r
}\r
\r
function createAsBorrower(uint lots, uint max_collateral_out, uint32 collateral_value, uint32 duration_days, uint32 apy, uint32 fill_duration_days, bytes calldata offchain_price_data) external nonReentrant returns(uint)\r
{\r
require(!stopped, "P2PLending:STOPPED!");\r
require(lots >= MIN_LOTS_AMOUNT \r
&& collateral_value >= TARGET_RELATIVE_VALUE\r
&& duration_days >= MIN_DURATION && duration_days <= MAX_DURATION\r
&& apy >= MIN_APY && apy <= MAX_APY\r
&& fill_duration_days >= MIN_FILL_DURATION\r
, "P2PLending:Invalid arguments!");\r
uint USDC_amount = LOT_SIZE*lots;\r
uint collateral_amount = collateral_value*usdc_to_collateral(USDC_amount, PRICE_ORACLE, offchain_price_data)/10**RATIOS_DECIMALS;\r
require(max_collateral_out >= collateral_amount && collateral_amount > 0, "P2PLending:Increase max_collateral_out!");\r
\r
IERC20(COLLATERAL).safeTransferFrom(_msgSender(), address(this), collateral_amount);\r
loan_conditions[next_loan_id] = LoanConditions({ \r
lots_required: lots,\r
lot_size: LOT_SIZE,\r
price_oracle: PRICE_ORACLE,\r
duration: duration_days * (1 days),\r
apy: apy,\r
target_relative_value: collateral_value,\r
liquidation_relative_value: LIQUIDATION_RELATIVE_VALUE,\r
fill_deadline: time_now() + fill_duration_days * (1 days)\r
});\r
\r
loan_state[next_loan_id] = LoanState({ \r
borrower: _msgSender(),\r
deadline: 0,\r
collateral_balance: collateral_amount,\r
claim_deadline: 0,\r
USDC_balance: 0 \r
});\r
\r
emit CreateAsBorrower(next_loan_id, loan_conditions[next_loan_id].lots_required, loan_conditions[next_loan_id].lot_size, address(loan_conditions[next_loan_id].price_oracle), loan_conditions[next_loan_id].duration, loan_conditions[next_loan_id].apy, loan_conditions[next_loan_id].target_relative_value, loan_conditions[next_loan_id].liquidation_relative_value,loan_conditions[next_loan_id].fill_deadline);\r
emit_state_updated(next_loan_id);\r
return next_loan_id++;\r
}\r
\r
function createAsLender(uint lots, uint32 collateral_value, uint32 duration_days, uint32 apy) external nonReentrant returns(uint)\r
{\r
require(!stopped, "P2PLending:STOPPED!");\r
require(lots >= MIN_LOTS_AMOUNT \r
&& collateral_value >= TARGET_RELATIVE_VALUE\r
&& duration_days >= MIN_DURATION && duration_days <= MAX_DURATION\r
&& apy >= MIN_APY && apy <= MAX_APY\r
, "P2PLending:Invalid arguments!");\r
USDC.safeTransferFrom(_msgSender(), address(this), LOT_SIZE*lots);\r
_mint(_msgSender(), next_loan_id, lots, "");\r
\r
loan_conditions[next_loan_id] = LoanConditions({ \r
lots_required: lots,\r
lot_size: LOT_SIZE,\r
price_oracle: PRICE_ORACLE,\r
duration: duration_days * (1 days),\r
apy: apy,\r
target_relative_value: collateral_value,\r
liquidation_relative_value: LIQUIDATION_RELATIVE_VALUE,\r
fill_deadline: 0\r
});\r
\r
loan_state[next_loan_id] = LoanState({ \r
borrower: address(0),\r
deadline: 0,\r
collateral_balance: 0,\r
claim_deadline: 0,\r
USDC_balance: 0 \r
});\r
\r
emit CreateAsLender(next_loan_id, _msgSender(), loan_conditions[next_loan_id].lots_required, loan_conditions[next_loan_id].lot_size, address(loan_conditions[next_loan_id].price_oracle), loan_conditions[next_loan_id].duration, loan_conditions[next_loan_id].apy, loan_conditions[next_loan_id].target_relative_value, loan_conditions[next_loan_id].liquidation_relative_value,loan_conditions[next_loan_id].fill_deadline);\r
return next_loan_id++;\r
}\r
\r
function lend(uint loan_id, uint target_lots, uint min_lots) external nonReentrant validate_loan(loan_id) returns(uint)\r
{\r
require(target_lots > 0 && target_lots >= min_lots, "P2PLending require:target_lots>0 && lots>=min_lots");\r
require(getLoanStatus(loan_id) == LoanStatus.FINANCING, "P2PLending:LoanStatus not equals FINANCING!");\r
\r
LoanConditions storage conditions = loan_conditions[loan_id];\r
uint available_lots = conditions.lots_required - totalSupply(loan_id);\r
require(available_lots >= min_lots, "P2PLending:Not enough available lots!");\r
uint lots = Math.min(target_lots, available_lots);\r
USDC.safeTransferFrom(_msgSender(), address(this), conditions.lot_size*lots);\r
_mint(_msgSender(), loan_id, lots, "");\r
require(totalSupply(loan_id) <= conditions.lots_required, "P2PLending require:supply<=lots_required");\r
uint96 epoch_now = time_now();\r
if(totalSupply(loan_id) == conditions.lots_required)\r
{\r
loan_state[loan_id].claim_deadline = epoch_now + CLAIM_PERIOD;\r
emit FullyFunded(loan_id, loan_state[loan_id].claim_deadline);\r
}\r
else \r
{\r
_withdraw_cooldown[_msgSender()][loan_id] = epoch_now + WITHDRAW_COOLDOWN;\r
}\r
\r
emit Lend(loan_id, _msgSender(), lots);\r
return lots;\r
}\r
\r
function borrow(uint loan_id, uint min_lots, uint max_lots, uint32 target_relative_value, uint min_collateral_in, uint max_collateral_out, bytes calldata offchain_price_data) external nonReentrant validate_loan(loan_id) returns(uint)\r
{\r
LoanConditions storage conditions = loan_conditions[loan_id];\r
LoanState storage state = loan_state[loan_id];\r
{\r
uint available_lots = totalSupply(loan_id);\r
require(min_lots>0 && available_lots >= min_lots && available_lots <= max_lots,"P2PLending require:0<min_lots<=supply<=max_lots");\r
LoanStatus status = getLoanStatus(loan_id);\r
require(status == LoanStatus.FINANCING || status == LoanStatus.FULLY_FUNDED, "P2PLending:Cannot borrow!"); \r
require(state.borrower == _msgSender() || state.borrower == address(0), "P2PLending:Caller is not owner!");\r
\r
conditions.lots_required = available_lots;\r
state.borrower = _msgSender();\r
state.deadline = time_now() + conditions.duration;\r
}\r
uint USDC_required = conditions.lots_required*conditions.lot_size;\r
uint collateral_amount_100 = usdc_to_collateral(USDC_required, conditions.price_oracle, offchain_price_data);\r
uint target_collateral_amount = conditions.target_relative_value*collateral_amount_100/10**RATIOS_DECIMALS;\r
if(target_relative_value != 0 || min_collateral_in > 0)\r
{\r
require(target_relative_value >= conditions.target_relative_value, "P2PLending:Increase target_relative_value!");\r
target_collateral_amount = Math.max(target_collateral_amount, target_relative_value*collateral_amount_100/10**RATIOS_DECIMALS);\r
if(target_collateral_amount > state.collateral_balance)\r
{\r
uint collateral_out = target_collateral_amount - state.collateral_balance;\r
require(max_collateral_out >= collateral_out, "P2PLending:Increase max_collateral_out!");\r
IERC20(COLLATERAL).safeTransferFrom(state.borrower, address(this), collateral_out);\r
}\r
else if(target_collateral_amount < state.collateral_balance)\r
{\r
uint collateral_in = state.collateral_balance - target_collateral_amount;\r
require(collateral_in >= min_collateral_in, "P2PLending:Decrease min_collateral_in!");\r
IERC20(COLLATERAL).safeTransfer(state.borrower, collateral_in);\r
}\r
state.collateral_balance = target_collateral_amount;\r
}\r
require(state.collateral_balance >= target_collateral_amount, "P2PLending:Increase collateral!");\r
{\r
uint fee = PROTOCOL_FEE*USDC_required/(10**RATIOS_DECIMALS);\r
USDC.safeTransfer(FEES_COLLECTOR, fee);\r
USDC.safeTransfer(state.borrower, USDC_required - fee);\r
}\r
emit Borrow(loan_id, conditions.lots_required);\r
emit_state_updated(loan_id); \r
return conditions.lots_required;\r
}\r
\r
function repay(uint loan_id) external nonReentrant validate_loan(loan_id)\r
{\r
require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
LoanState storage state = loan_state[loan_id];\r
require(state.borrower == _msgSender(), "P2PLending:Caller is not owner!");\r
uint required_USDC = getDebt(loan_id);\r
USDC.safeTransferFrom(state.borrower, address(this), required_USDC);\r
state.USDC_balance += required_USDC;\r
IERC20(COLLATERAL).safeTransfer(state.borrower, state.collateral_balance);\r
state.collateral_balance = 0;\r
\r
emit Repay(loan_id, required_USDC);\r
emit_state_updated(loan_id);\r
}\r
\r
function increaseCollateral(uint loan_id, uint collateral_amount) external nonReentrant validate_loan(loan_id)\r
{\r
require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
LoanState storage state = loan_state[loan_id];\r
require(state.borrower == _msgSender(), "P2PLending:Caller is not owner!");\r
IERC20(COLLATERAL).safeTransferFrom(state.borrower, address(this), collateral_amount);\r
state.collateral_balance += collateral_amount;\r
\r
emit IncreaseCollateral(loan_id, collateral_amount);\r
emit_state_updated(loan_id);\r
}\r
\r
function withdrawCollateral(uint loan_id) external nonReentrant validate_loan(loan_id)\r
{\r
LoanState storage state = loan_state[loan_id];\r
require(state.borrower == _msgSender(), "P2PLending:Caller is not owner!");\r
require(state.deadline == 0 && state.collateral_balance > 0, "P2PLending:Cannot Withdraw!");\r
if(getLoanStatus(loan_id) == LoanStatus.FINANCING && loan_conditions[loan_id].fill_deadline < time_now())\r
{\r
IERC20(COLLATERAL).safeTransfer(state.borrower, state.collateral_balance);\r
}\r
else \r
{\r
uint fee = PROTOCOL_FEE*state.collateral_balance/(10**RATIOS_DECIMALS);\r
IERC20(COLLATERAL).safeTransfer(FEES_COLLECTOR, fee);\r
IERC20(COLLATERAL).safeTransfer(state.borrower, state.collateral_balance - fee);\r
} \r
state.collateral_balance = 0;\r
state.USDC_balance = totalSupply(loan_id)*loan_conditions[loan_id].lot_size;\r
\r
emit WithdrawCollateral(loan_id);\r
emit_state_updated(loan_id);\r
}\r
\r
function withdrawUSDC(uint loan_id, uint lots) external nonReentrant validate_loan(loan_id)\r
{\r
require(lots > 0, "P2PLending:lots must be greater than zero!");\r
LoanStatus status = getLoanStatus(loan_id);\r
require(loan_state[loan_id].borrower == address(0) || status == LoanStatus.EXPIRED || (status == LoanStatus.FINANCING && time_now() > _withdraw_cooldown[_msgSender()][loan_id]), "P2PLending:Cannot withdraw!");\r
_burn(_msgSender(), loan_id, lots);\r
if(loan_state[loan_id].borrower == address(0))\r
{\r
uint supply_after_burn = totalSupply(loan_id);\r
require(supply_after_burn == 0 || supply_after_burn >= MIN_LOTS_AMOUNT,"P2PLending:Supply below MIN_LOTS_AMOUNT!");\r
}\r
USDC.safeTransfer(_msgSender(), loan_conditions[loan_id].lot_size*lots);\r
\r
emit WithdrawUSDC(loan_id, _msgSender(), lots);\r
}\r
\r
function claimUSDC(uint loan_id) external nonReentrant validate_loan(loan_id)\r
{\r
require(getLoanStatus(loan_id) == LoanStatus.FINISHED, "P2PLending:LoanStatus must equals FINISHED!");\r
uint loan_tokens_amount = balanceOf(_msgSender(), loan_id);\r
require(loan_tokens_amount > 0, "P2PLending:Nothing to claim!");\r
LoanState storage state = loan_state[loan_id]; \r
uint USDC_amount = loan_tokens_amount*state.USDC_balance/totalSupply(loan_id); \r
state.USDC_balance -= USDC_amount;\r
_burn(_msgSender(), loan_id, loan_tokens_amount);\r
USDC.safeTransfer(_msgSender(), USDC_amount);\r
\r
emit ClaimUSDC(loan_id, _msgSender(), loan_tokens_amount, USDC_amount);\r
emit_state_updated(loan_id);\r
}\r
\r
function claimCollateral(uint loan_id, bytes calldata offchain_price_data) external nonReentrant validate_loan(loan_id)\r
{\r
require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
LoanConditions storage conditions = loan_conditions[loan_id];\r
LoanState storage state = loan_state[loan_id];\r
require(state.deadline < time_now() || conditions.price_oracle.useRelativeCollateralValue(COLLATERAL, RATIOS_DECIMALS, state.collateral_balance, COLLATERAL_DECIMALS, getDebt(loan_id), offchain_price_data) < conditions.liquidation_relative_value, "P2PLending:Cannot Liquidate!"); \r
uint loan_tokens_amount = balanceOf(_msgSender(), loan_id);\r
require(loan_tokens_amount > 0, "P2PLending:Nothing to claim!");\r
\r
uint collateral_amount = loan_tokens_amount*state.collateral_balance/totalSupply(loan_id); \r
state.collateral_balance -= collateral_amount;\r
_burn(_msgSender(), loan_id, loan_tokens_amount);\r
IERC20(COLLATERAL).safeTransfer(_msgSender(), collateral_amount);\r
\r
emit ClaimCollateral(loan_id, _msgSender(), loan_tokens_amount, collateral_amount);\r
emit_state_updated(loan_id);\r
}\r
\r
function isCollateralClaimable(uint loan_id, bytes calldata offchain_price_data) external view returns(bool)\r
{\r
if(!(loan_id > 0 && loan_id < next_loan_id)\r
|| getLoanStatus(loan_id) != LoanStatus.ACTIVE\r
|| !(loan_state[loan_id].deadline < time_now() || loan_conditions[loan_id].price_oracle.readRelativeCollateralValue(COLLATERAL, RATIOS_DECIMALS, loan_state[loan_id].collateral_balance, COLLATERAL_DECIMALS, getDebt(loan_id), offchain_price_data) < loan_conditions[loan_id].liquidation_relative_value))\r
{\r
return false;\r
}\r
else\r
{\r
return true;\r
}\r
}\r
\r
function getRelativeCollateralValue(uint loan_id, bytes calldata offchain_price_data) external validate_loan(loan_id) view returns(uint)\r
{\r
require(getLoanStatus(loan_id) == LoanStatus.ACTIVE, "P2PLending:LoanStatus must equals ACTIVE!");\r
return loan_conditions[loan_id].price_oracle.readRelativeCollateralValue(COLLATERAL, RATIOS_DECIMALS, loan_state[loan_id].collateral_balance, COLLATERAL_DECIMALS, getDebt(loan_id), offchain_price_data);\r
}\r
\r
function USDCValueOf(uint loan_id, uint amount) external validate_loan(loan_id) view returns(uint)\r
{\r
uint supply = totalSupply(loan_id);\r
if(supply > 0)\r
{\r
uint loan_USDC_value = loan_state[loan_id].USDC_balance == 0 ? getDebt(loan_id) : loan_state[loan_id].USDC_balance;\r
return amount*loan_USDC_value/supply;\r
}\r
else \r
{\r
return 0;\r
}\r
}\r
\r
function getLoanInfo(uint loan_id) external validate_loan(loan_id) view returns(LoanConditions memory, LoanState memory, uint)\r
{\r
return (loan_conditions[loan_id], loan_state[loan_id], totalSupply(loan_id));\r
}\r
\r
function setLotSize(uint _LOT_SIZE) external onlyOwner\r
{\r
require(_LOT_SIZE > 0);\r
LOT_SIZE = _LOT_SIZE;\r
emit SetLotSize(LOT_SIZE);\r
}\r
\r
function setMinLotsAmount(uint32 _MIN_LOTS_AMOUNT) external onlyOwner\r
{\r
require(_MIN_LOTS_AMOUNT > 0);\r
MIN_LOTS_AMOUNT = _MIN_LOTS_AMOUNT;\r
emit SetMinLotsAmount(MIN_LOTS_AMOUNT);\r
}\r
\r
function setMinDuration(uint32 _MIN_DURATION) external onlyOwner\r
{\r
require(_MIN_DURATION > 0);\r
require(MAX_DURATION >= _MIN_DURATION);\r
MIN_DURATION = _MIN_DURATION;\r
emit SetMinDuration(MIN_DURATION);\r
}\r
\r
function setMaxDuration(uint32 _MAX_DURATION) external onlyOwner\r
{\r
require(_MAX_DURATION >= MIN_DURATION);\r
MAX_DURATION = _MAX_DURATION;\r
emit SetMaxDuration(MAX_DURATION);\r
}\r
\r
function setMinFillDuration(uint32 _MIN_FILL_DURATION) external onlyOwner\r
{\r
MIN_FILL_DURATION = _MIN_FILL_DURATION;\r
emit SetMinFillDuration(MIN_FILL_DURATION);\r
}\r
\r
function setMinAPY(uint32 _MIN_APY) external onlyOwner\r
{\r
require(MAX_APY >= _MIN_APY);\r
MIN_APY = _MIN_APY;\r
emit SetMinAPY(MIN_APY);\r
}\r
\r
function setMaxAPY(uint32 _MAX_APY) external onlyOwner\r
{\r
require(_MAX_APY >= MIN_APY);\r
MAX_APY = _MAX_APY;\r
emit SetMaxAPY(MAX_APY);\r
}\r
\r
function setTargetRelativeValue(uint32 _TARGET_RELATIVE_VALUE) external onlyOwner\r
{\r
require(_TARGET_RELATIVE_VALUE >= LIQUIDATION_RELATIVE_VALUE);\r
TARGET_RELATIVE_VALUE = _TARGET_RELATIVE_VALUE;\r
emit SetTargetRelativeValue(TARGET_RELATIVE_VALUE);\r
}\r
\r
function setLiquidationRelativeValue(uint32 _LIQUIDATION_RELATIVE_VALUE) external onlyOwner\r
{\r
require(_LIQUIDATION_RELATIVE_VALUE > 10**RATIOS_DECIMALS);\r
require(TARGET_RELATIVE_VALUE >= _LIQUIDATION_RELATIVE_VALUE);\r
LIQUIDATION_RELATIVE_VALUE = _LIQUIDATION_RELATIVE_VALUE;\r
emit SetLiquidationRelativeValue(LIQUIDATION_RELATIVE_VALUE);\r
}\r
\r
function setProtocolFee(uint32 _PROTOCOL_FEE) external onlyOwner\r
{\r
PROTOCOL_FEE = _PROTOCOL_FEE;\r
emit SetProtocolFee(PROTOCOL_FEE);\r
}\r
\r
function setFeesCollector(address _FEES_COLLECTOR) external onlyOwner\r
{\r
FEES_COLLECTOR = _FEES_COLLECTOR;\r
emit SetFeesCollector(FEES_COLLECTOR);\r
}\r
\r
function setPriceOracle(address _PRICE_ORACLE) external onlyOwner\r
{\r
PRICE_ORACLE = IPriceData(_PRICE_ORACLE);\r
emit SetPriceOracle(_PRICE_ORACLE);\r
}\r
\r
event Stopped();\r
function stop() external onlyOwner\r
{\r
require(!stopped);\r
stopped = true;\r
emit Stopped();\r
}\r
\r
}\r
"
},
"contracts_prod/interface/IP2PLending.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
import "./IPriceData.sol";\r
\r
interface IP2PLending\r
{\r
struct LoanConditions\r
{ \r
uint lots_required;\r
uint lot_size;\r
IPriceData price_oracle;\r
uint96 duration;\r
uint32 apy;\r
uint32 target_relative_value;\r
uint32 liquidation_relative_value;\r
uint96 fill_deadline;\r
}\r
\r
struct LoanState\r
{\r
address borrower;\r
uint96 deadline;\r
uint collateral_balance;\r
uint USDC_balance;\r
uint96 claim_deadline;\r
}\r
\r
enum LoanStatus\r
{\r
UNDEFINED,\r
FINANCING,\r
FULLY_FUNDED,\r
EXPIRED,\r
ACTIVE,\r
FINISHED\r
}\r
\r
event CreateAsBorrower(uint indexed loan_id, uint lots_required, uint lot_size, address price_oracle, uint96 duration, uint32 apy, uint32 target_relative_value, uint32 liquidation_relative_value, uint96 fill_deadline);\r
event CreateAsLender(uint indexed loan_id, address indexed lender, uint lots_required, uint lot_size, address price_oracle, uint96 duration, uint32 apy, uint32 target_relative_value, uint32 liquidation_relative_value, uint96 fill_deadline);\r
event StateUpdated(uint indexed loan_id, address indexed borrower, uint96 deadline, uint collateral_balance, uint USDC_balance, uint96 claim_deadline);\r
event FullyFunded(uint indexed loan_id, uint96 claim_deadline);\r
event Lend(uint indexed loan_id, address indexed lender, uint lots);\r
event Borrow(uint indexed loan_id, uint lots);\r
event Repay(uint indexed loan_id, uint USDC_amount);\r
event IncreaseCollateral(uint indexed load_id, uint collateral_amount);\r
event WithdrawCollateral(uint indexed load_id);\r
event WithdrawUSDC(uint indexed loan_id, address indexed lender, uint lots);\r
event ClaimUSDC(uint indexed loan_id, address indexed lender, uint lots, uint USDC_amount); \r
event ClaimCollateral(uint indexed loan_id, address indexed lender, uint lots, uint collateral_amount);\r
\r
function getDebt(uint loan_id) external view returns(uint USDC_value);\r
\r
function getLoanStatus(uint loan_id) external view returns(LoanStatus status);\r
\r
function createAsBorrower(uint lots, uint max_collateral_out, uint32 collateral_value, uint32 duration_days, uint32 apy, uint32 fill_duration_days, bytes calldata offchain_price_data) external returns(uint loan_id);\r
\r
function createAsLender(uint lots, uint32 collateral_value, uint32 duration_days, uint32 apy) external returns(uint loan_id);\r
\r
function lend(uint loan_id, uint target_lots, uint min_lots) external returns(uint lots);\r
\r
function borrow(uint loan_id, uint min_lots, uint max_lots, uint32 target_relative_value, uint min_collateral_in, uint max_collateral_out, bytes calldata offchain_price_data) external returns(uint lots);\r
\r
function repay(uint loan_id) external;\r
\r
function withdrawCollateral(uint loan_id) external;\r
\r
function withdrawUSDC(uint loan_id, uint lots) external;\r
\r
function claimUSDC(uint loan_id) external;\r
\r
function claimCollateral(uint loan_id, bytes calldata offchain_price_data) external;\r
\r
function isCollateralClaimable(uint loan_id, bytes calldata offchain_price_data) external view returns(bool);\r
\r
function getRelativeCollateralValue(uint loan_id, bytes calldata offchain_price_data) external view returns(uint relatie_value);\r
\r
function USDCValueOf(uint loan_id, uint amount) external view returns(uint USDC_value);\r
\r
function getLoanInfo(uint loan_id) external view returns(LoanConditions memory conditions, LoanState memory state, uint supply);\r
\r
event SetLotSize(uint LOT_SIZE);\r
event SetMinLotsAmount(uint32 MIN_LOTS_AMOUNT);\r
event SetMinDuration(uint32 MIN_LOTS_AMOUNT);\r
event SetMaxDuration(uint32 MIN_LOTS_AMOUNT);\r
event SetMinFillDuration(uint32 MIN_FILL_DURATION);\r
event SetMinAPY(uint32 MIN_APY);\r
event SetMaxAPY(uint32 MAX_APY);\r
event SetTargetRelativeValue(uint32 TARGET_RELATIVE_VALUE);\r
event SetLiquidationRelativeValue(uint32 LIQUIDATION_RELATIVE_VALUE);\r
event SetProtocolFee(uint32 PROTOCOL_FEE);\r
event SetFeesCollector(address FEES_COLLECTOR);\r
event SetPriceOracle(address PRICE_ORACLE);\r
\r
}\r
"
},
"contracts_prod/interface/IERC20_Decimals.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
interface IERC20_Decimals \r
{\r
function decimals() external view returns (uint8); \r
}\r
"
},
"@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @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 (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @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 _status == ENTERED;
}
}
"
},
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.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 Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(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);
}
}
"
},
"@openzeppelin/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);
}
"
},
"@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155Supply.sol)
pragma solidity ^0.8.20;
import {ERC1155} from "../ERC1155.sol";
import {Arrays} from "../../../utils/Arrays.sol";
/**
* @dev Extension of ERC-1155 that adds tracking of total supply per id.
*
* Useful for scenarios where Fungible and Non-fungible tokens have to be
* clearly identified. Note: While a totalSupply of 1 might mean the
* corresponding is an NFT, there is no guarantees that no other token with the
* same id are not going to be minted.
*
* NOTE: This contract implies a global limit of 2**256 - 1 to the number of tokens
* that can be minted.
*
* CAUTION: This extension should not be added in an upgrade to an already deployed contract.
*/
abstract contract ERC1155Supply is ERC1155 {
using Arrays for uint256[];
mapping(uint256 id => uint256) private _totalSupply;
uint256 private _totalSupplyAll;
/**
* @dev Total value of tokens in with a given id.
*/
function totalSupply(uint256 id) public view virtual returns (uint256) {
return _totalSupply[id];
}
/**
* @dev Total value of tokens.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupplyAll;
}
/**
* @dev Indicates whether any token exist with a given id, or not.
*/
function exists(uint256 id) public view virtual returns (bool) {
return totalSupply(id) > 0;
}
/**
* @dev See {ERC1155-_update}.
*/
function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory values
) internal virtual override {
super._update(from, to, ids, values);
if (from == address(0)) {
uint256 totalMintValue = 0;
for (uint256 i = 0; i < ids.length; ++i) {
uint256 value = values.unsafeMemoryAccess(i);
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply[ids.unsafeMemoryAccess(i)] += value;
totalMintValue += value;
}
// Overflow check required: The rest of the code assumes that totalSupplyAll never overflows
_totalSupplyAll += totalMintValue;
}
if (to == address(0)) {
uint256 totalBurnValue = 0;
for (uint256 i = 0; i < ids.length; ++i) {
uint256 value = values.unsafeMemoryAccess(i);
unchecked {
// Overflow not possible: values[i] <= balanceOf(from, ids[i]) <= totalSupply(ids[i])
_totalSupply[ids.unsafeMemoryAccess(i)] -= value;
// Overflow not possible: sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll
totalBurnValue += value;
}
}
unchecked {
// Overflow not possible: totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll
_totalSupplyAll -= totalBurnValue;
}
}
}
}
"
},
"@openzeppelin/contracts/token/ERC1155/ERC1155.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/ERC1155.sol)
pragma solidity ^0.8.20;
import {IERC1155} from "./IERC1155.sol";
import {IERC1155MetadataURI} from "./extensions/IERC1155MetadataURI.sol";
import {ERC1155Utils} from "./utils/ERC1155Utils.sol";
import {Context} from "../../utils/Context.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {Arrays} from "../../utils/Arrays.sol";
import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the basic standard multi-token.
* See https://eips.ethereum.org/EIPS/eip-1155
* Originally based on code by Enjin: https://github.com/enjin/erc-1155
*/
abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors {
using Arrays for uint256[];
using Arrays for address[];
mapping(uint256 id => mapping(address account => uint256)) private _balances;
mapping(address account => mapping(address operator => bool)) private _operatorApprovals;
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
string private _uri;
/**
* @dev See {_setURI}.
*/
constructor(string memory uri_) {
_setURI(uri_);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC1155).interfaceId ||
interfaceId == type(IERC1155MetadataURI).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC1155MetadataURI-uri}.
*
* This implementation returns the same URI for *all* token types. It relies
* on the token type ID substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the ERC].
*
* Clients calling this function must replace the `\{id\}` substring with the
* actual token type ID.
*/
function uri(uint256 /* id */) public view virtual returns (string memory) {
return _uri;
}
/**
* @dev See {IERC1155-balanceOf}.
*/
function balanceOf(address account, uint256 id) public view virtual returns (uint256) {
return _balances[id][account];
}
/**
* @dev See {IERC1155-balanceOfBatch}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(
address[] memory accounts,
uint256[] memory ids
) public view virtual returns (uint256[] memory) {
if (accounts.length != ids.length) {
revert ERC1155InvalidArrayLength(ids.length, accounts.length);
}
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i));
}
return batchBalances;
}
/**
* @dev See {IERC1155-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC1155-isApprovedForAll}.
*/
function isApprovedForAll(address account, address operator) public view virtual returns (bool) {
return _operatorApprovals[account][operator];
}
/**
* @dev See {IERC1155-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) public virtual {
address sender = _msgSender();
if (from != sender && !isApprovedForAll(from, sender)) {
revert ERC1155MissingApprovalForAll(sender, from);
}
_safeTransferFrom(from, to, id, value, data);
}
/**
* @dev See {IERC1155-safeBatchTransferFrom}.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) public virtual {
address sender = _msgSender();
if (from != sender && !isApprovedForAll(from, sender)) {
revert ERC1155MissingApprovalForAll(sender, from);
}
_safeBatchTransferFrom(from, to, ids, values, data);
}
/**
* @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from`
* (or `to`) is the zero address.
*
* Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received}
* or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value.
* - `ids` and `values` must have the same length.
*
* NOTE: The ERC-1155 acceptance check is not performed in this function. See {_updateWithAcceptanceCheck} instead.
*/
function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual {
if (ids.length != values.length) {
revert ERC1155InvalidArrayLength(ids.length, values.length);
}
address operator = _msgSender();
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids.unsafeMemoryAccess(i);
uint256 value = values.unsafeMemoryAccess(i);
if (from != address(0)) {
uint256 fromBalance = _balances[id][from];
if (fromBalance < value) {
revert ERC1155InsufficientBalance(from, fromBalance, value, id);
}
unchecked {
// Overflow not possible: value <= fromBalance
_balances[id][from] = fromBalance - value;
}
}
if (to != address(0)) {
_balances[id][to] += value;
}
}
if (ids.length == 1) {
uint256 id = ids.unsafeMemoryAccess(0);
uint256 value = values.unsafeMemoryAccess(0);
emit TransferSingle(operator, from, to, id, value);
} else {
emit TransferBatch(operator, from, to, ids, values);
}
}
/**
* @dev Version of {_update} that performs the token acceptance check by calling
* {IERC1155Receiver-onERC1155Received} or {IERC1155Receiver-onERC1155BatchReceived} on the receiver address if it
* contains code (eg. is a smart contract at the moment of execution).
*
* IMPORTANT: Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any
* update to the contract state after this function would break the check-effect-interaction pattern. Consider
* overriding {_update} instead.
*/
function _updateWithAcceptanceCheck(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal virtual {
_update(from, to, ids, values);
if (to != address(0)) {
address operator = _msgSender();
if (ids.length == 1) {
uint256 id = ids.unsafeMemoryAccess(0);
uint256 value = values.unsafeMemoryAccess(0);
ERC1155Utils.checkOnERC1155Received(operator, from, to, id, value, data);
} else {
ERC1155Utils.checkOnERC1155BatchReceived(operator, from, to, ids, values, data);
}
}
}
/**
* @dev Transfers a `value` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `from` must have a balance of tokens of type `id` of at least `value` amount.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
(uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
_updateWithAcceptanceCheck(from, to, ids, values, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
* - `ids` and `values` must have the same length.
*/
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
_updateWithAcceptanceCheck(from, to, ids, values, data);
}
/**
* @dev Sets a new URI for all token types, by relying on the token type ID
* substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the ERC].
*
* By this mechanism, any occurrence of the `\{id\}` substring in either the
* URI or any of the values in the JSON file at said URI will be replaced by
* clients with the token type ID.
*
* For example, the `https://token-cdn-domain/\{id\}.json` URI would be
* interpreted by clients as
* `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
* for token type ID 0x4cce0.
*
* See {uri}.
*
* Because these URIs cannot be meaningfully represented by the {URI} event,
* this function emits no events.
*/
function _setURI(string memory newuri) internal virtual {
_uri = newuri;
}
/**
* @dev Creates a `value` amount of tokens of type `id`, and assigns them to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _mint(address to, uint256 id, uint256 value, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
(uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
_updateWithAcceptanceCheck(address(0), to, ids, values, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `values` must have the same length.
* - `to` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
_updateWithAcceptanceCheck(address(0), to, ids, values, data);
}
/**
* @dev Destroys a `value` amount of tokens of type `id` from `from`
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `from` must have at least `value` amount of tokens of type `id`.
*/
function _burn(address from, uint256 id, uint256 value) internal {
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
(uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
_updateWithAcceptanceCheck(from, address(0), ids, values, "");
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `from` must have at least `value` amount of tokens of type `id`.
* - `ids` and `values` must have the same length.
*/
function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal {
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
_updateWithAcceptanceCheck(from, address(0), ids, values, "");
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*
* Requirements:
*
* - `operator` cannot be the zero address.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
if (operator == address(0)) {
revert ERC1155InvalidOperator(address(0));
}
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Creates an array in memory with only one value for each of the elements provided.
*/
function _asSingletonArrays(
uint256 element1,
uint256 element2
) private pure returns (uint256[] memory array1, uint256[] memory array2) {
assembly ("memory-safe") {
// Load the free memory pointer
array1 := mload(0x40)
// Set array length to 1
mstore(array1, 1)
// Store the single element at the next word after the length (where content starts)
mstore(add(array1, 0x20), element1)
// Repeat for next array locating it right after the first array
array2 := add(array1, 0x40)
mstore(array2, 1)
mstore(add(array2, 0x20), element2)
// Update the free memory pointer by pointing after the second array
mstore(0x40, add(array2, 0x40))
}
}
}
"
},
"@openzeppelin/contracts/utils/math/Math.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Return the 512-bit
Submitted on: 2025-10-03 09:21:44
Comments
Log in to comment.
No comments yet.