Address Contract Verified
Address
0x9fF39f7735b2596F984bcB72Fdb7C0c4E5900B8c
Balance
0 ETH
Nonce
1
Code Size
8254 bytes
Creator
0xaD00eb5d...f1Ef at tx 0xafcec023...e00c29
Indexed Transactions
0
Contract Bytecode
8254 bytes
0x60a0604052600436101561001257600080fd5b6000803560e01c806301d918d7146114ca57806303f34181146113ed57806306cb5b66146113375780630ee6c89c146112da5780631a5da6c8146112245780632093c15b146111a3578063241d382714611072578063265ce50114610fe25780633facfafb14610e6e5780635d30c31d14610dba578063715018a614610d605780637b10399914610d375780637fb5853714610c735780638b6516c814610ba65780638da5cb5b14610b7f578063a1c69d0214610a8b578063a65faca014610a2e578063d84c91e0146109b1578063e3d1e6d614610982578063e9c3a06d14610642578063ee538c1714610360578063f2aef13b146102ee578063f2fde38b14610228578063f77c4791146101ff578063f9cd9080146101675763fd769d631461013b57600080fd5b346101645760203660031901126101645760406020916004358152600383522054604051908152f35b80fd5b50346101645761017636611940565b9161017f611f2e565b50818152600360205260408120548310156101c457610120926040826101b5946101af9452600360205220611956565b50611f73565b6101c260405180926119d2565bf35b60405162461bcd60e51b8152602060048201526013602482015272496e646578206f7574206f6620626f756e647360681b6044820152606490fd5b50346101645780600319360112610164576001546040516001600160a01b039091168152602090f35b5034610164576020366003190112610164576102426117cd565b61024a611a34565b6001600160a01b0390811690811561029a57600054826001600160601b0360a01b821617600055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a380f35b60405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b503461016457602036600319011261016457604090600435815260046020522080549061035c600182015492600283015490600384015493600481015460058201549060018060a01b039660078860068601541694015495604051998a9960ff8960a01c169816968a611988565b0390f35b50346101645760a0366003190112610164576044356024356004356064356084356001600160401b03811161063e5784916103a2610418923690600401611761565b6040805160208082018881528284018a90526060830197909752608082018590524660a083015295906103e28160c081015b03601f198101835282611740565b5190209160018060a01b03600154169282519586928392631b594def60e31b845260048401528460248401526044830190611aad565b0381845afa92831561054557889089946105f3575b501561059d57604051637d10535160e11b81528560048201528260248201528481604481855afa80156105925785918a91610550575b5091610470604493611aed565b6040519283809263f67dab9160e01b82528960048301528b60248301525afa9384156105455788946104fa575b50506104b7600080516020611fe983398151915293611b2a565b6104c48282888888611c5f565b6104ce8685611d3d565b6040805196875260208701919091526001600160a01b0390911690850152339380606081015b0390a480f35b90809450813d831161053e575b6105118183611740565b8101031261053a576104b7610534600080516020611fe983398151915294611a8c565b9361049d565b8680fd5b503d610507565b6040513d8a823e3d90fd5b82819392503d831161058b575b6105678183611740565b810103126105875760449161047061057f8793611a8c565b919350610463565b8880fd5b503d61055d565b6040513d8b823e3d90fd5b60405162461bcd60e51b815260048101859052602860248201527f496e76616c6964207369676e6174757265206f7220756e617574686f72697a65604482015267321039b4b3b732b960c11b6064820152608490fd5b9350506040833d604011610636575b8161060f60409383611740565b810103126106325761062b8461062485611a8c565b9401611a99565b923861042d565b8780fd5b3d9150610602565b8580fd5b50346101645760a0366003190112610164576044356024356004356001600160401b0360643560843582811161053a57610680903690600401611858565b60408051602080820187815292820188905260608201899052608082018590524660a083015291906106b58160c081016103d4565b5190206106f28960018060a01b0394856001541693604051938492839263bd42ccf560e01b84526004840152604060248401526044830190611b6c565b0381855afa958615610878578a918b976108c8575b50501561088357604051637d10535160e11b81528660048201528460248201528281604481855afa80156108785783918b91610836575b509161074b604493611aed565b6040519283809263f67dab9160e01b82528a60048301528c60248301525afa9182156105925789926107e7575b5050600080516020611fe9833981519152936107966107bd92611b2a565b6107ae836107a383611bc1565b5116858a8a8a611c5f565b6107b88887611d3d565b611bc1565b51604080519788526020880193909352166001600160a01b031690850152339380606081016104f4565b90809250813d831161082f575b6107fe8183611740565b8101031261063257600080516020611fe9833981519152936107966108256107bd93611a8c565b9250819550610778565b503d6107f4565b82819392503d8311610871575b61084d8183611740565b8101031261086d5760449161074b6108658593611a8c565b91935061073e565b8980fd5b503d610843565b6040513d8c823e3d90fd5b60405162461bcd60e51b815260048101839052601d60248201527f496e73756666696369656e742076616c6964207369676e6174757265730000006044820152606490fd5b915095503d808b833e6108db8183611740565b81019560608288031261097a576108f182611a8c565b918481015160ff81160361097e57604081015191821161097e57019580601f8801121561097a578651610923816117e3565b97610931604051998a611740565b81895285808a019260051b8201019283116109765785809101915b83831061095e57505050503880610707565b819061096984611a99565b815201910190859061094c565b8c80fd5b8a80fd5b8b80fd5b50346101645760203660031901126101645760ff60406020926004358152600584522054166040519015158152f35b503461016457806109c136611940565b906109ca611a34565b6002546001600160a01b031691823b15610a2957604484928360405195869485936306c2648f60e51b8552600485015260248401525af18015610a1e57610a0e5750f35b610a17906116fb565b6101645780f35b6040513d84823e3d90fd5b505050fd5b50346101645780610a3e36611940565b90610a47611a34565b6002546001600160a01b031691823b15610a295760448492836040519586948593630532fd6560e51b8552600485015260248401525af18015610a1e57610a0e5750f35b50346101645760031960c036820112610b7b576001600160401b0390604435828111610b7757610abf903690600401611761565b91606435908111610b7757610ad8903690600401611761565b6084359060ff8216809203610b72578493610af1611a34565b6002546001600160a01b031690813b1561063e5785610b44918195610b53604051988997889687956350e34e8160e11b87526004356004880152602435602488015260c0604488015260c4870190611aad565b91858303016064860152611aad565b90608483015260a43560a483015203925af18015610a1e57610a0e5750f35b600080fd5b8380fd5b5080fd5b5034610164578060031936011261016457546040516001600160a01b039091168152602090f35b50346101645780600319608036820112610c70576001600160401b03604435818111610a2957610bda903690600401611761565b90606435908111610a2957610bf3903690600401611761565b610bfb611a34565b6002546001600160a01b0316803b15610c6c57610c4c858094610c5b6040519788968795869463116ca2d960e31b865260043560048701526024356024870152608060448701526084860190611aad565b91848303016064850152611aad565b03925af18015610a1e57610a0e5750f35b8480fd5b50fd5b503461016457602090816003193601126101645790600435825260038152604082208054610ca0816117e3565b90610cae6040519283611740565b8082528382018093865284862086915b838310610d1057505050506040519280840191818552518092526040840192945b828110610cec5784840385f35b9091928261012082610d016001948a516119d2565b01960191019492919094610cdf565b600887600192610d24859b9a98999b611f73565b8152019201920191909694939596610cbe565b50346101645780600319360112610164576002546040516001600160a01b039091168152602090f35b5034610164578060031936011261016457610d79611a34565b600080546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b503461016457606036600319011261016457604435602435600435610ddd611a34565b8215610e2957610df03342858585611c5f565b610dfa8382611d3d565b6040519283527f3bf5f96322f516889099e59b30976b34bd3407780bf6506d84898abad75a24e860203394a480f35b60405162461bcd60e51b815260206004820152601c60248201527f56616c7565206d7573742062652067726561746572207468616e2030000000006044820152606490fd5b50346101645760031960c036820112610b7b576001600160401b0390600435828111610b7757610ea2903690600401611761565b90602435926006841015610c6c5760443581811161063e57610ec8903690600401611761565b9185606435838111610b7b57610ee2903690600401611761565b92608435908111610b7b57610efb903690600401611761565b9460a435801515809103610fde57610f86610f5294602098610f1b611a34565b610f7760018060a01b036002541698610f676040519d8e9c8d9b8c9a633facfafb60e01b8c5260c060048d015260c48c0190611aad565b9160248b0152868a83030160448b0152611aad565b9084888303016064890152611aad565b91858303016084860152611aad565b9060a483015203925af1908115610a1e578291610fa9575b602082604051908152f35b90506020813d8211610fd6575b81610fc360209383611740565b81010312610b7b57602091505138610f9e565b3d9150610fb6565b8280fd5b50346101645760203660031901126101645760043590611000611f2e565b50818152600560205260ff6040822054161561102d57604081610120936101b59352600460205220611f73565b60405162461bcd60e51b815260206004820152601860248201527f4e6f2070726f6f667320666f72207468697320617373657400000000000000006044820152606490fd5b503461016457610100366003190112610164576004356001600160401b03602435818111610b77576110a8903690600401611761565b604435828111610c6c576110c0903690600401611761565b9084606435848111610b7b576110da903690600401611761565b608435946110e66117b7565b9060e435908111610b77576110ff903690600401611761565b91611108611a34565b6002546001600160a01b0316803b15610c6c578685876111478b9783978e6040519a8b998a988997635a0413d360e01b895260a4359460048a01611d87565b03925af18015610a1e5761118f575b50506111897f3432148e671f9811b981a836dda2d6428dfde101f64d3d1e5d2fd35d4a6fbb209360405193849384611df7565b0390a280f35b611198906116fb565b610c6c578438611156565b5034610164576111b236611940565b9082526003602052604082208054821015610fde576111d19250611956565b50805460018201546002830154600384015460048501546005860154600687015460079097015460405197889761035c9760a084901c60ff16976001600160a01b0394851697939094169593918a611988565b50346101645760203660031901126101645761123e6117cd565b611246611a34565b6001600160a01b0390811690811561129557600254826001600160601b0360a01b821617600255167f482b97c53e48ffa324a976e2738053e9aff6eee04d8aac63b10e19411d869b828380a380f35b60405162461bcd60e51b815260206004820152601860248201527f496e76616c6964207265676973747279206164647265737300000000000000006044820152606490fd5b503461016457806112ea36611940565b906112f3611a34565b6002546001600160a01b031691823b15610a2957604484928360405195869485936303b9b22760e21b8552600485015260248401525af18015610a1e57610a0e5750f35b5034610164576020366003190112610164576113516117cd565b611359611a34565b6001600160a01b039081169081156113a857600154826001600160601b0360a01b821617600155167f1c87e2bbc4e5fa5d7f6f8c44d66cb241dff224b8602eb5435ca2076d2a5c6fc28380a380f35b60405162461bcd60e51b815260206004820152601a60248201527f496e76616c696420636f6e74726f6c6c657220616464726573730000000000006044820152606490fd5b50346101645760c036600319011261016457806001600160401b036024358181116114c6576114209036906004016117fa565b604435828111610a2957611438903690600401611858565b606435838111610c6c57611450903690600401611858565b9060843584811161063e576114699036906004016118d8565b9360a43590811161063e576114829036906004016117fa565b9061148b611a34565b6002546001600160a01b031691823b1561053a578694610c5b8692604051988997889687956303f3418160e01b875260043560048801611e59565b5050fd5b5034610164576101a0366003190112610164576001600160401b0390602435828111610b7b576114fe903690600401611761565b604435838111610fde57611516903690600401611761565b90606435848111610b775761152f903690600401611761565b936115386117b7565b60e43582811161063e57611550903690600401611761565b956101043583811161053a5761156a9036906004016117fa565b916101243584811161063257611584903690600401611858565b90610144358581116105875761159e903690600401611858565b926101643586811161086d576115b89036906004016118d8565b956101843590811161086d576115d29036906004016117fa565b996115db611a34565b6002546001600160a01b031692833b1561097a57916116248b94928a9460405180608052635a0413d360e01b90528c60805196879560a435926084359260043560048a01611d87565b039083608051915af180156116f0576116da575b60025496979596949588956001600160a01b03169190823b1561053a57869461167c8692604051988997889687956303f3418160e01b875260043560048801611e59565b03925af18015610a1e576116c6575b50506111897f3432148e671f9811b981a836dda2d6428dfde101f64d3d1e5d2fd35d4a6fbb2091604051918291600435956084359184611df7565b6116cf906116fb565b610fde57823861168b565b9394956116e86080516116fb565b959493611638565b6040513d89823e3d90fd5b6001600160401b03811161170e57604052565b634e487b7160e01b600052604160045260246000fd5b61012081019081106001600160401b0382111761170e57604052565b90601f801991011681019081106001600160401b0382111761170e57604052565b81601f82011215610b72578035906001600160401b03821161170e5760405192611795601f8401601f191660200185611740565b82845260208383010111610b7257816000926020809301838601378301015290565b60c435906001600160a01b0382168203610b7257565b600435906001600160a01b0382168203610b7257565b6001600160401b03811161170e5760051b60200190565b81601f82011215610b7257803591611811836117e3565b9261181f6040519485611740565b808452602092838086019260051b820101928311610b72578301905b828210611849575050505090565b8135815290830190830161183b565b9080601f83011215610b7257813590611870826117e3565b9261187e6040519485611740565b828452602092838086019160051b83010192808411610b7257848301915b8483106118ac5750505050505090565b82356001600160401b038111610b725786916118cd84848094890101611761565b81520192019161189c565b81601f82011215610b72578035916118ef836117e3565b926118fd6040519485611740565b808452602092838086019260051b820101928311610b72578301905b828210611927575050505090565b813560ff81168103610b72578152908301908301611919565b6040906003190112610b72576004359060243590565b80548210156119725760005260206000209060031b0190600090565b634e487b7160e01b600052603260045260246000fd5b979390999895926101009795926101208a019b8a5260208a015260408901526060880152608087015260a086015260018060a01b0380921660c08601521660e08401521515910152565b805182526020810151602083015260408101516040830152606081015160608301526080810151608083015260a081015160a083015260018060a01b038060c08301511660c084015260e08201511660e0830152610100809101511515910152565b6000546001600160a01b03163303611a4857565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b51908115158203610b7257565b51906001600160a01b0382168203610b7257565b919082519283825260005b848110611ad9575050826000602080949584010152601f8019910116010190565b602081830181015184830182015201611ab8565b15611af457565b60405162461bcd60e51b815260206004820152600e60248201526d4461746120746f6f207374616c6560901b6044820152606490fd5b15611b3157565b60405162461bcd60e51b815260206004820152601360248201527256616c7565206f7574206f6620626f756e647360681b6044820152606490fd5b90815180825260208092019182818360051b85019501936000915b848310611b975750505050505090565b9091929394958480611bb183856001950387528a51611aad565b9801930193019194939290611b87565b8051156119725760200190565b90805182556020810151600183015560408101516002830155606081015160038301556080810151600483015560a081015160058301556007600683019260018060a01b03938460c0850151166001600160601b0360a01b825416179055019160e08201511682549161010060ff60a01b910151151560a01b16916affffffffffffffffffffff60a81b1617179055565b939192909260405193611c7185611724565b8585526020850152604084015260608301524360808301524260a08301523360c083015260018060a01b031660e0820152600161010082015260009180835260036020526040832080549068010000000000000000821015611d295790611cdd91600182018155611956565b611d155791611d0291611cf38260409695611bce565b83526004602052838320611bce565b600560205220805460ff19166001179055565b634e487b7160e01b84526004849052602484fd5b634e487b7160e01b85526041600452602485fd5b6002546001600160a01b031691823b15610b72576044600092836040519586948593631136e0db60e21b8552600485015260248401525af1611d7c5750565b611d85906116fb565b565b94969390611df4989693611daf611dcb94611dbd9389526101008060208b0152890190611aad565b908782036040890152611aad565b908582036060870152611aad565b608084019590955260a08301526001600160a01b031660c082015280830360e090910152611aad565b90565b939291611e2090611e12604093606088526060880190611aad565b908682036020880152611aad565b930152565b90815180825260208080930193019160005b828110611e45575050505090565b835185529381019392810192600101611e37565b92611e899197969492978452611e7b60209860c08a87015260c0860190611e25565b908482036040860152611b6c565b828103606084015281518082528782019188808360051b8301019401926000915b8a848410611f0357505050505050818103608083015285808551928381520194019060005b818110611eec57505050611df493945060a0818403910152611e25565b825160ff1686529487019491870191600101611ecf565b80611f1f6001939495969798601f198682030187528951611aad565b97019301930191939290611eaa565b60405190611f3b82611724565b816101006000918281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201520152565b90604051611f8081611724565b61010060ff82948054845260018101546020850152600281015460408501526003810154606085015260048101546080850152600581015460a0850152600760018060a01b03918260068201541660c0870152015490811660e085015260a01c16151591015256fe111ce3ee07b4109e2d6c522160830a49eef0f954c097d4964a4eb78496a7e5e7a2646970667358221220aa02980ebab7d68b96aa844bd973656ec50f60a47f302fe4de78907a667069e164736f6c63430008110033
Verified Source Code Full Match
Compiler: v0.8.17+commit.8df45f5f
EVM: london
Optimization: Yes (200 runs)
AccessControl.sol 248 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
IAccessControl.sol 88 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../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.
*
* By default, the owner account will be the one that deploys the contract. 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;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @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 {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @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 {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_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);
}
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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;
}
}
ECDSA.sol 217 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32")
mstore(0x1c, hash)
message := keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x00", validator, data));
}
}
ERC165.sol 29 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* 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[EIP 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);
}
Math.sol 339 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
SignedMath.sol 43 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
Strings.sol 85 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
QXMPDynamicRegistryV2.sol 655 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
/**
* @title QXMPDynamicRegistryV2
* @notice Universal asset registry with schema-less dynamic fields
* @dev Supports ANY asset type with ANY number of fields
*
* Key Features:
* - Global field schema registry (define field types once, reuse forever)
* - Per-asset dynamic field storage (0 to 100+ fields per asset)
* - Field-level confidence tracking (AI extraction confidence)
* - Admin verification system (manual verification flag per field)
* - IPFS backup integration (full metadata stored off-chain)
* - Gas optimized (only store fields that exist)
*
* Asset Types Supported: Gold, Diamond, Land, Art, Vehicles, IP, ANY future type
*/
contract QXMPDynamicRegistryV2 {
// ═══════════════════════════════════════════════════════════════════════
// ENUMS
// ═══════════════════════════════════════════════════════════════════════
/**
* @dev Data types for field values
* Each type determines how the bytes value is encoded/decoded
*/
enum DataType {
String, // 0: Text data (name, description, location)
Uint256, // 1: Unsigned integer (quantities, amounts)
Int256, // 2: Signed integer (coordinates, deltas)
Address, // 3: Ethereum address (holder, certifier)
Bytes32, // 4: Hash or identifier
Bool // 5: Boolean flag
}
// ═══════════════════════════════════════════════════════════════════════
// STRUCTS
// ═══════════════════════════════════════════════════════════════════════
/**
* @dev Field schema definition
* Registered once globally, reused for all assets that have this field
*/
struct FieldSchema {
bytes32 schemaId; // keccak256(fieldName) - unique identifier
string fieldName; // Human-readable name (e.g., "Gold Ounces In Situ")
DataType dataType; // How to interpret the bytes value
string unit; // Unit of measurement ("oz", "m²", "USD", "")
string category; // Grouping ("Resource", "Location", "Financial", "Legal")
string description; // Human-readable description for UI
bool isNumeric; // Can be used in oracle price feeds
bool isRequired; // Required for certain asset types (optional enforcement)
uint256 registeredAt; // Block timestamp when schema was created
bool exists; // Schema exists flag
}
/**
* @dev Core asset record (minimal, universal)
* Same fields for ALL asset types
*/
struct AssetCore {
bytes32 assetCode; // Unique identifier (keccak256 of human-readable code)
string assetName; // Human-readable asset name
string assetType; // Dynamic type: "Gold Mine", "Land Parcel", "Fine Art", etc.
string jurisdiction; // Location / country code
uint256 primaryValueUsd; // Main USD value (18 decimals) - oracle verified
bytes32 reportHash; // SHA-256 hash of source PDF document
uint256 registeredAt; // Block timestamp of registration
uint256 lastUpdated; // Last update timestamp (field changes, oracle updates)
address holder; // Asset holder wallet address
bool isActive; // Active status (can be deactivated)
string ipfsCid; // IPFS CID for full metadata backup
}
/**
* @dev Dynamic field value with metadata
* Each asset can have 0 to N of these
*/
struct AssetField {
bytes32 schemaId; // References FieldSchema.schemaId
bytes value; // ABI-encoded value (decode based on schema.dataType)
string displayValue; // Human-readable string for UI display
uint8 confidence; // AI extraction confidence (0-100)
bool isVerified; // Admin manual verification flag
bool isDeleted; // Soft delete flag (filtered from queries)
uint256 updatedAt; // Last update timestamp
bytes32 sourceHash; // Hash of PDF page/section where extracted (optional)
}
// ═══════════════════════════════════════════════════════════════════════
// STATE VARIABLES
// ═══════════════════════════════════════════════════════════════════════
// Owner address (QXMP Labs or ProofOfReserve contract)
address public owner;
// Global field schema registry
mapping(bytes32 => FieldSchema) public fieldSchemas;
bytes32[] public fieldSchemaIds;
// Asset core data
mapping(bytes32 => AssetCore) public assets;
bytes32[] public assetCodes;
// Asset fields (assetCode => array of fields)
mapping(bytes32 => AssetField[]) private assetFields;
// Field index for O(1) lookups (assetCode => schemaId => index in array)
mapping(bytes32 => mapping(bytes32 => uint256)) private fieldIndex;
mapping(bytes32 => mapping(bytes32 => bool)) private hasField;
// ═══════════════════════════════════════════════════════════════════════
// EVENTS
// ═══════════════════════════════════════════════════════════════════════
event FieldSchemaRegistered(
bytes32 indexed schemaId,
string fieldName,
DataType dataType,
string category
);
event AssetRegistered(
bytes32 indexed assetCode,
string assetName,
string assetType,
uint256 primaryValueUsd,
uint256 fieldCount
);
event FieldAdded(
bytes32 indexed assetCode,
bytes32 indexed schemaId,
string displayValue
);
event FieldUpdated(
bytes32 indexed assetCode,
bytes32 indexed schemaId,
string newDisplayValue
);
event FieldVerified(
bytes32 indexed assetCode,
bytes32 indexed schemaId,
address verifier
);
event FieldDeleted(
bytes32 indexed assetCode,
bytes32 indexed schemaId,
address deletedBy
);
event PrimaryValueUpdated(
bytes32 indexed assetCode,
uint256 oldValue,
uint256 newValue
);
event AssetDeactivated(bytes32 indexed assetCode);
event AssetReactivated(bytes32 indexed assetCode);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ═══════════════════════════════════════════════════════════════════════
// MODIFIERS
// ═══════════════════════════════════════════════════════════════════════
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
modifier assetExists(bytes32 assetCode) {
require(assets[assetCode].isActive, "Asset does not exist or is inactive");
_;
}
modifier schemaExists(bytes32 schemaId) {
require(fieldSchemas[schemaId].exists, "Field schema does not exist");
_;
}
// ═══════════════════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════════════════════════
constructor() {
owner = msg.sender;
}
// ═══════════════════════════════════════════════════════════════════════
// FIELD SCHEMA MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Register a new field schema
* @dev Can be called by owner or automatically when new field type is discovered
* @param fieldName Human-readable field name
* @param dataType Data type enum value
* @param unit Unit of measurement (empty string if none)
* @param category Grouping category
* @param description Human-readable description
* @param isNumeric Whether field can be used in oracle feeds
* @return schemaId Generated schema identifier
*/
function registerFieldSchema(
string memory fieldName,
DataType dataType,
string memory unit,
string memory category,
string memory description,
bool isNumeric
) external onlyOwner returns (bytes32 schemaId) {
// Generate schema ID from field name
schemaId = keccak256(abi.encodePacked(fieldName));
require(!fieldSchemas[schemaId].exists, "Field schema already exists");
require(bytes(fieldName).length > 0, "Field name cannot be empty");
fieldSchemas[schemaId] = FieldSchema({
schemaId: schemaId,
fieldName: fieldName,
dataType: dataType,
unit: unit,
category: category,
description: description,
isNumeric: isNumeric,
isRequired: false,
registeredAt: block.timestamp,
exists: true
});
fieldSchemaIds.push(schemaId);
emit FieldSchemaRegistered(schemaId, fieldName, dataType, category);
return schemaId;
}
/**
* @notice Get field schema by ID
*/
function getFieldSchema(bytes32 schemaId)
external
view
schemaExists(schemaId)
returns (FieldSchema memory)
{
return fieldSchemas[schemaId];
}
/**
* @notice Get total number of registered field schemas
*/
function getFieldSchemaCount() external view returns (uint256) {
return fieldSchemaIds.length;
}
/**
* @notice Check if field schema exists
*/
function fieldSchemaExists(bytes32 schemaId) external view returns (bool) {
return fieldSchemas[schemaId].exists;
}
// ═══════════════════════════════════════════════════════════════════════
// ASSET CORE MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Register asset core data (called by ProofOfReserve contract)
* @dev Fields are added separately with addField() or in batch with addFields()
*/
function registerAssetCore(
bytes32 assetCode,
string memory assetName,
string memory assetType,
string memory jurisdiction,
uint256 primaryValueUsd,
bytes32 reportHash,
address holder,
string memory ipfsCid
) external onlyOwner {
require(assets[assetCode].registeredAt == 0, "Asset code already used");
require(primaryValueUsd > 0, "Primary value must be greater than 0");
require(holder != address(0), "Invalid holder address");
require(bytes(assetName).length > 0, "Asset name cannot be empty");
assets[assetCode] = AssetCore({
assetCode: assetCode,
assetName: assetName,
assetType: assetType,
jurisdiction: jurisdiction,
primaryValueUsd: primaryValueUsd,
reportHash: reportHash,
registeredAt: block.timestamp,
lastUpdated: block.timestamp,
holder: holder,
isActive: true,
ipfsCid: ipfsCid
});
assetCodes.push(assetCode);
emit AssetRegistered(assetCode, assetName, assetType, primaryValueUsd, 0);
}
/**
* @notice Update primary value (called by ProofOfReserve after oracle verification)
*/
function updatePrimaryValue(bytes32 assetCode, uint256 newValueUsd)
external
onlyOwner
assetExists(assetCode)
{
require(newValueUsd > 0, "Value must be greater than 0");
uint256 oldValue = assets[assetCode].primaryValueUsd;
assets[assetCode].primaryValueUsd = newValueUsd;
assets[assetCode].lastUpdated = block.timestamp;
emit PrimaryValueUpdated(assetCode, oldValue, newValueUsd);
}
/**
* @notice Deactivate an asset
*/
function deactivateAsset(bytes32 assetCode)
external
onlyOwner
assetExists(assetCode)
{
assets[assetCode].isActive = false;
emit AssetDeactivated(assetCode);
}
/**
* @notice Reactivate a deactivated asset
*/
function reactivateAsset(bytes32 assetCode) external onlyOwner {
require(assets[assetCode].registeredAt > 0, "Asset never existed");
require(!assets[assetCode].isActive, "Asset already active");
assets[assetCode].isActive = true;
assets[assetCode].lastUpdated = block.timestamp;
emit AssetReactivated(assetCode);
}
/**
* @notice Get asset core data
*/
function getAsset(bytes32 assetCode)
external
view
assetExists(assetCode)
returns (AssetCore memory)
{
return assets[assetCode];
}
/**
* @notice Get total number of registered assets
*/
function getAssetCount() external view returns (uint256) {
return assetCodes.length;
}
/**
* @notice Get asset code by index
*/
function getAssetCodeByIndex(uint256 index) external view returns (bytes32) {
require(index < assetCodes.length, "Index out of bounds");
return assetCodes[index];
}
// ═══════════════════════════════════════════════════════════════════════
// DYNAMIC FIELD MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Add a field to an existing asset
* @dev Schema must exist before calling this
*/
function addField(
bytes32 assetCode,
bytes32 schemaId,
bytes memory value,
string memory displayValue,
uint8 confidence,
bytes32 sourceHash
) external onlyOwner assetExists(assetCode) schemaExists(schemaId) {
require(!hasField[assetCode][schemaId], "Field already exists, use updateField");
require(confidence <= 100, "Confidence must be 0-100");
AssetField memory field = AssetField({
schemaId: schemaId,
value: value,
displayValue: displayValue,
confidence: confidence,
isVerified: false,
isDeleted: false,
updatedAt: block.timestamp,
sourceHash: sourceHash
});
assetFields[assetCode].push(field);
fieldIndex[assetCode][schemaId] = assetFields[assetCode].length - 1;
hasField[assetCode][schemaId] = true;
assets[assetCode].lastUpdated = block.timestamp;
emit FieldAdded(assetCode, schemaId, displayValue);
}
/**
* @notice Add multiple fields in batch (gas efficient)
*/
function addFields(
bytes32 assetCode,
bytes32[] memory schemaIds,
bytes[] memory values,
string[] memory displayValues,
uint8[] memory confidences,
bytes32[] memory sourceHashes
) external onlyOwner assetExists(assetCode) {
require(schemaIds.length > 0, "Cannot add zero fields");
require(schemaIds.length <= 50, "Max 50 fields per batch to prevent out-of-gas");
require(schemaIds.length == values.length, "Array length mismatch");
require(schemaIds.length == displayValues.length, "Array length mismatch");
require(schemaIds.length == confidences.length, "Array length mismatch");
require(schemaIds.length == sourceHashes.length, "Array length mismatch");
for (uint i = 0; i < schemaIds.length; i++) {
bytes32 schemaId = schemaIds[i];
require(fieldSchemas[schemaId].exists, "Field schema does not exist");
require(!hasField[assetCode][schemaId], "Field already exists");
require(confidences[i] <= 100, "Confidence must be 0-100");
AssetField memory field = AssetField({
schemaId: schemaId,
value: values[i],
displayValue: displayValues[i],
confidence: confidences[i],
isVerified: false,
isDeleted: false,
updatedAt: block.timestamp,
sourceHash: sourceHashes[i]
});
assetFields[assetCode].push(field);
fieldIndex[assetCode][schemaId] = assetFields[assetCode].length - 1;
hasField[assetCode][schemaId] = true;
emit FieldAdded(assetCode, schemaId, displayValues[i]);
}
assets[assetCode].lastUpdated = block.timestamp;
}
/**
* @notice Update an existing field value
*/
function updateField(
bytes32 assetCode,
bytes32 schemaId,
bytes memory newValue,
string memory newDisplayValue
) external onlyOwner assetExists(assetCode) {
require(hasField[assetCode][schemaId], "Field does not exist");
uint256 idx = fieldIndex[assetCode][schemaId];
assetFields[assetCode][idx].value = newValue;
assetFields[assetCode][idx].displayValue = newDisplayValue;
assetFields[assetCode][idx].updatedAt = block.timestamp;
assetFields[assetCode][idx].isVerified = false; // Reset verification
assets[assetCode].lastUpdated = block.timestamp;
emit FieldUpdated(assetCode, schemaId, newDisplayValue);
}
/**
* @notice Verify a field (admin approval)
*/
function verifyField(bytes32 assetCode, bytes32 schemaId)
external
onlyOwner
assetExists(assetCode)
{
require(hasField[assetCode][schemaId], "Field does not exist");
uint256 idx = fieldIndex[assetCode][schemaId];
assetFields[assetCode][idx].isVerified = true;
assetFields[assetCode][idx].updatedAt = block.timestamp;
emit FieldVerified(assetCode, schemaId, msg.sender);
}
/**
* @notice Remove (soft delete) a field from an asset
* @dev Marks field as deleted but keeps it in array for audit trail
*/
function removeField(bytes32 assetCode, bytes32 schemaId)
external
onlyOwner
assetExists(assetCode)
{
require(hasField[assetCode][schemaId], "Field does not exist");
uint256 idx = fieldIndex[assetCode][schemaId];
require(!assetFields[assetCode][idx].isDeleted, "Field already deleted");
assetFields[assetCode][idx].isDeleted = true;
assetFields[assetCode][idx].updatedAt = block.timestamp;
assets[assetCode].lastUpdated = block.timestamp;
emit FieldDeleted(assetCode, schemaId, msg.sender);
}
/**
* @notice Restore a deleted field
* @dev Unmarks the deleted flag
*/
function restoreField(bytes32 assetCode, bytes32 schemaId)
external
onlyOwner
assetExists(assetCode)
{
require(hasField[assetCode][schemaId], "Field does not exist");
uint256 idx = fieldIndex[assetCode][schemaId];
require(assetFields[assetCode][idx].isDeleted, "Field not deleted");
assetFields[assetCode][idx].isDeleted = false;
assetFields[assetCode][idx].updatedAt = block.timestamp;
assets[assetCode].lastUpdated = block.timestamp;
emit FieldAdded(assetCode, schemaId, assetFields[assetCode][idx].displayValue);
}
/**
* @notice Get all fields for an asset (excluding deleted)
*/
function getAssetFields(bytes32 assetCode)
external
view
assetExists(assetCode)
returns (AssetField[] memory)
{
// Count non-deleted fields
uint256 count = 0;
for (uint i = 0; i < assetFields[assetCode].length; i++) {
if (!assetFields[assetCode][i].isDeleted) {
count++;
}
}
// Build array of non-deleted fields
AssetField[] memory activeFields = new AssetField[](count);
uint256 index = 0;
for (uint i = 0; i < assetFields[assetCode].length; i++) {
if (!assetFields[assetCode][i].isDeleted) {
activeFields[index] = assetFields[assetCode][i];
index++;
}
}
return activeFields;
}
/**
* @notice Get all fields including deleted ones (admin view)
*/
function getAllAssetFields(bytes32 assetCode)
external
view
assetExists(assetCode)
returns (AssetField[] memory)
{
return assetFields[assetCode];
}
/**
* @notice Get specific field value
*/
function getFieldValue(bytes32 assetCode, bytes32 schemaId)
external
view
assetExists(assetCode)
returns (
bytes memory value,
string memory displayValue,
uint8 confidence,
bool isVerified
)
{
require(hasField[assetCode][schemaId], "Field does not exist");
uint256 idx = fieldIndex[assetCode][schemaId];
AssetField memory field = assetFields[assetCode][idx];
require(!field.isDeleted, "Field has been deleted");
return (field.value, field.displayValue, field.confidence, field.isVerified);
}
/**
* @notice Get field count for an asset
*/
function getFieldCount(bytes32 assetCode)
external
view
assetExists(assetCode)
returns (uint256)
{
// Count non-deleted fields
uint256 count = 0;
for (uint i = 0; i < assetFields[assetCode].length; i++) {
if (!assetFields[assetCode][i].isDeleted) {
count++;
}
}
return count;
}
/**
* @notice Check if asset has specific field
*/
function assetHasField(bytes32 assetCode, bytes32 schemaId)
external
view
returns (bool)
{
return hasField[assetCode][schemaId];
}
// ═══════════════════════════════════════════════════════════════════════
// ADMIN FUNCTIONS
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Transfer ownership
*/
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "Invalid new owner address");
address previousOwner = owner;
owner = newOwner;
emit OwnershipTransferred(previousOwner, newOwner);
}
}
QXMPOracleController.sol 426 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @title QXMPOracleController
* @notice Manages authorized oracle signers and signature verification for QXMP
* @dev Handles signer management, threshold configuration, and signature verification
*
* Key Features:
* - Role-based access control (Admin, Operator roles)
* - Dynamic signer management (add/remove authorized signers)
* - Configurable signature threshold (1-of-N, N-of-M)
* - Per-asset staleness configuration
* - Per-asset value bounds for anomaly detection
* - ECDSA signature verification
*
* Scalability:
* - Phase 1: Single QXMP signer (1-of-1)
* - Phase 2: Multiple QXMP signers (N-of-M)
* - Phase 3: Partner oracle integration
*/
contract QXMPOracleController is Ownable, AccessControl {
using ECDSA for bytes32;
// ═══════════════════════════════════════════════════════════════════════
// ROLES
// ═══════════════════════════════════════════════════════════════════════
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
// ═══════════════════════════════════════════════════════════════════════
// STATE VARIABLES
// ═══════════════════════════════════════════════════════════════════════
// Authorized oracle signers
mapping(address => bool) public isAuthorizedSigner;
address[] public signers;
// Signature threshold (e.g., 1 for single signer, 2 for 2-of-3)
uint8 public requiredSignatures;
// Staleness protection
uint256 public defaultMaxStaleness;
mapping(bytes32 => uint256) public assetMaxStaleness; // Per-asset override
// Value bounds for anomaly detection
mapping(bytes32 => uint256) public assetMinValue;
mapping(bytes32 => uint256) public assetMaxValue;
// ═══════════════════════════════════════════════════════════════════════
// EVENTS
// ═══════════════════════════════════════════════════════════════════════
event SignerAdded(
address indexed signer,
address indexed addedBy,
uint256 timestamp
);
event SignerRemoved(
address indexed signer,
address indexed removedBy,
uint256 timestamp
);
event ThresholdUpdated(
uint8 oldThreshold,
uint8 newThreshold,
address indexed updatedBy
);
event DefaultMaxStalenessUpdated(
uint256 oldMaxStaleness,
uint256 newMaxStaleness,
address indexed updatedBy
);
event AssetMaxStalenessUpdated(
bytes32 indexed assetCode,
uint256 oldMaxStaleness,
uint256 newMaxStaleness,
address indexed updatedBy
);
event AssetValueBoundsUpdated(
bytes32 indexed assetCode,
uint256 minValue,
uint256 maxValue,
address indexed updatedBy
);
// ═══════════════════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════════════════════════
/**
* @param initialSigner First authorized oracle signer (typically QXMP wallet)
* @param _defaultMaxStaleness Default staleness threshold (e.g., 24 hours)
*/
constructor(address initialSigner, uint256 _defaultMaxStaleness) {
require(initialSigner != address(0), "Invalid initial signer");
require(_defaultMaxStaleness > 0, "Invalid staleness threshold");
// Set up roles
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(ADMIN_ROLE, msg.sender);
_setupRole(OPERATOR_ROLE, msg.sender);
// Add initial signer
isAuthorizedSigner[initialSigner] = true;
signers.push(initialSigner);
// Set initial threshold to 1 (single signature required)
requiredSignatures = 1;
// Set default staleness
defaultMaxStaleness = _defaultMaxStaleness;
emit SignerAdded(initialSigner, msg.sender, block.timestamp);
emit ThresholdUpdated(0, 1, msg.sender);
emit DefaultMaxStalenessUpdated(0, _defaultMaxStaleness, msg.sender);
}
// ═══════════════════════════════════════════════════════════════════════
// SIGNER MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Add a new authorized oracle signer
* @param signer Address of the new signer
*/
function addSigner(address signer) external onlyRole(ADMIN_ROLE) {
require(signer != address(0), "Invalid signer address");
require(!isAuthorizedSigner[signer], "Signer already authorized");
isAuthorizedSigner[signer] = true;
signers.push(signer);
emit SignerAdded(signer, msg.sender, block.timestamp);
}
/**
* @notice Remove an authorized oracle signer
* @param signer Address of the signer to remove
*/
function removeSigner(address signer) external onlyRole(ADMIN_ROLE) {
require(isAuthorizedSigner[signer], "Signer not authorized");
require(signers.length > 1, "Cannot remove last signer");
isAuthorizedSigner[signer] = false;
// Remove from array
for (uint i = 0; i < signers.length; i++) {
if (signers[i] == signer) {
signers[i] = signers[signers.length - 1];
signers.pop();
break;
}
}
emit SignerRemoved(signer, msg.sender, block.timestamp);
}
/**
* @notice Get all authorized signers
* @return Array of authorized signer addresses
*/
function getSigners() external view returns (address[] memory) {
return signers;
}
/**
* @notice Get number of authorized signers
* @return Count of authorized signers
*/
function getSignerCount() external view returns (uint256) {
return signers.length;
}
// ═══════════════════════════════════════════════════════════════════════
// THRESHOLD MANAGEMENT
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Set the required number of signatures
* @param newThreshold Number of signatures required (e.g., 2 for 2-of-3)
*/
function setThreshold(uint8 newThreshold) external onlyRole(ADMIN_ROLE) {
require(newThreshold > 0, "Threshold must be greater than 0");
require(newThreshold <= signers.length, "Threshold exceeds signer count");
uint8 oldThreshold = requiredSignatures;
requiredSignatures = newThreshold;
emit ThresholdUpdated(oldThreshold, newThreshold, msg.sender);
}
// ═══════════════════════════════════════════════════════════════════════
// STALENESS CONFIGURATION
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Set default maximum staleness for all assets
* @param newMaxStaleness Maximum age in seconds (e.g., 86400 for 24 hours)
*/
function setDefaultMaxStaleness(uint256 newMaxStaleness)
external
onlyRole(ADMIN_ROLE)
{
require(newMaxStaleness > 0, "Staleness must be greater than 0");
uint256 oldMaxStaleness = defaultMaxStaleness;
defaultMaxStaleness = newMaxStaleness;
emit DefaultMaxStalenessUpdated(oldMaxStaleness, newMaxStaleness, msg.sender);
}
/**
* @notice Set maximum staleness for a specific asset
* @param assetCode Asset identifier
* @param maxStaleness Maximum age in seconds (0 to use default)
*/
function setAssetMaxStaleness(bytes32 assetCode, uint256 maxStaleness)
external
onlyRole(ADMIN_ROLE)
{
uint256 oldMaxStaleness = assetMaxStaleness[assetCode];
assetMaxStaleness[assetCode] = maxStaleness;
emit AssetMaxStalenessUpdated(assetCode, oldMaxStaleness, maxStaleness, msg.sender);
}
/**
* @notice Get maximum staleness for an asset
* @param assetCode Asset identifier
* @return Maximum staleness in seconds
*/
function getMaxStaleness(bytes32 assetCode) public view returns (uint256) {
uint256 assetStaleness = assetMaxStaleness[assetCode];
return assetStaleness > 0 ? assetStaleness : defaultMaxStaleness;
}
// ═══════════════════════════════════════════════════════════════════════
// VALUE BOUNDS CONFIGURATION
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Set value bounds for anomaly detection
* @param assetCode Asset identifier
* @param minValue Minimum acceptable value (0 for no minimum)
* @param maxValue Maximum acceptable value (0 for no maximum)
*/
function setAssetValueBounds(
bytes32 assetCode,
uint256 minValue,
uint256 maxValue
) external onlyRole(ADMIN_ROLE) {
if (maxValue > 0) {
require(minValue < maxValue, "Min must be less than max");
}
assetMinValue[assetCode] = minValue;
assetMaxValue[assetCode] = maxValue;
emit AssetValueBoundsUpdated(assetCode, minValue, maxValue, msg.sender);
}
/**
* @notice Check if a value is within bounds
* @param assetCode Asset identifier
* @param value Value to check
* @return bool True if value is within bounds (or no bounds set)
*/
function isValueWithinBounds(bytes32 assetCode, uint256 value)
public
view
returns (bool)
{
uint256 minValue = assetMinValue[assetCode];
uint256 maxValue = assetMaxValue[assetCode];
// No bounds set
if (minValue == 0 && maxValue == 0) {
return true;
}
// Check bounds
if (minValue > 0 && value < minValue) {
return false;
}
if (maxValue > 0 && value > maxValue) {
return false;
}
return true;
}
// ═══════════════════════════════════════════════════════════════════════
// SIGNATURE VERIFICATION
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Verify a single signature
* @param messageHash Hash of the signed message
* @param signature ECDSA signature
* @return isValid True if signature is from an authorized signer
* @return signer Address that signed the message
*/
function verifySignature(bytes32 messageHash, bytes memory signature)
public
view
returns (bool isValid, address signer)
{
// Recover signer from signature
signer = messageHash.toEthSignedMessageHash().recover(signature);
// Check if signer is authorized
isValid = isAuthorizedSigner[signer];
return (isValid, signer);
}
/**
* @notice Verify multiple signatures (for multi-sig)
* @param messageHash Hash of the signed message
* @param signatures Array of ECDSA signatures
* @return isValid True if enough valid signatures from unique signers
* @return validCount Number of valid signatures
* @return recoveredSigners Array of signer addresses
*/
function verifyMultipleSignatures(
bytes32 messageHash,
bytes[] memory signatures
)
public
view
returns (
bool isValid,
uint8 validCount,
address[] memory recoveredSigners
)
{
require(signatures.length > 0, "No signatures provided");
require(
signatures.length <= signers.length,
"More signatures than signers"
);
recoveredSigners = new address[](signatures.length);
validCount = 0;
bytes32 ethSignedHash = messageHash.toEthSignedMessageHash();
for (uint i = 0; i < signatures.length; i++) {
address signer = ethSignedHash.recover(signatures[i]);
recoveredSigners[i] = signer;
// Check if signer is authorized and not duplicate
if (isAuthorizedSigner[signer]) {
// Check for duplicates
bool isDuplicate = false;
for (uint j = 0; j < i; j++) {
if (recoveredSigners[j] == signer) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
validCount++;
}
}
}
// Check if we have enough valid signatures
isValid = validCount >= requiredSignatures;
return (isValid, validCount, recoveredSigners);
}
/**
* @notice Recover signer address from signature (utility function)
* @param messageHash Hash of the signed message
* @param signature ECDSA signature
* @return signer Address that signed the message
*/
function recoverSigner(bytes32 messageHash, bytes memory signature)
public
pure
returns (address signer)
{
return messageHash.toEthSignedMessageHash().recover(signature);
}
// ═══════════════════════════════════════════════════════════════════════
// VALIDATION HELPERS
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Check if data is stale
* @param assetCode Asset identifier
* @param timestamp Data timestamp
* @return bool True if data is fresh (not stale)
*/
function isFresh(bytes32 assetCode, uint256 timestamp)
public
view
returns (bool)
{
uint256 maxStaleness = getMaxStaleness(assetCode);
// Prevent underflow: if timestamp is in the future (e.g., clock skew),
// consider it fresh rather than reverting
if (timestamp >= block.timestamp) {
return true;
}
// Check if the time difference is within acceptable staleness
return block.timestamp - timestamp <= maxStaleness;
}
}
QXMPProofOfReserveV3.sol 605 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./QXMPOracleController.sol";
import "./QXMPDynamicRegistryV2.sol";
/**
* @title QXMPProofOfReserveV3
* @notice Oracle-verified proof-of-reserves for QXMP assets with FIXED ownership architecture
* @dev V3 OWNS the Registry contract - fixes the ownership bug from V2
*
* Key Changes from V2:
* - V3 now OWNS the Registry contract (deployed first, then Registry ownership transferred)
* - This allows V3 to call registry.updatePrimaryValue() successfully
* - Fixes the admin blockchain sync feature
*
* Architecture:
* Deployer Wallet (0xaD00eb5dC02E56d628d68AbD144B8c223A6Cf1Ef)
* ↓ owns
* QXMPProofOfReserveV3 (this contract)
* ↓ owns
* QXMPDynamicRegistryV2
*
* Layer 1: QXMPOracleController (signature verification)
* Layer 2: QXMPProofOfReserveV3 (this contract - proof storage)
* Layer 3: QXMPDynamicRegistryV2 (asset data storage)
*/
contract QXMPProofOfReserveV3 is Ownable {
// ═══════════════════════════════════════════════════════════════════════
// STATE VARIABLES
// ═══════════════════════════════════════════════════════════════════════
QXMPOracleController public controller;
QXMPDynamicRegistryV2 public registry;
// ═══════════════════════════════════════════════════════════════════════
// STRUCTS
// ═══════════════════════════════════════════════════════════════════════
/**
* @dev Proof-of-reserve record with full audit trail
*/
struct Proof {
bytes32 assetCode; // Asset identifier
bytes32 dataFeedId; // Data feed ID (QXMP:ASSET-STANDARD-JURISDICTION)
uint256 value; // USD value (18 decimals)
uint256 timestamp; // Off-chain timestamp when data was gathered
uint256 blockNumber; // On-chain block number when proof was submitted
uint256 blockTimestamp; // On-chain timestamp when proof was submitted
address submitter; // Address that submitted the transaction
address signer; // Oracle signer who signed the data
bool verified; // Always true (only verified proofs are stored)
}
// ═══════════════════════════════════════════════════════════════════════
// STORAGE
// ═══════════════════════════════════════════════════════════════════════
// All proofs for an asset (complete history)
mapping(bytes32 => Proof[]) public proofs;
// Latest proof for an asset (for quick access)
mapping(bytes32 => Proof) public latestProofs;
// Track if asset has any proofs
mapping(bytes32 => bool) public hasProof;
// ═══════════════════════════════════════════════════════════════════════
// EVENTS
// ═══════════════════════════════════════════════════════════════════════
event ProofSubmitted(
bytes32 indexed assetCode,
bytes32 indexed dataFeedId,
uint256 value,
uint256 timestamp,
address indexed submitter,
address signer
);
event ProofSubmittedDirect(
bytes32 indexed assetCode,
bytes32 indexed dataFeedId,
uint256 value,
address indexed submitter
);
event AssetRegisteredViaProof(
bytes32 indexed assetCode,
string assetName,
string assetType,
uint256 primaryValueUsd
);
event ControllerUpdated(
address indexed oldController,
address indexed newController
);
event RegistryUpdated(
address indexed oldRegistry,
address indexed newRegistry
);
// ═══════════════════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════════════════════════
/**
* @param _controllerAddress Address of QXMPOracleController
* @param _registryAddress Address of QXMPDynamicRegistryV2
*/
constructor(address _controllerAddress, address _registryAddress) {
require(_controllerAddress != address(0), "Invalid controller address");
require(_registryAddress != address(0), "Invalid registry address");
controller = QXMPOracleController(_controllerAddress);
registry = QXMPDynamicRegistryV2(_registryAddress);
}
// ═══════════════════════════════════════════════════════════════════════
// PROOF SUBMISSION - SIGNATURE-BASED (Phase 1)
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Submit proof with single oracle signature
* @dev Message format: keccak256(assetCode, dataFeedId, value, timestamp, chainId)
*
* @param assetCode Asset identifier
* @param dataFeedId Data feed ID (format: QXMP:ASSET-STANDARD-JURISDICTION)
* @param value USD value (18 decimals)
* @param timestamp Off-chain timestamp when data was gathered
* @param signature ECDSA signature from authorized oracle
*/
function submitProofWithSignature(
bytes32 assetCode,
bytes32 dataFeedId,
uint256 value,
uint256 timestamp,
bytes memory signature
) external {
// 1. Construct message hash
bytes32 messageHash = keccak256(
abi.encodePacked(
assetCode,
dataFeedId,
value,
timestamp,
block.chainid
)
);
// 2. Verify signature through controller
(bool isValid, address signer) = controller.verifySignature(
messageHash,
signature
);
require(isValid, "Invalid signature or unauthorized signer");
// 3. Check timestamp freshness
require(
controller.isFresh(assetCode, timestamp),
"Data too stale"
);
// 4. Check value bounds (if configured)
require(
controller.isValueWithinBounds(assetCode, value),
"Value out of bounds"
);
// 5. Store proof
_storeProof(assetCode, dataFeedId, value, timestamp, signer);
// 6. Update registry (V3 owns Registry, so this will work)
_updateRegistry(assetCode, value);
emit ProofSubmitted(
assetCode,
dataFeedId,
value,
timestamp,
msg.sender,
signer
);
}
// ═══════════════════════════════════════════════════════════════════════
// PROOF SUBMISSION - MULTI-SIGNATURE (Phase 2 - Future)
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Submit proof with multiple oracle signatures
* @dev For high-value assets requiring consensus from N-of-M oracles
*
* @param assetCode Asset identifier
* @param dataFeedId Data feed ID
* @param value USD value (18 decimals)
* @param timestamp Off-chain timestamp
* @param signatures Array of ECDSA signatures from authorized oracles
*/
function submitProofMultiSig(
bytes32 assetCode,
bytes32 dataFeedId,
uint256 value,
uint256 timestamp,
bytes[] memory signatures
) external {
// 1. Construct message hash
bytes32 messageHash = keccak256(
abi.encodePacked(
assetCode,
dataFeedId,
value,
timestamp,
block.chainid
)
);
// 2. Verify multiple signatures through controller
(
bool isValid,
uint8 validCount,
address[] memory signers
) = controller.verifyMultipleSignatures(messageHash, signatures);
require(isValid, "Insufficient valid signatures");
// 3. Check timestamp freshness
require(
controller.isFresh(assetCode, timestamp),
"Data too stale"
);
// 4. Check value bounds
require(
controller.isValueWithinBounds(assetCode, value),
"Value out of bounds"
);
// 5. Store proof (use first signer for simplicity)
_storeProof(assetCode, dataFeedId, value, timestamp, signers[0]);
// 6. Update registry (V3 owns Registry, so this will work)
_updateRegistry(assetCode, value);
emit ProofSubmitted(
assetCode,
dataFeedId,
value,
timestamp,
msg.sender,
signers[0] // Primary signer
);
}
// ═══════════════════════════════════════════════════════════════════════
// PROOF SUBMISSION - DIRECT (Admin Fallback)
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Submit proof directly without signature (owner only)
* @dev Emergency fallback if signature system has issues
* Uses block.timestamp as data timestamp
*
* @param assetCode Asset identifier
* @param dataFeedId Data feed ID
* @param value USD value (18 decimals)
*/
function submitProofDirect(
bytes32 assetCode,
bytes32 dataFeedId,
uint256 value
) external onlyOwner {
require(value > 0, "Value must be greater than 0");
// Store proof with block.timestamp as data timestamp
_storeProof(assetCode, dataFeedId, value, block.timestamp, msg.sender);
// Update registry (V3 owns Registry, so this will work)
_updateRegistry(assetCode, value);
emit ProofSubmittedDirect(
assetCode,
dataFeedId,
value,
msg.sender
);
}
// ═══════════════════════════════════════════════════════════════════════
// INTERNAL HELPERS
// ═══════════════════════════════════════════════════════════════════════
/**
* @dev Internal function to store proof
*/
function _storeProof(
bytes32 assetCode,
bytes32 dataFeedId,
uint256 value,
uint256 timestamp,
address signer
) private {
Proof memory proof = Proof({
assetCode: assetCode,
dataFeedId: dataFeedId,
value: value,
timestamp: timestamp,
blockNumber: block.number,
blockTimestamp: block.timestamp,
submitter: msg.sender,
signer: signer,
verified: true
});
// Add to history
proofs[assetCode].push(proof);
// Update latest
latestProofs[assetCode] = proof;
// Mark as having proof
hasProof[assetCode] = true;
}
/**
* @dev Update registry - V3 owns Registry so this will succeed
* @notice Only updates if asset exists in registry
*/
function _updateRegistry(bytes32 assetCode, uint256 value) private {
try registry.updatePrimaryValue(assetCode, value) {
// Registry updated successfully
} catch {
// Registry update failed (asset might not be registered yet)
// This is OK - proof is still stored
}
}
// ═══════════════════════════════════════════════════════════════════════
// ASSET REGISTRATION (Convenience wrappers)
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Register asset in dynamic registry
* @dev Convenience wrapper - forwards to registry contract
*/
function registerAsset(
bytes32 assetCode,
string memory assetName,
string memory assetType,
string memory jurisdiction,
uint256 primaryValueUsd,
bytes32 reportHash,
address holder,
string memory ipfsCid
) external onlyOwner {
registry.registerAssetCore(
assetCode,
assetName,
assetType,
jurisdiction,
primaryValueUsd,
reportHash,
holder,
ipfsCid
);
emit AssetRegisteredViaProof(assetCode, assetName, assetType, primaryValueUsd);
}
/**
* @notice Register asset with fields in single transaction
* @dev Convenience wrapper for registry.registerAssetCore + addFields
*/
function registerAssetWithFields(
bytes32 assetCode,
string memory assetName,
string memory assetType,
string memory jurisdiction,
uint256 primaryValueUsd,
bytes32 reportHash,
address holder,
string memory ipfsCid,
bytes32[] memory schemaIds,
bytes[] memory values,
string[] memory displayValues,
uint8[] memory confidences,
bytes32[] memory sourceHashes
) external onlyOwner {
// Register core asset
registry.registerAssetCore(
assetCode,
assetName,
assetType,
jurisdiction,
primaryValueUsd,
reportHash,
holder,
ipfsCid
);
// Add fields
registry.addFields(
assetCode,
schemaIds,
values,
displayValues,
confidences,
sourceHashes
);
emit AssetRegisteredViaProof(assetCode, assetName, assetType, primaryValueUsd);
}
// ═══════════════════════════════════════════════════════════════════════
// PROOF QUERIES
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Get latest proof for an asset
*/
function getLatestProof(bytes32 assetCode)
external
view
returns (Proof memory)
{
require(hasProof[assetCode], "No proofs for this asset");
return latestProofs[assetCode];
}
/**
* @notice Get all proofs for an asset
*/
function getProofs(bytes32 assetCode)
external
view
returns (Proof[] memory)
{
return proofs[assetCode];
}
/**
* @notice Get proof count for an asset
*/
function getProofCount(bytes32 assetCode)
external
view
returns (uint256)
{
return proofs[assetCode].length;
}
/**
* @notice Get specific proof by index
*/
function getProofByIndex(bytes32 assetCode, uint256 index)
external
view
returns (Proof memory)
{
require(index < proofs[assetCode].length, "Index out of bounds");
return proofs[assetCode][index];
}
// ═══════════════════════════════════════════════════════════════════════
// FIELD SCHEMA & FIELD MANAGEMENT (Convenience wrappers)
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Register field schema in registry
*/
function registerFieldSchema(
string memory fieldName,
QXMPDynamicRegistryV2.DataType dataType,
string memory unit,
string memory category,
string memory description,
bool isNumeric
) external onlyOwner returns (bytes32 schemaId) {
return registry.registerFieldSchema(
fieldName,
dataType,
unit,
category,
description,
isNumeric
);
}
/**
* @notice Add field to asset
*/
function addField(
bytes32 assetCode,
bytes32 schemaId,
bytes memory value,
string memory displayValue,
uint8 confidence,
bytes32 sourceHash
) external onlyOwner {
registry.addField(
assetCode,
schemaId,
value,
displayValue,
confidence,
sourceHash
);
}
/**
* @notice Add multiple fields to asset
*/
function addFields(
bytes32 assetCode,
bytes32[] memory schemaIds,
bytes[] memory values,
string[] memory displayValues,
uint8[] memory confidences,
bytes32[] memory sourceHashes
) external onlyOwner {
registry.addFields(
assetCode,
schemaIds,
values,
displayValues,
confidences,
sourceHashes
);
}
/**
* @notice Update an existing field
*/
function updateField(
bytes32 assetCode,
bytes32 schemaId,
bytes memory newValue,
string memory newDisplayValue
) external onlyOwner {
registry.updateField(
assetCode,
schemaId,
newValue,
newDisplayValue
);
}
/**
* @notice Remove (soft delete) a field from asset
*/
function removeField(
bytes32 assetCode,
bytes32 schemaId
) external onlyOwner {
registry.removeField(assetCode, schemaId);
}
/**
* @notice Restore a deleted field
*/
function restoreField(
bytes32 assetCode,
bytes32 schemaId
) external onlyOwner {
registry.restoreField(assetCode, schemaId);
}
/**
* @notice Verify a field (admin approval)
*/
function verifyField(
bytes32 assetCode,
bytes32 schemaId
) external onlyOwner {
registry.verifyField(assetCode, schemaId);
}
// ═══════════════════════════════════════════════════════════════════════
// ADMIN FUNCTIONS
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Update controller contract address
*/
function updateController(address newController) external onlyOwner {
require(newController != address(0), "Invalid controller address");
address oldController = address(controller);
controller = QXMPOracleController(newController);
emit ControllerUpdated(oldController, newController);
}
/**
* @notice Update registry contract address
*/
function updateRegistry(address newRegistry) external onlyOwner {
require(newRegistry != address(0), "Invalid registry address");
address oldRegistry = address(registry);
registry = QXMPDynamicRegistryV2(newRegistry);
emit RegistryUpdated(oldRegistry, newRegistry);
}
}
Read Contract
controller 0xf77c4791 → address
getLatestProof 0x265ce501 → tuple
getProofByIndex 0xf9cd9080 → tuple
getProofCount 0xfd769d63 → uint256
getProofs 0x7fb58537 → tuple[]
hasProof 0xe3d1e6d6 → bool
latestProofs 0xf2aef13b → bytes32, bytes32, uint256, uint256, uint256, uint256, address, address, bool
owner 0x8da5cb5b → address
proofs 0x2093c15b → bytes32, bytes32, uint256, uint256, uint256, uint256, address, address, bool
registry 0x7b103999 → address
Write Contract 16 functions
These functions modify contract state and require a wallet transaction to execute.
addField 0xa1c69d02
bytes32 assetCode
bytes32 schemaId
bytes value
string displayValue
uint8 confidence
bytes32 sourceHash
addFields 0x03f34181
bytes32 assetCode
bytes32[] schemaIds
bytes[] values
string[] displayValues
uint8[] confidences
bytes32[] sourceHashes
registerAsset 0x241d3827
bytes32 assetCode
string assetName
string assetType
string jurisdiction
uint256 primaryValueUsd
bytes32 reportHash
address holder
string ipfsCid
registerAssetWithFields 0x01d918d7
bytes32 assetCode
string assetName
string assetType
string jurisdiction
uint256 primaryValueUsd
bytes32 reportHash
address holder
string ipfsCid
bytes32[] schemaIds
bytes[] values
string[] displayValues
uint8[] confidences
bytes32[] sourceHashes
registerFieldSchema 0x3facfafb
string fieldName
uint8 dataType
string unit
string category
string description
bool isNumeric
returns: bytes32
removeField 0x0ee6c89c
bytes32 assetCode
bytes32 schemaId
renounceOwnership 0x715018a6
No parameters
restoreField 0xa65faca0
bytes32 assetCode
bytes32 schemaId
submitProofDirect 0x5d30c31d
bytes32 assetCode
bytes32 dataFeedId
uint256 value
submitProofMultiSig 0xe9c3a06d
bytes32 assetCode
bytes32 dataFeedId
uint256 value
uint256 timestamp
bytes[] signatures
submitProofWithSignature 0xee538c17
bytes32 assetCode
bytes32 dataFeedId
uint256 value
uint256 timestamp
bytes signature
transferOwnership 0xf2fde38b
address newOwner
updateController 0x06cb5b66
address newController
updateField 0x8b6516c8
bytes32 assetCode
bytes32 schemaId
bytes newValue
string newDisplayValue
updateRegistry 0x1a5da6c8
address newRegistry
verifyField 0xd84c91e0
bytes32 assetCode
bytes32 schemaId
Recent Transactions
No transactions found for this address