Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
- Contract name:
- RWAToken
- Optimization enabled
- false
- Compiler version
- v0.8.24+commit.e11b9ed9
- EVM Version
- Verified at
- 2026-10-16T00:05:24.795061Z
Constructor Arguments
0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000d3c21bcecceda10000000000000000000000000000000eb5556032f9ca9ab629439afaecd79c40aab863000000000000000000000000f119beb1b343e46c4ed0e78362cd3f87e87ef4cc00000000000000000000000050555531d7a9ae79a5ad9100f114def88c6691d9000000000000000000000000d37d29526d271770f42c2553faae7f282effc1630000000000000000000000005a5db3d01a38d640a81182b1769a9f1e4d1f0f7400000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc3200000000000000000000000000091991f58804cc5aceca5e87d4667e68d22e082480000000000000000000000000000000000000000000000000000000000000010525741206d6f6a64656c65c5be2e73690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055257414d440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042697066733a2f2f6261666b7265696866746e616d73367a686d346e32656568617968637533707461656878696d656e6278727876646561366f6679337078737a3461000000000000000000000000000000000000000000000000000000000000
contracts/RWAToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./AdminZamik.sol";
interface IKYCWhitelist {
function isWhitelisted(address account) external view returns (bool);
}
interface IUORSRegistry {
function authorizedUORS(address operater) external view returns (bool);
}
/**
* @title RWAToken
* @notice PRC-20 token representing a Real World Asset on PulseChain
* @dev Backed by verified off-chain data stored on IPFS.
* Minting requires a valid ZK proof from ZKVerifier.
* Transfer fee: 0.5% ob vsakem transferu → feeRecipient (platforma).
*
* OPCIJSKA KYC transfer-restrikcija (permissioned token):
* - če je `kyc` nastavljen (!= address(0)), morata biti POŠILJATELJ in
* PREJEMNIK na KYC beli listi (izjeme: mint, burn, fee-leg na feeRecipient).
* - To prepreči, da bi RWA pristal na nepreverjeni denarnici (zahteva, ki jo
* regulacija običajno postavlja finančnim instrumentom — MiFID II/ZTFI-1).
* - POMEMBNO: naslov Marketplace pogodbe (escrow listingov/ponudb) mora biti
* na beli listi, sicer list()/buy()/acceptOffer() padejo.
* - Privzeto IZKLOPLJENO (kyc == address(0)) → navaden prosto-prenosljiv ERC-20.
*/
/**
* @notice Nastavitve novega zetona v ENI strukturi.
* @dev Ne zaradi lepote: devet locenih parametrov konstruktorja je tovarno
* pognalo v "stack too deep" (Groth16 assembly + `new` z devetimi argumenti).
* Struktura gre v pomnilnik namesto na sklad.
*/
struct NastavitveZetona {
string ime;
string simbol;
string metapodatkiCID;
uint256 vrednostUSD;
address zkVerifier;
address entropyOracle;
address feeRecipient; // NIKOLI privzeto msg.sender — glej opombo pri konstruktorju
address kyc; // 0x0 = brez omejitve, a mora biti napisano izrecno
address lastnik;
address uors; // UORSRegistry — kdo sme sredstvo zamrzniti (glej spodaj)
address overitelj; // UORS operater, ki je sredstvo overil — glej `samoOveritelj`
}
contract RWAToken is ERC20, Ownable, AdminZamik {
string public assetMetadataCID;
uint256 public assetValueUSD;
address public zkVerifier;
address public entropyOracle;
// --- Provizije ---
uint256 public constant TRANSFER_FEE_BPS = 50; // 0,5% = 50 basis points
address public feeRecipient; // prejemnik provizij (platforma)
// ── Kdo je sredstvo overil ─────────────────────────────────────────────────
//
// Cenitev sme spremeniti SAMO ta naslov. Cenitev je izjava o vrednosti
// tujega premozenja in doloca ceno prve prodaje; odgovornost zanjo nosi
// tisti overitelj, ki je sredstvo overil, ne katerikoli drug.
//
// Zapise ga tovarna ob nastanku (`msg.sender` v `ustvariSredstvo`), zato je
// `immutable`: naknadno ga ne more prestaviti nihce, tudi lastnik ne.
address public immutable overitelj;
// ── Prvotni lastnik in enkratnost kovanja ─────────────────────────────────
//
// `prvotniLastnik` je naslov, ki je prejel prvo (in edino) kovanje tega
// sredstva. Trznica iz njega izpelje, ali je oglas PRIMARNI: primarna je
// prodaja tistega, ki je sredstvo dobil ob tokenizaciji, in samo zanjo velja
// omejitev, da mora biti cena enaka overjeni cenitvi. Ce bi to zastavico
// vpisoval prodajalec sam, bi bila omejitev obvod z enim argumentom.
//
// Vrednost `address(0)` pomeni "se ni skovano" in hkrati sluzi kot kljucavnica:
// kovanje je mogoce natanko enkrat. Enkratnost ni kozmetika — cena na primarnem
// trgu je `cenitev / stevilo enot`, zato bi vsako naknadno kovanje tiho premaknilo
// razmerje "ena enota = 1/N sredstva" vsem dosedanjim imetnikom.
address public prvotniLastnik;
// Dovoljenje za en sam `mintFee`, ki sledi kovanju. Ni javno: je notranje
// stanje ene transakcije, ne podatek o sredstvu.
bool private provizijaOdprta;
/// @dev Kolicina, skovana prvotnemu lastniku — zgornja meja za provizijo ob kovanju.
uint256 private skovanoLastniku;
// --- KYC transfer-restrikcija (opcijska) ---
address public kyc; // KYCWhitelist; če address(0), je restrikcija IZKLOPLJENA
// ─────────────────────────────────────────────────────────────────────────
// ZAPORA PRENOSA — varovalo za kupca
//
// Kovanje v denarnico lastnika sredstva je takojsnje; PRENOS komurkoli
// drugemu (in s tem oglas na trznici) je mogoc sele po `aktivenOd`.
// V tem oknu je sredstvo javno vidno in ga je mogoce ustaviti, preden je
// prenosljivo tretji osebi.
//
// Omejitev je v zetonu, ne v Marketplaceu: neposreden `transfer` iz
// denarnice bi jo sicer obsel.
//
// ⚠️ Dolzino zapore doloca VERIGA, ne clovek — enako kot `zamik()`:
// na produkcijski verigi 48 ur, drugod 10 minut, z ISTO bajtno kodo.
// Prej je bila `constant` 48 ur povsod, kar je na testnetu pomenilo dvodnevno
// cakanje pri vsakem preizkusu. Fiksna vrednost, ki bi jo bilo treba pred
// produkcijo rocno vrniti, bi bila zanasanje na spomin: tako je nihce ne more
// skrajsati in nihce je ne more pozabiti prestaviti nazaj.
//
// ⚠️ Ime NI ZAPORA_PRENOSA (velike crke): vrednost ni konstanta, ker je odvisna
// od verige. Ime, ki obljublja konstanto, bi bilo napacno poimenovana stevilka.
// ─────────────────────────────────────────────────────────────────────────
function _zaporaPrenosa() internal view returns (uint256) {
return block.chainid == MAINNET ? 48 hours : 10 minutes;
}
/// @notice Koliko casa po nastanku sredstvo se ni prenosljivo. Bere ga stran,
/// da lahko pove pravo stevilko na pravi verigi.
function zaporaPrenosa() external view returns (uint256) {
return _zaporaPrenosa();
}
uint256 public immutable aktivenOd;
// ─────────────────────────────────────────────────────────────────────────
// ZAMRZNITEV — pravico ima izkljucno pooblasceni UORS operater
//
// Obstoj sredstva potrdi UORS, zato je tudi preklic v njegovi pristojnosti.
// Lastnik (admin) tega zetona ne more zamrzniti; s tem bi presojal o
// posameznem sredstvu in prevzel del odgovornosti, ki je pri UORS.
//
// Posledica, ki je del zasnove: admin posameznega sredstva ne more ustaviti.
// Njegova pot je sistemska — odvzem pooblastila operaterju in `pause()` na
// ZKVerifierju oziroma Marketplaceu.
// ─────────────────────────────────────────────────────────────────────────
address public immutable uors; // UORSRegistry
bool public zamrznjen;
event Zamrznjeno(address indexed kdo);
event Odmrznjeno(address indexed kdo);
mapping(bytes32 => bool) public usedProofs;
// Dogodka za spremembo opisa ni: `assetMetadataCID` je nespremenljiv.
/// @notice Sprememba prijavljene vrednosti sredstva; oddana ob vsaki spremembi,
/// da je razvidna iz dnevnika verige in ne le iz stanja pogodbe.
event AssetValueUpdated(uint256 newValueUSD);
event TokensMinted(address indexed to, uint256 amount, bytes32 proofHash);
/// @notice Zapisan prvotni lastnik — iz njega trznica izpelje primarni oglas.
event PrvotniLastnikZapisan(address indexed lastnik);
event TransferFeeCollected(address indexed from, address indexed to, uint256 fee);
event FeeRecipientUpdated(address indexed newRecipient);
event KycUpdated(address indexed newKyc);
/**
* @notice `feeRecipient`, `kyc` in `lastnik` se nastavijo v konstruktorju,
* ne po postavitvi.
* @dev Nobena od teh vrednosti nima privzetka: `feeRecipient` in `lastnik`
* sta obvezna (`require` spodaj), `kyc` pa je lahko `address(0)` samo,
* ce je to izrecno podano. Zeton brez KYC omejitve je s tem zavestna
* izbira ob postavitvi in ne posledica neizvedenega naknadnega koraka.
*/
constructor(NastavitveZetona memory n)
ERC20(n.ime, n.simbol)
Ownable(n.lastnik)
{
require(n.zkVerifier != address(0), "ZKVerifier je nujen");
require(n.feeRecipient != address(0), "feeRecipient je nujen");
require(n.lastnik != address(0), "Lastnik je nujen");
require(n.uors != address(0), "UORSRegistry je nujen");
assetMetadataCID = n.metapodatkiCID;
assetValueUSD = n.vrednostUSD;
zkVerifier = n.zkVerifier;
entropyOracle = n.entropyOracle;
feeRecipient = n.feeRecipient;
kyc = n.kyc; // sme biti 0x0, a mora biti napisano izrecno
uors = n.uors;
overitelj = n.overitelj; // 0x0 pomeni: cenitve ne more spremeniti nihce
aktivenOd = block.timestamp + _zaporaPrenosa();
}
modifier samoUORS() {
require(uors != address(0) && IUORSRegistry(uors).authorizedUORS(msg.sender),
"Samo UORS operater");
_;
}
/**
* @dev Samo TISTI overitelj, ki je sredstvo overil — in samo dokler je se
* pooblascen. Odvzem pooblastila mu pravico takoj vzame; skrbnik s tem
* obdrzi veto, ne da bi dobil moznost cenitev spremeniti sam.
*
* ⚠️ Trije LOCENI razlogi, ne en skupen: bralec mora vedeti, ali sredstvo
* overitelja nima, ali ga ima in to ni on, ali pa mu je bilo pooblastilo
* odvzeto. Nevednost in prepoved nista isto.
*/
modifier samoOveritelj() {
require(overitelj != address(0), "Sredstvo nima zapisanega overitelja");
require(msg.sender == overitelj, "Samo overitelj tega sredstva");
require(uors != address(0) && IUORSRegistry(uors).authorizedUORS(msg.sender),
"Overitelj ni vec pooblascen");
_;
}
/// @notice Ali je sredstvo mogoce prenasati (in s tem prodajati).
/// @dev Namenjeno strani, da lahko pove RAZLOG in odstevanje, namesto da
/// uporabnik dobi golo revertirano transakcijo brez pojasnila.
function jePrenosMogoc() external view returns (bool mogoc, uint256 odKdaj, bool zamrznjeno) {
return (block.timestamp >= aktivenOd && !zamrznjen, aktivenOd, zamrznjen);
}
/// @notice Zamrzne prenose tega sredstva. Takojsno — nevarnost ne caka.
function zamrzni() external samoUORS {
zamrznjen = true;
emit Zamrznjeno(msg.sender);
}
/// @notice Odmrzne. Prav tako samo UORS: kdor je odgovoren za izjavo, jo tudi
/// umakne in vrne.
function odmrzni() external samoUORS {
zamrznjen = false;
emit Odmrznjeno(msg.sender);
}
/**
* @notice Mintanje prek ZKVerifier (brez transfer fee — to je kreacija tokenov).
*/
function mintWithProof(address to, uint256 amount, bytes32 proofHash) external {
require(msg.sender == zkVerifier, "Only ZK verifier");
require(!usedProofs[proofHash], "Proof already used");
// Sredstvo se skuje natanko enkrat. Brez te omejitve bi lahko overitelj
// registriral novo kodo za isti zeton in obtok bi zrasel — s tem bi se
// razmerje "ena enota = 1/N sredstva" in primarna cena premaknila vsem.
require(prvotniLastnik == address(0), "Sredstvo je ze skovano");
usedProofs[proofHash] = true;
prvotniLastnik = to;
provizijaOdprta = true;
skovanoLastniku = amount;
_mint(to, amount);
emit TokensMinted(to, amount, proofHash);
emit PrvotniLastnikZapisan(to);
}
/**
* @notice Mintanje provizije ob tokenizaciji — pokliče samo ZKVerifier.
* @dev Brez usedProofs check — fee mint ne porabi proof.
*/
function mintFee(address to, uint256 amount) external {
require(msg.sender == zkVerifier, "Only ZK verifier");
require(to != address(0), "Zero address");
// Provizija sme nastati samo takoj za kovanjem in samo enkrat. Brez tega
// bi bila enkratnost kovanja odvisna od tega, kako se vede DRUGA pogodba
// (ZKVerifier), namesto da bi jo ta pogodba zagotavljala sama.
require(provizijaOdprta, "Provizija brez kovanja");
// Meja zneska: provizija 0,5 % od skupne kolicine je najvec 1/199 dela,
// ki ga dobi lastnik (fee = floor(g/200), neto = g - fee ⇒ 199·fee ≤ neto).
// Tako enkratnost in velikost obtoka drzita tudi, ce bi se ZKVerifier
// vedel drugace, kot je predvideno.
require(amount * 199 <= skovanoLastniku, "Provizija previsoka");
provizijaOdprta = false;
_mint(to, amount);
}
/**
* @notice Override _update: ob vsakem transferu (ne mint/burn) zaračuna 0,5% fee.
* @dev OZ v5 uporablja _update namesto _transfer/_beforeTokenTransfer.
* Fee se ne zaračuna pri: mint (from==0), burn (to==0),
* feeRecipient transakcijah (izogne se loop-u).
*/
function _update(address from, address to, uint256 amount) internal override {
bool isMint = (from == address(0));
bool isBurn = (to == address(0));
// --- KYC transfer-restrikcija (opcijska; aktivna le če kyc != address(0)) ---
if (kyc != address(0)) {
// Prejemnik mora biti preverjen (izjema: burn). Tudi feeRecipient je
// stranka kot vsaka druga in mora biti na beli listi; provizija ob
// prenosu gre mimo te preverbe, ker je zapisana s super._update.
if (!isBurn) {
require(IKYCWhitelist(kyc).isWhitelisted(to), "Prejemnik ni KYC preverjen");
}
// Pošiljatelj mora biti preverjen (izjema: mint).
if (!isMint) {
require(IKYCWhitelist(kyc).isWhitelisted(from), "Posiljatelj ni KYC preverjen");
}
}
// --- Zapora prenosa in zamrznitev ---
// Mint (from == 0) je dovoljen takoj: lastnik sredstva ni ogrozen, ker
// nihce se nicesar ni placal. Vse ostalo caka na aktivacijo.
if (!isMint) {
require(!zamrznjen, "Sredstvo je zamrznjeno");
require(block.timestamp >= aktivenOd, "Sredstvo se ni aktivno za prenos");
}
// Mint, burn ali fee-related prenos → brez dodatnega fee
if (isMint || isBurn || from == feeRecipient || to == feeRecipient) {
super._update(from, to, amount);
return;
}
uint256 fee = amount * TRANSFER_FEE_BPS / 10_000;
uint256 net = amount - fee;
super._update(from, feeRecipient, fee); // 0,5% → platforma
super._update(from, to, net); // 99,5% → prejemnik
emit TransferFeeCollected(from, to, fee);
}
// --- Admin ---
// Vse spodnje spremembe tecejo prek javne napovedi in casovnega zamika
// (glej AdminZamik.sol). Imetnik zetona tako vsako spremembo vidi priti in
// ima cas izstopiti — namesto da se zbudi v drugacen instrument.
/// @notice Nastavi KYCWhitelist za transfer-restrikcijo. address(0) = izklopljeno.
/// @dev POZOR: ob vklopu mora biti naslov Marketplace pogodbe na beli listi (escrow).
function napoviSetKyc(address _kyc) external onlyOwner {
_napovej(keccak256(abi.encode("setKyc", _kyc)), "RWAToken.setKyc");
}
function setKyc(address _kyc) external onlyOwner {
_porabi(keccak256(abi.encode("setKyc", _kyc)), "RWAToken.setKyc");
kyc = _kyc;
emit KycUpdated(_kyc);
}
function napoviSetFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), "Zero address");
_napovej(keccak256(abi.encode("setFeeRecipient", _feeRecipient)), "RWAToken.setFeeRecipient");
}
function setFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), "Zero address");
_porabi(keccak256(abi.encode("setFeeRecipient", _feeRecipient)), "RWAToken.setFeeRecipient");
feeRecipient = _feeRecipient;
emit FeeRecipientUpdated(_feeRecipient);
}
// ── Metapodatki (opis sredstva na IPFS) so NESPREMENLJIVI ──────────────────
//
// `assetMetadataCID` se nastavi ob postavitvi in ga ne more spremeniti nihce —
// ne lastnik in ne UORS operater; funkcije za to ni. Imetnik zato bere isti
// opis, na podlagi katerega je sredstvo pridobil.
//
// Posledici, ki sta del zasnove:
// 1. napake v opisu ni mogoce popraviti; popravek pomeni novo sredstvo,
// 2. vsebina zivi na IPFS in je dosegljiva, dokler je kdo pinja — CID na
// verigi doloca, KATERA vsebina je veljavna, ne zagotavlja pa hrambe.
// ── Cenitev sredstva: spremeni jo lahko SAMO UORS ──────────────────────────
//
// `assetValueUSD` je izjava o vrednosti realnega sredstva. Za tako izjavo
// odgovarja overitelj, ne skrbnik sistema; skrbnik lahko dejanje prepreci
// (`pause`, odvzem pooblastila), ne more pa ga opraviti namesto njega.
//
// Cenitev doloca tudi ceno, po kateri je sredstvo mogoce prvic prodati, zato
// bi pravica skrbnika pomenila, da skrbnik doloca prodajno ceno tujega
// sredstva. Sprememba gre skozi napoved in zamik, torej je javna vnaprej.
//
// ⚠️ Obe funkciji morata imeti ISTO omejitev. Ce bi eno napovedoval skrbnik
// in drugo izvajal overitelj, bi bila polovica postopka v napacnih rokah.
function napoviUpdateAssetValue(uint256 newValueUSD) external samoOveritelj {
_napovej(keccak256(abi.encode("updateAssetValue", newValueUSD)), "RWAToken.updateAssetValue");
}
function updateAssetValue(uint256 newValueUSD) external samoOveritelj {
_porabi(keccak256(abi.encode("updateAssetValue", newValueUSD)), "RWAToken.updateAssetValue");
assetValueUSD = newValueUSD;
emit AssetValueUpdated(newValueUSD);
}
/// @notice Onemogoceno: renounceOwnership() bi trajno ubil setKyc in
/// setFeeRecipient.
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
}
contracts/KYCWhitelistTestnet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./KYCWhitelist.sol";
/**
* @title KYCWhitelistTestnet
* @notice TESTNET varianta bele liste — poleg obicajnega (owner) odobravanja
* omogoca se samopostrezno odobritev prek mockApproveMe().
*
* ZAKAJ LOCEN KONTRAKT:
* Prej je mockApproveMe() zivel v KYCWhitelist.sol z opozorilom
* "ODSTRANI PRED MAINNETOM". To je zanasanje na cloveski spomin — klasicna
* tocka odpovedi. Zdaj je mock v LOCENEM kontraktu, ki se na mainnet preprosto
* ne deploya. Mainnet dobi cisto KYCWhitelist, ki te funkcije NIMA — ne glede
* na to, ali se je kdo spomnil nanjo.
*
* ⚠️ TA KONTRAKT NE SME NIKOLI NA MAINNET (chainId 369).
*/
contract KYCWhitelistTestnet is KYCWhitelist {
constructor(address initialOwner) KYCWhitelist(initialOwner) {}
/// @notice SAMO TESTNET: vsak se lahko sam doda na belo listo
/// (simulacija opravljenega KYC, da je testiranje prijazno).
function mockApproveMe() external {
isWhitelisted[msg.sender] = true;
emit KYCApproved(msg.sender);
}
}
@openzeppelin/contracts/utils/Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
contracts/EntropyOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./AdminZamik.sol";
/**
* @title EntropyOracle
* @notice Collects validator entropy from PulseChain block data
* @dev Uses block.prevrandao (post-merge randomness) + validator signatures
* as entropy sources for ZK circuit inputs
*
* Hardening (v2) — iz varnostnega pregleda (Copilot/Gemini + verifikacija):
* - deauthorizeValidator(): možnost odvzema pooblastila (kompromitiran validator)
* - commitEntropy(): require(entropyHash != 0) — prepreči neveljavne/uninicializirane zapise
* - getEntropy(): bounds check → revert namesto vračanja bytes32(0)
* - dogodki: ValidatorDeauthorized, EntropyMarkedUsed; validator je 'indexed'
* OPOMBA: zaupanje v off-chain validatorja (entropyHash ni on-chain preverjen
* proti block.prevrandao) ostaja po zasnovi. Varnost uporabnika nosi entropija
* 12-besedne kode (pk), ne ta vrednost. Za večjo trust-minimizacijo razmisli o
* mešanju on-chain entropije: keccak256(block.prevrandao, msg.sender, recordCount).
*/
contract EntropyOracle is Ownable, AdminZamik {
struct EntropyRecord {
bytes32 entropyHash;
uint256 blockNumber;
uint256 timestamp;
address validator;
bool used;
}
mapping(uint256 => EntropyRecord) public entropyRecords;
mapping(address => bool) public authorizedValidators;
uint256 public recordCount;
event EntropyCommitted(uint256 indexed recordId, bytes32 entropyHash, address indexed validator);
event ValidatorAuthorized(address indexed validator);
event ValidatorDeauthorized(address indexed validator);
event EntropyMarkedUsed(uint256 indexed recordId);
constructor() Ownable(msg.sender) {}
/**
* @notice Validator commits entropy derived from block randomness
* @dev entropyHash = keccak256(block.prevrandao + validatorSig + nonce)
*/
function commitEntropy(bytes32 entropyHash) external returns (uint256 recordId) {
require(authorizedValidators[msg.sender], "Not authorized validator");
require(entropyHash != bytes32(0), "Zero entropy");
recordId = recordCount++;
entropyRecords[recordId] = EntropyRecord({
entropyHash: entropyHash,
blockNumber: block.number,
timestamp: block.timestamp,
validator: msg.sender,
used: false
});
emit EntropyCommitted(recordId, entropyHash, msg.sender);
}
function getEntropy(uint256 recordId) external view returns (bytes32) {
require(recordId < recordCount, "Invalid record index");
return entropyRecords[recordId].entropyHash;
}
function markUsed(uint256 recordId) external onlyOwner {
require(recordId < recordCount, "Invalid record index");
entropyRecords[recordId].used = true;
emit EntropyMarkedUsed(recordId);
}
/**
* PODELITEV MOCI GRE SKOZI ZAMIK, ODVZEM NE.
*
* Pooblascen validator vnasa entropijo, na kateri slonijo commitmenti.
* Podtaknjen validator je zato napad na sam vir nakljucja — in je viden
* vnaprej, zato javna napoved + zamik.
*
* deauthorizeValidator ostaja takojsen: ob kompromitiranem kljucu je treba
* ukrepati zdaj.
*/
function napoviAuthorizeValidator(address validator) external onlyOwner {
require(validator != address(0), "Zero address");
_napovej(keccak256(abi.encode("authorizeValidator", validator)), "EntropyOracle.authorizeValidator");
}
function authorizeValidator(address validator) external onlyOwner {
require(validator != address(0), "Zero address");
_porabi(keccak256(abi.encode("authorizeValidator", validator)), "EntropyOracle.authorizeValidator");
authorizedValidators[validator] = true;
emit ValidatorAuthorized(validator);
}
/// @notice Odvzame pooblastilo validatorju (npr. ob kompromitaciji ključa).
function deauthorizeValidator(address validator) external onlyOwner {
authorizedValidators[validator] = false;
emit ValidatorDeauthorized(validator);
}
/// @notice Onemogoceno: renounceOwnership() bi trajno onemogocil
/// (de)avtorizacijo validatorjev — oracle bi ostal zamrznjen.
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
}
@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
contracts/mocks/TestTokens.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/**
* @title MockERC20
* @notice Testni ERC20 s konfigurabilnimi decimalkami (npr. 6 za USDC, 18 za RWA).
* SAMO za Hardhat teste — nikoli na mainnet.
*/
contract MockERC20 is ERC20 {
uint8 private _decimals;
// Polji, ki ju Marketplace bere z RWA zetona, da izracuna primarni oglas in
// primarno ceno. Privzeto sta prazni: tak zeton ni primaren za nikogar, kar
// ustreza obnasanju v vecini testov (prodajalec je kupec iz sekundarnega trga).
address public prvotniLastnik;
uint256 public assetValueUSD;
constructor(string memory name_, string memory symbol_, uint8 decimals__) ERC20(name_, symbol_) {
_decimals = decimals__;
}
function decimals() public view override returns (uint8) {
return _decimals;
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function nastaviPrvotnegaLastnika(address kdo) external { prvotniLastnik = kdo; }
function nastaviCenitev(uint256 v) external { assetValueUSD = v; }
}
/**
* @title MockFeeERC20
* @notice Testni ERC20 z 0,5 % transfer fee (posnema obnašanje RWAToken._update),
* da preverimo, da Marketplace.list() pravilno shrani DEJANSKO prejeto količino.
*/
contract MockFeeERC20 is ERC20 {
uint256 public constant FEE_BPS = 50; // 0,5 %
address public feeRecipient;
// Glej opombo pri MockERC20 — Marketplace ju bere z vsakega RWA zetona.
address public prvotniLastnik;
uint256 public assetValueUSD;
constructor(address feeRecipient_) ERC20("Fee RWA", "fRWA") {
feeRecipient = feeRecipient_;
}
function nastaviPrvotnegaLastnika(address kdo) external { prvotniLastnik = kdo; }
function nastaviCenitev(uint256 v) external { assetValueUSD = v; }
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function _update(address from, address to, uint256 amount) internal override {
// brez fee pri mintu/burnu in ob prenosih na/od feeRecipient
if (from == address(0) || to == address(0) || from == feeRecipient || to == feeRecipient) {
super._update(from, to, amount);
return;
}
uint256 fee = amount * FEE_BPS / 10_000;
super._update(from, feeRecipient, fee);
super._update(from, to, amount - fee);
}
}
/**
* @title MockNoOwnerERC20
* @notice Navaden ERC20 BREZ `prvotniLastnik()` — posnema tuj zeton, ki ga nasa
* tovarna ni ustvarila. Marketplace mora tak oglas zavrniti z razlogom,
* ne pa ga tiho steti za sekundarnega.
*/
contract MockNoOwnerERC20 is ERC20 {
constructor() ERC20("Tuji zeton", "TUJ") {}
function mint(address to, uint256 amount) external { _mint(to, amount); }
}
contracts/MarketplaceTestnet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./Marketplace.sol";
/**
* @title MarketplaceTestnet
* @notice SAMO ZA TESTNET (943). Enak kot Marketplace, le da je casovni zamik
* skrajsan na 10 minut, da je postopek napoved → izvedba mogoce
* preizkusiti v eni seji namesto v dveh dneh.
*
* ZAKAJ LOCENA POGODBA IN NE STIKALO:
* Stikalo bi bilo mogoce pomotoma nastaviti na mainnetu in nihce ne bi opazil —
* zamik bi bil videti prisoten, a bi bil brez ucinka. Locena pogodba tega ne
* dopusca: skrajsani zamik fizicno ne obstaja v Marketplace.sol. Isti prijem
* kot pri KYCWhitelistTestnet.
*
* ⚠️ Te pogodbe se NE deploya na mainnet (369).
*/
contract MarketplaceTestnet is Marketplace {
constructor(address _usdc, address _feeRecipient, address _kyc)
Marketplace(_usdc, _feeRecipient, _kyc)
{}
function zamik() public pure override returns (uint256) {
return 10 minutes;
}
}
@openzeppelin/contracts/access/Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
@openzeppelin/contracts/interfaces/draft-IERC6093.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (interfaces/draft-IERC6093.sol)
pragma solidity >=0.8.4;
/**
* @dev Standard ERC-20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC-721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC-1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
@openzeppelin/contracts/token/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @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);
}
contracts/RWATokenFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./RWAToken.sol";
interface IZKVerifierRegistracija {
function registrirajZeton(address zeton) external;
}
interface IUORSRegister {
function authorizedUORS(address operater) external view returns (bool);
}
/**
* @title RWATokenFactory
* @notice Postavi po en ERC-20 zeton za VSAKO realno sredstvo.
*
* EN ZETON NA SREDSTVO
* Vsako sredstvo ima svojo ERC-20 pogodbo. Deleži razlicnih sredstev tako niso
* medsebojno zamenljivi in je vrednost (`assetValueUSD`) lastnost posameznega
* sredstva, ne skupna vsem. Locitev je na verigi in je z metapodatki ni mogoce
* nadomestiti.
*
* ZAKAJ TOVARNA IN NE ROCNA POSTAVITEV
* Postavitev zetona zahteva vec zaporednih korakov (nastavitev prejemnika
* provizij in KYC seznama, prenos lastnistva, registracija pri ZKVerifierju).
* Tovarna jih opravi v eni transakciji, zato delno izveden postopek ni mogoc.
*
* KAJ TOVARNA NE POCNE
* Ne kuje zetonov. Ti nastanejo izkljucno prek ZK dokaza v ZKVerifierju;
* tovarna ustvari le pogodbo brez zaloge.
*/
contract RWATokenFactory is Ownable {
address public immutable zkVerifier;
address public immutable entropyOracle;
// ─────────────────────────────────────────────────────────────────────────
// KDO SME USTVARITI SREDSTVO
//
// Sredstvo sme ustvariti izkljucno pooblasceni UORS operater (overitelj), ne
// lastnik te pogodbe. Ustvaritev je izjava, da dolocen predmet obstaja, je
// ocenjen in ima lastnika; to preverjanje opravi overitelj.
//
// Lastnik pogodbe ni v poti nastanka niti kot sopodpisnik — sicer bi bil
// soizdajatelj in odgovornost za vsebino izjave bi bila deljena.
// ─────────────────────────────────────────────────────────────────────────
address public immutable uors; // UORSRegistry
/// Skupne nastavitve, ki jih dobi vsak nov zeton.
address public feeRecipient;
address public kyc;
address[] public sredstva;
mapping(address => bool) public jeNase;
event SredstvoUstvarjeno(
address indexed zeton,
string ime,
string simbol,
uint256 vrednostUSD,
string metapodatkiCID
);
event NastavitveSpremenjene(address feeRecipient, address kyc);
modifier samoUORS() {
require(IUORSRegister(uors).authorizedUORS(msg.sender), "Samo UORS operater");
_;
}
constructor(
address _zkVerifier,
address _entropyOracle,
address _feeRecipient,
address _kyc,
address _lastnik,
address _uors
) Ownable(_lastnik) {
require(_zkVerifier != address(0), "ZKVerifier je nujen");
require(_feeRecipient != address(0), "feeRecipient je nujen");
require(_lastnik != address(0), "Lastnik je nujen");
require(_uors != address(0), "UORSRegistry je nujen");
uors = _uors;
zkVerifier = _zkVerifier;
entropyOracle = _entropyOracle;
feeRecipient = _feeRecipient;
kyc = _kyc;
}
/**
* @notice Ustvari novo sredstvo — en zeton, ena stvar iz resnicnega sveta.
* @dev Vsako sredstvo je zavesten podpis lastnika. Za regulirano dejavnost je
* to prednost, ne ovira: namerno dejanje na sredstvo je bolje kot tiho
* dodajanje v skupno posodo.
*/
function ustvariSredstvo(
string calldata ime,
string calldata simbol,
string calldata metapodatkiCID,
uint256 vrednostUSD
) external samoUORS returns (address zeton) {
require(bytes(ime).length != 0, "Ime je nujno");
require(bytes(simbol).length != 0, "Simbol je nujen");
require(vrednostUSD != 0, "Vrednost je nujna");
zeton = address(new RWAToken(NastavitveZetona({
ime: ime,
simbol: simbol,
metapodatkiCID: metapodatkiCID,
vrednostUSD: vrednostUSD,
zkVerifier: zkVerifier,
entropyOracle: entropyOracle,
feeRecipient: feeRecipient,
kyc: kyc,
lastnik: owner(), // lastnik zetona = lastnik tovarne (Ledger / multisig)
uors: uors, // zamrznitev sredstva sme SAMO UORS (glej RWAToken)
// Overitelj je TISTI operater, ki sredstvo ustvarja zdaj. Zapise se
// nespremenljivo in doloca, kdo sme pozneje spremeniti cenitev —
// odgovornost za izjavo o vrednosti nosi tisti, ki jo je dal.
overitelj: msg.sender
})));
sredstva.push(zeton);
jeNase[zeton] = true;
// Sele s tem sme ZKVerifier kovati na tem zetonu.
IZKVerifierRegistracija(zkVerifier).registrirajZeton(zeton);
emit SredstvoUstvarjeno(zeton, ime, simbol, vrednostUSD, metapodatkiCID);
}
/// @notice Spremeni nastavitve za PRIHODNJE zetone. Ze ustvarjenih ne dotakne —
/// tam se feeRecipient in kyc spreminjata na zetonu samem, pod zamikom.
function nastavi(address _feeRecipient, address _kyc) external onlyOwner {
require(_feeRecipient != address(0), "feeRecipient je nujen");
feeRecipient = _feeRecipient;
kyc = _kyc;
emit NastavitveSpremenjene(_feeRecipient, _kyc);
}
function steviloSredstev() external view returns (uint256) {
return sredstva.length;
}
/// @notice Vsa sredstva naenkrat — stran jih rabi za seznam zetonov.
function vsaSredstva() external view returns (address[] memory) {
return sredstva;
}
/// @notice Onemogoceno: brez lastnika tovarna ne bi mogla vec ustvariti sredstva.
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
}
@openzeppelin/contracts/utils/introspection/IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @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);
}
@openzeppelin/contracts/interfaces/IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";
@openzeppelin/contracts/interfaces/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";
contracts/AdminZamik.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title AdminZamik
* @notice Casovni zamik na nevarne admin spremembe.
*
* ZAKAJ TO OBSTAJA
* ----------------
* Owner kljuc na tej platformi NE more krasti: ne more kovati zetonov, ne more
* premikati tujih zetonov in ne more v escrow. Kar PA lahko, je tiho zamenjati
* nastavitve pod nogami uporabnikov — KYC listo, placilni token, prijavljeno
* vrednost sredstva.
*
* Vsi trije so napadi, ki jih je mogoce VIDETI VNAPREJ. Zato jih ne branimo z
* multisigom (ki varuje lastnika kljuca), ampak s casovnim zamikom (ki varuje
* UPORABNIKA): sprememba se mora najprej javno napovedati, sele po zamiku jo je
* mogoce izvesti. Uporabnik ima ves ta cas, da spremembo vidi in izstopi.
*
* Namenoma NI zunanje timelock pogodbe kot lastnika. Tak vzorec doda tveganje
* nedosegljivosti: ce lastniku kdaj ne uspe sestaviti transakcije skozi tujo
* pogodbo, so kontrakti zamrznjeni za vedno. Tu je zamik vgrajen, lastnik pa
* ostane navadna denarnica.
*
* KAJ ZAMIK NE POKRIVA (namerno)
* ------------------------------
* - pause() — zasilna zavora mora biti takojsnja, sicer ni zavora
* - unpause() — vrnitev v obratovanje naj bo hitra
* - odvzem pravice zaustavljalca — ob razkritem kljucu ga moras vreci TAKOJ
* Nevarnost caka, zasilna zavora ne.
*/
abstract contract AdminZamik is Ownable {
/// @notice Rok, v katerem je napoved uporabna. Po njem potece.
/// @dev Brez tega bi stara napoved lezala na verigi kot nabita puska:
/// napoves nekaj neopaznega danes, izvedes cez pol leta, ko nihce vec
/// ne gleda. Napoved, ki ni bila izvedena v roku, je treba ponoviti.
uint256 public constant ROK = 7 days;
/// @notice id napovedi → cas, od katerega je izvedljiva (0 = ni napovedi)
mapping(bytes32 => uint256) public izvedljivoOd;
event SpremembaNapovedana(bytes32 indexed id, string kaj, uint256 izvedljivoOd, uint256 potece);
event SpremembaIzvedena(bytes32 indexed id, string kaj);
event SpremembaPreklicana(bytes32 indexed id, string kaj);
/// @notice PulseChain mainnet. Edina veriga, kjer zamik varuje pravo vrednost.
uint256 internal constant MAINNET = 369;
/// @notice Koliko casa mora napovedana sprememba cakati, preden je izvedljiva.
///
/// mainnet (369) 48 ur imetnik ima cas spremembo videti in izstopiti
/// druge verige 10 minut razvojno okolje
///
/// @dev Vrednost doloca `block.chainid`, ne nastavitev ob postavitvi. Ista
/// bajtna koda tako na produkcijski verigi vedno vrne 48 ur; razvojne
/// vrednosti ni mogoce prenesti na mainnet, niti pomotoma.
/// @dev `virtual` zato, da lahko posamezna testna pogodba vrednost povozi.
function zamik() public view virtual returns (uint256) {
return block.chainid == MAINNET ? 48 hours : 10 minutes;
}
// ── Notranje ────────────────────────────────────────────────────────────
/// @dev id MORA vsebovati tocne parametre spremembe, sicer bi lahko
/// napovedal nedolzno vrednost in izvedel skodljivo.
function _napovej(bytes32 id, string memory kaj) internal {
require(izvedljivoOd[id] == 0, "Ze napovedano");
uint256 od = block.timestamp + zamik();
izvedljivoOd[id] = od;
emit SpremembaNapovedana(id, kaj, od, od + ROK);
}
function _porabi(bytes32 id, string memory kaj) internal {
uint256 od = izvedljivoOd[id];
require(od != 0, "Ni napovedano");
require(block.timestamp >= od, "Zamik se ni potekel");
require(block.timestamp <= od + ROK, "Napoved je potekla");
delete izvedljivoOd[id];
emit SpremembaIzvedena(id, kaj);
}
// ── Javno ───────────────────────────────────────────────────────────────
/// @notice Preklic napovedane spremembe. Takojsen — premisliti se mora biti
/// mogoce hitreje, kot se izvede.
function prekliciSpremembo(bytes32 id, string calldata kaj) external onlyOwner {
require(izvedljivoOd[id] != 0, "Ni napovedano");
delete izvedljivoOd[id];
emit SpremembaPreklicana(id, kaj);
}
/// @notice Stanje napovedi — da ga stran in nadzorni sistem lahko prikazeta.
function stanjeNapovedi(bytes32 id)
external
view
returns (bool napovedana, uint256 od, uint256 potece, bool izvedljivaZdaj)
{
uint256 t = izvedljivoOd[id];
if (t == 0) return (false, 0, 0, false);
return (true, t, t + ROK, block.timestamp >= t && block.timestamp <= t + ROK);
}
}
@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.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 {
if (!_safeTransfer(token, to, value, true)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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 {
if (!_safeTransferFrom(token, from, to, value, true)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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 _safeTransfer(token, to, value, false);
}
/**
* @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 _safeTransferFrom(token, from, to, value, false);
}
/**
* @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 {
if (!_safeApprove(token, spender, value, false)) {
if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));
if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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 relies 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 relies 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}.
* Oppositely, 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 `token.transfer(to, value)` call, 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 to The recipient of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
bytes4 selector = IERC20.transfer.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(to, shr(96, not(0))))
mstore(0x24, value)
success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
}
}
/**
* @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, 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 from The sender of the tokens
* @param to The recipient of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value,
bool bubble
) private returns (bool success) {
bytes4 selector = IERC20.transferFrom.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(from, shr(96, not(0))))
mstore(0x24, and(to, shr(96, not(0))))
mstore(0x44, value)
success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
mstore(0x60, 0)
}
}
/**
* @dev Imitates a Solidity `token.approve(spender, value)` call, 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 spender The spender of the tokens
* @param value The amount of token to transfer
* @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
*/
function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {
bytes4 selector = IERC20.approve.selector;
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(0x00, selector)
mstore(0x04, and(spender, shr(96, not(0))))
mstore(0x24, value)
success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
// if call success and return is true, all is good.
// otherwise (not success or return is not true), we need to perform further checks
if iszero(and(success, eq(mload(0x00), 1))) {
// if the call was a failure and bubble is enabled, bubble the error
if and(iszero(success), bubble) {
returndatacopy(fmp, 0x00, returndatasize())
revert(fmp, returndatasize())
}
// if the return value is not true, then the call is only successful if:
// - the token address has code
// - the returndata is empty
success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
}
mstore(0x40, fmp)
}
}
}
@openzeppelin/contracts/utils/ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
/**
* @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].
*
* IMPORTANT: Deprecated. This storage-based reentrancy guard will be removed and replaced
* by the {ReentrancyGuardTransient} variant in v6.0.
*
* @custom:stateless
*/
abstract contract ReentrancyGuard {
using StorageSlot for bytes32;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant REENTRANCY_GUARD_STORAGE =
0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
// 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;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_reentrancyGuardStorageSlot().getUint256Slot().value = 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();
}
/**
* @dev A `view` only version of {nonReentrant}. Use to block view functions
* from being called, preventing reading from inconsistent contract state.
*
* CAUTION: This is a "view" modifier and does not change the reentrancy
* status. Use it only on view functions. For payable or non-payable functions,
* use the standard {nonReentrant} modifier instead.
*/
modifier nonReentrantView() {
_nonReentrantBeforeView();
_;
}
function _nonReentrantBeforeView() private view {
if (_reentrancyGuardEntered()) {
revert ReentrancyGuardReentrantCall();
}
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
_nonReentrantBeforeView();
// Any calls to nonReentrant after this point will fail
_reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_reentrancyGuardStorageSlot().getUint256Slot().value = 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 _reentrancyGuardStorageSlot().getUint256Slot().value == ENTERED;
}
function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
return REENTRANCY_GUARD_STORAGE;
}
}
@openzeppelin/contracts/token/ERC20/ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/// @inheritdoc IERC20
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/// @inheritdoc IERC20
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/// @inheritdoc IERC20
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
contracts/UORSRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./AdminZamik.sol";
/**
* @title UORSRegistry
* @notice On-chain log notarskih registracij — vsak UORS operator je avtonomen.
*
* Model:
* - Platforma (owner) registrira UORS operatorje (addUORS)
* - Vsak UORS operator si sam uredi plačilo s stranko (fiat/crypto/gotovina)
* - Ko potrdi plačilo → pokliče recordPayment() → on-chain zapis
* - Platforma prejme 0.5% mint fee prek ZKVerifier (ločen kontrakt)
*
* Tok:
* 1. UORS potrdi plačilo stranke (off-chain)
* 2. UORS pokliče recordPayment(commitment, payer, amountEurCents, paymentRef)
* 3. UORS pokliče ZKVerifier.registerCommitment(commitment)
* 4. Stranka minta v UORS portalu → 0.5% fee → platforma
*/
contract UORSRegistry is Ownable, AdminZamik {
struct ServiceRecord {
uint256 commitment; // Poseidon(privateKey, entropyNonce)
address payer; // strankina denarnica
address uorsOperator; // UORS ki je potrdil plačilo
uint256 amountEurCents; // znesek v euro centih (5000 = €50.00)
uint256 timestamp; // block.timestamp
string paymentRef; // referenca plačila (poljubno: račun, txHash, SEPA ref...)
bool mintReady; // true po registerCommitment v ZKVerifier
}
// commitment → ServiceRecord
mapping(uint256 => ServiceRecord) public records;
// seznam vseh commitmentov
uint256[] public commitmentList;
// pooblaščeni UORS operatorji (platforma jih doda/odstrani)
mapping(address => bool) public authorizedUORS;
address[] public uorsList;
// skupni zaslužek po UORS operatorju (informativno, v euro centih)
mapping(address => uint256) public revenueByUORS;
uint256 public totalRevenueEurCents;
event PaymentRecorded(
uint256 indexed commitment,
address indexed payer,
address indexed uorsOperator,
uint256 amountEurCents,
string paymentRef
);
event MintReadySet(uint256 indexed commitment);
event UORSAdded(address indexed uors);
event UORSRemoved(address indexed uors);
modifier onlyUORS() {
require(authorizedUORS[msg.sender], "Not authorized UORS");
_;
}
constructor() Ownable(msg.sender) {}
// ── Platforma upravlja UORS operatorje ────────────────────────────────────
/**
* PODELITEV MOCI GRE SKOZI ZAMIK, ODVZEM NE.
* Nov UORS operater sme potrjevati placila in oznacevati mintReady.
* removeUORS ostaja takojsen — glej AdminZamik.sol.
*/
function napoviAddUORS(address uors) external onlyOwner {
require(uors != address(0), "Zero address");
require(!authorizedUORS[uors], "Already added");
_napovej(keccak256(abi.encode("addUORS", uors)), "UORSRegistry.addUORS");
}
function addUORS(address uors) external onlyOwner {
require(uors != address(0), "Zero address");
require(!authorizedUORS[uors], "Already added");
_porabi(keccak256(abi.encode("addUORS", uors)), "UORSRegistry.addUORS");
authorizedUORS[uors] = true;
uorsList.push(uors);
emit UORSAdded(uors);
}
function removeUORS(address uors) external onlyOwner {
require(authorizedUORS[uors], "Not found");
authorizedUORS[uors] = false;
emit UORSRemoved(uors);
}
// ── UORS operator potrdi plačilo ──────────────────────────────────────────
/**
* @notice UORS operator zapiše potrjeno plačilo stranke.
* @param commitment Poseidon hash asseta (iz UORS portala)
* @param payer Strankina denarnica
* @param amountEurCents Znesek v centih (€50 = 5000)
* @param paymentRef Referenca plačila (račun, SEPA ref, Stripe ID, TxHash...)
*/
function recordPayment(
uint256 commitment,
address payer,
uint256 amountEurCents,
string calldata paymentRef
) external onlyUORS {
require(commitment != 0, "Zero commitment");
require(payer != address(0), "Zero payer");
require(amountEurCents > 0, "Zero amount");
require(records[commitment].timestamp == 0, "Already recorded");
records[commitment] = ServiceRecord({
commitment: commitment,
payer: payer,
uorsOperator: msg.sender,
amountEurCents: amountEurCents,
timestamp: block.timestamp,
paymentRef: paymentRef,
mintReady: false
});
commitmentList.push(commitment);
revenueByUORS[msg.sender] += amountEurCents;
totalRevenueEurCents += amountEurCents;
emit PaymentRecorded(commitment, payer, msg.sender, amountEurCents, paymentRef);
}
/**
* @notice UORS označi commitment kot mint-ready po registerCommitment v ZKVerifier.
*/
function setMintReady(uint256 commitment) external onlyUORS {
require(records[commitment].timestamp != 0, "Not recorded");
require(records[commitment].uorsOperator == msg.sender, "Not your record");
records[commitment].mintReady = true;
emit MintReadySet(commitment);
}
// ── View funkcije ─────────────────────────────────────────────────────────
function getRecord(uint256 commitment) external view returns (ServiceRecord memory) {
return records[commitment];
}
function registrationCount() external view returns (uint256) {
return commitmentList.length;
}
function uorsCount() external view returns (uint256) {
return uorsList.length;
}
/// @notice Onemogoceno: renounceOwnership() bi trajno onemogocil
/// dodajanje/odstranjevanje UORS operatorjev.
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
}
contracts/Marketplace.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "./AdminZamik.sol";
/**
* @title Marketplace
* @notice Primarni in sekundarni trg za RWA tokene na mojdelez.si
*
* Tok:
* 1. Prodajalec listira X RWA tokenov po ceni P USDC/token (opcijsko dovoli ponudbe)
* 2. Kupec lahko kupi takoj (fiksna cena) ALI odda ponudbo (zaklene USDC v escrow)
* 3. Prodajalec lahko ponudbo sprejme (prenos RWA kupcu, USDC prodajalcu)
* 4. Portal zaračuna 0.5% marketplace fee na vsak uspešen nakup/sprejeto ponudbo
*
* Opomba: RWAToken._update zaračuna 0.5% transfer fee ob vsakem prenosu RWA tokenov.
*
* ─────────────────────────────────────────────────────────────────────────────
* PRIMARNI IN SEKUNDARNI TRG
*
* Primarna je prodaja tistega naslova, ki je sredstvo prejel ob tokenizaciji
* (`RWAToken.prvotniLastnik`). Zanjo velja ena omejitev: cena mora biti enaka
* overjeni cenitvi, deljeni s stevilom enot. Overitev s tem ni vec zgolj
* podatek, ampak varovalo ob vstopu.
*
* Sekundarna je vsaka nadaljnja prodaja. Cena je prosta — kdor je na primarnem
* trgu kupil, s svojim premozenjem razpolaga sam.
*
* ⚠️ Zastavice `isPrimary` pogodba NE sprejme kot argument, ampak jo izracuna.
* Argument bi pomenil, da prodajalec omejitev izklopi z eno besedo — trznica
* pa bi ob oznaki "PRIMARNI" narisala znacko, ki nicesar ne trdi.
*
* ⚠️ Omejitev cene mora veljati na VSEH treh poteh, ki ceno dolocijo:
* `list` (nastanek oglasa), `updateListing` (sprememba cene) in ponudbe
* kupcev (`makeOffer`/`acceptOffer`). Varovanje samo ob nastanku bi bilo
* varovalo, ki preveri prvo od treh dejanj.
*
* ⚠️ Cena SLEDI cenitvi: ko overitelj cenitev spremeni (skozi napoved in zamik),
* se primarna cena premakne z njo. Fiksna cena brez izhoda bi pomenila
* zamrznjen primarni trg, popust mimo cenitve pa bi overitev spet izpraznil.
* ─────────────────────────────────────────────────────────────────────────────
*/
interface IKYCWhitelist {
function isWhitelisted(address account) external view returns (bool);
}
interface IRWAToken {
/// @notice Naslov, ki je prejel prvo (in edino) kovanje sredstva.
function prvotniLastnik() external view returns (address);
/// @notice Overjena cenitev sredstva, 18 decimalk.
function assetValueUSD() external view returns (uint256);
function totalSupply() external view returns (uint256);
}
contract Marketplace is Ownable, ReentrancyGuard, Pausable, AdminZamik {
using SafeERC20 for IERC20;
// --- Konstante ---
uint256 public constant MARKETPLACE_FEE_BPS = 50; // 0.5%
// --- State ---
address public feeRecipient;
address public usdc; // USDC contract na PulseChain
address public kyc; // KYCWhitelist; ce address(0), je KYC gating IZKLOPLJEN (testno)
uint256 public nextListingId;
uint256 public nextOfferId;
// --- Escrow racunovodstvo (zascita pred dvigom tujih sredstev) ---
/// @notice USDC, zaklenjen v aktivnih ponudbah kupcev.
uint256 public escrowedUsdc;
/// @notice RWA tokeni po naslovu, zaklenjeni v aktivnih listingih.
mapping(address => uint256) public escrowedRwa;
struct Listing {
address seller;
address rwaToken; // kateri RWA asset
uint256 amount; // koliko RWA tokenov je na prodaj
uint256 pricePerToken; // cena v USDC (6 decimals) za 1 RWA token (18 decimals)
bool active;
bool isPrimary; // IZRACUNAN, ne vpisan: prodajalec == RWAToken.prvotniLastnik
bool acceptsOffers; // NOVO: true = kupci lahko oddajo svoje ponudbe
}
struct Offer {
address buyer;
uint256 listingId;
uint256 amount; // količina RWA (18 decimals)
uint256 pricePerToken; // ponujena cena v USDC (6 decimals)
uint256 lockedUsdc; // dejanski znesek zaklenjenega USDC-ja (escrow)
bool active; // true = ponudba čaka na odgovor
}
mapping(uint256 => Listing) public listings;
mapping(uint256 => Offer) public offers;
// rwaToken → seznam aktivnih listing ID-jev
mapping(address => uint256[]) public tokenListings;
// listingId → seznam vseh ponudb (offer ID-jev)
mapping(uint256 => uint256[]) public listingOffers;
// --- Events ---
event Listed(
uint256 indexed listingId,
address indexed seller,
address indexed rwaToken,
uint256 amount,
uint256 pricePerToken,
bool isPrimary,
bool acceptsOffers
);
event Purchased(
uint256 indexed listingId,
address indexed buyer,
uint256 amount,
uint256 totalUsdc,
uint256 fee
);
event ListingCancelled(uint256 indexed listingId, address indexed seller);
event ListingUpdated(uint256 indexed listingId, uint256 newPrice, uint256 newAmount);
event FeeRecipientUpdated(address indexed newRecipient);
// Novi eventi za modul ponudb
event OfferMade(
uint256 indexed offerId,
uint256 indexed listingId,
address indexed buyer,
uint256 amount,
uint256 pricePerToken,
uint256 lockedUsdc
);
event OfferAccepted(
uint256 indexed offerId,
uint256 indexed listingId,
address indexed buyer,
uint256 amount,
uint256 lockedUsdc
);
event OfferCancelled(uint256 indexed offerId, address indexed by);
// --- Constructor ---
constructor(address _usdc, address _feeRecipient, address _kyc) Ownable(msg.sender) {
require(_usdc != address(0), "USDC zero address");
require(_feeRecipient != address(0), "Fee recipient zero address");
usdc = _usdc;
feeRecipient = _feeRecipient;
kyc = _kyc; // lahko address(0) = KYC gating izklopljen (testno)
}
// --- KYC ---
/// @notice Ce je kyc nastavljen, mora klicatelj biti na beli listi.
modifier onlyVerified() {
require(kyc == address(0) || IKYCWhitelist(kyc).isWhitelisted(msg.sender), "Zahtevan KYC pregled");
_;
}
/// @notice 1. korak: javno napovej zamenjavo KYC liste.
/// @dev Zamenjava KYC liste je najmocnejsa nastavitev, ki jo owner ima —
/// napacna ali podtaknjena lista lahko komurkoli odpre trgovanje ali
/// posteno preverjenim zapre. Zato javno in z zamikom.
function napoviSetKyc(address _kyc) external onlyOwner {
_napovej(keccak256(abi.encode("setKyc", _kyc)), "Marketplace.setKyc");
}
/// @notice 2. korak: izvedi, ko je zamik potekel. Velja SAMO za napovedani naslov.
function setKyc(address _kyc) external onlyOwner {
_porabi(keccak256(abi.encode("setKyc", _kyc)), "Marketplace.setKyc");
kyc = _kyc;
}
// --- Primarna cena ---
/**
* @notice Ali bi bil oglas tega naslova za to sredstvo PRIMARNI.
* @dev Bere se z zetona; prodajalec na to nima vpliva. Sredstvo, ki se ni
* skovano, ima `prvotniLastnik == address(0)` — takrat ni primarno za
* nikogar (in oglasa tudi ni mogoce ustvariti, ker ni enot).
*/
function jePrimarni(address rwaToken, address prodajalec) public view returns (bool) {
// Zeton, ki prvotnega lastnika ne pove, se NE steje za sekundarnega —
// oglas se zavrne z razlogom. "Ne vem" ni "sekundarno": sicer bi vsak
// tuji ERC-20 dobil prosto ceno pod vmesnikom, ki je videti overjen.
try IRWAToken(rwaToken).prvotniLastnik() returns (address pl) {
return prodajalec != address(0) && pl == prodajalec;
} catch {
revert("Zeton ne pove prvotnega lastnika");
}
}
/**
* @notice Cena, po kateri je sredstvo mogoce prodati na PRIMARNEM trgu.
* @return cena USDC (6 decimalk) za eno celo enoto
*
* @dev `assetValueUSD` ima 18 decimalk, `totalSupply` prav tako, cena pa 6 —
* od tod mnozenje z 1e6. Primer: 3.500 USD na 100 enot da 35,00 USDC.
*
* Deljenje je celostevilsko in se odreze navzdol. Posledica je zapisana
* vnaprej: pri neugodnem razmerju je iztrzek za drobec nizji od cenitve.
* Cene NE zaokrozujemo navzgor — sicer bi bila prodaja nad overjeno
* vrednostjo, kar je natanko tisto, kar naj bi omejitev preprecila.
*
* Stran to vrednost prebere in z njo napolni polje, zato prodajalec ne
* ugiba in se zaokrozitev na obeh straneh ne more razhajati.
*/
function primarnaCena(address rwaToken) public view returns (uint256) {
uint256 enot = IRWAToken(rwaToken).totalSupply();
require(enot > 0, "Sredstvo se ni skovano");
return IRWAToken(rwaToken).assetValueUSD() * 1e6 / enot;
}
// --- Listing ---
/**
* @notice Ustvari nov listing. Ali je PRIMARNI, izracuna pogodba sama.
* @param rwaToken naslov RWA ERC-20 kontrakta
* @param amount število RWA tokenov za prodajo (18 decimals)
* @param pricePerToken cena v USDC za 1 RWA token (6 decimals);
* pri primarnem oglasu se mora ujemati s `primarnaCena(rwaToken)`
* @param acceptsOffers true = omogoči zbiranje ponudb na oglasu;
* pri primarnem oglasu mora biti false (ponudba je pot mimo cenitve)
*
* @dev NAMERNO brez onlyVerified: kdor ima RWA tokene za listanje, je zaradi
* KYC restrikcije v RWAToken._update ze preverjen (nepreverjen naslov jih
* sploh ne more prejeti). Dodaten gating tu bi bil odvecen, prodajalcu pa
* bi lahko zaprl pot do prodaje.
*/
function list(
address rwaToken,
uint256 amount,
uint256 pricePerToken,
bool acceptsOffers
) external nonReentrant whenNotPaused returns (uint256 listingId) {
bool isPrimary = jePrimarni(rwaToken, msg.sender);
if (isPrimary) {
require(pricePerToken == primarnaCena(rwaToken),
"Primarna cena mora biti enaka cenitvi");
require(!acceptsOffers, "Primarni oglas ne sprejema ponudb");
}
require(rwaToken != address(0), "Invalid token");
require(amount > 0, "Amount must be > 0");
require(pricePerToken > 0, "Price must be > 0");
// Preveri da prodajalec ima dovolj tokenov
require(
IERC20(rwaToken).balanceOf(msg.sender) >= amount,
"Insufficient RWA balance"
);
// Escrow: prenesi RWA tokene v Marketplace kontrakt
// (RWAToken._update bo zaračunal 0.5% transfer fee — tokeni prispejo zmanjšani)
uint256 beforeBalance = IERC20(rwaToken).balanceOf(address(this));
IERC20(rwaToken).safeTransferFrom(msg.sender, address(this), amount);
uint256 afterBalance = IERC20(rwaToken).balanceOf(address(this));
uint256 received = afterBalance - beforeBalance; // dejanski prejeti znesek (po RWA fee)
escrowedRwa[rwaToken] += received;
listingId = nextListingId++;
listings[listingId] = Listing({
seller: msg.sender,
rwaToken: rwaToken,
amount: received, // dejansko shranjeni tokeni (po 0.5% RWA transfer fee)
pricePerToken: pricePerToken,
active: true,
isPrimary: isPrimary,
acceptsOffers: acceptsOffers
});
tokenListings[rwaToken].push(listingId);
emit Listed(listingId, msg.sender, rwaToken, received, pricePerToken, isPrimary, acceptsOffers);
}
/**
* @notice Kupi RWA tokene iz listinga (Fiksna cena)
*/
function buy(uint256 listingId, uint256 amount) external nonReentrant whenNotPaused onlyVerified {
Listing storage l = listings[listingId];
require(l.active, "Listing not active");
require(amount > 0 && amount <= l.amount, "Invalid amount");
require(msg.sender != l.seller, "Cannot buy your own listing");
// Izračun USDC stroškov
uint256 totalUsdc = amount * l.pricePerToken / 1e18;
require(totalUsdc > 0, "Price too low");
// Marketplace fee: 0.5% od kupca
uint256 fee = totalUsdc * MARKETPLACE_FEE_BPS / 10_000;
uint256 sellerReceives = totalUsdc - fee;
// Kupec plača USDC
IERC20(usdc).safeTransferFrom(msg.sender, l.seller, sellerReceives);
IERC20(usdc).safeTransferFrom(msg.sender, feeRecipient, fee);
// Posodobi listing
l.amount -= amount;
if (l.amount == 0) {
l.active = false;
}
escrowedRwa[l.rwaToken] -= amount;
// Pošlji RWA tokene kupcu
IERC20(l.rwaToken).safeTransfer(msg.sender, amount);
emit Purchased(listingId, msg.sender, amount, totalUsdc, fee);
}
/**
* @notice Prekliči listing in vrni RWA tokene prodajalcu
*/
function cancelListing(uint256 listingId) external nonReentrant {
Listing storage l = listings[listingId];
require(l.active, "Not active");
require(msg.sender == l.seller || msg.sender == owner(), "Not authorized");
l.active = false;
// Vrni tokene prodajalcu
uint256 vrniAmount = l.amount;
l.amount = 0;
escrowedRwa[l.rwaToken] -= vrniAmount;
IERC20(l.rwaToken).safeTransfer(l.seller, vrniAmount);
emit ListingCancelled(listingId, l.seller);
}
/**
* @notice Posodobi ceno ali količino obstoječega listinga
*/
function updateListing(
uint256 listingId,
uint256 newPricePerToken,
uint256 additionalAmount
) external nonReentrant {
Listing storage l = listings[listingId];
require(l.active, "Not active");
require(msg.sender == l.seller, "Not seller");
require(newPricePerToken > 0, "Price must be > 0");
// Druga od treh poti, ki dolocijo ceno. Brez te bi prodajalec oglas
// ustvaril po cenitvi in ga takoj zatem prestavil na poljubno ceno.
// Nova cena se sme spremeniti samo tako, da SLEDI cenitvi: ko overitelj
// `assetValueUSD` spremeni (skozi napoved in zamik), se sme oglas
// uskladiti — drugam ne more.
if (l.isPrimary) {
require(newPricePerToken == primarnaCena(l.rwaToken),
"Primarna cena mora biti enaka cenitvi");
}
if (newPricePerToken != l.pricePerToken) {
l.pricePerToken = newPricePerToken;
}
if (additionalAmount > 0) {
uint256 before = IERC20(l.rwaToken).balanceOf(address(this));
IERC20(l.rwaToken).safeTransferFrom(msg.sender, address(this), additionalAmount);
uint256 received = IERC20(l.rwaToken).balanceOf(address(this)) - before;
l.amount += received;
escrowedRwa[l.rwaToken] += received;
}
emit ListingUpdated(listingId, newPricePerToken, l.amount);
}
// --- Modul: Daj Ponudbo (Make an Offer) ---
/**
* @notice Kupec odda ponudbo in zaklene svoj USDC v pametno pogodbo
*/
function makeOffer(uint256 listingId, uint256 amount, uint256 pricePerToken) external nonReentrant whenNotPaused onlyVerified {
Listing storage l = listings[listingId];
require(l.active, "Listing not active");
// Tretja od treh poti, ki dolocijo ceno. `acceptsOffers` je pri primarnem
// oglasu ze zavrnjen ob nastanku; ta vrstica je namerno se enkrat, ker se
// omejitev ne sme opirati na to, da je druga zastavica pravilno nastavljena.
require(!l.isPrimary, "Na primarnem oglasu ponudbe niso mogoce");
require(l.acceptsOffers, "Offers not allowed on this listing");
require(amount > 0 && amount <= l.amount, "Invalid amount");
require(pricePerToken > 0, "Price must be > 0");
require(msg.sender != l.seller, "Cannot bid on own listing");
uint256 totalUsdc = amount * pricePerToken / 1e18;
require(totalUsdc > 0, "Total USDC too low");
uint256 offerId = nextOfferId++;
// Escrow: Zakleni kupčev USDC v pogodbo
IERC20(usdc).safeTransferFrom(msg.sender, address(this), totalUsdc);
escrowedUsdc += totalUsdc;
offers[offerId] = Offer({
buyer: msg.sender,
listingId: listingId,
amount: amount,
pricePerToken: pricePerToken,
lockedUsdc: totalUsdc,
active: true
});
listingOffers[listingId].push(offerId);
emit OfferMade(offerId, listingId, msg.sender, amount, pricePerToken, totalUsdc);
}
/**
* @notice Prodajalec sprejme ponudbo. RWA gre kupcu, USDC prodajalcu.
*/
function acceptOffer(uint256 offerId) external nonReentrant whenNotPaused {
Offer storage o = offers[offerId];
require(o.active, "Offer not active");
Listing storage l = listings[o.listingId];
require(l.active, "Listing not active");
require(msg.sender == l.seller, "Only seller can accept");
// Če se je listing medtem delno razprodal, ponudba morda presega zalogo
require(o.amount <= l.amount, "Offer amount exceeds available RWA");
o.active = false;
// Izračun feejev (od zaklenjenega USDC zneska)
uint256 fee = o.lockedUsdc * MARKETPLACE_FEE_BPS / 10_000;
uint256 sellerReceives = o.lockedUsdc - fee;
// Posodobi zalogo v listingu
l.amount -= o.amount;
if (l.amount == 0) {
l.active = false;
}
// Sprosti escrow racunovodstvo (USDC gre prodajalcu+fee, RWA kupcu)
escrowedUsdc -= o.lockedUsdc;
escrowedRwa[l.rwaToken] -= o.amount;
// 1. Prenos USDC (iz escrowa pogodbe → prodajalcu in marketplaceu)
IERC20(usdc).safeTransfer(l.seller, sellerReceives);
IERC20(usdc).safeTransfer(feeRecipient, fee);
// 2. Prenos RWA (iz escrowa pogodbe → kupcu)
// Opozorilo: RWAToken pogodba bo kupcu odštela 0.5% pri prejemu,
// ampak iz Marketplace pogodbe gre točen amount, kar ohranja natančno računovodstvo.
IERC20(l.rwaToken).safeTransfer(o.buyer, o.amount);
emit OfferAccepted(offerId, o.listingId, o.buyer, o.amount, o.lockedUsdc);
}
/**
* @notice Kupec umakne ponudbo ali prodajalec zavrne ponudbo (USDC se vrne kupcu)
*/
function cancelOffer(uint256 offerId) external nonReentrant {
Offer storage o = offers[offerId];
require(o.active, "Offer not active");
Listing storage l = listings[o.listingId];
// Preklic lahko sproži samo kupec ALI lastnik listinga (kot zavrnitev)
require(msg.sender == o.buyer || msg.sender == l.seller, "Not authorized to cancel");
o.active = false;
// Vrni zaklenjeni USDC kupcu
escrowedUsdc -= o.lockedUsdc;
IERC20(usdc).safeTransfer(o.buyer, o.lockedUsdc);
emit OfferCancelled(offerId, msg.sender);
}
// --- View funkcije ---
/**
* @notice Vrni vse aktivne listinge za določen RWA token
*/
function getActiveListings(address rwaToken) external view returns (uint256[] memory activeIds) {
uint256[] storage all = tokenListings[rwaToken];
uint256 count = 0;
for (uint256 i = 0; i < all.length; i++) {
if (listings[all[i]].active) count++;
}
activeIds = new uint256[](count);
uint256 idx = 0;
for (uint256 i = 0; i < all.length; i++) {
if (listings[all[i]].active) {
activeIds[idx++] = all[i];
}
}
}
/**
* @notice Vrni vse aktivne ponudbe za specifičen listing
*/
function getActiveOffers(uint256 listingId) external view returns (uint256[] memory activeOfferIds) {
uint256[] storage all = listingOffers[listingId];
uint256 count = 0;
for (uint256 i = 0; i < all.length; i++) {
if (offers[all[i]].active) count++;
}
activeOfferIds = new uint256[](count);
uint256 idx = 0;
for (uint256 i = 0; i < all.length; i++) {
if (offers[all[i]].active) {
activeOfferIds[idx++] = all[i];
}
}
}
/**
* @notice Izračun skupnega USDC za fiksni nakup
*/
function quoteBuy(uint256 listingId, uint256 amount)
external view
returns (uint256 totalUsdc, uint256 fee, uint256 sellerReceives)
{
Listing storage l = listings[listingId];
require(l.active, "Not active");
require(amount <= l.amount, "Exceeds available");
totalUsdc = amount * l.pricePerToken / 1e18;
fee = totalUsdc * MARKETPLACE_FEE_BPS / 10_000;
sellerReceives = totalUsdc - fee;
}
/**
* @notice Izračun stroškov pri oddaji ponudbe brez predhodnega kreiranja
*/
function quoteOffer(uint256 amount, uint256 pricePerToken)
external pure
returns (uint256 totalUsdc, uint256 fee, uint256 sellerReceives)
{
totalUsdc = amount * pricePerToken / 1e18;
fee = totalUsdc * MARKETPLACE_FEE_BPS / 10_000;
sellerReceives = totalUsdc - fee;
}
// --- Admin ---
function napoviSetFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), "Zero address");
_napovej(keccak256(abi.encode("setFeeRecipient", _feeRecipient)), "Marketplace.setFeeRecipient");
}
function setFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), "Zero address");
_porabi(keccak256(abi.encode("setFeeRecipient", _feeRecipient)), "Marketplace.setFeeRecipient");
feeRecipient = _feeRecipient;
emit FeeRecipientUpdated(_feeRecipient);
}
/**
* @notice Zamenjava placilnega tokena — dovoljena SAMO, ko ni zaklenjenega USDC.
* @dev Zamenjava med aktivnimi ponudbami bi pomenila, da `cancelOffer` vrne
* drug token od zaklenjenega, zato je takrat onemogocena.
*/
function napoviSetUsdc(address _usdc) external onlyOwner {
require(_usdc != address(0), "Zero address");
_napovej(keccak256(abi.encode("setUsdc", _usdc)), "Marketplace.setUsdc");
}
function setUsdc(address _usdc) external onlyOwner {
require(_usdc != address(0), "Zero address");
require(escrowedUsdc == 0, "Escrow ni prazen");
_porabi(keccak256(abi.encode("setUsdc", _usdc)), "Marketplace.setUsdc");
usdc = _usdc;
}
// --- Zasilna zavora ---
/**
* Zakaj ima pravico ustaviti VEC naslovov, spreminjati nastavitve pa samo owner:
* skoda, ki jo zmore podtaknjen zaustavljalec, je ustavljeno trgovanje —
* neprijetno, a ni kraje, in owner ga z unpause() takoj razveljavi. Skoda
* zaradi PREPOZNE zaustavitve pa je lahko trajna. Zato je pri tej edini
* funkciji hitrost vredna vec kot ozek dostop.
*
* Ledger je lahko v sefu; nadzorni sistem in rezervna denarnica ne.
*/
mapping(address => bool) public jeZaustavljalec;
event ZaustavljalecSpremenjen(address indexed kdo, bool lahko);
/// @dev Takojsno v obe smeri. Podelitev je nizko tveganje, odvzem pa mora
/// biti hiter — ob razkritem kljucu ga ne smes cakati 48 ur odstraniti.
function nastaviZaustavljalca(address kdo, bool lahko) external onlyOwner {
require(kdo != address(0), "Zero address");
jeZaustavljalec[kdo] = lahko;
emit ZaustavljalecSpremenjen(kdo, lahko);
}
/// @notice Ustavi NOVE posle (list/buy/makeOffer/acceptOffer).
/// @dev Izhodi (cancelListing, cancelOffer, updateListing) NAMERNO ostanejo
/// delujoci tudi med pavzo — uporabnik mora vedno lahko do svojih sredstev.
function pause() external {
require(msg.sender == owner() || jeZaustavljalec[msg.sender], "Ni pooblascen za zaustavitev");
_pause();
}
/// @dev Zagon nazaj ostane samo pri ownerju: ustaviti mora biti lahko,
/// spet odpreti pa tezko.
function unpause() external onlyOwner {
_unpause();
}
/**
* @notice Emergency: owner lahko dvigne SAMO presezek nad zaklenjenimi sredstvi
* (npr. pomotoma poslane tokene).
* @dev Zaklenjenih sredstev (USDC v ponudbah kupcev, RWA v aktivnih oglasih)
* s to funkcijo ni mogoce dvigniti: `escrowedUsdc` / `escrowedRwa` tvorita
* spodnjo mejo, ki je lastnik ne more prebiti.
*/
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
uint256 zaklenjeno = token == usdc ? escrowedUsdc : escrowedRwa[token];
uint256 stanje = IERC20(token).balanceOf(address(this));
require(stanje >= zaklenjeno, "Nekonsistenten escrow");
require(stanje - zaklenjeno >= amount, "Presega prosti presezek");
IERC20(token).safeTransfer(owner(), amount);
}
/// @notice Onemogoceno: pomotoma poslan renounceOwnership() bi trajno ubil
/// pause(), fee nastavitve in KYC preklop.
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
}
contracts/TestnaPipa.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title TestnaPipa
* @notice Pipa testnega USDC za preizkus nakupa na testnem omrezju.
*
* Vsak naslov sme dobiti `znesek` enkrat na `premor` sekund. Plin placa
* klicatelj sam. Pogodba ne hrani nobenega kljuca in ne odloca o nicemer
* drugem: zalogo napolni lastnik z navadnim prenosom, preostanek lahko dvigne.
*
* Na produkcijski verigi (chainid 369) je ni mogoce postaviti.
*/
contract TestnaPipa is Ownable {
using SafeERC20 for IERC20;
uint256 private constant PRODUKCIJA = 369;
IERC20 public immutable zeton;
uint256 public immutable znesek;
uint256 public immutable premor;
mapping(address => uint256) public zadnjic;
event Izplacano(address indexed prejemnik, uint256 znesek);
event Dvignjeno(address indexed kam, uint256 znesek);
constructor(address _zeton, uint256 _znesek, uint256 _premor) Ownable(msg.sender) {
require(block.chainid != PRODUKCIJA, "Samo testno omrezje");
require(_zeton != address(0), "Zeton je nujen");
require(_znesek > 0, "Znesek je 0");
zeton = IERC20(_zeton);
znesek = _znesek;
premor = _premor;
}
/// @notice Kdaj sme naslov znova dobiti (0 = takoj).
function naslednjic(address kdo) public view returns (uint256) {
uint256 z = zadnjic[kdo];
return z == 0 ? 0 : z + premor;
}
/// @notice Zaloga v pipi.
function zaloga() external view returns (uint256) {
return zeton.balanceOf(address(this));
}
function dobi() external {
uint256 n = naslednjic(msg.sender);
require(n == 0 || block.timestamp >= n, "Prezgodaj");
require(zeton.balanceOf(address(this)) >= znesek, "Pipa je prazna");
zadnjic[msg.sender] = block.timestamp;
zeton.safeTransfer(msg.sender, znesek);
emit Izplacano(msg.sender, znesek);
}
function dvigni(uint256 koliko) external onlyOwner {
zeton.safeTransfer(owner(), koliko);
emit Dvignjeno(owner(), koliko);
}
/// @notice Onemogoceno: brez lastnika zaloge ne bi bilo mogoce dvigniti.
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
}
@openzeppelin/contracts/utils/Pausable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
contracts/KYCWhitelist.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title KYCWhitelist
* @notice Bela lista KYC-preverjenih naslovov za trgovanje na mojdelez.si.
*
* TA KONTRAKT JE MAINNET-VARIANTA: na belo listo doda SAMO owner
* (approveKYC / approveBatch), ki ga klice pravi (placljivi) KYC ponudnik po
* opravljenem preverjanju osebe. Samopostrezne bele liste TU NI.
*
* TESTNET: uporabi contracts/KYCWhitelistTestnet.sol, ki temu kontraktu doda
* mockApproveMe() za samopostrezno testiranje. Locitev je namerna — mock se
* tako FIZICNO ne more znajti na mainnetu (ni odvisno od tega, ali se kdo
* spomni izbrisati funkcijo pred deployem).
*/
contract KYCWhitelist is Ownable {
mapping(address => bool) public isWhitelisted;
event KYCApproved(address indexed account);
event KYCRevoked(address indexed account);
constructor(address initialOwner) Ownable(initialOwner) {}
/// @notice Owner (ali pravi KYC backend) doda preverjen naslov.
function approveKYC(address account) external onlyOwner {
require(account != address(0), "Zero address");
isWhitelisted[account] = true;
emit KYCApproved(account);
}
/// @notice Batch dodajanje vec naslovov hkrati (owner).
function approveBatch(address[] calldata accounts) external onlyOwner {
for (uint256 i = 0; i < accounts.length; i++) {
if (accounts[i] != address(0)) {
isWhitelisted[accounts[i]] = true;
emit KYCApproved(accounts[i]);
}
}
}
function revokeKYC(address account) external onlyOwner {
isWhitelisted[account] = false;
emit KYCRevoked(account);
}
/// @notice Onemogoceno: pomotoma poslan renounceOwnership() bi trajno ubil
/// KYC upravljanje (nihce vec ne bi mogel odobriti/preklicati naslova).
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
}
contracts/ZKVerifier.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./AdminZamik.sol";
interface IRWAToken {
function mintWithProof(address to, uint256 amount, bytes32 proofHash) external;
function mintFee(address to, uint256 amount) external;
function assetValueUSD() external view returns (uint256);
function feeRecipient() external view returns (address);
}
/**
* @title ZKVerifier
* @notice Verifies Groth16 ZK proofs for RWA ownership claims
*
* Security fixes (v2):
* - recipient mora biti msg.sender → prepreči frontrunning
* - amount je omejen na maxMintable (izračunan iz assetValueUSD) → prepreči neomejeni mint
* - amount > 0 check
* - proofHash vključuje recipient in amount
* - registeredCommitments počiščen po mintu
* - setRwaToken odstranjen (mrtva koda — konstruktor že nastavi token)
*/
contract ZKVerifier is Ownable, AdminZamik {
// === snarkjs-generated VK constants (from rwa_proof_verifier.sol) ===
// Scalar field size
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// Verification Key data
uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042;
uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958;
uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132;
uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731;
uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679;
uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856;
uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant deltax1 = 7634694382555350249182314939945794752801670761871442236918755611747247981945;
uint256 constant deltax2 = 7785580959050401778007424472707980943542624615417776150098749352587788464225;
uint256 constant deltay1 = 19797755760015837479531733607381070868376789853134730363187779619202029563244;
uint256 constant deltay2 = 19885317433472164527823093690594480541727870532036884219862146880419864556528;
uint256 constant IC0x = 5313285605904455468883044812252347068829101215429313644709835484872601966339;
uint256 constant IC0y = 2611852344649716617799171908939889777235094010478351269767823832353876722738;
uint256 constant IC1x = 3721507159761245641438686913490987492444926420712387384343195655500770466953;
uint256 constant IC1y = 10884128966892936170203469452538504289719178213118369098919656563615016733370;
uint256 constant IC2x = 11919587884350751358344086380913156139705038053372491645096979466761922566199;
uint256 constant IC2y = 11737085826314024049430266969005569805271747491015946543900350977318458074708;
// Memory data
uint16 constant pVk = 0;
uint16 constant pPairing = 128;
uint16 constant pLastMem = 896;
// === end generated constants ===
address public entropyOracle;
/**
* VEC SREDSTEV, VSAKO SVOJ ZETON
*
* En ZKVerifier strezhe vsem zetonom; vsak zeton predstavlja eno sredstvo.
* Commitment se registrira za DOLOCEN zeton, zato dokaz, izdan za sredstvo A,
* ne more kovati na sredstvu B, tudi ce je sam po sebi veljaven.
*
* Vez med commitmentom in zetonom je stanje na verigi, ne javni signal
* dokaza; ZK vezje je zato neodvisno od stevila sredstev.
*/
mapping(address => bool) public pooblasceniZetoni; // kateri zetoni smejo kovati prek tega verifierja
address public tovarna; // RWATokenFactory — sme registrirati svoje zetone
// Asset registry: samo vnaprej registrirani assetCommitment-i lahko mintajo.
// assetCommitment = Poseidon(privateKey, entropyNonce) = javni signal _pubSignals[0]
mapping(uint256 => address) public commitmentZeton; // commitment → za KATERI zeton je registriran
mapping(uint256 => bool) public commitmentUsed; // porabljeno (vsak asset minta enkrat)
/// @notice Ali je commitment registriran (za katerikoli zeton).
function registeredCommitments(uint256 commitment) external view returns (bool) {
return commitmentZeton[commitment] != address(0);
}
// Pooblaščeni UORS operaterji — smejo registrirati commitmente
mapping(address => bool) public authorizedUORS;
// Tokenizacijska provizija: 0,5% ob mintu
uint256 public constant MINT_FEE_BPS = 50; // 50 = 0,5%
event ProofVerified(address indexed claimant, bytes32 proofHash);
event TovarnaNastavljena(address indexed tovarna);
event ZetonPooblascen(address indexed zeton);
event ZetonOdvzet(address indexed zeton);
event MintFeeCollected(address indexed feeRecipient, uint256 fee);
event AssetRegistered(uint256 indexed commitment, address indexed zeton);
event AssetUnregistered(uint256 indexed commitment);
event AssetMinted(uint256 indexed commitment, address indexed recipient, uint256 amount, address indexed zeton);
event UORSAuthorized(address indexed uors);
event UORSRevoked(address indexed uors);
event Zaustavljeno(address indexed kdo);
event Sproscen(address indexed kdo);
// ─────────────────────────────────────────────────────────────────────────
// SISTEMSKA ZAUSTAVITEV
//
// Zaustavitev velja za VSE kovanje hkrati in ne presoja posameznega sredstva;
// zamrznitev posameznega zetona je v pristojnosti pooblascenega UORS
// operaterja (glej RWAToken.zamrzni).
//
// `pause` sme sprozit vec naslovov (lastnik + pooblasceni zaustavljalci,
// npr. nadzorni proces), `unpause` pa samo lastnik: ustavitev mora biti
// hitra, ponoven zagon pa premisljen.
// ─────────────────────────────────────────────────────────────────────────
bool public paused;
mapping(address => bool) public jeZaustavljalec;
event ZaustavljalecSpremenjen(address indexed kdo, bool lahko);
/// @dev Takojsno v obe smeri: ob razkritem kljucu odvzema ne smes cakati.
function nastaviZaustavljalca(address kdo, bool lahko) external onlyOwner {
require(kdo != address(0), "Zero address");
jeZaustavljalec[kdo] = lahko;
emit ZaustavljalecSpremenjen(kdo, lahko);
}
function pause() external {
require(msg.sender == owner() || jeZaustavljalec[msg.sender], "Ni pooblascen za zaustavitev");
paused = true;
emit Zaustavljeno(msg.sender);
}
function unpause() external onlyOwner {
paused = false;
emit Sproscen(msg.sender);
}
/// @dev Registracija commitmenta je izjava overitelja (UORS), zato je skrbnik
/// (owner) NE more opraviti sam: skrbnik lahko rece samo »ne« (revokeUORS,
/// unregisterCommitment, pause), nikoli »da«.
modifier onlyUORS() {
require(authorizedUORS[msg.sender], "Not authorized");
_;
}
/// @dev Krozne odvisnosti ni vec: verifier se postavi PRVI, zetoni pridejo za njim.
constructor(address _entropyOracle) Ownable(msg.sender) {
entropyOracle = _entropyOracle;
}
// ─────────────────────────────────────────────────────────────────────────
// Pooblascanje zetonov
//
// Kdor sme dodati zeton, sme kovati nova sredstva — to je enako mocno kot
// pooblastitev UORS operaterja. Zato gre owner-jeva pot SKOZI ZAMIK.
//
// Tovarna je izjema in mora biti: ce bi vsako novo sredstvo cakalo 48 ur,
// bi bilo ustvarjanje sredstev neuporabno. Namesto tega gre skozi zamik
// NASTAVITEV TOVARNE — enkrat, vidno, vnaprej — potem pa tovarna registrira
// samo zetone, ki jih je sama postavila.
// ─────────────────────────────────────────────────────────────────────────
function napoviNastaviTovarno(address _tovarna) external onlyOwner {
require(_tovarna != address(0), "Zero address");
_napovej(keccak256(abi.encode("nastaviTovarno", _tovarna)), "ZKVerifier.nastaviTovarno");
}
function nastaviTovarno(address _tovarna) external onlyOwner {
require(_tovarna != address(0), "Zero address");
_porabi(keccak256(abi.encode("nastaviTovarno", _tovarna)), "ZKVerifier.nastaviTovarno");
tovarna = _tovarna;
emit TovarnaNastavljena(_tovarna);
}
/// @notice Tovarna registrira zeton, ki ga je pravkar postavila.
function registrirajZeton(address zeton) external {
require(msg.sender == tovarna, "Samo tovarna");
require(zeton != address(0), "Zero address");
pooblasceniZetoni[zeton] = true;
emit ZetonPooblascen(zeton);
}
function napoviDodajZeton(address zeton) external onlyOwner {
require(zeton != address(0), "Zero address");
_napovej(keccak256(abi.encode("dodajZeton", zeton)), "ZKVerifier.dodajZeton");
}
/// @notice Rocno pooblastilo zetona (mimo tovarne) — pod zamikom.
function dodajZeton(address zeton) external onlyOwner {
require(zeton != address(0), "Zero address");
_porabi(keccak256(abi.encode("dodajZeton", zeton)), "ZKVerifier.dodajZeton");
pooblasceniZetoni[zeton] = true;
emit ZetonPooblascen(zeton);
}
/// @notice Odvzem je TAKOJSEN — isto merilo kot revokeUORS: nevarnost ne caka.
/// @dev Samo za zeton, ki je res pooblascen: dogodek ZetonOdvzet bereta
/// admin dnevnik in watchdog, zato ne sme pokazati odvzema, ki ni nicesar odvzel.
function odvzemiZeton(address zeton) external onlyOwner {
require(pooblasceniZetoni[zeton], "Zeton ni pooblascen");
pooblasceniZetoni[zeton] = false;
emit ZetonOdvzet(zeton);
}
/**
* @notice Registrira legitimen assetCommitment — samo lastnik (izdajatelj sredstva).
* @dev Lastnik sredstva izračuna commitment = Poseidon(privateKey, entropyNonce)
* in deli SAMO commitment (ne ključa). Izdajatelj ga tu registrira.
*/
/**
* PODELITEV MOCI GRE SKOZI ZAMIK, ODVZEM NE.
*
* Nov UORS lahko registrira commitmente, ti pa so pot do kovanja zetonov.
* Podtaknjen operater je zato med najmocnejsimi napadi, ki jih owner kljuc
* sploh omogoca — in je hkrati napad, ki ga je mogoce VIDETI VNAPREJ.
* Zato javna napoved + zamik.
*
* revokeUORS ostaja takojsen: ob razkritem kljucu operaterja ga moras vreci
* ven zdaj, ne cez 48 ur. Nevarnost caka, zasilna zavora ne.
*/
function napoviAuthorizeUORS(address uors) external onlyOwner {
require(uors != address(0), "Zero address");
_napovej(keccak256(abi.encode("authorizeUORS", uors)), "ZKVerifier.authorizeUORS");
}
/// @notice Pooblasti UORS operaterja za registracijo commitmentov.
function authorizeUORS(address uors) external onlyOwner {
require(uors != address(0), "Zero address");
_porabi(keccak256(abi.encode("authorizeUORS", uors)), "ZKVerifier.authorizeUORS");
authorizedUORS[uors] = true;
emit UORSAuthorized(uors);
}
/// @notice Prekliče pooblastilo UORS operaterja.
function revokeUORS(address uors) external onlyOwner {
authorizedUORS[uors] = false;
emit UORSRevoked(uors);
}
/**
* @notice Registrira commitment ZA DOLOCEN zeton (sredstvo).
* @dev Prav ta vez preprecuje, da bi dokaz za sredstvo A kovav na sredstvu B.
* Brez nje bi vsak veljaven dokaz odprl vsak zeton.
*/
function registerCommitment(uint256 commitment, address zeton) external onlyUORS {
require(commitment != 0, "Zero commitment");
require(pooblasceniZetoni[zeton], "Zeton ni pooblascen");
require(commitmentZeton[commitment] == address(0), "Already registered");
commitmentZeton[commitment] = zeton;
emit AssetRegistered(commitment, zeton);
}
/// @notice Paketna registracija — vsi commitmenti za ISTO sredstvo.
/// @dev Vse ali nic: nicelni ali ze registriran commitment revertira cel paket,
/// enako kot posamicna pot. Polovicno opravljen paket ne sme biti videti
/// enako kot opravljen.
function registerCommitments(uint256[] calldata commitments, address zeton) external onlyUORS {
require(pooblasceniZetoni[zeton], "Zeton ni pooblascen");
for (uint256 i = 0; i < commitments.length; i++) {
uint256 c = commitments[i];
require(c != 0, "Zero commitment");
require(commitmentZeton[c] == address(0), "Already registered");
commitmentZeton[c] = zeton;
emit AssetRegistered(c, zeton);
}
}
/// @notice Prekliče registracijo (le če še ni bil porabljen).
function unregisterCommitment(uint256 commitment) external onlyOwner {
require(commitmentZeton[commitment] != address(0), "Not registered");
require(!commitmentUsed[commitment], "Already used");
commitmentZeton[commitment] = address(0);
emit AssetUnregistered(commitment);
}
/// @notice Onemogoceno: renounceOwnership() bi trajno onemogocil
/// (de)avtorizacijo UORS operatorjev in registracijo commitmentov.
function renounceOwnership() public pure override {
revert("Renounce onemogocen");
}
/**
* @notice Verify ZK proof and mint RWA tokens to caller.
* @dev recipient je vedno msg.sender — prepreči frontrunning.
* amount je omejen na maxMintable = assetValueUSD / 1e16
* (1 RWA token = $0.01 USD pri 18 decimalkah).
* Prilagodi delilnik glede na tvojo tokenomiko.
*/
function verifyAndMint(
uint[2] calldata _pA,
uint[2][2] calldata _pB,
uint[2] calldata _pC,
uint[2] calldata _pubSignals,
address zeton,
uint256 amount
) external {
// FIX #2: recipient je vedno msg.sender — prepreči frontrunning iz mempoola
address recipient = msg.sender;
// FIX #1a: amount > 0
require(amount > 0, "Zero amount");
require(!paused, "Kovanje je zaustavljeno");
require(pooblasceniZetoni[zeton], "Zeton ni pooblascen");
// Asset registry: dokaz mora pripadati registriranemu, še neporabljenemu sredstvu
uint256 commitment = _pubSignals[0];
// ⚠️ Jedro locitve sredstev: commitment mora biti registriran ZA TA zeton.
// Brez te vrstice bi veljaven dokaz za sredstvo A koval na sredstvu B.
require(commitmentZeton[commitment] == zeton, "Commitment ni za ta zeton");
require(!commitmentUsed[commitment], "Asset already minted");
// ─────────────────────────────────────────────────────────────────────
// Kolicina je vezana na DOKAZANO vrednost, ne le na prijavljeno.
//
// Veljati morata obe omejitvi:
// 1. dokazana vrednost (`_pubSignals[1]`) ne sme presegati vrednosti,
// ki jo je za sredstvo prijavil overitelj — ta je zgornja meja,
// 2. kovana kolicina ne sme presegati dokazane vrednosti.
//
// Sama preverba lastnistva torej ne zadosca za poljubno kolicino.
//
// Enote: dokazana vrednost je v celih enotah valute, `assetValueUSD` ima
// 18 decimalk (od tod mnozenje z 1e18); faktor 100 pomeni 1 zeton = 0,01.
// ─────────────────────────────────────────────────────────────────────
// Javni signal se tu se NI preverjen z dokazom (ta je drag in pride zadnji),
// zato je racunanje zapisano tako, da ne more prekoraciti obsega: za
// vsak vhod pride berljiv razlog, nikoli panic.
uint256 dokazanaVrednost = _pubSignals[1];
require(dokazanaVrednost < r, "Javni signal izven polja");
require(dokazanaVrednost > 0, "Dokazana vrednost je 0");
// d * 1e18 <= V <=> d <= V / 1e18 (celostevilsko enakovredno)
require(dokazanaVrednost <= IRWAToken(zeton).assetValueUSD() / 1e18,
"Dokazana vrednost presega prijavljeno");
// amount <= d * 1e20 <=> (amount - 1) / 1e20 < d (amount > 0)
require((amount - 1) / 1e20 < dokazanaVrednost,
"Kolicina presega dokazano vrednost");
require(verifyProof(_pA, _pB, _pC, _pubSignals), "Invalid ZK proof");
commitmentUsed[commitment] = true;
// FIX #3: počisti registracijo po mintu (clean state)
commitmentZeton[commitment] = address(0);
bytes32 proofHash = _odtisDokaza(_pA, _pB, _pC, _pubSignals, recipient, amount, zeton);
uint256 mintFee = amount * MINT_FEE_BPS / 10_000;
IRWAToken(zeton).mintWithProof(recipient, amount - mintFee, proofHash);
// Klic VEDNO, tudi ko se provizija zaokrozi na 0 (amount < 200) —
// sicer zastavica `provizijaOdprta` v zetonu ostane odprta.
address prejemnikProvizije = IRWAToken(zeton).feeRecipient();
IRWAToken(zeton).mintFee(prejemnikProvizije, mintFee);
emit MintFeeCollected(prejemnikProvizije, mintFee);
emit AssetMinted(commitment, recipient, amount - mintFee, zeton);
emit ProofVerified(recipient, proofHash);
}
/**
* @dev Locena funkcija zato, da `verifyAndMint` ne pride v "stack too deep" ob
* Groth16 assembly. Vsebinsko je pomembno TO, kaj je v odtisu:
*
* · recipient in amount — auditability (FIX #4)
* · zeton — sicer bi bil isti odtis veljaven na VEC sredstvih;
* to je razsiritev ugotovitve F8 (ponovitev ne le
* med verigami, ampak med sredstvi)
* · block.chainid — F8 sama: prepreci ponovitev na drugi verigi
*/
function _odtisDokaza(
uint[2] calldata _pA,
uint[2][2] calldata _pB,
uint[2] calldata _pC,
uint[2] calldata _pubSignals,
address recipient,
uint256 amount,
address zeton
) private view returns (bytes32) {
return keccak256(abi.encode(_pA, _pB, _pC, _pubSignals, recipient, amount, zeton, block.chainid));
}
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[2] calldata _pubSignals) internal view returns (bool result) {
assembly {
function checkField(v) {
if iszero(lt(v, r)) {
mstore(0, 0)
revert(0, 0)
}
}
// G1 function to multiply a G1 value(x,y) to value in an address
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn, 32), y)
mstore(add(mIn, 64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
revert(0, 0)
}
mstore(add(mIn, 64), mload(pR))
mstore(add(mIn, 96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
revert(0, 0)
}
}
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
let _pPairing := add(pMem, pPairing)
let _pVk := add(pMem, pVk)
mstore(_pVk, IC0x)
mstore(add(_pVk, 32), IC0y)
// Compute the linear combination vk_x
g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
// -A
mstore(_pPairing, calldataload(pA))
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
// B
mstore(add(_pPairing, 64), calldataload(pB))
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
// alpha1
mstore(add(_pPairing, 192), alphax)
mstore(add(_pPairing, 224), alphay)
// beta2
mstore(add(_pPairing, 256), betax1)
mstore(add(_pPairing, 288), betax2)
mstore(add(_pPairing, 320), betay1)
mstore(add(_pPairing, 352), betay2)
// vk_x
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
// gamma2
mstore(add(_pPairing, 448), gammax1)
mstore(add(_pPairing, 480), gammax2)
mstore(add(_pPairing, 512), gammay1)
mstore(add(_pPairing, 544), gammay2)
// C
mstore(add(_pPairing, 576), calldataload(pC))
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
// delta2
mstore(add(_pPairing, 640), deltax1)
mstore(add(_pPairing, 672), deltax2)
mstore(add(_pPairing, 704), deltay1)
mstore(add(_pPairing, 736), deltay2)
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
isOk := and(success, mload(_pPairing))
}
let pMem := mload(0x40)
mstore(0x40, add(pMem, pLastMem))
// Validate that all evaluations ∈ F
checkField(calldataload(add(_pubSignals, 0)))
checkField(calldataload(add(_pubSignals, 32)))
// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
result := isValid
}
}
}
@openzeppelin/contracts/interfaces/IERC1363.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
@openzeppelin/contracts/utils/StorageSlot.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}
Compiler Settings
{"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers","metadata"],"":["ast"]}},"optimizer":{"runs":200,"enabled":true},"evmVersion":"paris"}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"tuple","name":"n","internalType":"struct NastavitveZetona","components":[{"type":"string","name":"ime","internalType":"string"},{"type":"string","name":"simbol","internalType":"string"},{"type":"string","name":"metapodatkiCID","internalType":"string"},{"type":"uint256","name":"vrednostUSD","internalType":"uint256"},{"type":"address","name":"zkVerifier","internalType":"address"},{"type":"address","name":"entropyOracle","internalType":"address"},{"type":"address","name":"feeRecipient","internalType":"address"},{"type":"address","name":"kyc","internalType":"address"},{"type":"address","name":"lastnik","internalType":"address"},{"type":"address","name":"uors","internalType":"address"},{"type":"address","name":"overitelj","internalType":"address"}]}]},{"type":"error","name":"ERC20InsufficientAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"allowance","internalType":"uint256"},{"type":"uint256","name":"needed","internalType":"uint256"}]},{"type":"error","name":"ERC20InsufficientBalance","inputs":[{"type":"address","name":"sender","internalType":"address"},{"type":"uint256","name":"balance","internalType":"uint256"},{"type":"uint256","name":"needed","internalType":"uint256"}]},{"type":"error","name":"ERC20InvalidApprover","inputs":[{"type":"address","name":"approver","internalType":"address"}]},{"type":"error","name":"ERC20InvalidReceiver","inputs":[{"type":"address","name":"receiver","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSender","inputs":[{"type":"address","name":"sender","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSpender","inputs":[{"type":"address","name":"spender","internalType":"address"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"type":"address","name":"owner","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"AssetValueUpdated","inputs":[{"type":"uint256","name":"newValueUSD","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FeeRecipientUpdated","inputs":[{"type":"address","name":"newRecipient","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"KycUpdated","inputs":[{"type":"address","name":"newKyc","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"Odmrznjeno","inputs":[{"type":"address","name":"kdo","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"PrvotniLastnikZapisan","inputs":[{"type":"address","name":"lastnik","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"SpremembaIzvedena","inputs":[{"type":"bytes32","name":"id","internalType":"bytes32","indexed":true},{"type":"string","name":"kaj","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"SpremembaNapovedana","inputs":[{"type":"bytes32","name":"id","internalType":"bytes32","indexed":true},{"type":"string","name":"kaj","internalType":"string","indexed":false},{"type":"uint256","name":"izvedljivoOd","internalType":"uint256","indexed":false},{"type":"uint256","name":"potece","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SpremembaPreklicana","inputs":[{"type":"bytes32","name":"id","internalType":"bytes32","indexed":true},{"type":"string","name":"kaj","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"TokensMinted","inputs":[{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false},{"type":"bytes32","name":"proofHash","internalType":"bytes32","indexed":false}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"TransferFeeCollected","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"fee","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Zamrznjeno","inputs":[{"type":"address","name":"kdo","internalType":"address","indexed":true}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"ROK","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"TRANSFER_FEE_BPS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"aktivenOd","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"assetMetadataCID","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"assetValueUSD","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"decimals","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"entropyOracle","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"feeRecipient","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"izvedljivoOd","inputs":[{"type":"bytes32","name":"","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"mogoc","internalType":"bool"},{"type":"uint256","name":"odKdaj","internalType":"uint256"},{"type":"bool","name":"zamrznjeno","internalType":"bool"}],"name":"jePrenosMogoc","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"kyc","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mintFee","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mintWithProof","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"bytes32","name":"proofHash","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"napoviSetFeeRecipient","inputs":[{"type":"address","name":"_feeRecipient","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"napoviSetKyc","inputs":[{"type":"address","name":"_kyc","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"napoviUpdateAssetValue","inputs":[{"type":"uint256","name":"newValueUSD","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"odmrzni","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"overitelj","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"prekliciSpremembo","inputs":[{"type":"bytes32","name":"id","internalType":"bytes32"},{"type":"string","name":"kaj","internalType":"string"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"prvotniLastnik","inputs":[]},{"type":"function","stateMutability":"pure","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setFeeRecipient","inputs":[{"type":"address","name":"_feeRecipient","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setKyc","inputs":[{"type":"address","name":"_kyc","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"napovedana","internalType":"bool"},{"type":"uint256","name":"od","internalType":"uint256"},{"type":"uint256","name":"potece","internalType":"uint256"},{"type":"bool","name":"izvedljivaZdaj","internalType":"bool"}],"name":"stanjeNapovedi","inputs":[{"type":"bytes32","name":"id","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"uors","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateAssetValue","inputs":[{"type":"uint256","name":"newValueUSD","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"usedProofs","inputs":[{"type":"bytes32","name":"","internalType":"bytes32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"zamik","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"zamrzni","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"zamrznjen","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"zaporaPrenosa","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"zkVerifier","inputs":[]}]
Contract Creation Code
0x60e06040523480156200001157600080fd5b5060405162002b2a38038062002b2a833981016040819052620000349162000448565b6101008101518151602083015160036200004f838262000636565b5060046200005e828262000636565b5050506001600160a01b0381166200009157604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b6200009c81620002b3565b5060808101516001600160a01b0316620000f95760405162461bcd60e51b815260206004820152601360248201527f5a4b5665726966696572206a65206e756a656e00000000000000000000000000604482015260640162000088565b60c08101516001600160a01b0316620001555760405162461bcd60e51b815260206004820152601560248201527f666565526563697069656e74206a65206e756a656e0000000000000000000000604482015260640162000088565b6101008101516001600160a01b0316620001a55760405162461bcd60e51b815260206004820152601060248201526f2630b9ba3734b590353290373ab532b760811b604482015260640162000088565b6101208101516001600160a01b0316620002025760405162461bcd60e51b815260206004820152601560248201527f554f52535265676973747279206a65206e756a656e0000000000000000000000604482015260640162000088565b604081015160079062000216908262000636565b506060810151600855608080820151600980546001600160a01b03199081166001600160a01b039384161790915560a0840151600a8054831691841691909117905560c080850151600b8054841691851691909117905560e0850151600e805490931690841617909155610120840151821690526101408301511690526200029d62000305565b620002a9904262000702565b60a052506200072a565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600061017146146200031a576102586200031f565b6202a3005b62ffffff16905090565b634e487b7160e01b600052604160045260246000fd5b60405161016081016001600160401b038111828210171562000365576200036562000329565b60405290565b604051601f8201601f191681016001600160401b038111828210171562000396576200039662000329565b604052919050565b600082601f830112620003b057600080fd5b81516001600160401b03811115620003cc57620003cc62000329565b6020620003e2601f8301601f191682016200036b565b8281528582848701011115620003f757600080fd5b60005b8381101562000417578581018301518282018401528201620003fa565b506000928101909101919091529392505050565b80516001600160a01b03811681146200044357600080fd5b919050565b6000602082840312156200045b57600080fd5b81516001600160401b03808211156200047357600080fd5b9083019061016082860312156200048957600080fd5b620004936200033f565b825182811115620004a357600080fd5b620004b1878286016200039e565b825250602083015182811115620004c757600080fd5b620004d5878286016200039e565b602083015250604083015182811115620004ee57600080fd5b620004fc878286016200039e565b604083015250606083015160608201526200051a608084016200042b565b60808201526200052d60a084016200042b565b60a08201526200054060c084016200042b565b60c08201526200055360e084016200042b565b60e082015261010091506200056a8284016200042b565b828201526101209150620005808284016200042b565b828201526101409150620005968284016200042b565b91810191909152949350505050565b600181811c90821680620005ba57607f821691505b602082108103620005db57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000631576000816000526020600020601f850160051c810160208610156200060c5750805b601f850160051c820191505b818110156200062d5782815560010162000618565b5050505b505050565b81516001600160401b0381111562000652576200065262000329565b6200066a81620006638454620005a5565b84620005e1565b602080601f831160018114620006a25760008415620006895750858301515b600019600386901b1c1916600185901b1785556200062d565b600085815260208120601f198616915b82811015620006d357888601518255948401946001909101908401620006b2565b5085821015620006f25787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b808201808211156200072457634e487b7160e01b600052601160045260246000fd5b92915050565b60805160a05160c051612367620007c36000396000818161037c015281816106aa015281816106f001528181610a4901528181610a8f015281816111b0015281816111f6015281816114df01526115250152600081816105f401528181610828015281816108670152611cb40152600081816103c60152818161098b015281816109da01528181611421015261147001526123676000f3fe608060405234801561001057600080fd5b506004361061025e5760003560e01c8063776528cd11610146578063c1da7c97116100c3578063dd62ed3e11610087578063dd62ed3e14610573578063e74b981b146105ac578063e826f69a146105bf578063f2fde38b146105c9578063f7dc9243146105dc578063f9728ca1146105ef57600080fd5b8063c1da7c9714610524578063c30a0f251461052c578063cde2202a1461054f578063d48693d114610558578063d6df096d1461056057600080fd5b806399c272061161010a57806399c27206146104db578063a5826652146104ee578063a9059cbb146104f6578063b007d5fc14610509578063bfba6d5d1461051157600080fd5b8063776528cd146104655780638cc9d1071461049c5780638da5cb5b146104af57806390d6b45f146104c057806395d89b41146104d357600080fd5b8063263c1e61116101df57806346904840116101a357806346904840146103e85780635c57d8a8146103fb5780636a3b5c4d1461040e57806370a0823114610421578063715018a61461044a57806376e382c81461045257600080fd5b8063263c1e611461036457806329c3fe7c146103775780632bcc0bbe1461039e578063313ce567146103b2578063418ee99d146103c157600080fd5b8063179f548a11610226578063179f548a1461030757806318160ddd1461030f57806323b872dd1461031757806323eca6651461032a57806325b49a501461035157600080fd5b806306fdde031461026357806307601d27146102815780630854eef51461028b578063095ea7b3146102b95780630e78931d146102dc575b600080fd5b61026b610616565b6040516102789190611f86565b60405180910390f35b6102896106a8565b005b6102ab610299366004611fa0565b60066020526000908152604090205481565b604051908152602001610278565b6102cc6102c7366004611fd5565b6107e3565b6040519015158152602001610278565b600a546102ef906001600160a01b031681565b6040516001600160a01b039091168152602001610278565b6102ab603281565b6002546102ab565b6102cc610325366004611fff565b6107fd565b610332610821565b6040805193151584526020840192909252151590820152606001610278565b61028961035f36600461203b565b610899565b61028961037236600461203b565b61092a565b6102ef7f000000000000000000000000000000000000000000000000000000000000000081565b600e546102cc90600160a01b900460ff1681565b60405160128152602001610278565b6102ef7f000000000000000000000000000000000000000000000000000000000000000081565b600b546102ef906001600160a01b031681565b610289610409366004611fa0565b610989565b61028961041c366004612056565b610c0e565b6102ab61042f36600461203b565b6001600160a01b031660009081526020819052604090205490565b610289610dc7565b610289610460366004612089565b610e05565b610478610473366004611fa0565b610eac565b60408051941515855260208501939093529183015215156060820152608001610278565b6102896104aa366004611fd5565b610f19565b6005546001600160a01b03166102ef565b600e546102ef906001600160a01b031681565b61026b61104b565b6102896104e936600461203b565b61105a565b61026b611103565b6102cc610504366004611fd5565b611191565b6102ab61119f565b600c546102ef906001600160a01b031681565b6102896111ae565b6102cc61053a366004611fa0565b600f6020526000908152604090205460ff1681565b6102ab60085481565b6102ab6112ea565b6009546102ef906001600160a01b031681565b6102ab610581366004612105565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6102896105ba36600461203b565b61130c565b6102ab62093a8081565b6102896105d736600461203b565b6113e4565b6102896105ea366004611fa0565b61141f565b6102ab7f000000000000000000000000000000000000000000000000000000000000000081565b60606003805461062590612138565b80601f016020809104026020016040519081016040528092919081815260200182805461065190612138565b801561069e5780601f106106735761010080835404028352916020019161069e565b820191906000526020600020905b81548152906001019060200180831161068157829003601f168201915b5050505050905090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161580159061076357506040516338260bcb60e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063704c179690602401602060405180830381865afa15801561073f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107639190612172565b6107a95760405162461bcd60e51b815260206004820152601260248201527129b0b6b7902aa7a9299037b832b930ba32b960711b60448201526064015b60405180910390fd5b600e805460ff60a01b1916905560405133907fae0ecc60019f8d35cb80c8e02e2aeb74213dfa85a8557ba667398dc103b0e77a90600090a2565b6000336107f1818585611669565b60019150505b92915050565b60003361080b85828561167b565b6108168585856116fa565b506001949350505050565b60008060007f000000000000000000000000000000000000000000000000000000000000000042101580156108605750600e54600160a01b900460ff16155b600e5490947f00000000000000000000000000000000000000000000000000000000000000009450600160a01b90910460ff1692509050565b6108a1611759565b6001600160a01b0381166108c75760405162461bcd60e51b81526004016107a090612194565b610927816040516020016108db91906121ba565b60405160208183030381529060405280519060200120604051806040016040528060188152602001771495d0551bdad95b8b9cd95d119959549958da5c1a595b9d60421b815250611788565b50565b610932611759565b6109278160405160200161094691906121f4565b604051602081830303815290604052805190602001206040518060400160405280600f81526020016e525741546f6b656e2e7365744b796360881b815250611788565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166109cf5760405162461bcd60e51b81526004016107a090612225565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a475760405162461bcd60e51b815260206004820152601c60248201527f53616d6f206f7665726974656c6a20746567612073726564737476610000000060448201526064016107a0565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615801590610b0257506040516338260bcb60e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063704c179690602401602060405180830381865afa158015610ade573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b029190612172565b610b4e5760405162461bcd60e51b815260206004820152601b60248201527f4f7665726974656c6a206e692076656320706f6f626c617363656e000000000060448201526064016107a0565b6040805160208101829052601060608201526f757064617465417373657456616c756560801b6080820152908101829052610bd39060a0016040516020818303038152906040528051906020012060405180604001604052806019815260200178525741546f6b656e2e757064617465417373657456616c756560381b81525061183b565b60088190556040518181527f1cb5e221120a70c5978375b275179418e0cc46d0390597fad5b2d8757fe9eab59060200160405180910390a150565b6009546001600160a01b03163314610c5b5760405162461bcd60e51b815260206004820152601060248201526f27b7363c902d25903b32b934b334b2b960811b60448201526064016107a0565b6000818152600f602052604090205460ff1615610caf5760405162461bcd60e51b8152602060048201526012602482015271141c9bdbd988185b1c9958591e481d5cd95960721b60448201526064016107a0565b600c546001600160a01b031615610d015760405162461bcd60e51b8152602060048201526016602482015275537265647374766f206a65207a6520736b6f76616e6f60501b60448201526064016107a0565b6000818152600f60205260409020805460ff19166001179055600c80546001600160a81b0319166001600160a01b03851617600160a01b179055600d829055610d4a8383611964565b60408051838152602081018390526001600160a01b038516917f7369d801113c713520ca653010d32c6bcd6778f0ffb3df0bc9efb8d85b687e44910160405180910390a26040516001600160a01b038416907fe2cdd6bfbb0efc5299e0840dd4af6d4c9a195224ebad256485e9990726fd041c90600090a2505050565b60405162461bcd60e51b81526020600482015260136024820152722932b737bab731b29037b732b6b7b3b7b1b2b760691b60448201526064016107a0565b610e0d611759565b6000838152600660205260408120549003610e5a5760405162461bcd60e51b815260206004820152600d60248201526c4e69206e61706f766564616e6f60981b60448201526064016107a0565b600083815260066020526040808220919091555183907f72003eda7c2c39777210bccbf69141a92bbe56d746914a16f5ee3aaf5e6f7a8390610e9f9085908590612268565b60405180910390a2505050565b600081815260066020526040812054819081908190808203610edc57600080600080945094509450945050610f12565b600181610eec62093a80826122ad565b834210158015610f085750610f0462093a80856122ad565b4211155b9450945094509450505b9193509193565b6009546001600160a01b03163314610f665760405162461bcd60e51b815260206004820152601060248201526f27b7363c902d25903b32b934b334b2b960811b60448201526064016107a0565b6001600160a01b038216610f8c5760405162461bcd60e51b81526004016107a090612194565b600c54600160a01b900460ff16610fde5760405162461bcd60e51b815260206004820152601660248201527550726f76697a696a61206272657a206b6f76616e6a6160501b60448201526064016107a0565b600d54610fec8260c76122c0565b11156110305760405162461bcd60e51b815260206004820152601360248201527250726f76697a696a61207072657669736f6b6160681b60448201526064016107a0565b600c805460ff60a01b191690556110478282611964565b5050565b60606004805461062590612138565b611062611759565b6110b98160405160200161107691906121f4565b604051602081830303815290604052805190602001206040518060400160405280600f81526020016e525741546f6b656e2e7365744b796360881b81525061183b565b600e80546001600160a01b0319166001600160a01b0383169081179091556040517f9f5f8fd48aa64bb8a94c00d6adfc92b215610db623c189eb30698be4581265a490600090a250565b6007805461111090612138565b80601f016020809104026020016040519081016040528092919081815260200182805461113c90612138565b80156111895780601f1061115e57610100808354040283529160200191611189565b820191906000526020600020905b81548152906001019060200180831161116c57829003601f168201915b505050505081565b6000336107f18185856116fa565b60006111a96112ea565b905090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161580159061126957506040516338260bcb60e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063704c179690602401602060405180830381865afa158015611245573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112699190612172565b6112aa5760405162461bcd60e51b815260206004820152601260248201527129b0b6b7902aa7a9299037b832b930ba32b960711b60448201526064016107a0565b600e805460ff60a01b1916600160a01b17905560405133907f481d73db1c84a4cd0734a718818b266e3b59f2e485d8ba806e15763777d9d00690600090a2565b600061017146146112fd57610258611302565b6202a3005b62ffffff16905090565b611314611759565b6001600160a01b03811661133a5760405162461bcd60e51b81526004016107a090612194565b61139a8160405160200161134e91906121ba565b60405160208183030381529060405280519060200120604051806040016040528060188152602001771495d0551bdad95b8b9cd95d119959549958da5c1a595b9d60421b81525061183b565b600b80546001600160a01b0319166001600160a01b0383169081179091556040517f7a7b5a0a132f9e0581eb8527f66eae9ee89c2a3e79d4ac7e41a1f1f4d48a7fc290600090a250565b6113ec611759565b6001600160a01b03811661141657604051631e4fbdf760e01b8152600060048201526024016107a0565b6109278161199a565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166114655760405162461bcd60e51b81526004016107a090612225565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146114dd5760405162461bcd60e51b815260206004820152601c60248201527f53616d6f206f7665726974656c6a20746567612073726564737476610000000060448201526064016107a0565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161580159061159857506040516338260bcb60e11b81523360048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063704c179690602401602060405180830381865afa158015611574573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115989190612172565b6115e45760405162461bcd60e51b815260206004820152601b60248201527f4f7665726974656c6a206e692076656320706f6f626c617363656e000000000060448201526064016107a0565b6040805160208101829052601060608201526f757064617465417373657456616c756560801b60808201529081018290526109279060a0016040516020818303038152906040528051906020012060405180604001604052806019815260200178525741546f6b656e2e757064617465417373657456616c756560381b815250611788565b61167683838360016119ec565b505050565b6001600160a01b038381166000908152600160209081526040808320938616835292905220546000198110156116f457818110156116e557604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064016107a0565b6116f4848484840360006119ec565b50505050565b6001600160a01b03831661172457604051634b637e8f60e11b8152600060048201526024016107a0565b6001600160a01b03821661174e5760405163ec442f0560e01b8152600060048201526024016107a0565b611676838383611ac1565b6005546001600160a01b031633146117865760405163118cdaa760e01b81523360048201526024016107a0565b565b600082815260066020526040902054156117d45760405162461bcd60e51b815260206004820152600d60248201526c5a65206e61706f766564616e6f60981b60448201526064016107a0565b60006117de6112ea565b6117e890426122ad565b60008481526006602052604090208190559050827f63a0d065f46e07e30a4f246d28eb53f839982100439173db92dbd5700ab37c15838361182c62093a80826122ad565b604051610e9f939291906122d7565b6000828152600660205260408120549081900361188a5760405162461bcd60e51b815260206004820152600d60248201526c4e69206e61706f766564616e6f60981b60448201526064016107a0565b804210156118d05760405162461bcd60e51b815260206004820152601360248201527216985b5a5ac81cd9481b9a481c1bdd195ad95b606a1b60448201526064016107a0565b6118dd62093a80826122ad565b4211156119215760405162461bcd60e51b81526020600482015260126024820152714e61706f766564206a6520706f74656b6c6160701b60448201526064016107a0565b600083815260066020526040808220919091555183907ffac4537038ebc9eaa00d86a88a6c2e268012d306f6442966aab36014a195432390610e9f908590611f86565b6001600160a01b03821661198e5760405163ec442f0560e01b8152600060048201526024016107a0565b61104760008383611ac1565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001600160a01b038416611a165760405163e602df0560e01b8152600060048201526024016107a0565b6001600160a01b038316611a4057604051634a1406b160e11b8152600060048201526024016107a0565b6001600160a01b03808516600090815260016020908152604080832093871683529290522082905580156116f457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611ab391815260200190565b60405180910390a350505050565b600e546001600160a01b03848116159184821615911615611c5a5780611b9b57600e54604051633af32abf60e01b81526001600160a01b03868116600483015290911690633af32abf90602401602060405180830381865afa158015611b2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b4f9190612172565b611b9b5760405162461bcd60e51b815260206004820152601a60248201527f5072656a656d6e696b206e69204b5943207072657665726a656e00000000000060448201526064016107a0565b81611c5a57600e54604051633af32abf60e01b81526001600160a01b03878116600483015290911690633af32abf90602401602060405180830381865afa158015611bea573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c0e9190612172565b611c5a5760405162461bcd60e51b815260206004820152601c60248201527f506f73696c6a6174656c6a206e69204b5943207072657665726a656e0000000060448201526064016107a0565b81611d2257600e54600160a01b900460ff1615611cb25760405162461bcd60e51b8152602060048201526016602482015275537265647374766f206a65207a616d727a6e6a656e6f60501b60448201526064016107a0565b7f0000000000000000000000000000000000000000000000000000000000000000421015611d225760405162461bcd60e51b815260206004820181905260248201527f537265647374766f207365206e6920616b7469766e6f207a61207072656e6f7360448201526064016107a0565b8180611d2b5750805b80611d435750600b546001600160a01b038681169116145b80611d5b5750600b546001600160a01b038581169116145b15611d7257611d6b858585611e16565b5050505050565b6000612710611d826032866122c0565b611d8c91906122fc565b90506000611d9a828661231e565b600b54909150611db59088906001600160a01b031684611e16565b611dc0878783611e16565b856001600160a01b0316876001600160a01b03167ff403ce23f2694cf9716913477865dd71892b2d8917c097372a485281654fdcb184604051611e0591815260200190565b60405180910390a350505050505050565b6001600160a01b038316611e41578060026000828254611e3691906122ad565b90915550611eb39050565b6001600160a01b03831660009081526020819052604090205481811015611e945760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016107a0565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216611ecf57600280548290039055611eee565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611f3391815260200190565b60405180910390a3505050565b6000815180845260005b81811015611f6657602081850181015186830182015201611f4a565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000611f996020830184611f40565b9392505050565b600060208284031215611fb257600080fd5b5035919050565b80356001600160a01b0381168114611fd057600080fd5b919050565b60008060408385031215611fe857600080fd5b611ff183611fb9565b946020939093013593505050565b60008060006060848603121561201457600080fd5b61201d84611fb9565b925061202b60208501611fb9565b9150604084013590509250925092565b60006020828403121561204d57600080fd5b611f9982611fb9565b60008060006060848603121561206b57600080fd5b61207484611fb9565b95602085013595506040909401359392505050565b60008060006040848603121561209e57600080fd5b83359250602084013567ffffffffffffffff808211156120bd57600080fd5b818601915086601f8301126120d157600080fd5b8135818111156120e057600080fd5b8760208285010111156120f257600080fd5b6020830194508093505050509250925092565b6000806040838503121561211857600080fd5b61212183611fb9565b915061212f60208401611fb9565b90509250929050565b600181811c9082168061214c57607f821691505b60208210810361216c57634e487b7160e01b600052602260045260246000fd5b50919050565b60006020828403121561218457600080fd5b81518015158114611f9957600080fd5b6020808252600c908201526b5a65726f206164647265737360a01b604082015260600190565b6040808252600f908201526e1cd95d119959549958da5c1a595b9d608a1b60608201526001600160a01b0391909116602082015260800190565b6040808252600690820152657365744b796360d01b60608201526001600160a01b0391909116602082015260800190565b60208082526023908201527f537265647374766f206e696d61207a61706973616e656761206f7665726974656040820152626c6a6160e81b606082015260800190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f19160101919050565b634e487b7160e01b600052601160045260246000fd5b808201808211156107f7576107f7612297565b80820281158282048414176107f7576107f7612297565b6060815260006122ea6060830186611f40565b60208301949094525060400152919050565b60008261231957634e487b7160e01b600052601260045260246000fd5b500490565b818103818111156107f7576107f761229756fea264697066735822122084b1f3a15f165a51cb028ad99f609da56b3b15abe87a62cfa7b27663a611cb6364736f6c634300081800330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000d3c21bcecceda10000000000000000000000000000000eb5556032f9ca9ab629439afaecd79c40aab863000000000000000000000000f119beb1b343e46c4ed0e78362cd3f87e87ef4cc00000000000000000000000050555531d7a9ae79a5ad9100f114def88c6691d9000000000000000000000000d37d29526d271770f42c2553faae7f282effc1630000000000000000000000005a5db3d01a38d640a81182b1769a9f1e4d1f0f7400000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc3200000000000000000000000000091991f58804cc5aceca5e87d4667e68d22e082480000000000000000000000000000000000000000000000000000000000000010525741206d6f6a64656c65c5be2e73690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055257414d440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042697066733a2f2f6261666b7265696866746e616d73367a686d346e32656568617968637533707461656878696d656e6278727876646561366f6679337078737a3461000000000000000000000000000000000000000000000000000000000000
Deployed ByteCode
0x608060405234801561001057600080fd5b506004361061025e5760003560e01c8063776528cd11610146578063c1da7c97116100c3578063dd62ed3e11610087578063dd62ed3e14610573578063e74b981b146105ac578063e826f69a146105bf578063f2fde38b146105c9578063f7dc9243146105dc578063f9728ca1146105ef57600080fd5b8063c1da7c9714610524578063c30a0f251461052c578063cde2202a1461054f578063d48693d114610558578063d6df096d1461056057600080fd5b806399c272061161010a57806399c27206146104db578063a5826652146104ee578063a9059cbb146104f6578063b007d5fc14610509578063bfba6d5d1461051157600080fd5b8063776528cd146104655780638cc9d1071461049c5780638da5cb5b146104af57806390d6b45f146104c057806395d89b41146104d357600080fd5b8063263c1e61116101df57806346904840116101a357806346904840146103e85780635c57d8a8146103fb5780636a3b5c4d1461040e57806370a0823114610421578063715018a61461044a57806376e382c81461045257600080fd5b8063263c1e611461036457806329c3fe7c146103775780632bcc0bbe1461039e578063313ce567146103b2578063418ee99d146103c157600080fd5b8063179f548a11610226578063179f548a1461030757806318160ddd1461030f57806323b872dd1461031757806323eca6651461032a57806325b49a501461035157600080fd5b806306fdde031461026357806307601d27146102815780630854eef51461028b578063095ea7b3146102b95780630e78931d146102dc575b600080fd5b61026b610616565b6040516102789190611f86565b60405180910390f35b6102896106a8565b005b6102ab610299366004611fa0565b60066020526000908152604090205481565b604051908152602001610278565b6102cc6102c7366004611fd5565b6107e3565b6040519015158152602001610278565b600a546102ef906001600160a01b031681565b6040516001600160a01b039091168152602001610278565b6102ab603281565b6002546102ab565b6102cc610325366004611fff565b6107fd565b610332610821565b6040805193151584526020840192909252151590820152606001610278565b61028961035f36600461203b565b610899565b61028961037236600461203b565b61092a565b6102ef7f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc3200081565b600e546102cc90600160a01b900460ff1681565b60405160128152602001610278565b6102ef7f00000000000000000000000091991f58804cc5aceca5e87d4667e68d22e0824881565b600b546102ef906001600160a01b031681565b610289610409366004611fa0565b610989565b61028961041c366004612056565b610c0e565b6102ab61042f36600461203b565b6001600160a01b031660009081526020819052604090205490565b610289610dc7565b610289610460366004612089565b610e05565b610478610473366004611fa0565b610eac565b60408051941515855260208501939093529183015215156060820152608001610278565b6102896104aa366004611fd5565b610f19565b6005546001600160a01b03166102ef565b600e546102ef906001600160a01b031681565b61026b61104b565b6102896104e936600461203b565b61105a565b61026b611103565b6102cc610504366004611fd5565b611191565b6102ab61119f565b600c546102ef906001600160a01b031681565b6102896111ae565b6102cc61053a366004611fa0565b600f6020526000908152604090205460ff1681565b6102ab60085481565b6102ab6112ea565b6009546102ef906001600160a01b031681565b6102ab610581366004612105565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6102896105ba36600461203b565b61130c565b6102ab62093a8081565b6102896105d736600461203b565b6113e4565b6102896105ea366004611fa0565b61141f565b6102ab7f000000000000000000000000000000000000000000000000000000006ad1639b81565b60606003805461062590612138565b80601f016020809104026020016040519081016040528092919081815260200182805461065190612138565b801561069e5780601f106106735761010080835404028352916020019161069e565b820191906000526020600020905b81548152906001019060200180831161068157829003601f168201915b5050505050905090565b7f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b03161580159061076357506040516338260bcb60e11b81523360048201527f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b03169063704c179690602401602060405180830381865afa15801561073f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107639190612172565b6107a95760405162461bcd60e51b815260206004820152601260248201527129b0b6b7902aa7a9299037b832b930ba32b960711b60448201526064015b60405180910390fd5b600e805460ff60a01b1916905560405133907fae0ecc60019f8d35cb80c8e02e2aeb74213dfa85a8557ba667398dc103b0e77a90600090a2565b6000336107f1818585611669565b60019150505b92915050565b60003361080b85828561167b565b6108168585856116fa565b506001949350505050565b60008060007f000000000000000000000000000000000000000000000000000000006ad1639b42101580156108605750600e54600160a01b900460ff16155b600e5490947f000000000000000000000000000000000000000000000000000000006ad1639b9450600160a01b90910460ff1692509050565b6108a1611759565b6001600160a01b0381166108c75760405162461bcd60e51b81526004016107a090612194565b610927816040516020016108db91906121ba565b60405160208183030381529060405280519060200120604051806040016040528060188152602001771495d0551bdad95b8b9cd95d119959549958da5c1a595b9d60421b815250611788565b50565b610932611759565b6109278160405160200161094691906121f4565b604051602081830303815290604052805190602001206040518060400160405280600f81526020016e525741546f6b656e2e7365744b796360881b815250611788565b7f00000000000000000000000091991f58804cc5aceca5e87d4667e68d22e082486001600160a01b03166109cf5760405162461bcd60e51b81526004016107a090612225565b336001600160a01b037f00000000000000000000000091991f58804cc5aceca5e87d4667e68d22e082481614610a475760405162461bcd60e51b815260206004820152601c60248201527f53616d6f206f7665726974656c6a20746567612073726564737476610000000060448201526064016107a0565b7f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b031615801590610b0257506040516338260bcb60e11b81523360048201527f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b03169063704c179690602401602060405180830381865afa158015610ade573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b029190612172565b610b4e5760405162461bcd60e51b815260206004820152601b60248201527f4f7665726974656c6a206e692076656320706f6f626c617363656e000000000060448201526064016107a0565b6040805160208101829052601060608201526f757064617465417373657456616c756560801b6080820152908101829052610bd39060a0016040516020818303038152906040528051906020012060405180604001604052806019815260200178525741546f6b656e2e757064617465417373657456616c756560381b81525061183b565b60088190556040518181527f1cb5e221120a70c5978375b275179418e0cc46d0390597fad5b2d8757fe9eab59060200160405180910390a150565b6009546001600160a01b03163314610c5b5760405162461bcd60e51b815260206004820152601060248201526f27b7363c902d25903b32b934b334b2b960811b60448201526064016107a0565b6000818152600f602052604090205460ff1615610caf5760405162461bcd60e51b8152602060048201526012602482015271141c9bdbd988185b1c9958591e481d5cd95960721b60448201526064016107a0565b600c546001600160a01b031615610d015760405162461bcd60e51b8152602060048201526016602482015275537265647374766f206a65207a6520736b6f76616e6f60501b60448201526064016107a0565b6000818152600f60205260409020805460ff19166001179055600c80546001600160a81b0319166001600160a01b03851617600160a01b179055600d829055610d4a8383611964565b60408051838152602081018390526001600160a01b038516917f7369d801113c713520ca653010d32c6bcd6778f0ffb3df0bc9efb8d85b687e44910160405180910390a26040516001600160a01b038416907fe2cdd6bfbb0efc5299e0840dd4af6d4c9a195224ebad256485e9990726fd041c90600090a2505050565b60405162461bcd60e51b81526020600482015260136024820152722932b737bab731b29037b732b6b7b3b7b1b2b760691b60448201526064016107a0565b610e0d611759565b6000838152600660205260408120549003610e5a5760405162461bcd60e51b815260206004820152600d60248201526c4e69206e61706f766564616e6f60981b60448201526064016107a0565b600083815260066020526040808220919091555183907f72003eda7c2c39777210bccbf69141a92bbe56d746914a16f5ee3aaf5e6f7a8390610e9f9085908590612268565b60405180910390a2505050565b600081815260066020526040812054819081908190808203610edc57600080600080945094509450945050610f12565b600181610eec62093a80826122ad565b834210158015610f085750610f0462093a80856122ad565b4211155b9450945094509450505b9193509193565b6009546001600160a01b03163314610f665760405162461bcd60e51b815260206004820152601060248201526f27b7363c902d25903b32b934b334b2b960811b60448201526064016107a0565b6001600160a01b038216610f8c5760405162461bcd60e51b81526004016107a090612194565b600c54600160a01b900460ff16610fde5760405162461bcd60e51b815260206004820152601660248201527550726f76697a696a61206272657a206b6f76616e6a6160501b60448201526064016107a0565b600d54610fec8260c76122c0565b11156110305760405162461bcd60e51b815260206004820152601360248201527250726f76697a696a61207072657669736f6b6160681b60448201526064016107a0565b600c805460ff60a01b191690556110478282611964565b5050565b60606004805461062590612138565b611062611759565b6110b98160405160200161107691906121f4565b604051602081830303815290604052805190602001206040518060400160405280600f81526020016e525741546f6b656e2e7365744b796360881b81525061183b565b600e80546001600160a01b0319166001600160a01b0383169081179091556040517f9f5f8fd48aa64bb8a94c00d6adfc92b215610db623c189eb30698be4581265a490600090a250565b6007805461111090612138565b80601f016020809104026020016040519081016040528092919081815260200182805461113c90612138565b80156111895780601f1061115e57610100808354040283529160200191611189565b820191906000526020600020905b81548152906001019060200180831161116c57829003601f168201915b505050505081565b6000336107f18185856116fa565b60006111a96112ea565b905090565b7f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b03161580159061126957506040516338260bcb60e11b81523360048201527f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b03169063704c179690602401602060405180830381865afa158015611245573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112699190612172565b6112aa5760405162461bcd60e51b815260206004820152601260248201527129b0b6b7902aa7a9299037b832b930ba32b960711b60448201526064016107a0565b600e805460ff60a01b1916600160a01b17905560405133907f481d73db1c84a4cd0734a718818b266e3b59f2e485d8ba806e15763777d9d00690600090a2565b600061017146146112fd57610258611302565b6202a3005b62ffffff16905090565b611314611759565b6001600160a01b03811661133a5760405162461bcd60e51b81526004016107a090612194565b61139a8160405160200161134e91906121ba565b60405160208183030381529060405280519060200120604051806040016040528060188152602001771495d0551bdad95b8b9cd95d119959549958da5c1a595b9d60421b81525061183b565b600b80546001600160a01b0319166001600160a01b0383169081179091556040517f7a7b5a0a132f9e0581eb8527f66eae9ee89c2a3e79d4ac7e41a1f1f4d48a7fc290600090a250565b6113ec611759565b6001600160a01b03811661141657604051631e4fbdf760e01b8152600060048201526024016107a0565b6109278161199a565b7f00000000000000000000000091991f58804cc5aceca5e87d4667e68d22e082486001600160a01b03166114655760405162461bcd60e51b81526004016107a090612225565b336001600160a01b037f00000000000000000000000091991f58804cc5aceca5e87d4667e68d22e0824816146114dd5760405162461bcd60e51b815260206004820152601c60248201527f53616d6f206f7665726974656c6a20746567612073726564737476610000000060448201526064016107a0565b7f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b03161580159061159857506040516338260bcb60e11b81523360048201527f00000000000000000000000016eeba545a07a88cdf21cfdde973ee67cdc320006001600160a01b03169063704c179690602401602060405180830381865afa158015611574573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115989190612172565b6115e45760405162461bcd60e51b815260206004820152601b60248201527f4f7665726974656c6a206e692076656320706f6f626c617363656e000000000060448201526064016107a0565b6040805160208101829052601060608201526f757064617465417373657456616c756560801b60808201529081018290526109279060a0016040516020818303038152906040528051906020012060405180604001604052806019815260200178525741546f6b656e2e757064617465417373657456616c756560381b815250611788565b61167683838360016119ec565b505050565b6001600160a01b038381166000908152600160209081526040808320938616835292905220546000198110156116f457818110156116e557604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064016107a0565b6116f4848484840360006119ec565b50505050565b6001600160a01b03831661172457604051634b637e8f60e11b8152600060048201526024016107a0565b6001600160a01b03821661174e5760405163ec442f0560e01b8152600060048201526024016107a0565b611676838383611ac1565b6005546001600160a01b031633146117865760405163118cdaa760e01b81523360048201526024016107a0565b565b600082815260066020526040902054156117d45760405162461bcd60e51b815260206004820152600d60248201526c5a65206e61706f766564616e6f60981b60448201526064016107a0565b60006117de6112ea565b6117e890426122ad565b60008481526006602052604090208190559050827f63a0d065f46e07e30a4f246d28eb53f839982100439173db92dbd5700ab37c15838361182c62093a80826122ad565b604051610e9f939291906122d7565b6000828152600660205260408120549081900361188a5760405162461bcd60e51b815260206004820152600d60248201526c4e69206e61706f766564616e6f60981b60448201526064016107a0565b804210156118d05760405162461bcd60e51b815260206004820152601360248201527216985b5a5ac81cd9481b9a481c1bdd195ad95b606a1b60448201526064016107a0565b6118dd62093a80826122ad565b4211156119215760405162461bcd60e51b81526020600482015260126024820152714e61706f766564206a6520706f74656b6c6160701b60448201526064016107a0565b600083815260066020526040808220919091555183907ffac4537038ebc9eaa00d86a88a6c2e268012d306f6442966aab36014a195432390610e9f908590611f86565b6001600160a01b03821661198e5760405163ec442f0560e01b8152600060048201526024016107a0565b61104760008383611ac1565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001600160a01b038416611a165760405163e602df0560e01b8152600060048201526024016107a0565b6001600160a01b038316611a4057604051634a1406b160e11b8152600060048201526024016107a0565b6001600160a01b03808516600090815260016020908152604080832093871683529290522082905580156116f457826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611ab391815260200190565b60405180910390a350505050565b600e546001600160a01b03848116159184821615911615611c5a5780611b9b57600e54604051633af32abf60e01b81526001600160a01b03868116600483015290911690633af32abf90602401602060405180830381865afa158015611b2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b4f9190612172565b611b9b5760405162461bcd60e51b815260206004820152601a60248201527f5072656a656d6e696b206e69204b5943207072657665726a656e00000000000060448201526064016107a0565b81611c5a57600e54604051633af32abf60e01b81526001600160a01b03878116600483015290911690633af32abf90602401602060405180830381865afa158015611bea573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c0e9190612172565b611c5a5760405162461bcd60e51b815260206004820152601c60248201527f506f73696c6a6174656c6a206e69204b5943207072657665726a656e0000000060448201526064016107a0565b81611d2257600e54600160a01b900460ff1615611cb25760405162461bcd60e51b8152602060048201526016602482015275537265647374766f206a65207a616d727a6e6a656e6f60501b60448201526064016107a0565b7f000000000000000000000000000000000000000000000000000000006ad1639b421015611d225760405162461bcd60e51b815260206004820181905260248201527f537265647374766f207365206e6920616b7469766e6f207a61207072656e6f7360448201526064016107a0565b8180611d2b5750805b80611d435750600b546001600160a01b038681169116145b80611d5b5750600b546001600160a01b038581169116145b15611d7257611d6b858585611e16565b5050505050565b6000612710611d826032866122c0565b611d8c91906122fc565b90506000611d9a828661231e565b600b54909150611db59088906001600160a01b031684611e16565b611dc0878783611e16565b856001600160a01b0316876001600160a01b03167ff403ce23f2694cf9716913477865dd71892b2d8917c097372a485281654fdcb184604051611e0591815260200190565b60405180910390a350505050505050565b6001600160a01b038316611e41578060026000828254611e3691906122ad565b90915550611eb39050565b6001600160a01b03831660009081526020819052604090205481811015611e945760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016107a0565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b038216611ecf57600280548290039055611eee565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611f3391815260200190565b60405180910390a3505050565b6000815180845260005b81811015611f6657602081850181015186830182015201611f4a565b506000602082860101526020601f19601f83011685010191505092915050565b602081526000611f996020830184611f40565b9392505050565b600060208284031215611fb257600080fd5b5035919050565b80356001600160a01b0381168114611fd057600080fd5b919050565b60008060408385031215611fe857600080fd5b611ff183611fb9565b946020939093013593505050565b60008060006060848603121561201457600080fd5b61201d84611fb9565b925061202b60208501611fb9565b9150604084013590509250925092565b60006020828403121561204d57600080fd5b611f9982611fb9565b60008060006060848603121561206b57600080fd5b61207484611fb9565b95602085013595506040909401359392505050565b60008060006040848603121561209e57600080fd5b83359250602084013567ffffffffffffffff808211156120bd57600080fd5b818601915086601f8301126120d157600080fd5b8135818111156120e057600080fd5b8760208285010111156120f257600080fd5b6020830194508093505050509250925092565b6000806040838503121561211857600080fd5b61212183611fb9565b915061212f60208401611fb9565b90509250929050565b600181811c9082168061214c57607f821691505b60208210810361216c57634e487b7160e01b600052602260045260246000fd5b50919050565b60006020828403121561218457600080fd5b81518015158114611f9957600080fd5b6020808252600c908201526b5a65726f206164647265737360a01b604082015260600190565b6040808252600f908201526e1cd95d119959549958da5c1a595b9d608a1b60608201526001600160a01b0391909116602082015260800190565b6040808252600690820152657365744b796360d01b60608201526001600160a01b0391909116602082015260800190565b60208082526023908201527f537265647374766f206e696d61207a61706973616e656761206f7665726974656040820152626c6a6160e81b606082015260800190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f19160101919050565b634e487b7160e01b600052601160045260246000fd5b808201808211156107f7576107f7612297565b80820281158282048414176107f7576107f7612297565b6060815260006122ea6060830186611f40565b60208301949094525060400152919050565b60008261231957634e487b7160e01b600052601260045260246000fd5b500490565b818103818111156107f7576107f761229756fea264697066735822122084b1f3a15f165a51cb028ad99f609da56b3b15abe87a62cfa7b27663a611cb6364736f6c63430008180033