polkamarkets-js 1.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.openzeppelin/unknown-1337.json +2056 -0
- package/CONTRIBUTING.md +36 -0
- package/README.md +24 -25
- package/_book/README.md +590 -0
- package/_book/core.md +50 -0
- package/_book/gitbook/fonts/fontawesome/FontAwesome.otf +0 -0
- package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot +0 -0
- package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.svg +685 -0
- package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf +0 -0
- package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff +0 -0
- package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 +0 -0
- package/_book/gitbook/gitbook-plugin-fontsettings/fontsettings.js +240 -0
- package/_book/gitbook/gitbook-plugin-fontsettings/website.css +291 -0
- package/_book/gitbook/gitbook-plugin-highlight/ebook.css +135 -0
- package/_book/gitbook/gitbook-plugin-highlight/website.css +434 -0
- package/_book/gitbook/gitbook-plugin-lunr/lunr.min.js +7 -0
- package/_book/gitbook/gitbook-plugin-lunr/search-lunr.js +59 -0
- package/_book/gitbook/gitbook-plugin-search/lunr.min.js +7 -0
- package/_book/gitbook/gitbook-plugin-search/search-engine.js +50 -0
- package/_book/gitbook/gitbook-plugin-search/search.css +35 -0
- package/_book/gitbook/gitbook-plugin-search/search.js +213 -0
- package/_book/gitbook/gitbook-plugin-sharing/buttons.js +90 -0
- package/_book/gitbook/gitbook.js +4 -0
- package/_book/gitbook/images/apple-touch-icon-precomposed-152.png +0 -0
- package/_book/gitbook/images/favicon.ico +0 -0
- package/_book/gitbook/style.css +9 -0
- package/_book/gitbook/theme.js +4 -0
- package/_book/index.html +705 -0
- package/_book/intro.md +32 -0
- package/_book/search_index.json +1 -0
- package/book.json +8 -0
- package/build/contracts/AccessControl.json +1 -0
- package/build/contracts/AccessControlEnumerable.json +1 -0
- package/build/contracts/Achievements.json +1 -0
- package/build/contracts/Address.json +1 -0
- package/build/contracts/BalanceHolder.json +1 -0
- package/build/contracts/BalanceHolder_ERC20.json +1 -0
- package/build/contracts/CeilDiv.json +1 -0
- package/build/contracts/Clones.json +1 -0
- package/build/contracts/Context.json +1 -0
- package/build/contracts/Counters.json +1 -0
- package/build/contracts/ERC165.json +1 -0
- package/build/contracts/ERC20.json +1 -0
- package/build/contracts/ERC20Burnable.json +1 -0
- package/build/contracts/ERC20Pausable.json +1 -0
- package/build/contracts/ERC20PresetMinterPauser.json +1 -0
- package/build/contracts/ERC721.json +1 -0
- package/build/contracts/EnumerableMap.json +1 -0
- package/build/contracts/EnumerableSet.json +1 -0
- package/build/contracts/FantasyERC20.json +1 -0
- package/build/contracts/IAccessControl.json +1 -0
- package/build/contracts/IAccessControlEnumerable.json +1 -0
- package/build/contracts/IBalanceHolder_ERC20.json +1 -0
- package/build/contracts/IERC165.json +1 -0
- package/build/contracts/IERC20.json +1 -0
- package/build/contracts/IERC20Metadata.json +1 -0
- package/build/contracts/IERC20Permit.json +1 -0
- package/build/contracts/IERC721.json +1 -0
- package/build/contracts/IERC721Enumerable.json +1 -0
- package/build/contracts/IERC721Metadata.json +1 -0
- package/build/contracts/IERC721Receiver.json +1 -0
- package/build/contracts/IFantasyERC20.json +1 -0
- package/build/contracts/IPredictionMarketV3.json +1 -0
- package/build/contracts/IPredictionMarketV3Factory.json +1 -0
- package/build/contracts/IPredictionMarketV3Manager.json +1 -0
- package/build/contracts/IRealityETH_ERC20.json +1 -0
- package/build/contracts/IRealityETH_IERC20.json +1 -0
- package/build/contracts/IWETH.json +1 -0
- package/build/contracts/LandFactory.json +1 -0
- package/build/contracts/Math.json +1 -0
- package/build/contracts/Migrations.json +1 -0
- package/build/contracts/Ownable.json +1 -0
- package/build/contracts/Pausable.json +1 -0
- package/build/contracts/PredictionMarket.json +1 -0
- package/build/contracts/PredictionMarketV2.json +1 -0
- package/build/contracts/PredictionMarketV3.json +1 -0
- package/build/contracts/PredictionMarketV3Controller.json +1 -0
- package/build/contracts/PredictionMarketV3Factory.json +1 -0
- package/build/contracts/PredictionMarketV3Manager.json +1 -0
- package/build/contracts/PredictionMarketV3Querier.json +1 -0
- package/build/contracts/RealitioERC20.json +1 -0
- package/build/contracts/RealitioForeignArbitrationProxyWithAppeals.json +1 -0
- package/build/contracts/RealitioHomeArbitrationProxy.json +1 -0
- package/build/contracts/RealitioSafeMath256.json +1 -0
- package/build/contracts/RealitioSafeMath32.json +1 -0
- package/build/contracts/RealityETH_ERC20_Factory.json +1 -0
- package/build/contracts/RealityETH_ERC20_v3_0.json +1 -0
- package/build/contracts/ReentrancyGuard.json +1 -0
- package/build/contracts/SafeERC20.json +1 -0
- package/build/contracts/SafeMath.json +1 -0
- package/build/contracts/Strings.json +1 -0
- package/build/contracts/Voting.json +1 -0
- package/build/contracts/WETH9.json +1 -0
- package/build/contracts/test.json +1 -0
- package/cleanContracts.js +22 -0
- package/contracts/FantasyERC20.sol +81 -0
- package/contracts/IFantasyERC20.sol +20 -0
- package/contracts/IPredictionMarketV3.sol +207 -0
- package/contracts/IPredictionMarketV3Factory.sol +10 -0
- package/contracts/IPredictionMarketV3Manager.sol +12 -0
- package/contracts/IRealityETH_ERC20.sol +64 -0
- package/contracts/LandFactory.sol +248 -0
- package/contracts/Migrations.sol +24 -0
- package/contracts/PredictionMarketV3.sol +1332 -0
- package/contracts/PredictionMarketV3Controller.sol +87 -0
- package/contracts/PredictionMarketV3Factory.sol +205 -0
- package/contracts/PredictionMarketV3Manager.sol +45 -0
- package/contracts/PredictionMarketV3Querier.sol +79 -0
- package/contracts/RealityETH_ERC20_Factory.sol +54 -0
- package/contracts/Voting.sol +153 -0
- package/contracts/WETH9.sol +62 -0
- package/help.txt +8 -0
- package/index.js +3 -0
- package/migrations/10_deploy_weth.js +5 -0
- package/migrations/11_deploy_full_flow.js +99 -0
- package/migrations/12_deploy_pm_v3_querier.js +7 -0
- package/migrations/13_deploy_pm_v3_factory.js +14 -0
- package/migrations/1_initial_migration.js +5 -0
- package/migrations/2_deploy_erc20.js +10 -0
- package/migrations/3_deploy_realitio.js +11 -0
- package/migrations/4_deploy_pm.js +20 -0
- package/migrations/5_seed_markets.js +51 -0
- package/migrations/6_deploy_achievements.js +5 -0
- package/migrations/7_deploy_voting.js +14 -0
- package/migrations/8_deploy_pm_v2.js +20 -0
- package/migrations/9_seed_markets_v2.js +68 -0
- package/package.json +106 -13
- package/src/Application.js +421 -0
- package/src/interfaces/index.js +19 -0
- package/src/models/AchievementsContract.js +217 -0
- package/src/models/ArbitrationContract.js +69 -0
- package/src/models/ArbitrationProxyContract.js +32 -0
- package/src/models/ERC20Contract.js +156 -0
- package/src/models/FantasyERC20Contract.js +92 -0
- package/src/models/IContract.js +1002 -0
- package/src/models/PolkamarketsSmartAccount.js +100 -0
- package/src/models/PredictionMarketContract.js +562 -0
- package/src/models/PredictionMarketV2Contract.js +830 -0
- package/src/models/PredictionMarketV3Contract.js +233 -0
- package/src/models/PredictionMarketV3ControllerContract.js +102 -0
- package/src/models/PredictionMarketV3FactoryContract.js +96 -0
- package/src/models/PredictionMarketV3ManagerContract.js +111 -0
- package/src/models/PredictionMarketV3QuerierContract.js +24 -0
- package/src/models/RealitioERC20Contract.js +286 -0
- package/src/models/VotingContract.js +182 -0
- package/src/models/WETH9Contract.js +92 -0
- package/src/models/index.js +33 -0
- package/src/utils/Account.js +40 -0
- package/src/utils/Contract.js +120 -0
- package/src/utils/Numbers.js +94 -0
- package/tests/fantasyERC20Contract.js +225 -0
- package/tests/index.js +10 -0
- package/tests/predictionMarketContract.js +466 -0
- package/tests/predictionMarketV2Contract.js +1042 -0
- package/tests/predictionMarketV3Contract.js +1079 -0
- package/tests/predictionMarketV3ControllerContract.js +613 -0
- package/tests/predictionMarketV3FactoryContract.js +469 -0
- package/tests/predictionMarketV3ManagerContract.js +610 -0
- package/tests/utils.js +16 -0
- package/tests/votingContract.js +490 -0
- package/tooling/docs/jsdoc.json +6 -0
- package/truffle-config.js +134 -0
- package/polkamarkets.js +0 -436
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.18;
|
|
3
|
+
|
|
4
|
+
import "./FantasyERC20.sol";
|
|
5
|
+
import "./RealityETH_ERC20_Factory.sol";
|
|
6
|
+
import "./IPredictionMarketV3Factory.sol";
|
|
7
|
+
import "./LandFactory.sol";
|
|
8
|
+
|
|
9
|
+
// openzeppelin ownable contract import
|
|
10
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
11
|
+
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
12
|
+
|
|
13
|
+
contract PredictionMarketV3Controller is LandFactory {
|
|
14
|
+
address public immutable PMV3Factory; // PredictionMarketFactory contract
|
|
15
|
+
mapping(address => LandPermissions) public landPermissions;
|
|
16
|
+
|
|
17
|
+
// inherited variables from LandFactory
|
|
18
|
+
// address public immutable PMV3
|
|
19
|
+
// IERC20 public immutable token
|
|
20
|
+
// uint256 public lockAmount
|
|
21
|
+
// RealityETH_ERC20_Factory public immutable realitioFactory;
|
|
22
|
+
// mapping(address => Land) public lands;
|
|
23
|
+
// address[] public landTokens;
|
|
24
|
+
// uint256 public landTokensLength;
|
|
25
|
+
|
|
26
|
+
struct LandPermissions {
|
|
27
|
+
bool openMarketCreation;
|
|
28
|
+
bool openAdminManagement;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
address _PMV3,
|
|
33
|
+
address _realitioLibraryAddress,
|
|
34
|
+
address _PMV3Factory
|
|
35
|
+
) {
|
|
36
|
+
require(_PMV3Factory != address(0), "PMV3Factory address cannot be 0 address");
|
|
37
|
+
|
|
38
|
+
PMV3 = _PMV3;
|
|
39
|
+
realitioFactory = new RealityETH_ERC20_Factory(_realitioLibraryAddress);
|
|
40
|
+
PMV3Factory = _PMV3Factory;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createLand(
|
|
44
|
+
string memory name,
|
|
45
|
+
string memory symbol,
|
|
46
|
+
uint256 tokenAmountToClaim,
|
|
47
|
+
IERC20 tokenToAnswer
|
|
48
|
+
) external override returns (FantasyERC20) {
|
|
49
|
+
require(
|
|
50
|
+
IPredictionMarketV3Factory(PMV3Factory).isPMControllerAdmin(address(this), msg.sender),
|
|
51
|
+
"Not allowed to create land"
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// create a new fantasyERC20 token
|
|
55
|
+
return _createLand(name, symbol, tokenAmountToClaim, tokenToAnswer, PMV3Factory, address(this));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function setLandEveryoneCanCreateMarkets(IERC20 landToken, bool canCreate) external {
|
|
59
|
+
Land storage land = lands[address(landToken)];
|
|
60
|
+
|
|
61
|
+
require(land.active, "Land does not exist");
|
|
62
|
+
require(land.admins[msg.sender], "Not admin of the land");
|
|
63
|
+
|
|
64
|
+
landPermissions[address(landToken)].openMarketCreation = canCreate;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isAllowedToCreateMarket(IERC20 marketToken, address user) public view override returns (bool) {
|
|
68
|
+
return
|
|
69
|
+
lands[address(marketToken)].active &&
|
|
70
|
+
(lands[address(marketToken)].admins[user] ||
|
|
71
|
+
landPermissions[address(lands[address(marketToken)].token)].openMarketCreation ||
|
|
72
|
+
IPredictionMarketV3Factory(PMV3Factory).isPMControllerAdmin(address(this), user));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isAllowedToResolveMarket(IERC20 marketToken, address user) public view override returns (bool) {
|
|
76
|
+
return
|
|
77
|
+
lands[address(marketToken)].active &&
|
|
78
|
+
(lands[address(marketToken)].admins[user] ||
|
|
79
|
+
IPredictionMarketV3Factory(PMV3Factory).isPMControllerAdmin(address(this), user));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isLandAdmin(IERC20 landToken, address user) public view override returns (bool) {
|
|
83
|
+
return
|
|
84
|
+
lands[address(landToken)].admins[user] ||
|
|
85
|
+
IPredictionMarketV3Factory(PMV3Factory).isPMControllerAdmin(address(this), user);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.18;
|
|
3
|
+
|
|
4
|
+
import "./PredictionMarketV3Controller.sol";
|
|
5
|
+
|
|
6
|
+
// openzeppelin ownable contract import
|
|
7
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
8
|
+
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
9
|
+
import "@openzeppelin/contracts/proxy/Clones.sol";
|
|
10
|
+
|
|
11
|
+
contract PredictionMarketV3Factory is Ownable, ReentrancyGuard {
|
|
12
|
+
IERC20 public immutable token; // Governance IERC20
|
|
13
|
+
uint256 public lockAmount; // amount necessary to lock to create a land
|
|
14
|
+
address public PMV3LibraryAddress; // PredictionMarketV3 library address
|
|
15
|
+
address public realitioLibraryAddress; // PredictionMarketV3 contract
|
|
16
|
+
|
|
17
|
+
event ControllerCreated(address indexed user, address indexed controller, uint256 amountLocked);
|
|
18
|
+
|
|
19
|
+
event ControllerDisabled(address indexed user, address indexed controller, uint256 amountUnlocked);
|
|
20
|
+
|
|
21
|
+
event ControllerEnabled(address indexed user, address indexed controller, uint256 amountUnlocked);
|
|
22
|
+
|
|
23
|
+
event ControllerOffsetUnlocked(address indexed user, address indexed controller, uint256 amountUnlocked);
|
|
24
|
+
|
|
25
|
+
event ControllerAdminAdded(address indexed user, address indexed controller, address indexed admin);
|
|
26
|
+
|
|
27
|
+
event ControllerAdminRemoved(address indexed user, address indexed controller, address indexed admin);
|
|
28
|
+
|
|
29
|
+
struct Controller {
|
|
30
|
+
bool active;
|
|
31
|
+
mapping(address => bool) admins;
|
|
32
|
+
uint256 lockAmount;
|
|
33
|
+
address lockUser;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
mapping(address => Controller) public controllers;
|
|
37
|
+
address[] public controllersAddresses;
|
|
38
|
+
uint256 public controllersLength;
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
IERC20 _token,
|
|
42
|
+
uint256 _lockAmount,
|
|
43
|
+
address _PMV3LibraryAddress,
|
|
44
|
+
address _realitioLibraryAddress
|
|
45
|
+
) {
|
|
46
|
+
token = _token;
|
|
47
|
+
lockAmount = _lockAmount;
|
|
48
|
+
PMV3LibraryAddress = _PMV3LibraryAddress;
|
|
49
|
+
realitioLibraryAddress = _realitioLibraryAddress;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function updateLockAmount(uint256 newLockAmount) external onlyOwner {
|
|
53
|
+
require(newLockAmount != lockAmount, "Lock amount is the same");
|
|
54
|
+
|
|
55
|
+
lockAmount = newLockAmount;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function updatePMV3LibraryAddress(address _PMV3LibraryAddress) external onlyOwner {
|
|
59
|
+
require(_PMV3LibraryAddress != address(0), "PMV3LibraryAddress address cannot be 0 address");
|
|
60
|
+
|
|
61
|
+
PMV3LibraryAddress = _PMV3LibraryAddress;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function updateRealitioLibraryAddress(address _realitioLibraryAddress) external onlyOwner {
|
|
65
|
+
require(_realitioLibraryAddress != address(0), "RealitioLibraryAddress address cannot be 0 address");
|
|
66
|
+
|
|
67
|
+
realitioLibraryAddress = _realitioLibraryAddress;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// lockAmount is the amount of tokens that the user needs to lock to create a land
|
|
71
|
+
// by locking the amount the factory will create a PredictionMarketController contract and store it in the contract
|
|
72
|
+
// the user will be the admin of the PredictionMarketController contract
|
|
73
|
+
function createPMController(address _PMV3) external returns (PredictionMarketV3Controller) {
|
|
74
|
+
require(token.balanceOf(msg.sender) >= lockAmount, "Not enough tokens to lock");
|
|
75
|
+
|
|
76
|
+
// a PMV3 address can be provided, if not, the factory will deploy a new one
|
|
77
|
+
if (_PMV3 == address(0)) {
|
|
78
|
+
// deploy new PredictionMarketV3 contract
|
|
79
|
+
_PMV3 = Clones.clone(PMV3LibraryAddress);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// deploy prediction market controller contract
|
|
83
|
+
PredictionMarketV3Controller PMV3Controller = new PredictionMarketV3Controller(
|
|
84
|
+
_PMV3,
|
|
85
|
+
realitioLibraryAddress,
|
|
86
|
+
address(this)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// store the new controller in the contract
|
|
90
|
+
Controller storage controller = controllers[address(PMV3Controller)];
|
|
91
|
+
controller.active = true;
|
|
92
|
+
controller.admins[msg.sender] = true;
|
|
93
|
+
controller.lockAmount = lockAmount;
|
|
94
|
+
controller.lockUser = msg.sender;
|
|
95
|
+
controllersAddresses.push(address(PMV3Controller));
|
|
96
|
+
controllersLength++;
|
|
97
|
+
|
|
98
|
+
// transfer the lockAmount to the contract
|
|
99
|
+
token.transferFrom(msg.sender, address(this), lockAmount);
|
|
100
|
+
|
|
101
|
+
// ControllerCreated event
|
|
102
|
+
emit ControllerCreated(msg.sender, address(PMV3Controller), lockAmount);
|
|
103
|
+
|
|
104
|
+
return PMV3Controller;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function disablePMController(address controllerAddress) external returns (uint256) {
|
|
108
|
+
Controller storage controller = controllers[address(controllerAddress)];
|
|
109
|
+
|
|
110
|
+
require(controller.active, "Controller is not active");
|
|
111
|
+
require(controller.admins[msg.sender], "Not admin of the controller");
|
|
112
|
+
|
|
113
|
+
uint256 amountToUnlock = controller.lockAmount;
|
|
114
|
+
|
|
115
|
+
token.transfer(controller.lockUser, amountToUnlock);
|
|
116
|
+
|
|
117
|
+
// disable the land
|
|
118
|
+
controller.active = false;
|
|
119
|
+
controller.lockAmount = 0;
|
|
120
|
+
controller.lockUser = address(0);
|
|
121
|
+
|
|
122
|
+
// ControllerDisabled event
|
|
123
|
+
emit ControllerDisabled(msg.sender, address(controllerAddress), amountToUnlock);
|
|
124
|
+
|
|
125
|
+
return amountToUnlock;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function enablePMController(address controllerAddress) external returns (uint256) {
|
|
129
|
+
Controller storage controller = controllers[address(controllerAddress)];
|
|
130
|
+
|
|
131
|
+
require(!controller.active, "Controller is already active");
|
|
132
|
+
require(controller.admins[msg.sender], "Not admin of the controller");
|
|
133
|
+
|
|
134
|
+
uint256 amountToLock = lockAmount > controller.lockAmount ? lockAmount - controller.lockAmount : 0;
|
|
135
|
+
|
|
136
|
+
// transfer the lockAmount to the contract
|
|
137
|
+
token.transferFrom(msg.sender, address(this), amountToLock);
|
|
138
|
+
|
|
139
|
+
// enable the land
|
|
140
|
+
controller.active = true;
|
|
141
|
+
controller.lockAmount = controller.lockAmount + amountToLock;
|
|
142
|
+
controller.lockUser = msg.sender;
|
|
143
|
+
|
|
144
|
+
// ControllerEnabled event
|
|
145
|
+
emit ControllerEnabled(msg.sender, address(controllerAddress), amountToLock);
|
|
146
|
+
|
|
147
|
+
return amountToLock;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function unlockOffsetFromPMController(address controllerAddress) external returns (uint256) {
|
|
151
|
+
Controller storage controller = controllers[address(controllerAddress)];
|
|
152
|
+
|
|
153
|
+
require(controller.active, "controller does not exist");
|
|
154
|
+
require(controller.admins[msg.sender], "Not admin of the controller");
|
|
155
|
+
|
|
156
|
+
uint256 amountToUnlock = controller.lockAmount > lockAmount ? controller.lockAmount - lockAmount : 0;
|
|
157
|
+
|
|
158
|
+
if (amountToUnlock > 0) {
|
|
159
|
+
token.transfer(msg.sender, amountToUnlock);
|
|
160
|
+
controller.lockAmount = controller.lockAmount - amountToUnlock;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ControllerOffsetUnlocked event
|
|
164
|
+
emit ControllerOffsetUnlocked(msg.sender, address(controllerAddress), amountToUnlock);
|
|
165
|
+
|
|
166
|
+
return amountToUnlock;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function addAdminToPMController(address controllerAddress, address admin) external {
|
|
170
|
+
Controller storage controller = controllers[address(controllerAddress)];
|
|
171
|
+
|
|
172
|
+
require(controller.active, "Controller does not exist");
|
|
173
|
+
require(controller.admins[msg.sender], "Not admin of the controller");
|
|
174
|
+
|
|
175
|
+
controller.admins[admin] = true;
|
|
176
|
+
|
|
177
|
+
// ControllerAdminAdded event
|
|
178
|
+
emit ControllerAdminAdded(msg.sender, address(controllerAddress), admin);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function removeAdminFromPMController(address controllerAddress, address admin) external {
|
|
182
|
+
Controller storage controller = controllers[address(controllerAddress)];
|
|
183
|
+
|
|
184
|
+
require(controller.active, "Controller does not exist");
|
|
185
|
+
require(controller.admins[msg.sender], "Not admin of the controller");
|
|
186
|
+
require(controller.lockUser != admin, "Can not remove from admin the lockUser");
|
|
187
|
+
|
|
188
|
+
controller.admins[admin] = false;
|
|
189
|
+
|
|
190
|
+
// ControllerAdminRemoved event
|
|
191
|
+
emit ControllerAdminRemoved(msg.sender, address(controllerAddress), admin);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isPMControllerActive(address controllerAddress) external view returns (bool) {
|
|
195
|
+
Controller storage controller = controllers[address(controllerAddress)];
|
|
196
|
+
|
|
197
|
+
return controller.active;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isPMControllerAdmin(address controllerAddress, address user) external view returns (bool) {
|
|
201
|
+
Controller storage controller = controllers[address(controllerAddress)];
|
|
202
|
+
|
|
203
|
+
return controller.admins[user];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.18;
|
|
3
|
+
|
|
4
|
+
import "./FantasyERC20.sol";
|
|
5
|
+
import "./RealityETH_ERC20_Factory.sol";
|
|
6
|
+
import "./LandFactory.sol";
|
|
7
|
+
|
|
8
|
+
// openzeppelin ownable contract import
|
|
9
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
10
|
+
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
11
|
+
|
|
12
|
+
contract PredictionMarketV3Manager is LandFactory {
|
|
13
|
+
// inherited variables from LandFactory
|
|
14
|
+
// address public immutable PMV3
|
|
15
|
+
// IERC20 public immutable token
|
|
16
|
+
// uint256 public lockAmount
|
|
17
|
+
// RealityETH_ERC20_Factory public immutable realitioFactory;
|
|
18
|
+
// mapping(address => Land) public lands;
|
|
19
|
+
// address[] public landTokens;
|
|
20
|
+
// uint256 public landTokensLength;
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
address _PMV3,
|
|
24
|
+
IERC20 _token,
|
|
25
|
+
uint256 _lockAmount,
|
|
26
|
+
address _realitioLibraryAddress
|
|
27
|
+
) {
|
|
28
|
+
PMV3 = _PMV3;
|
|
29
|
+
token = _token;
|
|
30
|
+
lockAmount = _lockAmount;
|
|
31
|
+
realitioFactory = new RealityETH_ERC20_Factory(_realitioLibraryAddress);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// lockAmount is the amount of tokens that the user needs to lock to create a land
|
|
35
|
+
// by locking the amount the factory will create a fantasyERC20 token and store it in the contract
|
|
36
|
+
// the user will be the admin of the land
|
|
37
|
+
function createLand(
|
|
38
|
+
string memory name,
|
|
39
|
+
string memory symbol,
|
|
40
|
+
uint256 tokenAmountToClaim,
|
|
41
|
+
IERC20 tokenToAnswer
|
|
42
|
+
) external override returns (FantasyERC20) {
|
|
43
|
+
return _createLand(name, symbol, tokenAmountToClaim, tokenToAnswer, address(0), address(0));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.18;
|
|
3
|
+
|
|
4
|
+
// local imports
|
|
5
|
+
import "./IPredictionMarketV3.sol";
|
|
6
|
+
|
|
7
|
+
/// @title Market Contract Factory
|
|
8
|
+
contract PredictionMarketV3Querier {
|
|
9
|
+
IPredictionMarketV3 public immutable PredictionMarketV3;
|
|
10
|
+
|
|
11
|
+
struct UserMarketData {
|
|
12
|
+
uint256 liquidityShares;
|
|
13
|
+
uint256[] outcomeShares;
|
|
14
|
+
bool winningsToClaim;
|
|
15
|
+
bool winningsClaimed;
|
|
16
|
+
bool liquidityToClaim;
|
|
17
|
+
bool liquidityClaimed;
|
|
18
|
+
bool voidedSharesToClaim;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @dev protocol is immutable and has no ownership
|
|
22
|
+
constructor(IPredictionMarketV3 _PredictionMarketV3) {
|
|
23
|
+
PredictionMarketV3 = _PredictionMarketV3;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getUserMarketData(uint256 marketId, address user) public view returns (UserMarketData memory) {
|
|
27
|
+
bool isMarketVoided = PredictionMarketV3.isMarketVoided(marketId);
|
|
28
|
+
bool voidedSharesToClaim = false;
|
|
29
|
+
|
|
30
|
+
(uint256 liquidityShares, uint256[] memory outcomeShares) = PredictionMarketV3.getUserMarketShares(marketId, user);
|
|
31
|
+
(bool winningsToClaim, bool winningsClaimed, bool liquidityToClaim, bool liquidityClaimed, ) = PredictionMarketV3
|
|
32
|
+
.getUserClaimStatus(marketId, user);
|
|
33
|
+
|
|
34
|
+
if (isMarketVoided) {
|
|
35
|
+
for (uint256 i = 0; i < outcomeShares.length; i++) {
|
|
36
|
+
if (outcomeShares[i] > 0) {
|
|
37
|
+
voidedSharesToClaim = true;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return
|
|
44
|
+
UserMarketData({
|
|
45
|
+
liquidityShares: liquidityShares,
|
|
46
|
+
outcomeShares: outcomeShares,
|
|
47
|
+
winningsToClaim: winningsToClaim,
|
|
48
|
+
winningsClaimed: winningsClaimed,
|
|
49
|
+
liquidityToClaim: liquidityToClaim,
|
|
50
|
+
liquidityClaimed: liquidityClaimed,
|
|
51
|
+
voidedSharesToClaim: voidedSharesToClaim
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getUserMarketsData(uint256[] calldata marketIds, address user)
|
|
56
|
+
external
|
|
57
|
+
view
|
|
58
|
+
returns (UserMarketData[] memory)
|
|
59
|
+
{
|
|
60
|
+
UserMarketData[] memory userMarketsData = new UserMarketData[](marketIds.length);
|
|
61
|
+
|
|
62
|
+
for (uint256 i = 0; i < marketIds.length; i++) {
|
|
63
|
+
userMarketsData[i] = getUserMarketData(marketIds[i], user);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return userMarketsData;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getUserAllMarketsData(address user) external view returns (UserMarketData[] memory) {
|
|
70
|
+
uint256[] memory marketIds = PredictionMarketV3.getMarkets();
|
|
71
|
+
UserMarketData[] memory userMarketsData = new UserMarketData[](marketIds.length);
|
|
72
|
+
|
|
73
|
+
for (uint256 i = 0; i < marketIds.length; i++) {
|
|
74
|
+
userMarketsData[i] = getUserMarketData(marketIds[i], user);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return userMarketsData;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
// Based on https://github.com/RealityETH/reality-eth-monorepo/blob/main/packages/contracts/development/contracts/RealityETH_ERC20_Factory.sol
|
|
3
|
+
|
|
4
|
+
pragma solidity ^0.8.18;
|
|
5
|
+
|
|
6
|
+
// local imports
|
|
7
|
+
import "./IRealityETH_ERC20.sol";
|
|
8
|
+
|
|
9
|
+
// definining interface this way instead of IERC20 to avoid conflicts
|
|
10
|
+
interface IRealityETH_IERC20 {
|
|
11
|
+
function decimals() external view returns (uint8);
|
|
12
|
+
function symbol() external view returns (string memory);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
contract RealityETH_ERC20_Factory {
|
|
16
|
+
|
|
17
|
+
address public libraryAddress;
|
|
18
|
+
mapping(address => address) public deployments;
|
|
19
|
+
|
|
20
|
+
event RealityETH_ERC20_deployed (address reality_eth, address token, uint8 decimals, string token_ticker);
|
|
21
|
+
|
|
22
|
+
constructor(address _libraryAddress) {
|
|
23
|
+
libraryAddress = _libraryAddress;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// @notice Returns the address of a proxy based on the specified address
|
|
27
|
+
/// @dev based on https://github.com/optionality/clone-factory
|
|
28
|
+
function _deployProxy(address _target)
|
|
29
|
+
internal returns (address result) {
|
|
30
|
+
bytes20 targetBytes = bytes20(_target);
|
|
31
|
+
assembly {
|
|
32
|
+
let clone := mload(0x40)
|
|
33
|
+
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
|
|
34
|
+
mstore(add(clone, 0x14), targetBytes)
|
|
35
|
+
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
|
|
36
|
+
result := create(0, clone, 0x37)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createInstance(address _token) external {
|
|
41
|
+
require(deployments[_token] == address(0), "There should only be one deployment per version per token");
|
|
42
|
+
uint8 decimals = IRealityETH_IERC20(_token).decimals();
|
|
43
|
+
string memory ticker = IRealityETH_IERC20(_token).symbol();
|
|
44
|
+
address clone = _deployProxy(libraryAddress);
|
|
45
|
+
IRealityETH_ERC20(clone).setToken(_token);
|
|
46
|
+
IRealityETH_ERC20(clone).createTemplate('{"title": "%s", "type": "bool", "category": "%s", "lang": "%s"}');
|
|
47
|
+
IRealityETH_ERC20(clone).createTemplate('{"title": "%s", "type": "uint", "decimals": 18, "category": "%s", "lang": "%s"}');
|
|
48
|
+
IRealityETH_ERC20(clone).createTemplate('{"title": "%s", "type": "single-select", "outcomes": [%s], "category": "%s", "lang": "%s"}');
|
|
49
|
+
IRealityETH_ERC20(clone).createTemplate('{"title": "%s", "type": "multiple-select", "outcomes": [%s], "category": "%s", "lang": "%s"}');
|
|
50
|
+
IRealityETH_ERC20(clone).createTemplate('{"title": "%s", "type": "datetime", "category": "%s", "lang": "%s"}');
|
|
51
|
+
deployments[_token] = clone;
|
|
52
|
+
emit RealityETH_ERC20_deployed(clone, _token, decimals, ticker);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.18;
|
|
3
|
+
pragma experimental ABIEncoderV2;
|
|
4
|
+
|
|
5
|
+
// openzeppelin imports
|
|
6
|
+
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
|
|
7
|
+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
8
|
+
|
|
9
|
+
/// @title Upvoting / Downvoting Contract
|
|
10
|
+
contract Voting {
|
|
11
|
+
using SafeMath for uint256;
|
|
12
|
+
|
|
13
|
+
event ItemVotesUpdated(uint256 indexed itemId, uint256 upvotes, uint256 downvotes, uint256 timestamp);
|
|
14
|
+
|
|
15
|
+
event ItemVoteAction(address indexed user, VoteAction indexed action, uint256 indexed itemId, uint256 timestamp);
|
|
16
|
+
|
|
17
|
+
enum VoteAction {
|
|
18
|
+
upvote,
|
|
19
|
+
removeUpvote,
|
|
20
|
+
downvote,
|
|
21
|
+
removeDownvote
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
struct ItemVotes {
|
|
25
|
+
mapping(address => bool) usersUpvoted;
|
|
26
|
+
mapping(address => bool) usersDownvoted;
|
|
27
|
+
uint256 upvotes;
|
|
28
|
+
uint256 downvotes;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
mapping(uint256 => ItemVotes) items;
|
|
32
|
+
|
|
33
|
+
IERC20 public token;
|
|
34
|
+
uint256 public requiredBalance; // required balance for voting
|
|
35
|
+
|
|
36
|
+
// ------ Modifiers ------
|
|
37
|
+
|
|
38
|
+
modifier mustHoldRequiredBalance() {
|
|
39
|
+
require(token.balanceOf(msg.sender) >= requiredBalance, "msg.sender must hold minimum erc20 balance");
|
|
40
|
+
_;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ------ Modifiers End ------
|
|
44
|
+
|
|
45
|
+
/// @dev protocol is immutable and has no ownership
|
|
46
|
+
constructor(IERC20 _token, uint256 _requiredBalance) {
|
|
47
|
+
token = _token;
|
|
48
|
+
requiredBalance = _requiredBalance;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// @dev allows user to upvote an item
|
|
52
|
+
function upvoteItem(uint256 itemId) external mustHoldRequiredBalance {
|
|
53
|
+
voteItem(itemId, VoteAction.upvote);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// @dev allows user to downvote an item
|
|
57
|
+
function downvoteItem(uint256 itemId) external mustHoldRequiredBalance {
|
|
58
|
+
voteItem(itemId, VoteAction.downvote);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// @dev logic of upvoting/downvoting an item
|
|
62
|
+
function voteItem(uint256 itemId, VoteAction action) private {
|
|
63
|
+
require(
|
|
64
|
+
action == VoteAction.upvote || action == VoteAction.downvote,
|
|
65
|
+
"Function parameter has to be upvoted or downvoted"
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
ItemVotes storage item = items[itemId];
|
|
69
|
+
|
|
70
|
+
mapping(address => bool) storage listToAddVote = action == VoteAction.upvote
|
|
71
|
+
? item.usersUpvoted
|
|
72
|
+
: item.usersDownvoted;
|
|
73
|
+
mapping(address => bool) storage listToRemoveVote = action == VoteAction.upvote
|
|
74
|
+
? item.usersDownvoted
|
|
75
|
+
: item.usersUpvoted;
|
|
76
|
+
|
|
77
|
+
// check if user has not already voted on the selected direction
|
|
78
|
+
bool hasUserVotedSelectedDirection = listToAddVote[msg.sender];
|
|
79
|
+
|
|
80
|
+
require(hasUserVotedSelectedDirection == false, "User has already a vote on this item");
|
|
81
|
+
|
|
82
|
+
// check if user has already downvoted
|
|
83
|
+
bool hasUserVotedOtherDirection = listToRemoveVote[msg.sender];
|
|
84
|
+
|
|
85
|
+
// remove from total downvoted if needed
|
|
86
|
+
if (hasUserVotedOtherDirection) {
|
|
87
|
+
action == VoteAction.upvote ? item.downvotes = item.downvotes.sub(1) : item.upvotes = item.upvotes.sub(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// add the vote
|
|
91
|
+
action == VoteAction.upvote ? item.upvotes = item.upvotes.add(1) : item.downvotes = item.downvotes.add(1);
|
|
92
|
+
listToAddVote[msg.sender] = true;
|
|
93
|
+
listToRemoveVote[msg.sender] = false;
|
|
94
|
+
|
|
95
|
+
// emit events
|
|
96
|
+
emit ItemVoteAction(msg.sender, action, itemId, block.timestamp);
|
|
97
|
+
|
|
98
|
+
emit ItemVotesUpdated(itemId, item.upvotes, item.downvotes, block.timestamp);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// @dev allows user to remove an upvote from an item
|
|
102
|
+
function removeUpvoteItem(uint256 itemId) external mustHoldRequiredBalance {
|
|
103
|
+
removeVoteItem(itemId, VoteAction.removeUpvote);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// @dev allows user to remove a downvote from an item
|
|
107
|
+
function removeDownvoteItem(uint256 itemId) external mustHoldRequiredBalance {
|
|
108
|
+
removeVoteItem(itemId, VoteAction.removeDownvote);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// @dev logic of removing an upvote/downvote from an item
|
|
112
|
+
function removeVoteItem(uint256 itemId, VoteAction action) private {
|
|
113
|
+
require(
|
|
114
|
+
action == VoteAction.removeUpvote || action == VoteAction.removeDownvote,
|
|
115
|
+
"Function parameter has to be removeUpvoted or removeDownvoted"
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// get item votes
|
|
119
|
+
ItemVotes storage item = items[itemId];
|
|
120
|
+
|
|
121
|
+
mapping(address => bool) storage listToRemoveVote = action == VoteAction.removeUpvote
|
|
122
|
+
? item.usersUpvoted
|
|
123
|
+
: item.usersDownvoted;
|
|
124
|
+
|
|
125
|
+
// check if user has voted item
|
|
126
|
+
bool hasUserVoted = listToRemoveVote[msg.sender];
|
|
127
|
+
|
|
128
|
+
require(hasUserVoted == true, "User doesn't have a vote on this item");
|
|
129
|
+
|
|
130
|
+
// remove the vote
|
|
131
|
+
action == VoteAction.removeUpvote ? item.upvotes = item.upvotes.sub(1) : item.downvotes = item.downvotes.sub(1);
|
|
132
|
+
listToRemoveVote[msg.sender] = false;
|
|
133
|
+
|
|
134
|
+
// emit events
|
|
135
|
+
emit ItemVoteAction(msg.sender, action, itemId, block.timestamp);
|
|
136
|
+
|
|
137
|
+
emit ItemVotesUpdated(itemId, item.upvotes, item.downvotes, block.timestamp);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// @dev Returns the number of upvotes/downvotes of an item
|
|
141
|
+
function getItemVotes(uint256 itemId) external view returns (uint256, uint256) {
|
|
142
|
+
ItemVotes storage item = items[itemId];
|
|
143
|
+
|
|
144
|
+
return (item.upvotes, item.downvotes);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// @dev Returns info if the user has a upvote or a downvote in an item
|
|
148
|
+
function hasUserVotedItem(address user, uint256 itemId) external view returns (bool, bool) {
|
|
149
|
+
ItemVotes storage item = items[itemId];
|
|
150
|
+
|
|
151
|
+
return (item.usersUpvoted[user], item.usersDownvoted[user]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.18;
|
|
3
|
+
|
|
4
|
+
contract WETH9 {
|
|
5
|
+
string public name = "Wrapped Ether";
|
|
6
|
+
string public symbol = "WETH";
|
|
7
|
+
uint8 public decimals = 18;
|
|
8
|
+
|
|
9
|
+
event Approval(address indexed src, address indexed guy, uint256 wad);
|
|
10
|
+
event Transfer(address indexed src, address indexed dst, uint256 wad);
|
|
11
|
+
event Deposit(address indexed dst, uint256 wad);
|
|
12
|
+
event Withdrawal(address indexed src, uint256 wad);
|
|
13
|
+
|
|
14
|
+
mapping(address => uint256) public balanceOf;
|
|
15
|
+
mapping(address => mapping(address => uint256)) public allowance;
|
|
16
|
+
|
|
17
|
+
fallback() external payable {
|
|
18
|
+
deposit();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function deposit() public payable {
|
|
22
|
+
balanceOf[msg.sender] += msg.value;
|
|
23
|
+
emit Deposit(msg.sender, msg.value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function withdraw(uint256 wad) public {
|
|
27
|
+
require(balanceOf[msg.sender] >= wad);
|
|
28
|
+
balanceOf[msg.sender] -= wad;
|
|
29
|
+
payable(msg.sender).transfer(wad);
|
|
30
|
+
emit Withdrawal(msg.sender, wad);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function totalSupply() public view returns (uint256) {
|
|
34
|
+
return address(this).balance;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function approve(address guy, uint256 wad) public returns (bool) {
|
|
38
|
+
allowance[msg.sender][guy] = wad;
|
|
39
|
+
emit Approval(msg.sender, guy, wad);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function transfer(address dst, uint256 wad) public returns (bool) {
|
|
44
|
+
return transferFrom(msg.sender, dst, wad);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
|
|
48
|
+
require(balanceOf[src] >= wad);
|
|
49
|
+
|
|
50
|
+
if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
|
|
51
|
+
require(allowance[src][msg.sender] >= wad);
|
|
52
|
+
allowance[src][msg.sender] -= wad;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
balanceOf[src] -= wad;
|
|
56
|
+
balanceOf[dst] += wad;
|
|
57
|
+
|
|
58
|
+
emit Transfer(src, dst, wad);
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|