Ensure correct flags are passed to solc in std json mode (#440)

This tests the marketplace contracts to compile fine. In prior versions
this fails to compile, hitting the solc "Stack too deep" error.

Closes #409

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
xermicus authored and GitHub committed 2026-01-06 11:16:59 +01:00
1 parent cfcd2ff77c
commit 13070a4bdf
4 files changed
+89 -2

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
"Baseline": 911,
"Computation": 2337,
"DivisionArithmetics": 14488,
"ERC20": 17041,
"ERC20": 17189,
"Events": 1672,
"FibonacciIterative": 1454,
"Flipper": 2106,
+4
View File
@@ -33,6 +33,10 @@ pub const STANDARD_JSON_CONTRACTS_PATH: &str =
/// selection flags, it should compile fine regardless.
pub const STANDARD_JSON_NO_EVM_CODEGEN_PATH: &str =
"src/tests/data/standard_json/no_evm_codegen.json";
/// This is a complex contract from a real dApp, triggering the
/// infamous "Stack too deep" error in the EVM codegen.
pub const STANDARD_JSON_NO_EVM_CODEGEN_COMPLEX_PATH: &str =
"src/tests/data/standard_json/no_evm_codegen_complex.json";
/// The `resolc` YUL mode flag.
pub const RESOLC_YUL_FLAG: &str = "--yul";
+21 -1
View File
@@ -4,7 +4,8 @@ use revive_solc_json_interface::SolcStandardJsonOutput;
use crate::cli_utils::{
assert_command_success, assert_equal_exit_codes, execute_resolc_with_stdin_input,
execute_solc_with_stdin_input, STANDARD_JSON_CONTRACTS_PATH, STANDARD_JSON_NO_EVM_CODEGEN_PATH,
execute_solc_with_stdin_input, STANDARD_JSON_CONTRACTS_PATH,
STANDARD_JSON_NO_EVM_CODEGEN_COMPLEX_PATH, STANDARD_JSON_NO_EVM_CODEGEN_PATH,
};
const JSON_OPTION: &str = "--standard-json";
@@ -32,3 +33,22 @@ fn no_evm_codegen_requested() {
let output: SolcStandardJsonOutput = serde_json::from_str(&result.stdout).unwrap();
assert!(!output.errors.iter().any(|msg| msg.severity == "error"))
}
/// A variant with more complex contracts.
///
/// The fixture is from a real project known to trigger the "Stack too deep" error.
///
/// The test ensures we set the right flags when requesting the Yul IR from solc:
/// no EVM codegen should be involved.
#[test]
fn no_evm_codegen_requested_complex() {
let result =
execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NO_EVM_CODEGEN_COMPLEX_PATH);
assert_command_success(
&result,
"the EVM codegen std json complext input fixture should build fine",
);
let output: SolcStandardJsonOutput = serde_json::from_str(&result.stdout).unwrap();
assert!(!output.errors.iter().any(|msg| msg.severity == "error"))
}
@@ -0,0 +1,63 @@
{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.20;\n\nimport {OwnableUpgradeable} from \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * This extension of the {Ownable} contract includes a two-step mechanism to transfer\n * ownership, where the new owner must call {acceptOwnership} in order to replace the\n * old one. This can help prevent common mistakes, such as transfers of ownership to\n * incorrect accounts, or to contracts that are unable to interact with the\n * permission system.\n *\n * The initial owner is specified at deployment time in the constructor for `Ownable`. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step\n struct Ownable2StepStorage {\n address _pendingOwner;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Ownable2Step\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;\n\n function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {\n assembly {\n $.slot := Ownable2StepStorageLocation\n }\n }\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n function __Ownable2Step_init() internal onlyInitializing {\n }\n\n function __Ownable2Step_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n Ownable2StepStorage storage $ = _getOwnable2StepStorage();\n return $._pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n *\n * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n Ownable2StepStorage storage $ = _getOwnable2StepStorage();\n $._pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n Ownable2StepStorage storage $ = _getOwnable2StepStorage();\n delete $._pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n if (pendingOwner() != sender) {\n revert OwnableUnauthorizedAccount(sender);\n }\n _transferOwnership(sender);\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {ContextUpgradeable} from \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n /// @custom:storage-location erc7201:openzeppelin.storage.Ownable\n struct OwnableStorage {\n address _owner;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Ownable\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;\n\n function _getOwnableStorage() private pure returns (OwnableStorage storage $) {\n assembly {\n $.slot := OwnableStorageLocation\n }\n }\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n function __Ownable_init(address initialOwner) internal onlyInitializing {\n __Ownable_init_unchained(initialOwner);\n }\n\n function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n OwnableStorage storage $ = _getOwnableStorage();\n return $._owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n OwnableStorage storage $ = _getOwnableStorage();\n address oldOwner = $._owner;\n $._owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Storage of the initializable contract.\n *\n * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions\n * when using with upgradeable contracts.\n *\n * @custom:storage-location erc7201:openzeppelin.storage.Initializable\n */\n struct InitializableStorage {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n uint64 _initialized;\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool _initializing;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Initializable\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;\n\n /**\n * @dev The contract is already initialized.\n */\n error InvalidInitialization();\n\n /**\n * @dev The contract is not initializing.\n */\n error NotInitializing();\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint64 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any\n * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in\n * production.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n // solhint-disable-next-line var-name-mixedcase\n InitializableStorage storage $ = _getInitializableStorage();\n\n // Cache values to avoid duplicated sloads\n bool isTopLevelCall = !$._initializing;\n uint64 initialized = $._initialized;\n\n // Allowed calls:\n // - initialSetup: the contract is not in the initializing state and no previous version was\n // initialized\n // - construction: the contract is initialized at version 1 (no reinitialization) and the\n // current contract is just being deployed\n bool initialSetup = initialized == 0 && isTopLevelCall;\n bool construction = initialized == 1 && address(this).code.length == 0;\n\n if Line truncated
},
"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)\n\npragma solidity ^0.8.20;\n\nimport {ContextUpgradeable} from \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract PausableUpgradeable is Initializable, ContextUpgradeable {\n /// @custom:storage-location erc7201:openzeppelin.storage.Pausable\n struct PausableStorage {\n bool _paused;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Pausable\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;\n\n function _getPausableStorage() private pure returns (PausableStorage storage $) {\n assembly {\n $.slot := PausableStorageLocation\n }\n }\n\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n /**\n * @dev The operation failed because the contract is paused.\n */\n error EnforcedPause();\n\n /**\n * @dev The operation failed because the contract is not paused.\n */\n error ExpectedPause();\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n function __Pausable_init() internal onlyInitializing {\n }\n\n function __Pausable_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n PausableStorage storage $ = _getPausableStorage();\n return $._paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n if (paused()) {\n revert EnforcedPause();\n }\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n if (!paused()) {\n revert ExpectedPause();\n }\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n PausableStorage storage $ = _getPausableStorage();\n $._paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n PausableStorage storage $ = _getPausableStorage();\n $._paused = false;\n emit Unpaused(_msgSender());\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)\n\npragma solidity ^0.8.20;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,\n * consider using {ReentrancyGuardTransient} instead.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuardUpgradeable is Initializable {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant NOT_ENTERED = 1;\n uint256 private constant ENTERED = 2;\n\n /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard\n struct ReentrancyGuardStorage {\n uint256 _status;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.ReentrancyGuard\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;\n\n function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {\n assembly {\n $.slot := ReentrancyGuardStorageLocation\n }\n }\n\n /**\n * @dev Unauthorized reentrant call.\n */\n error ReentrancyGuardReentrantCall();\n\n function __ReentrancyGuard_init() internal onlyInitializing {\n __ReentrancyGuard_init_unchained();\n }\n\n function __ReentrancyGuard_init_unchained() internal onlyInitializing {\n ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n $._status = NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n // On the first call to nonReentrant, _status will be NOT_ENTERED\n if ($._status == ENTERED) {\n revert ReentrancyGuardReentrantCall();\n }\n\n // Any calls to nonReentrant after this point will fail\n $._status = ENTERED;\n }\n\n function _nonReentrantAfter() private {\n ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n $._status = NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();\n return $._status == ENTERED;\n }\n}\n"
},
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"
},
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)\n\npragma solidity >=0.4.16;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n"
},
"contracts/Marketplace.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\nimport {Initializable} from \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport {Ownable2StepUpgradeable} from \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\nimport {ReentrancyGuardUpgradeable} from \"@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol\";\nimport {PausableUpgradeable} from \"@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {IMockMobRule} from \"./interfaces/IMockMobRule.sol\";\n\n/**\n * @title Marketplace\n * @notice Decentralized marketplace for digital and physical goods on Asset Hub EVM\n * @dev Upgradeable contract with escrow, encrypted key storage, and multi-asset support\n */\ncontract Marketplace is Initializable, Ownable2StepUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable {\n /// @notice Version of the contract\n string public constant VERSION = \"1.0.0\";\n\n /// @notice Dispute period duration (48 hours)\n uint256 public constant DISPUTE_PERIOD = 48 hours;\n\n /// @notice Delivery timeout duration (14 days)\n uint256 public constant DELIVERY_TIMEOUT = 14 days;\n\n /// @notice Post-delivery dispute window (2 days)\n uint256 public constant POST_DELIVERY_TIMEOUT = 2 days;\n\n /// @notice Native asset address (0x0)\n address public constant NATIVE_ASSET = address(0);\n\n /**\n * Note: enums here have corresponding types in packages/types/src/item.ts\n * keep them in sync\n */\n\n enum ItemType {\n DIGITAL,\n PHYSICAL\n }\n\n enum PurchaseStatus {\n PENDING,\n COMPLETED,\n DISPUTED,\n REFUNDED,\n SHIPPED,\n DELIVERED\n }\n\n struct Item {\n uint256 id;\n address seller;\n ItemType itemType;\n string metadataIPFSHash; // JSON metadata with description, images, etc.\n uint256 price;\n address paymentAsset; // address(0) for native token, ERC20 address otherwise\n bool active;\n uint256 createdAt;\n string name; // Item title (max 200 chars)\n string category; // Lowercase category tag (max 50 chars)\n }\n\n struct Purchase {\n uint256 id;\n uint256 itemId;\n address buyer;\n address seller;\n uint256 amount;\n address paymentAsset;\n uint256 purchaseTime;\n uint256 releaseTime;\n PurchaseStatus status;\n // Shipping fields\n bytes32 deliveryCode; // Hash of delivery confirmation code (0x0 if not set)\n uint256 deliveryTimeout; // Block timestamp when delivery should be complete\n uint256 postDeliveryTimeout; // Block timestamp when post-delivery disputes end\n bool deliveryConfirmed; // True if buyer confirmed delivery\n bool disputePaused; // True if dispute pauses seller's ability to claim\n uint256 shippingTimestamp; // Block timestamp when delivery code was set (0 if not shipped)\n uint256 deliveryTimestamp; // Block timestamp when delivery was confirmed (0 if not confirmed)\n // Dispute fields\n uint256 disputeCaseId; // Case ID from MockMobRule (0 = no dispute, >0 = active dispute)\n string disputeMetadataCID; // IPFS CID from Bulletin Chain containing initiator's evidence (empty if no dispute)\n string counterEvidenceCID; // IPFS CID from Bulletin Chain containing counter-evidence from other party (empty if not submitted)\n address disputeInitiator; // Who raised the dispute (address(0) if no dispute, buyer/seller if dispute exists)\n bytes encryptedShippingAddress; // Encrypted shipping address (ECDH with seller's public key)\n string buyerPublicKey; // Buyer's X25519 public key (hex string) for decryption\n bytes encryptedTrackingInfo; // Encrypted tracking info in format \"CARRIER;TrackingNumber\" (ECDH with buyer's public key)\n }\n\n /// @notice Counter for item IDs\n uint256 private _nextItemId;\n\n /// @notice Counter for purchase IDs\n uint256 private _nextPurchaseId;\n\n /// @notice Mapping of item ID to Item\n mapping(uint256 => Item) public items;\n\n /// @notice Mapping of purchase ID to Purchase\n mapping(uint256 => Purchase) public purchases;\n\n /// @notice Mapping of item ID => buyer => purchased\n mapping(uint256 => mapping(address => bool)) public hasPurchased;\n\n /// @notice Mapping of allowed payment assets\n mapping(address => bool) public allowedAssets;\n\n /// @notice Address of MockMobRule contract for dispute resolution\n IMockMobRule public mockMobRule;\n\n // Events\n event ItemCreated(\n uint256 indexed itemId,\n address indexed seller,\n ItemType itemType,\n string name,\n string category,\n uint256 price,\n address paymenLine truncated
},
"contracts/MockMobRule.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\n\n/**\n * @title MockMobRule\n * @notice Mock dispute resolution contract simulating MobRule pallet for Phase 2\n * @dev Admin-resolved disputes - will be replaced with real MobRule in Phase 3\n * @author mark3t Team\n */\ncontract MockMobRule is Ownable {\n /// @notice Verdict types\n /// @dev Pending = not yet resolved, InitiatorWins = initiator's claim is valid, InitiatorLoses = initiator's claim rejected\n enum Verdict {\n Pending,\n InitiatorWins,\n InitiatorLoses\n }\n\n /// @notice Dispute case structure\n struct Case {\n uint256 caseId;\n uint256 purchaseId;\n address buyer;\n address seller;\n address initiator; // Who raised the dispute (buyer or seller)\n string disputeMetadataCID; // IPFS CID from Bulletin Chain containing full dispute metadata (title, description, evidence)\n string counterEvidenceCID; // IPFS CID from Bulletin Chain containing counter-evidence from the other party (empty if not submitted)\n Verdict verdict; // Pending until resolved\n uint256 createdAt;\n }\n\n /// @notice Counter for case IDs\n uint256 private _nextCaseId;\n\n /// @notice Mapping of case ID to Case\n mapping(uint256 => Case) public cases;\n\n /// @notice Mapping of purchase ID to case ID (one dispute per purchase)\n mapping(uint256 => uint256) public purchaseToCase;\n\n /// @notice Event emitted when a new dispute case is created\n /// @param caseId The case ID\n /// @param purchaseId The purchase ID\n /// @param buyer Buyer address\n /// @param seller Seller address\n /// @param initiator Who raised the dispute (buyer or seller)\n /// @param disputeMetadataCID IPFS CID from Bulletin Chain containing full dispute metadata\n event CaseCreated(\n uint256 indexed caseId,\n uint256 indexed purchaseId,\n address indexed buyer,\n address seller,\n address initiator,\n string disputeMetadataCID\n );\n\n /// @notice Event emitted when counter-evidence is added to a case\n /// @param caseId The case ID\n /// @param submitter The address that submitted counter-evidence\n /// @param counterEvidenceCID IPFS CID from Bulletin Chain containing counter-evidence metadata\n event CounterEvidenceAdded(uint256 indexed caseId, address indexed submitter, string counterEvidenceCID);\n\n /// @notice Event emitted when a case is resolved\n /// @param caseId The case ID\n /// @param verdict The verdict (1 = InitiatorWins, 2 = InitiatorLoses)\n /// @dev Admin dashboard can listen to this event for instant updates\n event CaseResolved(uint256 indexed caseId, Verdict indexed verdict);\n\n /**\n * @notice Initialize MockMobRule contract\n * @param initialOwner Address that will own the contract (admin)\n */\n constructor(address initialOwner) Ownable(initialOwner) {\n _nextCaseId = 1;\n }\n\n /**\n * @notice Create a new dispute case\n * @param purchaseId_ The purchase ID from Marketplace\n * @param buyer_ Buyer address\n * @param seller_ Seller address\n * @param initiator_ Who is raising the dispute (must be either buyer_ or seller_)\n * @param disputeMetadataCID_ IPFS CID from Bulletin Chain containing full dispute metadata (title, description, evidence)\n * @return caseId The ID of the created case\n */\n function createDispute(\n uint256 purchaseId_,\n address buyer_,\n address seller_,\n address initiator_,\n string memory disputeMetadataCID_\n ) external returns (uint256) {\n require(buyer_ != address(0), \"Invalid buyer\");\n require(seller_ != address(0), \"Invalid seller\");\n require(initiator_ != address(0), \"Invalid initiator\");\n require(initiator_ == buyer_ || initiator_ == seller_, \"Initiator must be buyer or seller\");\n require(bytes(disputeMetadataCID_).length > 0, \"CID required\");\n require(purchaseToCase[purchaseId_] == 0, \"Dispute exists\");\n\n uint256 caseId = _nextCaseId;\n ++_nextCaseId;\n cases[caseId] = Case({\n caseId: caseId,\n purchaseId: purchaseId_,\n buyer: buyer_,\n seller: seller_,\n initiator: initiator_,\n disputeMetadataCID: disputeMetadataCID_,\n counterEvidenceCID: \"\",\n verdict: Verdict.Pending,\n createdAt: block.timestamp\n });\n\n purchaseToCase[purchaseId_] = caseId;\n\n emit CaseCreated(caseId, purchaseId_, buyer_, seller_, initiator_, disputeMetadataCID_);\n\n return caseId;\n }\n\n /**\n * @notice Add counter-evidence to an existing dispute case\n * @param caseId_ The case ID\n * @param counterEvidenceCID_ IPFLine truncated
},
"contracts/interfaces/IMockMobRule.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n/**\n * @title IMockMobRule\n * @notice Interface for MockMobRule contract\n * @dev Used by Marketplace to interact with MockMobRule without direct dependency\n */\ninterface IMockMobRule {\n /// @notice Verdict types\n enum Verdict {\n Pending,\n InitiatorWins,\n InitiatorLoses\n }\n\n /// @notice Dispute case structure\n struct Case {\n uint256 caseId;\n uint256 purchaseId;\n address buyer;\n address seller;\n address initiator;\n string disputeMetadataCID;\n string counterEvidenceCID;\n Verdict verdict;\n uint256 createdAt;\n }\n\n /**\n * @notice Create a new dispute case\n * @param purchaseId_ The purchase ID from Marketplace\n * @param buyer_ Buyer address\n * @param seller_ Seller address\n * @param initiator_ Who is raising the dispute (must be either buyer_ or seller_)\n * @param disputeMetadataCID_ IPFS CID from Bulletin Chain containing full dispute metadata\n * @return caseId The ID of the created case\n */\n function createDispute(\n uint256 purchaseId_,\n address buyer_,\n address seller_,\n address initiator_,\n string memory disputeMetadataCID_\n ) external returns (uint256);\n\n /**\n * @notice Add counter-evidence to an existing dispute case\n * @param caseId_ The case ID\n * @param counterEvidenceCID_ IPFS CID from Bulletin Chain containing counter-evidence metadata\n */\n function addCounterEvidence(uint256 caseId_, string memory counterEvidenceCID_) external;\n\n /**\n * @notice Resolve a dispute case (admin only)\n * @param caseId_ The case ID to resolve\n * @param verdict_ The verdict (1 = InitiatorWins, 2 = InitiatorLoses)\n */\n function resolveCase(uint256 caseId_, Verdict verdict_) external;\n\n /**\n * @notice Get verdict for a case\n * @param caseId_ The case ID\n * @return verdict The verdict (0 = Pending, 1 = InitiatorWins, 2 = InitiatorLoses)\n */\n function getVerdict(uint256 caseId_) external view returns (Verdict verdict);\n\n /**\n * @notice Get full case information\n * @param caseId_ The case ID\n * @return case_ The full Case struct\n */\n function getCase(uint256 caseId_) external view returns (Case memory);\n\n /**\n * @notice Get case ID for a purchase\n * @param purchaseId_ The purchase ID\n * @return caseId The case ID (0 if no dispute exists)\n */\n function getCaseIdByPurchase(uint256 purchaseId_) external view returns (uint256);\n\n /**\n * @notice Get total number of cases\n * @return total The total number of cases created\n */\n function getTotalCases() external view returns (uint256);\n}\n"
}
},
"settings": {
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"evm.bytecode",
"abi",
"evm.methodIdentifiers",
"evm.deployedBytecode",
"storageLayout",
"metadata"
]
}
},
"optimizer": {
"enabled": true,
"details": {}
},
"metadata": {}
}
}