Transaction Hash:
Block:
20368981 at Jul-23-2024 11:37:59 AM +UTC
Transaction Fee:
0.000467946572012101 ETH
$1.18
Gas Used:
58,877 Gas / 7.947867113 Gwei
Emitted Events:
86 |
GnosisSafeProxy.0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e( 0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e, b0813e24cf4bc3ca8cabbcff89ac798261bd7a0dc1571490cc6032669f279456, 0000000000000000000000000000000000000000000000000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 14.905262055577310255 Eth | 14.905517972896827296 Eth | 0.000255917319517041 | |
0x4eB25a65...8Ca843BC7 | 4.864977733728258207 Eth | 3.864977733728258207 Eth | 1 | ||
0xE878Eb19...12277C066 |
0.12245702266900599 Eth
Nonce: 86
|
0.121989076096993889 Eth
Nonce: 87
| 0.000467946572012101 | ||
0xFE7f9Ece...7d6834C0C | 0 Eth | 1 Eth | 1 |
Execution Trace
GnosisSafeProxy.6a761202( )
GnosisSafe.execTransaction( to=0xFE7f9Ece94500d6381bA1BB0afd77577d6834C0C, value=1000000000000000000, data=0x, operation=0, safeTxGas=0, baseGas=0, gasPrice=0, gasToken=0x0000000000000000000000000000000000000000, refundReceiver=0x0000000000000000000000000000000000000000, signatures=0x95CAA1D61EAF705CB1357854DE35ADCD4F1C780B98F28F1A377AB4A329AC34A818CDE5EB90E5E5F72985E6DF2C5915600B173CCA615681F7FDE29D2C680094F420 ) => ( success=True )
-
Null: 0x000...001.0b1a26e2( )
- ETH 1
Chronicle_MOG_USD_1.CALL( )
-
File 1 of 3: GnosisSafeProxy
File 2 of 3: GnosisSafe
File 3 of 3: Chronicle_MOG_USD_1
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain /// @author Richard Meissner - <[email protected]> interface IProxy { function masterCopy() external view returns (address); } /// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafeProxy { // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated. // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt` address internal singleton; /// @dev Constructor function sets address of singleton contract. /// @param _singleton Singleton address. constructor(address _singleton) { require(_singleton != address(0), "Invalid singleton address provided"); singleton = _singleton; } /// @dev Fallback function forwards all transactions and returns all received return data. fallback() external payable { // solhint-disable-next-line no-inline-assembly assembly { let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) { mstore(0, _singleton) return(0, 0x20) } calldatacopy(0, 0, calldatasize()) let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) if eq(success, 0) { revert(0, returndatasize()) } return(0, returndatasize()) } } } /// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @author Stefan George - <[email protected]> contract GnosisSafeProxyFactory { event ProxyCreation(GnosisSafeProxy proxy, address singleton); /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param singleton Address of singleton contract. /// @param data Payload for message call sent to new proxy contract. function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) { proxy = new GnosisSafeProxy(singleton); if (data.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, singleton); } /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed. function proxyRuntimeCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).runtimeCode; } /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address. function proxyCreationCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).creationCode; } /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer. /// This method is only meant as an utility to be called from other methods /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function deployProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) internal returns (GnosisSafeProxy proxy) { // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton))); // solhint-disable-next-line no-inline-assembly assembly { proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) } require(address(proxy) != address(0), "Create2 call failed"); } /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function createProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) public returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); if (initializer.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, _singleton); } /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized. function createProxyWithCallback( address _singleton, bytes memory initializer, uint256 saltNonce, IProxyCreationCallback callback ) public returns (GnosisSafeProxy proxy) { uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback))); proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback); if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce); } /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce` /// This method is only meant for address calculation purpose when you use an initializer that would revert, /// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function calculateCreateProxyWithNonceAddress( address _singleton, bytes calldata initializer, uint256 saltNonce ) external returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); revert(string(abi.encodePacked(proxy))); } } interface IProxyCreationCallback { function proxyCreated( GnosisSafeProxy proxy, address _singleton, bytes calldata initializer, uint256 saltNonce ) external; }
File 2 of 3: GnosisSafe
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "./base/ModuleManager.sol"; import "./base/OwnerManager.sol"; import "./base/FallbackManager.sol"; import "./base/GuardManager.sol"; import "./common/EtherPaymentFallback.sol"; import "./common/Singleton.sol"; import "./common/SignatureDecoder.sol"; import "./common/SecuredTokenTransfer.sol"; import "./common/StorageAccessible.sol"; import "./interfaces/ISignatureValidator.sol"; import "./external/GnosisSafeMath.sol"; /// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafe is EtherPaymentFallback, Singleton, ModuleManager, OwnerManager, SignatureDecoder, SecuredTokenTransfer, ISignatureValidatorConstants, FallbackManager, StorageAccessible, GuardManager { using GnosisSafeMath for uint256; string public constant VERSION = "1.3.0"; // keccak256( // "EIP712Domain(uint256 chainId,address verifyingContract)" // ); bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; // keccak256( // "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)" // ); bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8; event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler); event ApproveHash(bytes32 indexed approvedHash, address indexed owner); event SignMsg(bytes32 indexed msgHash); event ExecutionFailure(bytes32 txHash, uint256 payment); event ExecutionSuccess(bytes32 txHash, uint256 payment); uint256 public nonce; bytes32 private _deprecatedDomainSeparator; // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners mapping(bytes32 => uint256) public signedMessages; // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners mapping(address => mapping(bytes32 => uint256)) public approvedHashes; // This constructor ensures that this contract can only be used as a master copy for Proxy contracts constructor() { // By setting the threshold it is not possible to call setup anymore, // so we create a Safe with 0 owners and threshold 1. // This is an unusable Safe, perfect for the singleton threshold = 1; } /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. /// @param to Contract address for optional delegate call. /// @param data Data payload for optional delegate call. /// @param fallbackHandler Handler for fallback calls to this contract /// @param paymentToken Token that should be used for the payment (0 is ETH) /// @param payment Value that should be paid /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin) function setup( address[] calldata _owners, uint256 _threshold, address to, bytes calldata data, address fallbackHandler, address paymentToken, uint256 payment, address payable paymentReceiver ) external { // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice setupOwners(_owners, _threshold); if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler); // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules setupModules(to, data); if (payment > 0) { // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself) // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment handlePayment(payment, 0, 1, paymentToken, paymentReceiver); } emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler); } /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. /// Note: The fees are always transferred, even if the user transaction fails. /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @param safeTxGas Gas that should be used for the Safe transaction. /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Gas price that should be used for the payment calculation. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}) function execTransaction( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures ) public payable virtual returns (bool success) { bytes32 txHash; // Use scope here to limit variable lifetime and prevent `stack too deep` errors { bytes memory txHashData = encodeTransactionData( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info nonce ); // Increase nonce and execute transaction. nonce++; txHash = keccak256(txHashData); checkSignatures(txHash, txHashData, signatures); } address guard = getGuard(); { if (guard != address(0)) { Guard(guard).checkTransaction( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info signatures, msg.sender ); } } // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500) // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150 require(gasleft() >= ((safeTxGas * 64) / 63).max(safeTxGas + 2500) + 500, "GS010"); // Use scope here to limit variable lifetime and prevent `stack too deep` errors { uint256 gasUsed = gasleft(); // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas) // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas); gasUsed = gasUsed.sub(gasleft()); // If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful // This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert require(success || safeTxGas != 0 || gasPrice != 0, "GS013"); // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls uint256 payment = 0; if (gasPrice > 0) { payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver); } if (success) emit ExecutionSuccess(txHash, payment); else emit ExecutionFailure(txHash, payment); } { if (guard != address(0)) { Guard(guard).checkAfterExecution(txHash, success); } } } function handlePayment( uint256 gasUsed, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver ) private returns (uint256 payment) { // solhint-disable-next-line avoid-tx-origin address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver; if (gasToken == address(0)) { // For ETH we will only adjust the gas price to not be higher than the actual used gas price payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice); require(receiver.send(payment), "GS011"); } else { payment = gasUsed.add(baseGas).mul(gasPrice); require(transferToken(gasToken, receiver, payment), "GS012"); } } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. */ function checkSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures ) public view { // Load threshold to avoid multiple storage loads uint256 _threshold = threshold; // Check that a threshold is set require(_threshold > 0, "GS001"); checkNSignatures(dataHash, data, signatures, _threshold); } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. * @param requiredSignatures Amount of required valid signatures. */ function checkNSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures, uint256 requiredSignatures ) public view { // Check that the provided signature data is not too short require(signatures.length >= requiredSignatures.mul(65), "GS020"); // There cannot be an owner with address 0. address lastOwner = address(0); address currentOwner; uint8 v; bytes32 r; bytes32 s; uint256 i; for (i = 0; i < requiredSignatures; i++) { (v, r, s) = signatureSplit(signatures, i); if (v == 0) { // If v is 0 then it is a contract signature // When handling contract signatures the address of the contract is encoded into r currentOwner = address(uint160(uint256(r))); // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes // This check is not completely accurate, since it is possible that more signatures than the threshold are send. // Here we only check that the pointer is not pointing inside the part that is being processed require(uint256(s) >= requiredSignatures.mul(65), "GS021"); // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes) require(uint256(s).add(32) <= signatures.length, "GS022"); // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length uint256 contractSignatureLen; // solhint-disable-next-line no-inline-assembly assembly { contractSignatureLen := mload(add(add(signatures, s), 0x20)) } require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "GS023"); // Check signature bytes memory contractSignature; // solhint-disable-next-line no-inline-assembly assembly { // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s contractSignature := add(add(signatures, s), 0x20) } require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024"); } else if (v == 1) { // If v is 1 then it is an approved hash // When handling approved hashes the address of the approver is encoded into r currentOwner = address(uint160(uint256(r))); // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025"); } else if (v > 30) { // If v > 30 then default va (27,28) has been adjusted for eth_sign flow // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover currentOwner = ecrecover(keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\ 32", dataHash)), v - 4, r, s); } else { // Default is the ecrecover flow with the provided data hash // Use ecrecover with the messageHash for EOA signatures currentOwner = ecrecover(dataHash, v, r, s); } require(currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, "GS026"); lastOwner = currentOwner; } } /// @dev Allows to estimate a Safe transaction. /// This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data. /// Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction` /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs). /// @notice Deprecated in favor of common/StorageAccessible.sol and will be removed in next version. function requiredTxGas( address to, uint256 value, bytes calldata data, Enum.Operation operation ) external returns (uint256) { uint256 startGas = gasleft(); // We don't provide an error message here, as we use it to return the estimate require(execute(to, value, data, operation, gasleft())); uint256 requiredGas = startGas - gasleft(); // Convert response to string and return via error message revert(string(abi.encodePacked(requiredGas))); } /** * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature. * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract. */ function approveHash(bytes32 hashToApprove) external { require(owners[msg.sender] != address(0), "GS030"); approvedHashes[msg.sender][hashToApprove] = 1; emit ApproveHash(hashToApprove, msg.sender); } /// @dev Returns the chain id used by this contract. function getChainId() public view returns (uint256) { uint256 id; // solhint-disable-next-line no-inline-assembly assembly { id := chainid() } return id; } function domainSeparator() public view returns (bytes32) { return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this)); } /// @dev Returns the bytes that are hashed to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Gas that should be used for the safe transaction. /// @param baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash bytes. function encodeTransactionData( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes memory) { bytes32 safeTxHash = keccak256( abi.encode( SAFE_TX_TYPEHASH, to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce ) ); return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash); } /// @dev Returns hash to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Fas that should be used for the safe transaction. /// @param baseGas Gas costs for data used to trigger the safe transaction. /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash. function getTransactionHash( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes32) { return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce)); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; /// @title Executor - A contract that can execute transactions /// @author Richard Meissner - <[email protected]> contract Executor { function execute( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 txGas ) internal returns (bool success) { if (operation == Enum.Operation.DelegateCall) { // solhint-disable-next-line no-inline-assembly assembly { success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) } } else { // solhint-disable-next-line no-inline-assembly assembly { success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) } } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title Fallback Manager - A contract that manages fallback calls made to this contract /// @author Richard Meissner - <[email protected]> contract FallbackManager is SelfAuthorized { event ChangedFallbackHandler(address handler); // keccak256("fallback_manager.handler.address") bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5; function internalSetFallbackHandler(address handler) internal { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, handler) } } /// @dev Allows to add a contract to handle fallback calls. /// Only fallback calls without value and with data will be forwarded. /// This can only be done via a Safe transaction. /// @param handler contract to handle fallbacks calls. function setFallbackHandler(address handler) public authorized { internalSetFallbackHandler(handler); emit ChangedFallbackHandler(handler); } // solhint-disable-next-line payable-fallback,no-complex-fallback fallback() external { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { let handler := sload(slot) if iszero(handler) { return(0, 0) } calldatacopy(0, 0, calldatasize()) // The msg.sender address is shifted to the left by 12 bytes to remove the padding // Then the address without padding is stored right after the calldata mstore(calldatasize(), shl(96, caller())) // Add 20 bytes for the address appended add the end let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0) returndatacopy(0, 0, returndatasize()) if iszero(success) { revert(0, returndatasize()) } return(0, returndatasize()) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; interface Guard { function checkTransaction( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures, address msgSender ) external; function checkAfterExecution(bytes32 txHash, bool success) external; } /// @title Fallback Manager - A contract that manages fallback calls made to this contract /// @author Richard Meissner - <[email protected]> contract GuardManager is SelfAuthorized { event ChangedGuard(address guard); // keccak256("guard_manager.guard.address") bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; /// @dev Set a guard that checks transactions before execution /// @param guard The address of the guard to be used or the 0 address to disable the guard function setGuard(address guard) external authorized { bytes32 slot = GUARD_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, guard) } emit ChangedGuard(guard); } function getGuard() internal view returns (address guard) { bytes32 slot = GUARD_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { guard := sload(slot) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; import "./Executor.sol"; /// @title Module Manager - A contract that manages modules that can execute transactions via this contract /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract ModuleManager is SelfAuthorized, Executor { event EnabledModule(address module); event DisabledModule(address module); event ExecutionFromModuleSuccess(address indexed module); event ExecutionFromModuleFailure(address indexed module); address internal constant SENTINEL_MODULES = address(0x1); mapping(address => address) internal modules; function setupModules(address to, bytes memory data) internal { require(modules[SENTINEL_MODULES] == address(0), "GS100"); modules[SENTINEL_MODULES] = SENTINEL_MODULES; if (to != address(0)) // Setup has to complete successfully or transaction fails. require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000"); } /// @dev Allows to add a module to the whitelist. /// This can only be done via a Safe transaction. /// @notice Enables the module `module` for the Safe. /// @param module Module to be whitelisted. function enableModule(address module) public authorized { // Module address cannot be null or sentinel. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); // Module cannot be added twice. require(modules[module] == address(0), "GS102"); modules[module] = modules[SENTINEL_MODULES]; modules[SENTINEL_MODULES] = module; emit EnabledModule(module); } /// @dev Allows to remove a module from the whitelist. /// This can only be done via a Safe transaction. /// @notice Disables the module `module` for the Safe. /// @param prevModule Module that pointed to the module to be removed in the linked list /// @param module Module to be removed. function disableModule(address prevModule, address module) public authorized { // Validate module address and check that it corresponds to module index. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); require(modules[prevModule] == module, "GS103"); modules[prevModule] = modules[module]; modules[module] = address(0); emit DisabledModule(module); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations. /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModule( address to, uint256 value, bytes memory data, Enum.Operation operation ) public virtual returns (bool success) { // Only whitelisted modules are allowed. require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104"); // Execute transaction without further confirmations. success = execute(to, value, data, operation, gasleft()); if (success) emit ExecutionFromModuleSuccess(msg.sender); else emit ExecutionFromModuleFailure(msg.sender); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModuleReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) public returns (bool success, bytes memory returnData) { success = execTransactionFromModule(to, value, data, operation); // solhint-disable-next-line no-inline-assembly assembly { // Load free memory location let ptr := mload(0x40) // We allocate memory for the return data by setting the free memory location to // current free memory location + data size + 32 bytes for data size value mstore(0x40, add(ptr, add(returndatasize(), 0x20))) // Store the size mstore(ptr, returndatasize()) // Store the data returndatacopy(add(ptr, 0x20), 0, returndatasize()) // Point the return data to the correct memory location returnData := ptr } } /// @dev Returns if an module is enabled /// @return True if the module is enabled function isModuleEnabled(address module) public view returns (bool) { return SENTINEL_MODULES != module && modules[module] != address(0); } /// @dev Returns array of modules. /// @param start Start of the page. /// @param pageSize Maximum number of modules that should be returned. /// @return array Array of modules. /// @return next Start of the next page. function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) { // Init array with max page size array = new address[](pageSize); // Populate return array uint256 moduleCount = 0; address currentModule = modules[start]; while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) { array[moduleCount] = currentModule; currentModule = modules[currentModule]; moduleCount++; } next = currentModule; // Set correct size of returned array // solhint-disable-next-line no-inline-assembly assembly { mstore(array, moduleCount) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title OwnerManager - Manages a set of owners and a threshold to perform actions. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract OwnerManager is SelfAuthorized { event AddedOwner(address owner); event RemovedOwner(address owner); event ChangedThreshold(uint256 threshold); address internal constant SENTINEL_OWNERS = address(0x1); mapping(address => address) internal owners; uint256 internal ownerCount; uint256 internal threshold; /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. function setupOwners(address[] memory _owners, uint256 _threshold) internal { // Threshold can only be 0 at initialization. // Check ensures that setup function can only be called once. require(threshold == 0, "GS200"); // Validate that threshold is smaller than number of added owners. require(_threshold <= _owners.length, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); // Initializing Safe owners. address currentOwner = SENTINEL_OWNERS; for (uint256 i = 0; i < _owners.length; i++) { // Owner address cannot be null. address owner = _owners[i]; require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[currentOwner] = owner; currentOwner = owner; } owners[currentOwner] = SENTINEL_OWNERS; ownerCount = _owners.length; threshold = _threshold; } /// @dev Allows to add a new owner to the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`. /// @param owner New owner address. /// @param _threshold New threshold. function addOwnerWithThreshold(address owner, uint256 _threshold) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[owner] = owners[SENTINEL_OWNERS]; owners[SENTINEL_OWNERS] = owner; ownerCount++; emit AddedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to remove an owner from the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`. /// @param prevOwner Owner that pointed to the owner to be removed in the linked list /// @param owner Owner address to be removed. /// @param _threshold New threshold. function removeOwner( address prevOwner, address owner, uint256 _threshold ) public authorized { // Only allow to remove an owner, if threshold can still be reached. require(ownerCount - 1 >= _threshold, "GS201"); // Validate owner address and check that it corresponds to owner index. require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == owner, "GS205"); owners[prevOwner] = owners[owner]; owners[owner] = address(0); ownerCount--; emit RemovedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to swap/replace an owner from the Safe with another address. /// This can only be done via a Safe transaction. /// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`. /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list /// @param oldOwner Owner address to be replaced. /// @param newOwner New owner address. function swapOwner( address prevOwner, address oldOwner, address newOwner ) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203"); // No duplicate owners allowed. require(owners[newOwner] == address(0), "GS204"); // Validate oldOwner address and check that it corresponds to owner index. require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == oldOwner, "GS205"); owners[newOwner] = owners[oldOwner]; owners[prevOwner] = newOwner; owners[oldOwner] = address(0); emit RemovedOwner(oldOwner); emit AddedOwner(newOwner); } /// @dev Allows to update the number of required confirmations by Safe owners. /// This can only be done via a Safe transaction. /// @notice Changes the threshold of the Safe to `_threshold`. /// @param _threshold New threshold. function changeThreshold(uint256 _threshold) public authorized { // Validate that threshold is smaller than number of owners. require(_threshold <= ownerCount, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); threshold = _threshold; emit ChangedThreshold(threshold); } function getThreshold() public view returns (uint256) { return threshold; } function isOwner(address owner) public view returns (bool) { return owner != SENTINEL_OWNERS && owners[owner] != address(0); } /// @dev Returns array of owners. /// @return Array of Safe owners. function getOwners() public view returns (address[] memory) { address[] memory array = new address[](ownerCount); // populate return array uint256 index = 0; address currentOwner = owners[SENTINEL_OWNERS]; while (currentOwner != SENTINEL_OWNERS) { array[index] = currentOwner; currentOwner = owners[currentOwner]; index++; } return array; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Enum - Collection of enums /// @author Richard Meissner - <[email protected]> contract Enum { enum Operation {Call, DelegateCall} } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments /// @author Richard Meissner - <[email protected]> contract EtherPaymentFallback { event SafeReceived(address indexed sender, uint256 value); /// @dev Fallback function accepts Ether transactions. receive() external payable { emit SafeReceived(msg.sender, msg.value); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SecuredTokenTransfer - Secure token transfer /// @author Richard Meissner - <[email protected]> contract SecuredTokenTransfer { /// @dev Transfers a token and returns if it was a success /// @param token Token that should be transferred /// @param receiver Receiver to whom the token should be transferred /// @param amount The amount of tokens that should be transferred function transferToken( address token, address receiver, uint256 amount ) internal returns (bool transferred) { // 0xa9059cbb - keccack("transfer(address,uint256)") bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount); // solhint-disable-next-line no-inline-assembly assembly { // We write the return value to scratch space. // See https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0x20) switch returndatasize() case 0 { transferred := success } case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(0)))) } default { transferred := 0 } } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SelfAuthorized - authorizes current contract to perform actions /// @author Richard Meissner - <[email protected]> contract SelfAuthorized { function requireSelfCall() private view { require(msg.sender == address(this), "GS031"); } modifier authorized() { // This is a function call as it minimized the bytecode size requireSelfCall(); _; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SignatureDecoder - Decodes signatures that a encoded as bytes /// @author Richard Meissner - <[email protected]> contract SignatureDecoder { /// @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`. /// @notice Make sure to peform a bounds check for @param pos, to avoid out of bounds access on @param signatures /// @param pos which signature to read. A prior bounds check of this parameter should be performed, to avoid out of bounds access /// @param signatures concatenated rsv signatures function signatureSplit(bytes memory signatures, uint256 pos) internal pure returns ( uint8 v, bytes32 r, bytes32 s ) { // The signature format is a compact form of: // {bytes32 r}{bytes32 s}{uint8 v} // Compact means, uint8 is not padded to 32 bytes. // solhint-disable-next-line no-inline-assembly assembly { let signaturePos := mul(0x41, pos) r := mload(add(signatures, add(signaturePos, 0x20))) s := mload(add(signatures, add(signaturePos, 0x40))) // Here we are loading the last 32 bytes, including 31 bytes // of 's'. There is no 'mload8' to do this. // // 'byte' is not working due to the Solidity parser, so lets // use the second best option, 'and' v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Singleton - Base for singleton contracts (should always be first super contract) /// This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`) /// @author Richard Meissner - <[email protected]> contract Singleton { // singleton always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract. // It should also always be ensured that the address is stored alone (uses a full word) address private singleton; } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title StorageAccessible - generic base contract that allows callers to access all internal storage. /// @notice See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol contract StorageAccessible { /** * @dev Reads `length` bytes of storage in the currents contract * @param offset - the offset in the current contract's storage in words to start reading from * @param length - the number of words (32 bytes) of data to read * @return the bytes that were read. */ function getStorageAt(uint256 offset, uint256 length) public view returns (bytes memory) { bytes memory result = new bytes(length * 32); for (uint256 index = 0; index < length; index++) { // solhint-disable-next-line no-inline-assembly assembly { let word := sload(add(offset, index)) mstore(add(add(result, 0x20), mul(index, 0x20)), word) } } return result; } /** * @dev Performs a delegetecall on a targetContract in the context of self. * Internally reverts execution to avoid side effects (making it static). * * This method reverts with data equal to `abi.encode(bool(success), bytes(response))`. * Specifically, the `returndata` after a call to this method will be: * `success:bool || response.length:uint256 || response:bytes`. * * @param targetContract Address of the contract containing the code to execute. * @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments). */ function simulateAndRevert(address targetContract, bytes memory calldataPayload) external { // solhint-disable-next-line no-inline-assembly assembly { let success := delegatecall(gas(), targetContract, add(calldataPayload, 0x20), mload(calldataPayload), 0, 0) mstore(0x00, success) mstore(0x20, returndatasize()) returndatacopy(0x40, 0, returndatasize()) revert(0, add(returndatasize(), 0x40)) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /** * @title GnosisSafeMath * @dev Math operations with safety checks that revert on error * Renamed from SafeMath to GnosisSafeMath to avoid conflicts * TODO: remove once open zeppelin update to solc 0.5.0 */ library GnosisSafeMath { /** * @dev Multiplies two numbers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two numbers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a >= b ? a : b; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; contract ISignatureValidatorConstants { // bytes4(keccak256("isValidSignature(bytes,bytes)") bytes4 internal constant EIP1271_MAGIC_VALUE = 0x20c13b0b; } abstract contract ISignatureValidator is ISignatureValidatorConstants { /** * @dev Should return whether the signature provided is valid for the provided data * @param _data Arbitrary length data signed on the behalf of address(this) * @param _signature Signature byte array associated with _data * * MUST return the bytes4 magic value 0x20c13b0b when function passes. * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) * MUST allow external calls */ function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4); }
File 3 of 3: Chronicle_MOG_USD_1
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; import {IChronicle} from "chronicle-std/IChronicle.sol"; import {IScribeOptimistic} from "./IScribeOptimistic.sol"; import {IScribe} from "./IScribe.sol"; import {Scribe} from "./Scribe.sol"; import {LibSchnorr} from "./libs/LibSchnorr.sol"; import {LibSecp256k1} from "./libs/LibSecp256k1.sol"; /** * @title ScribeOptimistic * * @notice Scribe based optimistic Oracle with onchain fault resolution */ contract ScribeOptimistic is IScribeOptimistic, Scribe { using LibSchnorr for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.Point[]; // -- Storage -- /// @inheritdoc IScribeOptimistic uint16 public opChallengePeriod; /// @inheritdoc IScribeOptimistic uint8 public opFeedId; /// @dev The truncated hash of the schnorrData provided in last opPoke. /// Binds the opFeed to their schnorrData. uint160 internal _schnorrDataCommitment; /// @dev The age of the pokeData provided in last opPoke. /// Ensures Schnorr signature can be verified after setting pokeData's /// age to block.timestamp during opPoke. uint32 internal _originalOpPokeDataAge; /// @dev opScribe's last opPoke'd value and corresponding age. PokeData internal _opPokeData; /// @inheritdoc IScribeOptimistic uint public maxChallengeReward; // -- Constructor and Receive Functionality -- constructor(address initialAuthed, bytes32 wat_) payable Scribe(initialAuthed, wat_) { // Note to have a non-zero challenge period. _setOpChallengePeriod(20 minutes); // Set maxChallengeReward to type(uint).max. _setMaxChallengeRewards(type(uint).max); } receive() external payable {} // -- Poke Functionality -- function _poke(PokeData calldata pokeData, SchnorrData calldata schnorrData) internal override(Scribe) { // Load current age from storage. uint32 age = _currentPokeData().age; // Revert if pokeData stale. if (pokeData.age <= age) { revert StaleMessage(pokeData.age, age); } // Revert if pokeData from the future. if (pokeData.age > uint32(block.timestamp)) { revert FutureMessage(pokeData.age, uint32(block.timestamp)); } // Revert if schnorrData does not prove integrity of pokeData. bool ok; bytes memory err; // forgefmt: disable-next-item (ok, err) = _verifySchnorrSignature( constructPokeMessage(pokeData), schnorrData ); if (!ok) { _revert(err); } // Store pokeData's val in _pokeData storage and set its age to now. _pokeData.val = pokeData.val; _pokeData.age = uint32(block.timestamp); emit Poked(msg.sender, pokeData.val, pokeData.age); } // -- opPoke Functionality -- /// @dev Optimized function selector: 0x00000000. /// Note that this function is _not_ defined via the IScribeOptimistic /// interface and one should _not_ depend on it. function opPoke_optimized_397084999( PokeData calldata pokeData, SchnorrData calldata schnorrData, ECDSAData calldata ecdsaData ) external payable { _opPoke(pokeData, schnorrData, ecdsaData); } /// @inheritdoc IScribeOptimistic function opPoke( PokeData calldata pokeData, SchnorrData calldata schnorrData, ECDSAData calldata ecdsaData ) external { _opPoke(pokeData, schnorrData, ecdsaData); } function _opPoke( PokeData calldata pokeData, SchnorrData calldata schnorrData, ECDSAData calldata ecdsaData ) internal { // Revert if schnorrData.feedIds' length is higher than bar's maximum // value. // // Note that this prevents opPoke's with such big schnorrData that it // becomes economically unprofitable to challenge them. if (schnorrData.feedIds.length > type(uint8).max) { revert BarNotReached(type(uint8).max, bar); } // Load _opPokeData from storage. PokeData memory opPokeData = _opPokeData; // Decide whether _opPokeData finalized. bool opPokeDataFinalized = opPokeData.age + opChallengePeriod <= uint32(block.timestamp); // Revert if _opPokeData not finalized, i.e. still challengeable. if (!opPokeDataFinalized) { revert InChallengePeriod(); } // Decide current age. uint32 age = opPokeData.age > _pokeData.age ? opPokeData.age : _pokeData.age; // Revert if pokeData stale. if (pokeData.age <= age) { revert StaleMessage(pokeData.age, age); } // Revert if pokeData from the future. if (pokeData.age > uint32(block.timestamp)) { revert FutureMessage(pokeData.age, uint32(block.timestamp)); } // Recover ECDSA signer. address signer = ecrecover( _constructOpPokeMessage(pokeData, schnorrData), ecdsaData.v, ecdsaData.r, ecdsaData.s ); // Compute feed id of signer. uint8 feedId = uint8(uint(uint160(signer)) >> 152); // Revert if signer not feed. // assert(_pubKeys[feedId].toAddress() != address(0)); if (_pubKeys[feedId].toAddress() != signer) { revert SignerNotFeed(signer); } // Store the feed's id as opFeedId and bind them to their provided // schnorrData. opFeedId = feedId; _schnorrDataCommitment = uint160( uint( keccak256( abi.encodePacked( schnorrData.signature, schnorrData.commitment, schnorrData.feedIds ) ) ) ); // If _opPokeData provides the current val, move it to the _pokeData // storage to free _opPokeData storage. If the current val is provided // by _pokeData, _opPokeData can be overwritten. if (opPokeData.age == age) { _pokeData = opPokeData; } // Store provided pokeData's val in _opPokeData storage. _opPokeData.val = pokeData.val; _opPokeData.age = uint32(block.timestamp); // Store pokeData's age to allow recreating original pokeMessage. _originalOpPokeDataAge = pokeData.age; emit OpPoked(msg.sender, signer, schnorrData, pokeData); } /// @inheritdoc IScribeOptimistic function opChallenge(SchnorrData calldata schnorrData) external returns (bool) { // Load _opPokeData from storage. PokeData memory opPokeData = _opPokeData; // Decide whether _opPokeData is challengeable. bool opPokeDataChallengeable = opPokeData.age + opChallengePeriod > uint32(block.timestamp); // Revert if _opPokeData is not challengeable. if (!opPokeDataChallengeable) { revert NoOpPokeToChallenge(); } // Construct truncated hash from schnorrData. uint160 schnorrDataHash = uint160( uint( keccak256( abi.encodePacked( schnorrData.signature, schnorrData.commitment, schnorrData.feedIds ) ) ) ); // Revert if schnorrDataHash does not match _schnorrDataCommitment. if (schnorrDataHash != _schnorrDataCommitment) { revert SchnorrDataMismatch(schnorrDataHash, _schnorrDataCommitment); } // Decide whether schnorrData verifies opPokeData. bool ok; bytes memory err; (ok, err) = _verifySchnorrSignature( constructPokeMessage( PokeData({val: opPokeData.val, age: _originalOpPokeDataAge}) ), schnorrData ); if (ok) { // Decide whether _opPokeData stale already. bool opPokeDataStale = opPokeData.age <= _pokeData.age; // If _opPokeData not stale, finalize it by moving it to the // _pokeData storage. Note to also clean the _opPokeData storage to // not block new opPoke's as _opPokeData's challenge period not over. if (!opPokeDataStale) { _pokeData = _opPokeData; delete _opPokeData; } emit OpPokeChallengedUnsuccessfully(msg.sender, schnorrData); } else { // Drop opFeed and delete invalid _opPokeData. // Note to use address(this) as caller to indicate self-governed // drop of feed. _drop(address(this), opFeedId); // Pay ETH reward to challenger. uint reward = challengeReward(); if (_sendETH(payable(msg.sender), reward)) { emit OpChallengeRewardPaid(msg.sender, schnorrData, reward); } emit OpPokeChallengedSuccessfully(msg.sender, schnorrData, err); } // Return whether challenging was successful. return !ok; } /// @inheritdoc IScribeOptimistic function constructOpPokeMessage( PokeData calldata pokeData, SchnorrData calldata schnorrData ) external view returns (bytes32) { return _constructOpPokeMessage(pokeData, schnorrData); } function _constructOpPokeMessage( PokeData calldata pokeData, SchnorrData calldata schnorrData ) internal view returns (bytes32) { return keccak256( abi.encodePacked( "\\x19Ethereum Signed Message:\ 32", keccak256( abi.encodePacked( wat, pokeData.val, pokeData.age, schnorrData.signature, schnorrData.commitment, schnorrData.feedIds ) ) ) ); } // -- Toll'ed Read Functionality -- // - IChronicle Functions /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function read() external view override(IChronicle, Scribe) toll returns (uint) { uint val = _currentPokeData().val; require(val != 0); return val; } /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function tryRead() external view override(IChronicle, Scribe) toll returns (bool, uint) { uint val = _currentPokeData().val; return (val != 0, val); } /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function readWithAge() external view override(IChronicle, Scribe) toll returns (uint, uint) { PokeData memory pokeData = _currentPokeData(); require(pokeData.val != 0); return (pokeData.val, pokeData.age); } /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function tryReadWithAge() external view override(IChronicle, Scribe) toll returns (bool, uint, uint) { PokeData memory pokeData = _currentPokeData(); return pokeData.val != 0 ? (true, pokeData.val, pokeData.age) : (false, 0, 0); } // - MakerDAO Compatibility /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function peek() external view override(IScribe, Scribe) toll returns (uint, bool) { uint val = _currentPokeData().val; return (val, val != 0); } /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function peep() external view override(IScribe, Scribe) toll returns (uint, bool) { uint val = _currentPokeData().val; return (val, val != 0); } // - Chainlink Compatibility /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function latestRoundData() external view override(IScribe, Scribe) toll returns ( uint80 roundId, int answer, uint startedAt, uint updatedAt, uint80 answeredInRound ) { PokeData memory pokeData = _currentPokeData(); roundId = 1; answer = int(uint(pokeData.val)); // assert(uint(answer) == uint(pokeData.val)); startedAt = 0; updatedAt = pokeData.age; answeredInRound = roundId; } /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function latestAnswer() external view virtual override(IScribe, Scribe) toll returns (int) { uint val = _currentPokeData().val; return int(val); } function _currentPokeData() internal view returns (PokeData memory) { // Load pokeData slots from storage. PokeData memory pokeData = _pokeData; PokeData memory opPokeData = _opPokeData; // Decide whether _opPokeData is finalized. bool opPokeDataFinalized = opPokeData.age + opChallengePeriod <= uint32(block.timestamp); // Decide and return current pokeData. if (opPokeDataFinalized && opPokeData.age > pokeData.age) { return opPokeData; } else { return pokeData; } } // -- Auth'ed Functionality -- /// @inheritdoc IScribeOptimistic function setOpChallengePeriod(uint16 opChallengePeriod_) external auth { _setOpChallengePeriod(opChallengePeriod_); } function _setOpChallengePeriod(uint16 opChallengePeriod_) internal { require(opChallengePeriod_ != 0); if (opChallengePeriod != opChallengePeriod_) { emit OpChallengePeriodUpdated( msg.sender, opChallengePeriod, opChallengePeriod_ ); opChallengePeriod = opChallengePeriod_; } _afterAuthedAction(); } function _drop(address caller, uint8 feedId) internal override(Scribe) { super._drop(caller, feedId); _afterAuthedAction(); } function _setBar(uint8 bar_) internal override(Scribe) { super._setBar(bar_); _afterAuthedAction(); } /// @dev Ensures an auth'ed configuration update does not enable /// successfully challenging a prior to the update valid opPoke. /// /// @custom:invariant Val is provided if _pokeData prior to the tx is /// non-empty. Note that this is the case if there were /// at least two valid calls ∊ {poke, opPoke}. /// preTx(_pokeData) != (0, 0) /// → (true, _) = postTx(tryRead()) /// @custom:invariant Val is provided via _pokeData after the tx. /// postTx(readWithAge()) = postTx(_pokeData) /// @custom:invariant _opPokeData is empty after the tx. /// (0, 0) = postTx(_opPokeData) function _afterAuthedAction() internal { // Do nothing during deployment. if (address(this).code.length == 0) return; // Load _opPokeData from storage. PokeData memory opPokeData = _opPokeData; // Decide whether _opPokeData is finalized. // // Note that the decision is based on the possibly updated // opChallengePeriod! This means a once finalized opPoke may be dropped // if the opChallengePeriod was increased. bool opPokeDataFinalized = opPokeData.age + opChallengePeriod <= uint32(block.timestamp); // Note that _opPokeData is in one of the following three states: // 1. finalized and newer than _pokeData // 2. finalized but older than _pokeData // 3. non-finalized // // Note that for state 1 _opPokeData can be moved to _pokeData and // afterwards deleted. // Note that for state 2 and 3 _opPokeData can be directly deleted. // If _opPokeData is in state 1, move it to the _pokeData storage. // // Note that this ensures the current value is provided via _pokeData. if (opPokeDataFinalized && opPokeData.age > _pokeData.age) { _pokeData = opPokeData; } // If _opPokeData is in state 3, emit event to indicate a possibly valid // opPoke was dropped. if (!opPokeDataFinalized) { emit OpPokeDataDropped(msg.sender, opPokeData); } // Now it is safe to delete _opPokeData. delete _opPokeData; // Note that the current value is now provided via _pokeData. // assert(_currentPokeData().val == _pokeData.val); // assert(_currentPokeData().age == _pokeData.age); // Set the age of contract's current value to block.timestamp. // // Note that this ensures an already signed, but now possibly invalid // with regards to contract configurations, opPoke payload cannot be // opPoke'd anymore. _pokeData.age = uint32(block.timestamp); } // -- Searcher Incentivization Logic -- /// @inheritdoc IScribeOptimistic function challengeReward() public view returns (uint) { uint balance = address(this).balance; return balance > maxChallengeReward ? maxChallengeReward : balance; } /// @inheritdoc IScribeOptimistic function setMaxChallengeReward(uint maxChallengeReward_) external auth { _setMaxChallengeRewards(maxChallengeReward_); } function _setMaxChallengeRewards(uint maxChallengeReward_) internal { if (maxChallengeReward != maxChallengeReward_) { emit MaxChallengeRewardUpdated( msg.sender, maxChallengeReward, maxChallengeReward_ ); maxChallengeReward = maxChallengeReward_; } } function _sendETH(address payable to, uint amount) internal returns (bool) { (bool ok,) = to.call{value: amount}(""); return ok; } } /** * @dev Contract overwrite to deploy contract instances with specific naming. * * For more info, see docs/Deployment.md. */ contract Chronicle_MOG_USD_1 is ScribeOptimistic { constructor(address initialAuthed, bytes32 wat_) ScribeOptimistic(initialAuthed, wat_) {} } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; /** * @title IChronicle * * @notice Interface for Chronicle Protocol's oracle products */ interface IChronicle { /// @notice Returns the oracle's identifier. /// @return wat The oracle's identifier. function wat() external view returns (bytes32 wat); /// @notice Returns the oracle's current value. /// @dev Reverts if no value set. /// @return value The oracle's current value. function read() external view returns (uint value); /// @notice Returns the oracle's current value and its age. /// @dev Reverts if no value set. /// @return value The oracle's current value. /// @return age The value's age. function readWithAge() external view returns (uint value, uint age); /// @notice Returns the oracle's current value. /// @return isValid True if value exists, false otherwise. /// @return value The oracle's current value if it exists, zero otherwise. function tryRead() external view returns (bool isValid, uint value); /// @notice Returns the oracle's current value and its age. /// @return isValid True if value exists, false otherwise. /// @return value The oracle's current value if it exists, zero otherwise. /// @return age The value's age if value exists, zero otherwise. function tryReadWithAge() external view returns (bool isValid, uint value, uint age); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import {IScribe} from "./IScribe.sol"; interface IScribeOptimistic is IScribe { /// @notice Thrown if attempted to opPoke while a previous opPoke is still /// in challenge period. error InChallengePeriod(); /// @notice Thrown if opChallenge called while no challengeable opPoke exists. error NoOpPokeToChallenge(); /// @notice Thrown if opChallenge called with SchnorrData not matching /// opPoke's SchnorrData. /// @param gotHash The truncated keccak256 hash of the SchnorrData argument. /// @param wantHash The truncated expected keccak256 hash of the SchnorrData /// argument. error SchnorrDataMismatch(uint160 gotHash, uint160 wantHash); /// @notice Thrown if opPoke called with non-feed ECDSA signature. /// @param signer The ECDSA signature's signer. error SignerNotFeed(address signer); /// @notice Emitted when oracles was successfully opPoked. /// @param caller The caller's address. /// @param opFeed The feed that signed the opPoke. /// @param schnorrData The schnorrData opPoked. /// @param pokeData The pokeData opPoked. event OpPoked( address indexed caller, address indexed opFeed, IScribe.SchnorrData schnorrData, IScribe.PokeData pokeData ); /// @notice Emitted when successfully challenged an opPoke. /// @param caller The caller's address. /// @param schnorrData The schnorrData challenged. /// @param schnorrErr The abi-encoded custom error returned from the failed /// Schnorr signature verification. event OpPokeChallengedSuccessfully( address indexed caller, IScribe.SchnorrData schnorrData, bytes schnorrErr ); /// @notice Emitted when unsuccessfully challenged an opPoke. /// @param caller The caller's address. /// @param schnorrData The schnorrData challenged. event OpPokeChallengedUnsuccessfully( address indexed caller, IScribe.SchnorrData schnorrData ); /// @notice Emitted when ETH reward paid for successfully challenging an /// opPoke. /// @param challenger The challenger to which the reward was send. /// @param schnorrData The schnorrData challenged. /// @param reward The ETH rewards paid. event OpChallengeRewardPaid( address indexed challenger, IScribe.SchnorrData schnorrData, uint reward ); /// @notice Emitted when an opPoke dropped. /// @dev opPoke's are dropped if security parameters are updated that could /// lead to an initially valid opPoke becoming invalid or if an opPoke /// was successfully challenged. /// @param caller The caller's address. /// @param pokeData The pokeData dropped. event OpPokeDataDropped(address indexed caller, IScribe.PokeData pokeData); /// @notice Emitted when length of opChallengePeriod updated. /// @param caller The caller's address. /// @param oldOpChallengePeriod The old opChallengePeriod's length. /// @param newOpChallengePeriod The new opChallengePeriod's length. event OpChallengePeriodUpdated( address indexed caller, uint16 oldOpChallengePeriod, uint16 newOpChallengePeriod ); /// @notice Emitted when maxChallengeReward updated. /// @param caller The caller's address. /// @param oldMaxChallengeReward The old maxChallengeReward. /// @param newMaxChallengeReward The new maxChallengeReward. event MaxChallengeRewardUpdated( address indexed caller, uint oldMaxChallengeReward, uint newMaxChallengeReward ); /// @notice Optimistically pokes the oracle. /// @dev Expects `pokeData`'s age to be greater than the timestamp of the /// last successful poke. /// @dev Expects `pokeData`'s age to not be greater than the current time. /// @dev Expects `ecdsaData` to be a signature from a feed. /// @dev Expects `ecdsaData` to prove the integrity of the `pokeData` and /// `schnorrData`. /// @dev If the `schnorrData` is proven to be invalid via the opChallenge /// function, the `ecdsaData` signing feed will be dropped. /// @param pokeData The PokeData being poked. /// @param schnorrData The SchnorrData optimistically assumed to be /// proving the `pokeData`'s integrity. /// @param ecdsaData The ECDSAData proving the integrity of the /// `pokeData` and `schnorrData`. function opPoke( PokeData calldata pokeData, SchnorrData calldata schnorrData, ECDSAData calldata ecdsaData ) external; /// @notice Challenges the current challengeable opPoke. /// @dev If opPoke is determined to be invalid, the caller receives an ETH /// bounty. The bounty is defined via the `challengeReward()(uint)` /// function. /// @dev If opPoke is determined to be invalid, the corresponding feed is /// dropped. /// @param schnorrData The SchnorrData initially provided via /// opPoke. /// @return ok True if opPoke declared invalid, false otherwise. function opChallenge(SchnorrData calldata schnorrData) external returns (bool ok); /// @notice Returns the message expected to be signed via ECDSA for calling /// opPoke. /// @dev The message is defined as: /// H(tag ‖ H(wat ‖ pokeData ‖ schnorrData)), where H() is the keccak256 function. /// @param pokeData The pokeData being optimistically poked. /// @param schnorrData The schnorrData proving `pokeData`'s integrity. /// @return opPokeMessage Message to be signed for an opPoke for `pokeData` /// and `schnorrData`. function constructOpPokeMessage( PokeData calldata pokeData, SchnorrData calldata schnorrData ) external view returns (bytes32 opPokeMessage); /// @notice Returns the feed id of the feed last opPoke'd. /// @return opFeedId Feed id of the feed last opPoke'd. function opFeedId() external view returns (uint8 opFeedId); /// @notice Returns the opChallengePeriod security parameter. /// @return opChallengePeriod The opChallengePeriod security parameter. function opChallengePeriod() external view returns (uint16 opChallengePeriod); /// @notice Returns the maxChallengeRewards parameter. /// @return maxChallengeReward The maxChallengeReward parameter. function maxChallengeReward() external view returns (uint maxChallengeReward); /// @notice Returns the ETH rewards being paid for successfully challenging /// an opPoke. /// @return challengeReward The ETH reward for successfully challenging an /// opPoke. function challengeReward() external view returns (uint challengeReward); /// @notice Updates the opChallengePeriod security parameter. /// @dev Only callable by auth'ed address. /// @dev Reverts if opChallengePeriod is zero. /// @dev Note that evaluating whether an opPoke is finalized happens via the /// _current_ opChallengePeriod. /// This means a finalized opPoke is dropped if opChallengePeriod is /// decreased to a value less than opPoke's age. /// @param opChallengePeriod The value to update opChallengePeriod to. function setOpChallengePeriod(uint16 opChallengePeriod) external; /// @notice Updates the maxChallengeReward parameter. /// @dev Only callable by auth'ed address. /// @param maxChallengeReward The value to update maxChallengeReward to. function setMaxChallengeReward(uint maxChallengeReward) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import {IChronicle} from "chronicle-std/IChronicle.sol"; import {LibSecp256k1} from "./libs/LibSecp256k1.sol"; interface IScribe is IChronicle { /// @dev PokeData encapsulates a value and its age. struct PokeData { uint128 val; uint32 age; } /// @dev SchnorrData encapsulates a (aggregated) Schnorr signature. /// Schnorr signatures are used to prove a PokeData's integrity. struct SchnorrData { bytes32 signature; address commitment; bytes feedIds; } /// @dev ECDSAData encapsulates an ECDSA signature. struct ECDSAData { uint8 v; bytes32 r; bytes32 s; } /// @notice Thrown if a poked value's age is not greater than the oracle's /// current value's age. /// @param givenAge The poked value's age. /// @param currentAge The oracle's current value's age. error StaleMessage(uint32 givenAge, uint32 currentAge); /// @notice Thrown if a poked value's age is greater than the current /// time. /// @param givenAge The poked value's age. /// @param currentTimestamp The current time. error FutureMessage(uint32 givenAge, uint32 currentTimestamp); /// @notice Thrown if Schnorr signature not signed by exactly bar many /// signers. /// @param numberSigners The number of signers for given Schnorr signature. /// @param bar The bar security parameter. error BarNotReached(uint8 numberSigners, uint8 bar); /// @notice Thrown if given feed id invalid. /// @param feedId The invalid feed id. error InvalidFeedId(uint8 feedId); /// @notice Thrown if double signing attempted. /// @param feedId The id of the feed attempting to double sign. error DoubleSigningAttempted(uint8 feedId); /// @notice Thrown if Schnorr signature verification failed. error SchnorrSignatureInvalid(); /// @notice Emitted when oracle was successfully poked. /// @param caller The caller's address. /// @param val The value poked. /// @param age The age of the value poked. event Poked(address indexed caller, uint128 val, uint32 age); /// @notice Emitted when new feed lifted. /// @param caller The caller's address. /// @param feed The feed address lifted. event FeedLifted(address indexed caller, address indexed feed); /// @notice Emitted when feed dropped. /// @param caller The caller's address. /// @param feed The feed address dropped. event FeedDropped(address indexed caller, address indexed feed); /// @notice Emitted when bar updated. /// @param caller The caller's address. /// @param oldBar The old bar's value. /// @param newBar The new bar's value. event BarUpdated(address indexed caller, uint8 oldBar, uint8 newBar); /// @notice Returns the feed registration message. /// @dev This message must be signed by a feed in order to be lifted. /// @return feedRegistrationMessage Chronicle Protocol's feed registration /// message. function feedRegistrationMessage() external view returns (bytes32 feedRegistrationMessage); /// @notice Returns the bar security parameter. /// @return bar The bar security parameter. function bar() external view returns (uint8 bar); /// @notice Returns the number of decimals of the oracle's value. /// @dev Provides partial compatibility with Chainlink's /// IAggregatorV3Interface. /// @return decimals The oracle value's number of decimals. function decimals() external view returns (uint8 decimals); /// @notice Returns the oracle's latest value. /// @dev Provides partial compatibility with Chainlink's /// IAggregatorV3Interface. /// @return roundId 1. /// @return answer The oracle's latest value. /// @return startedAt 0. /// @return updatedAt The timestamp of oracle's latest update. /// @return answeredInRound 1. function latestRoundData() external view returns ( uint80 roundId, int answer, uint startedAt, uint updatedAt, uint80 answeredInRound ); /// @notice Returns the oracle's latest value. /// @dev Provides partial compatibility with Chainlink's /// IAggregatorV3Interface. /// @custom:deprecated See https://docs.chain.link/data-feeds/api-reference/#latestanswer. /// @return answer The oracle's latest value. function latestAnswer() external view returns (int); /// @notice Pokes the oracle. /// @dev Expects `pokeData`'s age to be greater than the timestamp of the /// last successful poke. /// @dev Expects `pokeData`'s age to not be greater than the current time. /// @dev Expects `schnorrData` to prove `pokeData`'s integrity. /// See `isAcceptableSchnorrSignatureNow(bytes32,SchnorrData)(bool)`. /// @param pokeData The PokeData being poked. /// @param schnorrData The SchnorrData proving the `pokeData`'s /// integrity. function poke(PokeData calldata pokeData, SchnorrData calldata schnorrData) external; /// @notice Returns whether the Schnorr signature `schnorrData` is /// currently acceptable for message `message`. /// @dev Note that a valid Schnorr signature is only acceptable if the /// signature was signed by exactly bar many feeds. /// For more info, see `bar()(uint8)` and `feeds()(address[])`. /// @dev Note that bar and feeds are configurable, meaning a once acceptable /// Schnorr signature may become unacceptable in the future. /// @param message The message expected to be signed via `schnorrData`. /// @param schnorrData The SchnorrData to verify whether it proves /// the `message`'s integrity. /// @return ok True if Schnorr signature is acceptable, false otherwise. function isAcceptableSchnorrSignatureNow( bytes32 message, SchnorrData calldata schnorrData ) external view returns (bool ok); /// @notice Returns the message expected to be signed via Schnorr for /// `pokeData`. /// @dev The message is defined as: /// H(tag ‖ H(wat ‖ pokeData)), where H() is the keccak256 function. /// @param pokeData The pokeData to create the message for. /// @return pokeMessage Message for `pokeData`. function constructPokeMessage(PokeData calldata pokeData) external view returns (bytes32 pokeMessage); /// @notice Returns whether address `who` is a feed. /// @param who The address to check. /// @return isFeed True if `who` is feed, false otherwise. function feeds(address who) external view returns (bool isFeed); /// @notice Returns whether feed id `feedId` is a feed and, if so, the /// feed's address. /// @param feedId The feed id to check. /// @return isFeed True if `feedId` is a feed, false otherwise. /// @return feed Address of the feed with id `feedId` if `feedId` is a feed, /// zero-address otherwise. function feeds(uint8 feedId) external view returns (bool isFeed, address feed); /// @notice Returns list of feed addresses. /// @dev Note that this function has a high gas consumption and is not /// intended to be called onchain. /// @return feeds List of feed addresses. function feeds() external view returns (address[] memory feeds); /// @notice Lifts public key `pubKey` to being a feed. /// @dev Only callable by auth'ed address. /// @dev The message expected to be signed by `ecdsaData` is defined via /// `feedRegistrationMessage()(bytes32)`. /// @param pubKey The public key of the feed. /// @param ecdsaData ECDSA signed message by the feed's public key. /// @return feedId The id of the newly lifted feed. function lift(LibSecp256k1.Point memory pubKey, ECDSAData memory ecdsaData) external returns (uint8 feedId); /// @notice Lifts public keys `pubKeys` to being feeds. /// @dev Only callable by auth'ed address. /// @dev The message expected to be signed by `ecdsaDatas` is defined via /// `feedRegistrationMessage()(bytes32)`. /// @param pubKeys The public keys of the feeds. /// @param ecdsaDatas ECDSA signed message by the feeds' public keys. /// @return List of feed ids of the newly lifted feeds. function lift( LibSecp256k1.Point[] memory pubKeys, ECDSAData[] memory ecdsaDatas ) external returns (uint8[] memory); /// @notice Drops feed with id `feedId`. /// @dev Only callable by auth'ed address. /// @param feedId The feed id to drop. function drop(uint8 feedId) external; /// @notice Drops feeds with ids' `feedIds`. /// @dev Only callable by auth'ed address. /// @param feedIds The feed ids to drop. function drop(uint8[] memory feedIds) external; /// @notice Updates the bar security parameters to `bar`. /// @dev Only callable by auth'ed address. /// @dev Reverts if `bar` is zero. /// @param bar The value to update bar to. function setBar(uint8 bar) external; /// @notice Returns the oracle's current value. /// @custom:deprecated Use `tryRead()(bool,uint)` instead. /// @return value The oracle's current value if it exists, zero otherwise. /// @return isValid True if value exists, false otherwise. function peek() external view returns (uint value, bool isValid); /// @notice Returns the oracle's current value. /// @custom:deprecated Use `tryRead()(bool,uint)` instead. /// @return value The oracle's current value if it exists, zero otherwise. /// @return isValid True if value exists, false otherwise. function peep() external view returns (uint value, bool isValid); } // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; import {IChronicle} from "chronicle-std/IChronicle.sol"; import {Auth} from "chronicle-std/auth/Auth.sol"; import {Toll} from "chronicle-std/toll/Toll.sol"; import {IScribe} from "./IScribe.sol"; import {LibSchnorr} from "./libs/LibSchnorr.sol"; import {LibSecp256k1} from "./libs/LibSecp256k1.sol"; /** * @title Scribe * @custom:version 2.0.0 * * @notice Efficient Schnorr multi-signature based Oracle */ contract Scribe is IScribe, Auth, Toll { using LibSchnorr for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.JacobianPoint; /// @inheritdoc IScribe uint8 public constant decimals = 18; /// @inheritdoc IScribe bytes32 public constant feedRegistrationMessage = keccak256( abi.encodePacked( "\\x19Ethereum Signed Message:\ 32", keccak256("Chronicle Feed Registration") ) ); /// @inheritdoc IChronicle bytes32 public immutable wat; // -- Storage -- /// @dev Scribe's current value and corresponding age. PokeData internal _pokeData; /// @dev Statically allocated array of feeds' public keys. /// Indexed via the public keys address' highest-order byte. LibSecp256k1.Point[256] internal _pubKeys; /// @inheritdoc IScribe /// @dev Note to have as last in storage to enable downstream contracts to /// pack the slot. uint8 public bar; // -- Constructor -- constructor(address initialAuthed, bytes32 wat_) payable Auth(initialAuthed) { require(wat_ != 0); // Set wat immutable. wat = wat_; // Let initial bar be 2. _setBar(2); } // -- Poke Functionality -- /// @dev Optimized function selector: 0x00000082. /// Note that this function is _not_ defined via the IScribe interface /// and one should _not_ depend on it. function poke_optimized_7136211( PokeData calldata pokeData, SchnorrData calldata schnorrData ) external { _poke(pokeData, schnorrData); } /// @inheritdoc IScribe function poke(PokeData calldata pokeData, SchnorrData calldata schnorrData) external { _poke(pokeData, schnorrData); } function _poke(PokeData calldata pokeData, SchnorrData calldata schnorrData) internal virtual { // Revert if pokeData stale. if (pokeData.age <= _pokeData.age) { revert StaleMessage(pokeData.age, _pokeData.age); } // Revert if pokeData from the future. if (pokeData.age > uint32(block.timestamp)) { revert FutureMessage(pokeData.age, uint32(block.timestamp)); } // Revert if schnorrData does not prove integrity of pokeData. bool ok; bytes memory err; // forgefmt: disable-next-item (ok, err) = _verifySchnorrSignature( constructPokeMessage(pokeData), schnorrData ); if (!ok) { _revert(err); } // Store pokeData's val in _pokeData storage and set its age to now. _pokeData.val = pokeData.val; _pokeData.age = uint32(block.timestamp); emit Poked(msg.sender, pokeData.val, pokeData.age); } /// @inheritdoc IScribe function constructPokeMessage(PokeData memory pokeData) public view returns (bytes32) { return keccak256( abi.encodePacked( "\\x19Ethereum Signed Message:\ 32", keccak256(abi.encodePacked(wat, pokeData.val, pokeData.age)) ) ); } // -- Schnorr Signature Verification -- /// @inheritdoc IScribe function isAcceptableSchnorrSignatureNow( bytes32 message, SchnorrData calldata schnorrData ) external view returns (bool) { bool ok; (ok, /*err*/ ) = _verifySchnorrSignature(message, schnorrData); return ok; } /// @custom:invariant Reverts iff out of gas. /// @custom:invariant Runtime is O(bar). function _verifySchnorrSignature( bytes32 message, SchnorrData calldata schnorrData ) internal view returns (bool, bytes memory) { // Let feedPubKey be the currently processed feed's public key. LibSecp256k1.Point memory feedPubKey; // Let feedId be the currently processed feed's id. uint8 feedId; // Let aggPubKey be the sum of processed feeds' public keys. // Note that Jacobian coordinates are used. LibSecp256k1.JacobianPoint memory aggPubKey; // Let bloom be a bloom filter to check for double signing attempts. uint bloom; // Fail if number feeds unequal to bar. // // Note that requiring equality constrains the verification's runtime // from Ω(bar) to Θ(bar). uint numberFeeds = schnorrData.feedIds.length; if (numberFeeds != bar) { return (false, _errorBarNotReached(uint8(numberFeeds), bar)); } // Initiate feed variables with schnorrData's 0's feed index. feedId = uint8(schnorrData.feedIds[0]); feedPubKey = _pubKeys[feedId]; // Fail if feed not lifted. if (feedPubKey.isZeroPoint()) { return (false, _errorInvalidFeedId(feedId)); } // Initiate bloom filter with feedId set. bloom = 1 << feedId; // Initiate aggPubKey with value of first feed's public key. aggPubKey = feedPubKey.toJacobian(); for (uint8 i = 1; i < numberFeeds;) { // Update feed variables. feedId = uint8(schnorrData.feedIds[i]); feedPubKey = _pubKeys[feedId]; // Fail if feed not lifted. if (feedPubKey.isZeroPoint()) { return (false, _errorInvalidFeedId(feedId)); } // Fail if double signing attempted. if (bloom & (1 << feedId) != 0) { return (false, _errorDoubleSigningAttempted(feedId)); } // Update bloom filter. bloom |= 1 << feedId; // assert(aggPubKey.x != feedPubKey.x); // Indicates rogue-key attack // Add feedPubKey to already aggregated public keys. aggPubKey.addAffinePoint(feedPubKey); // forgefmt: disable-next-item unchecked { ++i; } } // Fail if signature verification fails. bool ok = aggPubKey.toAffine().verifySignature( message, schnorrData.signature, schnorrData.commitment ); if (!ok) { return (false, _errorSchnorrSignatureInvalid()); } // Otherwise Schnorr signature is valid. return (true, new bytes(0)); } // -- Toll'ed Read Functionality -- // - IChronicle Functions /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function read() external view virtual toll returns (uint) { uint val = _pokeData.val; require(val != 0); return val; } /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function tryRead() external view virtual toll returns (bool, uint) { uint val = _pokeData.val; return (val != 0, val); } /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function readWithAge() external view virtual toll returns (uint, uint) { uint val = _pokeData.val; uint age = _pokeData.age; require(val != 0); return (val, age); } /// @inheritdoc IChronicle /// @dev Only callable by toll'ed address. function tryReadWithAge() external view virtual toll returns (bool, uint, uint) { uint val = _pokeData.val; uint age = _pokeData.age; return val != 0 ? (true, val, age) : (false, 0, 0); } // - MakerDAO Compatibility /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function peek() external view virtual toll returns (uint, bool) { uint val = _pokeData.val; return (val, val != 0); } /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function peep() external view virtual toll returns (uint, bool) { uint val = _pokeData.val; return (val, val != 0); } // - Chainlink Compatibility /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function latestRoundData() external view virtual toll returns ( uint80 roundId, int answer, uint startedAt, uint updatedAt, uint80 answeredInRound ) { roundId = 1; answer = int(uint(_pokeData.val)); // assert(uint(answer) == uint(_pokeData.val)); startedAt = 0; updatedAt = _pokeData.age; answeredInRound = roundId; } /// @inheritdoc IScribe /// @dev Only callable by toll'ed address. function latestAnswer() external view virtual toll returns (int) { uint val = _pokeData.val; return int(val); } // -- Public Read Functionality -- /// @inheritdoc IScribe function feeds(address who) external view returns (bool) { uint8 feedId = uint8(uint(uint160(who)) >> 152); LibSecp256k1.Point memory pubKey = _pubKeys[feedId]; return !pubKey.isZeroPoint() && pubKey.toAddress() == who; } /// @inheritdoc IScribe function feeds(uint8 feedId) external view returns (bool, address) { LibSecp256k1.Point memory pubKey = _pubKeys[feedId]; return pubKey.isZeroPoint() ? (false, address(0)) : (true, pubKey.toAddress()); } /// @inheritdoc IScribe function feeds() external view returns (address[] memory) { address[] memory feeds_ = new address[](256); LibSecp256k1.Point memory pubKey; address feed; uint ctr; for (uint i; i < 256;) { pubKey = _pubKeys[uint8(i)]; if (!pubKey.isZeroPoint()) { feed = pubKey.toAddress(); feeds_[ctr] = feed; // forgefmt: disable-next-item unchecked { ++ctr; } } // forgefmt: disable-next-item unchecked { ++i; } } assembly ("memory-safe") { mstore(feeds_, ctr) } return feeds_; } // -- Auth'ed Functionality -- /// @inheritdoc IScribe function lift(LibSecp256k1.Point memory pubKey, ECDSAData memory ecdsaData) external auth returns (uint8) { return _lift(pubKey, ecdsaData); } /// @inheritdoc IScribe function lift( LibSecp256k1.Point[] memory pubKeys, ECDSAData[] memory ecdsaDatas ) external auth returns (uint8[] memory) { require(pubKeys.length == ecdsaDatas.length); uint8[] memory feedIds = new uint8[](pubKeys.length); for (uint i; i < pubKeys.length;) { feedIds[i] = _lift(pubKeys[i], ecdsaDatas[i]); // forgefmt: disable-next-item unchecked { ++i; } } return feedIds; } function _lift(LibSecp256k1.Point memory pubKey, ECDSAData memory ecdsaData) internal returns (uint8) { address feed = pubKey.toAddress(); // assert(feed != address(0)); // forgefmt: disable-next-item address recovered = ecrecover( feedRegistrationMessage, ecdsaData.v, ecdsaData.r, ecdsaData.s ); require(feed == recovered); uint8 feedId = uint8(uint(uint160(feed)) >> 152); LibSecp256k1.Point memory sPubKey = _pubKeys[feedId]; if (sPubKey.isZeroPoint()) { _pubKeys[feedId] = pubKey; emit FeedLifted(msg.sender, feed); } else { // Note to be idempotent. However, disallow updating an id's feed // via lifting without dropping the previous feed. require(feed == sPubKey.toAddress()); } return feedId; } /// @inheritdoc IScribe function drop(uint8 feedId) external auth { _drop(msg.sender, feedId); } /// @inheritdoc IScribe function drop(uint8[] memory feedIds) external auth { for (uint i; i < feedIds.length;) { _drop(msg.sender, feedIds[i]); // forgefmt: disable-next-item unchecked { ++i; } } } function _drop(address caller, uint8 feedId) internal virtual { LibSecp256k1.Point memory pubKey = _pubKeys[feedId]; if (!pubKey.isZeroPoint()) { delete _pubKeys[feedId]; emit FeedDropped(caller, pubKey.toAddress()); } } /// @inheritdoc IScribe function setBar(uint8 bar_) external auth { _setBar(bar_); } function _setBar(uint8 bar_) internal virtual { require(bar_ != 0); if (bar != bar_) { emit BarUpdated(msg.sender, bar, bar_); bar = bar_; } } // -- Internal Helpers -- function _revert(bytes memory err) internal pure { // assert(err.length != 0); assembly ("memory-safe") { let size := mload(err) let offset := add(err, 0x20) revert(offset, size) } } function _errorBarNotReached(uint8 got, uint8 want) internal pure returns (bytes memory) { // assert(got != want); return abi.encodeWithSelector(IScribe.BarNotReached.selector, got, want); } function _errorInvalidFeedId(uint8 feedId) internal pure returns (bytes memory) { // assert(_pubKeys[feedId].isZeroPoint()); return abi.encodeWithSelector(IScribe.InvalidFeedId.selector, feedId); } function _errorDoubleSigningAttempted(uint8 feedId) internal pure returns (bytes memory) { return abi.encodeWithSelector( IScribe.DoubleSigningAttempted.selector, feedId ); } function _errorSchnorrSignatureInvalid() internal pure returns (bytes memory) { return abi.encodeWithSelector(IScribe.SchnorrSignatureInvalid.selector); } // -- Overridden Toll Functions -- /// @dev Defines authorization for IToll's authenticated functions. function toll_auth() internal override(Toll) auth {} } /** * @dev Contract overwrite to deploy contract instances with specific naming. * * For more info, see docs/Deployment.md. */ contract Chronicle_MOG_USD_1 is Scribe { constructor(address initialAuthed, bytes32 wat_) Scribe(initialAuthed, wat_) {} } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import {LibSecp256k1} from "./LibSecp256k1.sol"; /** * @title LibSchnorr * * @notice Custom-purpose library for Schnorr signature verification on the * secp256k1 curve */ library LibSchnorr { using LibSecp256k1 for LibSecp256k1.Point; /// @dev Returns whether `signature` and `commitment` sign via `pubKey` /// message `message`. /// /// @custom:invariant Reverts iff out of gas. /// @custom:invariant Uses constant amount of gas. function verifySignature( LibSecp256k1.Point memory pubKey, bytes32 message, bytes32 signature, address commitment ) internal pure returns (bool) { // Return false if signature or commitment is zero. if (signature == 0 || commitment == address(0)) { return false; } // Note to enforce pubKey is valid secp256k1 point. // // While the Scribe contract ensures to only verify signatures for valid // public keys, this check is enabled as an additional defense // mechanism. if (!pubKey.isOnCurve()) { return false; } // Note to enforce signature is less than Q to prevent signature // malleability. // // While the Scribe contract only accepts messages with strictly // monotonically increasing timestamps, circumventing replay attack // vectors and therefore also signature malleability issues at a higher // level, this check is enabled as an additional defense mechanism. if (uint(signature) >= LibSecp256k1.Q()) { return false; } // Construct challenge = H(Pₓ ‖ Pₚ ‖ m ‖ Rₑ) mod Q uint challenge = uint( keccak256( abi.encodePacked( pubKey.x, uint8(pubKey.yParity()), message, commitment ) ) ) % LibSecp256k1.Q(); // Compute msgHash = -sig * Pₓ (mod Q) // = Q - (sig * Pₓ) (mod Q) // // Unchecked because the only protected operation performed is the // subtraction from Q where the subtrahend is the result of a (mod Q) // computation, i.e. the subtrahend is guaranteed to be less than Q. uint msgHash; unchecked { msgHash = LibSecp256k1.Q() - mulmod(uint(signature), pubKey.x, LibSecp256k1.Q()); } // Compute v = Pₚ + 27 // // Unchecked because pubKey.yParity() ∊ {0, 1} which cannot overflow // by adding 27. uint v; unchecked { v = pubKey.yParity() + 27; } // Set r = Pₓ uint r = pubKey.x; // Compute s = Q - (e * Pₓ) (mod Q) // // Unchecked because the only protected operation performed is the // subtraction from Q where the subtrahend is the result of a (mod Q) // computation, i.e. the subtrahend is guaranteed to be less than Q. uint s; unchecked { s = LibSecp256k1.Q() - mulmod(challenge, pubKey.x, LibSecp256k1.Q()); } // Compute ([s]G - [e]P)ₑ via ecrecover. address recovered = ecrecover(bytes32(msgHash), uint8(v), bytes32(r), bytes32(s)); // Verification succeeds iff ([s]G - [e]P)ₑ = Rₑ. // // Note that commitment is guaranteed to not be zero. return commitment == recovered; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; /** * @title LibSecp256k1 * * @notice Library for secp256k1 elliptic curve computations * * @dev This library was developed to efficiently compute aggregated public * keys for Schnorr signatures based on secp256k1, i.e. it is _not_ a * general purpose elliptic curve library! * * References to the Ethereum Yellow Paper are based on the following * version: "BERLIN VERSION beacfbd – 2022-10-24". */ library LibSecp256k1 { using LibSecp256k1 for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.JacobianPoint; uint private constant ADDRESS_MASK = 0x000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // -- Secp256k1 Constants -- // // Taken from https://www.secg.org/sec2-v2.pdf. // See section 2.4.1 "Recommended Parameters secp256k1". uint private constant _A = 0; uint private constant _B = 7; uint private constant _P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; /// @dev Returns the order of the group. function Q() internal pure returns (uint) { return 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; } /// @dev Returns the generator G. /// Note that the generator is also called base point. function G() internal pure returns (Point memory) { return Point({ x: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, y: 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 }); } /// @dev Returns the zero point. function ZERO_POINT() internal pure returns (Point memory) { return Point({x: 0, y: 0}); } // -- (Affine) Point -- /// @dev Point encapsulates a secp256k1 point in Affine coordinates. struct Point { uint x; uint y; } /// @dev Returns the Ethereum address of `self`. /// /// @dev An Ethereum address is defined as the rightmost 160 bits of the /// keccak256 hash of the concatenation of the hex-encoded x and y /// coordinates of the corresponding ECDSA public key. /// See "Appendix F: Signing Transactions" §134 in the Yellow Paper. function toAddress(Point memory self) internal pure returns (address) { address addr; // Functionally equivalent Solidity code: // addr = address(uint160(uint(keccak256(abi.encode(self.x, self.y))))); assembly ("memory-safe") { addr := and(keccak256(self, 0x40), ADDRESS_MASK) } return addr; } /// @dev Returns Affine point `self` in Jacobian coordinates. function toJacobian(Point memory self) internal pure returns (JacobianPoint memory) { return JacobianPoint({x: self.x, y: self.y, z: 1}); } /// @dev Returns whether `self` is the zero point. function isZeroPoint(Point memory self) internal pure returns (bool) { return (self.x | self.y) == 0; } /// @dev Returns whether `self` is a point on the curve. /// /// @dev The secp256k1 curve is specified as y² ≡ x³ + ax + b (mod P) /// where: /// a = 0 /// b = 7 function isOnCurve(Point memory self) internal pure returns (bool) { uint left = mulmod(self.y, self.y, _P); // Note that adding a * x can be waived as ∀x: a * x = 0. uint right = addmod(mulmod(self.x, mulmod(self.x, self.x, _P), _P), _B, _P); return left == right; } /// @dev Returns the parity of `self`'s y coordinate. /// /// @dev The value 0 represents an even y value and 1 represents an odd y /// value. /// See "Appendix F: Signing Transactions" in the Yellow Paper. function yParity(Point memory self) internal pure returns (uint) { return self.y & 1; } // -- Jacobian Point -- /// @dev JacobianPoint encapsulates a secp256k1 point in Jacobian /// coordinates. struct JacobianPoint { uint x; uint y; uint z; } /// @dev Returns Jacobian point `self` in Affine coordinates. /// /// @custom:invariant Reverts iff out of gas. /// @custom:invariant Does not run into an infinite loop. function toAffine(JacobianPoint memory self) internal pure returns (Point memory) { Point memory result; // Compute z⁻¹, i.e. the modular inverse of self.z. uint zInv = _invMod(self.z); // Compute (z⁻¹)² (mod P) uint zInv_2 = mulmod(zInv, zInv, _P); // Compute self.x * (z⁻¹)² (mod P), i.e. the x coordinate of given // Jacobian point in Affine representation. result.x = mulmod(self.x, zInv_2, _P); // Compute self.y * (z⁻¹)³ (mod P), i.e. the y coordinate of given // Jacobian point in Affine representation. result.y = mulmod(self.y, mulmod(zInv, zInv_2, _P), _P); return result; } /// @dev Adds Affine point `p` to Jacobian point `self`. /// /// It is the caller's responsibility to ensure given points are on the /// curve! /// /// Computation based on: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-madd-2007-bl. /// /// Note that the formula assumes z2 = 1, which always holds if z2's /// point is given in Affine coordinates. /// /// Note that eventhough the function is marked as pure, to be /// understood as only being dependent on the input arguments, it /// nevertheless has side effects by writing the result into the /// `self` memory variable. /// /// @custom:invariant Only mutates `self` memory variable. /// @custom:invariant Reverts iff out of gas. /// @custom:invariant Uses constant amount of gas. function addAffinePoint(JacobianPoint memory self, Point memory p) internal pure { // Addition formula: // x = r² - j - (2 * v) (mod P) // y = (r * (v - x)) - (2 * y1 * j) (mod P) // z = (z1 + h)² - z1² - h² (mod P) // // where: // r = 2 * (s - y1) (mod P) // j = h * i (mod P) // v = x1 * i (mod P) // h = u - x1 (mod P) // s = y2 * z1³ (mod P) Called s2 in reference // i = 4 * h² (mod P) // u = x2 * z1² (mod P) Called u2 in reference // // and: // x1 = self.x // y1 = self.y // z1 = self.z // x2 = p.x // y2 = p.y // // Note that in order to save memory allocations the result is stored // in the self variable, i.e. the following holds true after the // functions execution: // x = self.x // y = self.y // z = self.z // Cache self's coordinates on stack. uint x1 = self.x; uint y1 = self.y; uint z1 = self.z; // Compute z1_2 = z1² (mod P) // = z1 * z1 (mod P) uint z1_2 = mulmod(z1, z1, _P); // Compute h = u - x1 (mod P) // = u + (P - x1) (mod P) // = x2 * z1² + (P - x1) (mod P) // // Unchecked because the only protected operation performed is P - x1 // where x1 is guaranteed by the caller to be an x coordinate belonging // to a point on the curve, i.e. being less than P. uint h; unchecked { h = addmod(mulmod(p.x, z1_2, _P), _P - x1, _P); } // Compute h_2 = h² (mod P) // = h * h (mod P) uint h_2 = mulmod(h, h, _P); // Compute i = 4 * h² (mod P) uint i = mulmod(4, h_2, _P); // Compute z = (z1 + h)² - z1² - h² (mod P) // = (z1 + h)² - z1² + (P - h²) (mod P) // = (z1 + h)² + (P - z1²) + (P - h²) (mod P) // ╰───────╯ ╰───────╯ ╰──────╯ // left mid right // // Unchecked because the only protected operations performed are // subtractions from P where the subtrahend is the result of a (mod P) // computation, i.e. the subtrahend being guaranteed to be less than P. unchecked { uint left = mulmod(addmod(z1, h, _P), addmod(z1, h, _P), _P); uint mid = _P - z1_2; uint right = _P - h_2; self.z = addmod(left, addmod(mid, right, _P), _P); } // Compute v = x1 * i (mod P) uint v = mulmod(x1, i, _P); // Compute j = h * i (mod P) uint j = mulmod(h, i, _P); // Compute r = 2 * (s - y1) (mod P) // = 2 * (s + (P - y1)) (mod P) // = 2 * ((y2 * z1³) + (P - y1)) (mod P) // = 2 * ((y2 * z1² * z1) + (P - y1)) (mod P) // // Unchecked because the only protected operation performed is P - y1 // where y1 is guaranteed by the caller to be an y coordinate belonging // to a point on the curve, i.e. being less than P. uint r; unchecked { r = mulmod( 2, addmod(mulmod(p.y, mulmod(z1_2, z1, _P), _P), _P - y1, _P), _P ); } // Compute x = r² - j - (2 * v) (mod P) // = r² - j + (P - (2 * v)) (mod P) // = r² + (P - j) + (P - (2 * v)) (mod P) // ╰─────╯ ╰───────────╯ // mid right // // Unchecked because the only protected operations performed are // subtractions from P where the subtrahend is the result of a (mod P) // computation, i.e. the subtrahend being guaranteed to be less than P. unchecked { uint r_2 = mulmod(r, r, _P); uint mid = _P - j; uint right = _P - mulmod(2, v, _P); self.x = addmod(r_2, addmod(mid, right, _P), _P); } // Compute y = (r * (v - x)) - (2 * y1 * j) (mod P) // = (r * (v - x)) + (P - (2 * y1 * j)) (mod P) // = (r * (v + (P - x))) + (P - (2 * y1 * j)) (mod P) // ╰─────────────────╯ ╰────────────────╯ // left right // // Unchecked because the only protected operations performed are // subtractions from P where the subtrahend is the result of a (mod P) // computation, i.e. the subtrahend being guaranteed to be less than P. unchecked { uint left = mulmod(r, addmod(v, _P - self.x, _P), _P); uint right = _P - mulmod(2, mulmod(y1, j, _P), _P); self.y = addmod(left, right, _P); } } // -- Private Helpers -- /// @dev Returns the modular inverse of `x` for modulo `_P`. /// /// It is the caller's responsibility to ensure `x` is less than `_P`! /// /// The modular inverse of `x` is x⁻¹ such that x * x⁻¹ ≡ 1 (mod P). /// /// @dev Modified from Jordi Baylina's [ecsol](https://github.com/jbaylina/ecsol/blob/c2256afad126b7500e6f879a9369b100e47d435d/ec.sol#L51-L67). /// /// @custom:invariant Reverts iff out of gas. /// @custom:invariant Does not run into an infinite loop. function _invMod(uint x) private pure returns (uint) { uint t; uint q; uint newT = 1; uint r = _P; assembly ("memory-safe") { // Implemented in assembly to circumvent division-by-zero // and over-/underflow protection. // // Functionally equivalent Solidity code: // while (x != 0) { // q = r / x; // (t, newT) = (newT, addmod(t, (_P - mulmod(q, newT, _P)), _P)); // (r, x) = (x, r - (q * x)); // } // // For the division r / x, x is guaranteed to not be zero via the // loop condition. // // The subtraction of form P - mulmod(_, _, P) is guaranteed to not // underflow due to the subtrahend being a (mod P) result, // i.e. the subtrahend being guaranteed to be less than P. // // The subterm q * x is guaranteed to not overflow because // q * x ≤ r due to q = ⎣r / x⎦. // // The term r - (q * x) is guaranteed to not underflow because // q * x ≤ r and therefore r - (q * x) ≥ 0. for {} x {} { q := div(r, x) let tmp := t t := newT newT := addmod(tmp, sub(_P, mulmod(q, newT, _P)), _P) tmp := r r := x x := sub(tmp, mul(q, x)) } } return t; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import {IAuth} from "./IAuth.sol"; /** * @title Auth Module * * @dev The `Auth` contract module provides a basic access control mechanism, * where a set of addresses are granted access to protected functions. * These addresses are said to be _auth'ed_. * * Initially, the address given as constructor argument is the only address * auth'ed. Through the `rely(address)` and `deny(address)` functions, * auth'ed callers are able to grant/renounce auth to/from addresses. * * This module is used through inheritance. It will make available the * modifier `auth`, which can be applied to functions to restrict their * use to only auth'ed callers. */ abstract contract Auth is IAuth { /// @dev Mapping storing whether address is auth'ed. /// @custom:invariant Image of mapping is {0, 1}. /// ∀x ∊ Address: _wards[x] ∊ {0, 1} /// @custom:invariant Only address given as constructor argument is authenticated after deployment. /// deploy(initialAuthed) → (∀x ∊ Address: _wards[x] == 1 → x == initialAuthed) /// @custom:invariant Only functions `rely` and `deny` may mutate the mapping's state. /// ∀x ∊ Address: preTx(_wards[x]) != postTx(_wards[x]) /// → (msg.sig == "rely" ∨ msg.sig == "deny") /// @custom:invariant Mapping's state may only be mutated by authenticated caller. /// ∀x ∊ Address: preTx(_wards[x]) != postTx(_wards[x]) → _wards[msg.sender] = 1 mapping(address => uint) private _wards; /// @dev List of addresses possibly being auth'ed. /// @dev May contain duplicates. /// @dev May contain addresses not being auth'ed anymore. /// @custom:invariant Every address being auth'ed once is element of the list. /// ∀x ∊ Address: authed(x) -> x ∊ _wardsTouched address[] private _wardsTouched; /// @dev Ensures caller is auth'ed. modifier auth() { assembly ("memory-safe") { // Compute slot of _wards[msg.sender]. mstore(0x00, caller()) mstore(0x20, _wards.slot) let slot := keccak256(0x00, 0x40) // Revert if caller not auth'ed. let isAuthed := sload(slot) if iszero(isAuthed) { // Store selector of `NotAuthorized(address)`. mstore(0x00, 0x4a0bfec1) // Store msg.sender. mstore(0x20, caller()) // Revert with (offset, size). revert(0x1c, 0x24) } } _; } constructor(address initialAuthed) { _wards[initialAuthed] = 1; _wardsTouched.push(initialAuthed); // Note to use address(0) as caller to indicate address was auth'ed // during deployment. emit AuthGranted(address(0), initialAuthed); } /// @inheritdoc IAuth function rely(address who) external auth { if (_wards[who] == 1) return; _wards[who] = 1; _wardsTouched.push(who); emit AuthGranted(msg.sender, who); } /// @inheritdoc IAuth function deny(address who) external auth { if (_wards[who] == 0) return; _wards[who] = 0; emit AuthRenounced(msg.sender, who); } /// @inheritdoc IAuth function authed(address who) public view returns (bool) { return _wards[who] == 1; } /// @inheritdoc IAuth /// @custom:invariant Only contains auth'ed addresses. /// ∀x ∊ authed(): _wards[x] == 1 /// @custom:invariant Contains all auth'ed addresses. /// ∀x ∊ Address: _wards[x] == 1 → x ∊ authed() function authed() public view returns (address[] memory) { // Initiate array with upper limit length. address[] memory wardsList = new address[](_wardsTouched.length); // Iterate through all possible auth'ed addresses. uint ctr; for (uint i; i < wardsList.length; i++) { // Add address only if still auth'ed. if (_wards[_wardsTouched[i]] == 1) { wardsList[ctr++] = _wardsTouched[i]; } } // Set length of array to number of auth'ed addresses actually included. assembly ("memory-safe") { mstore(wardsList, ctr) } return wardsList; } /// @inheritdoc IAuth function wards(address who) public view returns (uint) { return _wards[who]; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import {IToll} from "./IToll.sol"; /** * @title Toll Module * * @notice "Toll paid, we kiss - but dissension looms, maybe diss?" * * @dev The `Toll` contract module provides a basic access control mechanism, * where a set of addresses are granted access to protected functions. * These addresses are said the be _tolled_. * * Initially, no address is tolled. Through the `kiss(address)` and * `diss(address)` functions, auth'ed callers are able to toll/de-toll * addresses. Authentication for these functions is defined via the * downstream implemented `toll_auth()` function. * * This module is used through inheritance. It will make available the * modifier `toll`, which can be applied to functions to restrict their * use to only tolled callers. */ abstract contract Toll is IToll { /// @dev Mapping storing whether address is tolled. /// @custom:invariant Image of mapping is {0, 1}. /// ∀x ∊ Address: _buds[x] ∊ {0, 1} /// @custom:invariant Only functions `kiss` and `diss` may mutate the mapping's state. /// ∀x ∊ Address: preTx(_buds[x]) != postTx(_buds[x]) /// → (msg.sig == "kiss" ∨ msg.sig == "diss") /// @custom:invariant Mapping's state may only be mutated by authenticated caller. /// ∀x ∊ Address: preTx(_buds[x]) != postTx(_buds[x]) /// → toll_auth() mapping(address => uint) private _buds; /// @dev List of addresses possibly being tolled. /// @dev May contain duplicates. /// @dev May contain addresses not being tolled anymore. /// @custom:invariant Every address being tolled once is element of the list. /// ∀x ∊ Address: tolled(x) → x ∊ _budsTouched address[] private _budsTouched; /// @dev Ensures caller is tolled. modifier toll() { assembly ("memory-safe") { // Compute slot of _buds[msg.sender]. mstore(0x00, caller()) mstore(0x20, _buds.slot) let slot := keccak256(0x00, 0x40) // Revert if caller not tolled. let isTolled := sload(slot) if iszero(isTolled) { // Store selector of `NotTolled(address)`. mstore(0x00, 0xd957b595) // Store msg.sender. mstore(0x20, caller()) // Revert with (offset, size). revert(0x1c, 0x24) } } _; } /// @dev Reverts if caller not allowed to access protected function. /// @dev Must be implemented in downstream contract. function toll_auth() internal virtual; /// @inheritdoc IToll function kiss(address who) external { toll_auth(); if (_buds[who] == 1) return; _buds[who] = 1; _budsTouched.push(who); emit TollGranted(msg.sender, who); } /// @inheritdoc IToll function diss(address who) external { toll_auth(); if (_buds[who] == 0) return; _buds[who] = 0; emit TollRenounced(msg.sender, who); } /// @inheritdoc IToll function tolled(address who) public view returns (bool) { return _buds[who] == 1; } /// @inheritdoc IToll /// @custom:invariant Only contains tolled addresses. /// ∀x ∊ tolled(): _tolled[x] /// @custom:invariant Contains all tolled addresses. /// ∀x ∊ Address: _tolled[x] == 1 → x ∊ tolled() function tolled() public view returns (address[] memory) { // Initiate array with upper limit length. address[] memory budsList = new address[](_budsTouched.length); // Iterate through all possible tolled addresses. uint ctr; for (uint i; i < budsList.length; i++) { // Add address only if still tolled. if (_buds[_budsTouched[i]] == 1) { budsList[ctr++] = _budsTouched[i]; } } // Set length of array to number of tolled addresses actually included. assembly ("memory-safe") { mstore(budsList, ctr) } return budsList; } /// @inheritdoc IToll function bud(address who) public view returns (uint) { return _buds[who]; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; interface IAuth { /// @notice Thrown by protected function if caller not auth'ed. /// @param caller The caller's address. error NotAuthorized(address caller); /// @notice Emitted when auth granted to address. /// @param caller The caller's address. /// @param who The address auth got granted to. event AuthGranted(address indexed caller, address indexed who); /// @notice Emitted when auth renounced from address. /// @param caller The caller's address. /// @param who The address auth got renounced from. event AuthRenounced(address indexed caller, address indexed who); /// @notice Grants address `who` auth. /// @dev Only callable by auth'ed address. /// @param who The address to grant auth. function rely(address who) external; /// @notice Renounces address `who`'s auth. /// @dev Only callable by auth'ed address. /// @param who The address to renounce auth. function deny(address who) external; /// @notice Returns whether address `who` is auth'ed. /// @param who The address to check. /// @return True if `who` is auth'ed, false otherwise. function authed(address who) external view returns (bool); /// @notice Returns full list of addresses granted auth. /// @dev May contain duplicates. /// @return List of addresses granted auth. function authed() external view returns (address[] memory); /// @notice Returns whether address `who` is auth'ed. /// @custom:deprecated Use `authed(address)(bool)` instead. /// @param who The address to check. /// @return 1 if `who` is auth'ed, 0 otherwise. function wards(address who) external view returns (uint); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; interface IToll { /// @notice Thrown by protected function if caller not tolled. /// @param caller The caller's address. error NotTolled(address caller); /// @notice Emitted when toll granted to address. /// @param caller The caller's address. /// @param who The address toll got granted to. event TollGranted(address indexed caller, address indexed who); /// @notice Emitted when toll renounced from address. /// @param caller The caller's address. /// @param who The address toll got renounced from. event TollRenounced(address indexed caller, address indexed who); /// @notice Grants address `who` toll. /// @dev Only callable by auth'ed address. /// @param who The address to grant toll. function kiss(address who) external; /// @notice Renounces address `who`'s toll. /// @dev Only callable by auth'ed address. /// @param who The address to renounce toll. function diss(address who) external; /// @notice Returns whether address `who` is tolled. /// @param who The address to check. /// @return True if `who` is tolled, false otherwise. function tolled(address who) external view returns (bool); /// @notice Returns full list of addresses tolled. /// @dev May contain duplicates. /// @return List of addresses tolled. function tolled() external view returns (address[] memory); /// @notice Returns whether address `who` is tolled. /// @custom:deprecated Use `tolled(address)(bool)` instead. /// @param who The address to check. /// @return 1 if `who` is tolled, 0 otherwise. function bud(address who) external view returns (uint); }