diff --git a/solidity/contracts/peripherals/Auction.sol b/solidity/contracts/peripherals/Auction.sol index 69da1cb..07f0cec 100644 --- a/solidity/contracts/peripherals/Auction.sol +++ b/solidity/contracts/peripherals/Auction.sol @@ -9,13 +9,14 @@ contract Auction { uint256 public auctionStarted; uint256 public ethAmountToBuy; bool public finalized; - address immutable owner; + address public owner; event Participated(address user, uint256 repAmount, uint256 ethAmount, uint256 totalRepPurchased); event FinalizedAuction(address user, uint256 repAmount, uint256 ethAmount); event AuctionStarted(uint256 ethAmountToBuy, uint256 repAvailable); - constructor(address _owner) { + function setOwner(address _owner) external { + require(owner == address(0x0), 'owner already set!'); owner = _owner; } diff --git a/solidity/contracts/peripherals/AuctionFactory.sol b/solidity/contracts/peripherals/AuctionFactory.sol new file mode 100644 index 0000000..70a6cab --- /dev/null +++ b/solidity/contracts/peripherals/AuctionFactory.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNICENSE +pragma solidity 0.8.30; +import { Auction } from './Auction.sol'; + +contract AuctionFactory { + function deployAuction(bytes32 salt) external returns (Auction) { + return new Auction{ salt: keccak256(abi.encodePacked(msg.sender, salt)) }(); + } +} diff --git a/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol b/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol index eb5606a..d181adf 100644 --- a/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol +++ b/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol @@ -34,7 +34,7 @@ contract PriceOracleManagerAndOperatorQueuer { uint256 public lastSettlementTimestamp; uint256 public lastPrice; // (REP * PRICE_PRECISION) / ETH; ReputationToken immutable reputationToken; - ISecurityPool public immutable securityPool; + ISecurityPool public securityPool; OpenOracle public immutable openOracle; event PriceReported(uint256 reportId, uint256 price); @@ -44,12 +44,16 @@ contract PriceOracleManagerAndOperatorQueuer { uint256 public previousQueuedOperationId; mapping(uint256 => QueuedOperation) public queuedOperations; - constructor(OpenOracle _openOracle, ISecurityPool _securityPool, ReputationToken _reputationToken) { + constructor(OpenOracle _openOracle, ReputationToken _reputationToken) { reputationToken = _reputationToken; - securityPool = _securityPool; openOracle = _openOracle; } + function setSecurityPool(ISecurityPool _securityPool) public { + require (address(securityPool) == address(0x0), 'already set!'); + securityPool = _securityPool; + } + function setRepEthPrice(uint256 _lastPrice) public { require(msg.sender == address(securityPool), 'only security pool can set'); lastPrice = _lastPrice; diff --git a/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuerFactory.sol b/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuerFactory.sol new file mode 100644 index 0000000..6d0e8e4 --- /dev/null +++ b/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuerFactory.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNICENSE +pragma solidity 0.8.30; +import { IShareToken } from './interfaces/IShareToken.sol'; +import { ShareToken } from './tokens/ShareToken.sol'; +import { ISecurityPool } from './interfaces/ISecurityPool.sol'; +import { Zoltar } from '../Zoltar.sol'; +import { OpenOracle } from './openOracle/OpenOracle.sol'; +import { ReputationToken } from '../ReputationToken.sol'; +import { PriceOracleManagerAndOperatorQueuer } from './PriceOracleManagerAndOperatorQueuer.sol'; + +contract PriceOracleManagerAndOperatorQueuerFactory { + function deployPriceOracleManagerAndOperatorQueuer(OpenOracle _openOracle, ReputationToken _reputationToken, bytes32 salt) external returns (PriceOracleManagerAndOperatorQueuer) { + return new PriceOracleManagerAndOperatorQueuer{ salt: keccak256(abi.encodePacked(msg.sender, salt)) }(_openOracle, _reputationToken); + } +} diff --git a/solidity/contracts/peripherals/SecurityPool.sol b/solidity/contracts/peripherals/SecurityPool.sol index 91fcc80..ad16ae5 100644 --- a/solidity/contracts/peripherals/SecurityPool.sol +++ b/solidity/contracts/peripherals/SecurityPool.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.30; import { Auction } from './Auction.sol'; import { Zoltar } from '../Zoltar.sol'; import { ReputationToken } from '../ReputationToken.sol'; -import { CompleteSet } from './CompleteSet.sol'; +import { IShareToken } from './interfaces/IShareToken.sol'; import { PriceOracleManagerAndOperatorQueuer } from './PriceOracleManagerAndOperatorQueuer.sol'; import { ISecurityPool, SecurityVault, SystemState, QuestionOutcome, ISecurityPoolFactory } from './interfaces/ISecurityPool.sol'; import { OpenOracle } from './openOracle/OpenOracle.sol'; @@ -38,8 +38,9 @@ contract SecurityPool is ISecurityPool { uint256 public truthAuctionStarted; SystemState public systemState; - CompleteSet public immutable completeSet; + IShareToken public immutable shareToken; Auction public immutable truthAuction; + ReputationToken public repToken; ISecurityPoolFactory public immutable securityPoolFactory; @@ -64,7 +65,7 @@ contract SecurityPool is ISecurityPool { _; } - constructor(ISecurityPoolFactory _securityPoolFactory, OpenOracle _openOracle, ISecurityPool _parent, Zoltar _zoltar, uint192 _universeId, uint56 _questionId, uint256 _securityMultiplier) { + constructor(ISecurityPoolFactory _securityPoolFactory, Auction _truthAuction, PriceOracleManagerAndOperatorQueuer _priceOracleManagerAndOperatorQueuer, IShareToken _shareToken, OpenOracle _openOracle, ISecurityPool _parent, Zoltar _zoltar, uint192 _universeId, uint56 _questionId, uint256 _securityMultiplier) { universeId = _universeId; securityPoolFactory = _securityPoolFactory; questionId = _questionId; @@ -72,14 +73,14 @@ contract SecurityPool is ISecurityPool { zoltar = _zoltar; parent = _parent; openOracle = _openOracle; + truthAuction = _truthAuction; + priceOracleManagerAndOperatorQueuer = _priceOracleManagerAndOperatorQueuer; if (address(parent) == address(0x0)) { // origin universe never does truthAuction systemState = SystemState.Operational; } else { systemState = SystemState.ForkMigration; - truthAuction = new Auction{ salt: bytes32(uint256(0x1)) }(address(this)); } - // todo, we can probably do these smarter so that we don't need migration - completeSet = new CompleteSet{ salt: bytes32(uint256(0x1)) }(address(this)); + shareToken = _shareToken; } function setStartingParams(uint256 _currentRetentionRate, uint256 _repEthPrice, uint256 _completeSetCollateralAmount) public { @@ -88,7 +89,6 @@ contract SecurityPool is ISecurityPool { currentRetentionRate = _currentRetentionRate; completeSetCollateralAmount = _completeSetCollateralAmount; (repToken,,) = zoltar.universes(universeId); - priceOracleManagerAndOperatorQueuer = new PriceOracleManagerAndOperatorQueuer{ salt: bytes32(uint256(0x1)) }(openOracle, this, repToken); priceOracleManagerAndOperatorQueuer.setRepEthPrice(_repEthPrice); } @@ -231,8 +231,9 @@ contract SecurityPool is ISecurityPool { require(systemState == SystemState.Operational, 'system is not Operational'); //todo, we want to be able to create complete sets in the children right away, figure accounting out updateCollateralAmount(); require(securityBondAllowance - completeSetCollateralAmount >= msg.value, 'no capacity to create that many sets'); - uint256 amountToMint = completeSet.totalSupply() == completeSetCollateralAmount ? msg.value : msg.value * completeSet.totalSupply() / completeSetCollateralAmount; - completeSet.mint(msg.sender, amountToMint); + uint256 totalSupply = shareToken.totalSupplyForUniverse(universeId); + uint256 amountToMint = totalSupply == completeSetCollateralAmount ? msg.value : msg.value * totalSupply / completeSetCollateralAmount; // todo this is wrong + shareToken.mintCompleteSets(universeId, msg.sender, amountToMint); completeSetCollateralAmount += msg.value; updateRetentionRate(); } @@ -241,20 +242,25 @@ contract SecurityPool is ISecurityPool { require(systemState == SystemState.Operational, 'system is not Operational'); // todo, we want to allow people to exit, but for accounting purposes that is difficult but maybe there's a way? updateCollateralAmount(); // takes in complete set and releases security bond and eth - uint256 ethValue = amount * completeSetCollateralAmount / completeSet.totalSupply(); - completeSet.burn(msg.sender, amount); + uint256 totalSupply = shareToken.totalSupplyForUniverse(universeId); + uint256 ethValue = amount * completeSetCollateralAmount / totalSupply; // this is wrong + shareToken.burnCompleteSets(universeId, msg.sender, amount); completeSetCollateralAmount -= ethValue; updateRetentionRate(); - (bool sent, ) = payable(msg.sender).call{value: ethValue}(''); + (bool sent, ) = payable(msg.sender).call{ value: ethValue }(''); require(sent, 'Failed to send Ether'); } - /* - function redeemShare() isOperational public { - require(zoltar.isFinalized(universeId, questionId), 'Question has not finalized!'); - //convertes yes,no or invalid share to 1 eth each, depending on market outcome + function redeemShares() isOperational external { + Zoltar.Outcome outcome = zoltar.finalizeQuestion(universeId, questionId); + require(outcome != Zoltar.Outcome.None, 'Question has not finalized!'); + uint256 tokenId = shareToken.getTokenId(universeId, outcome); + uint256 amount = shareToken.burnTokenId(tokenId, msg.sender); + uint256 totalSupply = shareToken.totalSupplyForUniverse(universeId); + uint256 ethValue = amount * completeSetCollateralAmount / totalSupply; // this is wrong + (bool sent, ) = payable(msg.sender).call{ value: ethValue }(''); + require(sent, 'Failed to send Ether'); } - */ //////////////////////////////////////// // FORKING (migrate vault (oi+rep), truth truthAuction) @@ -263,7 +269,6 @@ contract SecurityPool is ISecurityPool { (,, uint256 forkTime) = zoltar.universes(universeId); require(forkTime > 0, 'Zoltar needs to have forked before Security Pool can do so'); require(systemState == SystemState.Operational, 'System needs to be operational to trigger fork'); - require(securityPoolForkTriggeredTimestamp == 0, 'fork already triggered'); require(!zoltar.isFinalized(universeId, questionId), 'question has been finalized already'); systemState = SystemState.PoolForked; securityPoolForkTriggeredTimestamp = block.timestamp; @@ -285,7 +290,8 @@ contract SecurityPool is ISecurityPool { if (address(children[uint8(outcome)]) == address(0x0)) { // first vault migrater creates new pool and transfers all REP to it uint192 childUniverseId = (universeId << 2) + uint192(outcome) + 1; - children[uint8(outcome)] = securityPoolFactory.deploySecurityPool(openOracle, this, zoltar, childUniverseId, questionId, securityMultiplier, currentRetentionRate, priceOracleManagerAndOperatorQueuer.lastPrice(), 0); + children[uint8(outcome)] = securityPoolFactory.deployChildSecurityPool(shareToken, childUniverseId, questionId, securityMultiplier, currentRetentionRate, priceOracleManagerAndOperatorQueuer.lastPrice(), 0); + shareToken.authorize(children[uint8(outcome)]); ReputationToken childReputationToken = children[uint8(outcome)].repToken(); childReputationToken.transfer(address(children[uint8(outcome)]), childReputationToken.balanceOf(address(this))); } @@ -321,7 +327,6 @@ contract SecurityPool is ISecurityPool { function startTruthAuction() public { require(systemState == SystemState.ForkMigration, 'System needs to be in migration'); require(block.timestamp > securityPoolForkTriggeredTimestamp + SecurityPoolUtils.MIGRATION_TIME, 'migration time needs to pass first'); - require(truthAuctionStarted == 0, 'Auction already started'); systemState = SystemState.ForkTruthAuction; truthAuctionStarted = block.timestamp; parent.updateCollateralAmount(); diff --git a/solidity/contracts/peripherals/SecurityPoolFactory.sol b/solidity/contracts/peripherals/SecurityPoolFactory.sol index 18a7272..b8f11ea 100644 --- a/solidity/contracts/peripherals/SecurityPoolFactory.sol +++ b/solidity/contracts/peripherals/SecurityPoolFactory.sol @@ -1,15 +1,68 @@ // SPDX-License-Identifier: UNICENSE pragma solidity 0.8.30; +import { IShareToken } from './interfaces/IShareToken.sol'; import { SecurityPool } from './SecurityPool.sol'; import { ISecurityPool, ISecurityPoolFactory } from './interfaces/ISecurityPool.sol'; import { OpenOracle } from './openOracle/OpenOracle.sol'; import { Zoltar } from '../Zoltar.sol'; +import { ShareTokenFactory } from './ShareTokenFactory.sol'; +import { AuctionFactory } from './AuctionFactory.sol'; +import { Auction } from './Auction.sol'; +import { ShareToken } from './tokens/ShareToken.sol'; +import { PriceOracleManagerAndOperatorQueuerFactory } from './PriceOracleManagerAndOperatorQueuerFactory.sol'; +import { PriceOracleManagerAndOperatorQueuer } from './PriceOracleManagerAndOperatorQueuer.sol'; +import { ReputationToken } from '../ReputationToken.sol'; contract SecurityPoolFactory is ISecurityPoolFactory { - event DeploySecurityPool(ISecurityPool securityPool, OpenOracle openOracle, ISecurityPool parent, Zoltar zoltar, uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount); - function deploySecurityPool(OpenOracle openOracle, ISecurityPool parent, Zoltar zoltar, uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPoolAddress) { - securityPoolAddress = new SecurityPool{salt: bytes32(uint256(0x1))}(this, openOracle, parent, zoltar, universeId, questionId, securityMultiplier); - securityPoolAddress.setStartingParams(currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); - emit DeploySecurityPool(securityPoolAddress, openOracle, parent, zoltar, universeId, questionId, securityMultiplier, currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); + ShareTokenFactory shareTokenFactory; + AuctionFactory auctionFactory; + PriceOracleManagerAndOperatorQueuerFactory priceOracleManagerAndOperatorQueuerFactory; + Zoltar zoltar; + OpenOracle openOracle; + + event DeploySecurityPool(ISecurityPool securityPool, Auction truthAuction, PriceOracleManagerAndOperatorQueuer priceOracleManagerAndOperatorQueuer, IShareToken shareToken, ISecurityPool parent, uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount); + + constructor(OpenOracle _openOracle, Zoltar _zoltar, ShareTokenFactory _shareTokenFactory, AuctionFactory _auctionFactory, PriceOracleManagerAndOperatorQueuerFactory _priceOracleManagerAndOperatorQueuerFactory) { + shareTokenFactory = _shareTokenFactory; + auctionFactory = _auctionFactory; + priceOracleManagerAndOperatorQueuerFactory = _priceOracleManagerAndOperatorQueuerFactory; + zoltar = _zoltar; + openOracle = _openOracle; + } + + function deployChildSecurityPool(IShareToken shareToken, uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPool) { + ISecurityPool parent = ISecurityPool(payable(msg.sender)); + bytes32 securityPoolSalt = keccak256(abi.encodePacked(parent, universeId, questionId, securityMultiplier)); + (ReputationToken reputationToken,,) = zoltar.universes(universeId); + PriceOracleManagerAndOperatorQueuer priceOracleManagerAndOperatorQueuer = priceOracleManagerAndOperatorQueuerFactory.deployPriceOracleManagerAndOperatorQueuer(openOracle, reputationToken, securityPoolSalt); + + Auction truthAuction = auctionFactory.deployAuction(securityPoolSalt); + + securityPool = new SecurityPool{ salt: bytes32(uint256(0x0)) }(this, truthAuction, priceOracleManagerAndOperatorQueuer, shareToken, openOracle, parent, zoltar, universeId, questionId, securityMultiplier); + + priceOracleManagerAndOperatorQueuer.setSecurityPool(securityPool); + securityPool.setStartingParams(currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); + + truthAuction.setOwner(address(securityPool)); + emit DeploySecurityPool(securityPool, truthAuction, priceOracleManagerAndOperatorQueuer, shareToken, parent, universeId, questionId, securityMultiplier, currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); + } + + function deployOriginSecurityPool(uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPool) { + bytes32 securityPoolSalt = keccak256(abi.encodePacked(address(0x0), universeId, questionId, securityMultiplier)); + (ReputationToken reputationToken,,) = zoltar.universes(universeId); + PriceOracleManagerAndOperatorQueuer priceOracleManagerAndOperatorQueuer = priceOracleManagerAndOperatorQueuerFactory.deployPriceOracleManagerAndOperatorQueuer(openOracle, reputationToken, securityPoolSalt); + + // sharetoken has different salt as sharetoken address does not change in forks + bytes32 shareTokenSalt = keccak256(abi.encodePacked(securityMultiplier)); + IShareToken shareToken = shareTokenFactory.deployShareToken(questionId, shareTokenSalt); + + securityPool = new SecurityPool{ salt: bytes32(uint256(0x0)) }(this, Auction(address(0x0)), priceOracleManagerAndOperatorQueuer, shareToken, openOracle, ISecurityPool(payable(0x0)), zoltar, universeId, questionId, securityMultiplier); + + priceOracleManagerAndOperatorQueuer.setSecurityPool(securityPool); + securityPool.setStartingParams(currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); + + shareToken.authorize(securityPool); + + emit DeploySecurityPool(securityPool, Auction(address(0x0)), priceOracleManagerAndOperatorQueuer, shareToken, ISecurityPool(payable(0x0)), universeId, questionId, securityMultiplier, currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); } } diff --git a/solidity/contracts/peripherals/ShareTokenFactory.sol b/solidity/contracts/peripherals/ShareTokenFactory.sol new file mode 100644 index 0000000..bc5acd5 --- /dev/null +++ b/solidity/contracts/peripherals/ShareTokenFactory.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNICENSE +pragma solidity 0.8.30; +import { IShareToken } from './interfaces/IShareToken.sol'; +import { ShareToken } from './tokens/ShareToken.sol'; +import { ISecurityPool } from './interfaces/ISecurityPool.sol'; +import { Zoltar } from '../Zoltar.sol'; + +contract ShareTokenFactory { + Zoltar zoltar; + + constructor(Zoltar _zoltar) { + zoltar = _zoltar; + } + + function deployShareToken(uint56 questionId, bytes32 salt) external returns (IShareToken shareToken) { + return new ShareToken{ salt: salt }(msg.sender, zoltar, questionId); + } +} diff --git a/solidity/contracts/peripherals/interfaces/IERC1155.sol b/solidity/contracts/peripherals/interfaces/IERC1155.sol new file mode 100644 index 0000000..36c1ba4 --- /dev/null +++ b/solidity/contracts/peripherals/interfaces/IERC1155.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: apache +/* + +Copyright 2018 ZeroEx Intl. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +pragma solidity 0.8.30; + + +/// @title ERC-1155 Multi Token Standard +/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md +/// Note: The ERC-165 identifier for this interface is 0xd9b67a26. +interface IERC1155 { + + /// @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, + /// including zero value transfers as well as minting or burning. + /// Operator will always be msg.sender. + /// Either event from address `0x0` signifies a minting operation. + /// An event to address `0x0` signifies a burning or melting operation. + /// The total value transferred from address 0x0 minus the total value transferred to 0x0 may + /// be used by clients and exchanges to be added to the "circulating supply" for a given token ID. + /// To define a token ID with no initial balance, the contract SHOULD emit the TransferSingle event + /// from `0x0` to `0x0`, with the token creator as `_operator`. + event TransferSingle( + address indexed operator, + address indexed from, + address indexed to, + uint256 id, + uint256 value + ); + + /// @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, + /// including zero value transfers as well as minting or burning. + ///Operator will always be msg.sender. + /// Either event from address `0x0` signifies a minting operation. + /// An event to address `0x0` signifies a burning or melting operation. + /// The total value transferred from address 0x0 minus the total value transferred to 0x0 may + /// be used by clients and exchanges to be added to the "circulating supply" for a given token ID. + /// To define multiple token IDs with no initial balance, this SHOULD emit the TransferBatch event + /// from `0x0` to `0x0`, with the token creator as `_operator`. + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /// @dev MUST emit when an approval is updated. + event ApprovalForAll( + address indexed owner, + address indexed operator, + bool approved + ); + + /// @dev MUST emit when the URI is updated for a token ID. + /// URIs are defined in RFC 3986. + /// The URI MUST point a JSON file that conforms to the "ERC-1155 Metadata JSON Schema". + event URI( + string value, + uint256 indexed id + ); + + /// @notice Transfers value amount of an _id from the _from address to the _to address specified. + /// @dev MUST emit TransferSingle event on success. + /// Caller must be approved to manage the _from account's tokens (see isApprovedForAll). + /// MUST throw if `_to` is the zero address. + /// MUST throw if balance of sender for token `_id` is lower than the `_value` sent. + /// MUST throw on any other error. + /// @param from Source address + /// @param to Target address + /// @param id ID of the token type + /// @param value Transfer amount + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 value + ) + external; + + /// @notice Send multiple types of Tokens from a 3rd party in one transfer (with safety call). + /// @dev MUST emit TransferBatch event on success. + /// Caller must be approved to manage the _from account's tokens (see isApprovedForAll). + /// MUST throw if `_to` is the zero address. + /// MUST throw if length of `_ids` is not the same as length of `_values`. + /// MUST throw if any of the balance of sender for token `_ids` is lower than the respective `_values` sent. + /// MUST throw on any other error. + /// @param from Source addresses + /// @param to Target addresses + /// @param ids IDs of each token type + /// @param values Transfer amounts per token type + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata values + ) + external; + + /// @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. + /// @dev MUST emit the ApprovalForAll event on success. + /// @param operator Address to add to the set of authorized operators + /// @param approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address operator, bool approved) external; + + /// @notice Queries the approval status of an operator for a given owner. + /// @param owner The owner of the Tokens + /// @param operator Address of authorized operator + /// @return True if the operator is approved, false if not + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /// @notice Get the balance of an account's Tokens. + /// @param owner The address of the token holder + /// @param id ID of the Token + /// @return The _owner's balance of the Token type requested + function balanceOf(address owner, uint256 id) external view returns (uint256); + + /// @notice Get the total supply of a Token. + /// @param id ID of the Token + /// @return The total supply of the Token type requested + function totalSupply(uint256 id) external view returns (uint256); + + /// @notice Get the balance of multiple account/token pairs + /// @param owners The addresses of the token holders + /// @param ids ID of the Tokens + /// @return balances_ The _owner's balance of the Token types requested + function balanceOfBatch( + address[] calldata owners, + uint256[] calldata ids + ) + external + view + returns (uint256[] memory balances_); +} diff --git a/solidity/contracts/peripherals/interfaces/ISecurityPool.sol b/solidity/contracts/peripherals/interfaces/ISecurityPool.sol index 4058cbf..eb12799 100644 --- a/solidity/contracts/peripherals/interfaces/ISecurityPool.sol +++ b/solidity/contracts/peripherals/interfaces/ISecurityPool.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.30; import { Zoltar } from '../../Zoltar.sol'; import { OpenOracle } from "../openOracle/OpenOracle.sol"; import { Auction } from "../Auction.sol"; -import { CompleteSet } from "../CompleteSet.sol"; +import { IShareToken } from "./IShareToken.sol"; import { ReputationToken } from "../../ReputationToken.sol"; import { PriceOracleManagerAndOperatorQueuer } from "../PriceOracleManagerAndOperatorQueuer.sol"; @@ -50,7 +50,7 @@ interface ISecurityPool { function parent() external view returns (ISecurityPool); function truthAuctionStarted() external view returns (uint256); function systemState() external view returns (SystemState); - function completeSet() external view returns (CompleteSet); + function shareToken() external view returns (IShareToken); function truthAuction() external view returns (Auction); function repToken() external view returns (ReputationToken); function securityPoolFactory() external view returns (ISecurityPoolFactory); @@ -87,5 +87,6 @@ interface ISecurityPool { } interface ISecurityPoolFactory { - function deploySecurityPool(OpenOracle openOracle, ISecurityPool parent, Zoltar zoltar, uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPoolAddress); + function deployChildSecurityPool(IShareToken shareToken, uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPool); + function deployOriginSecurityPool(uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPool); } diff --git a/solidity/contracts/peripherals/interfaces/IShareToken.sol b/solidity/contracts/peripherals/interfaces/IShareToken.sol new file mode 100644 index 0000000..00d6933 --- /dev/null +++ b/solidity/contracts/peripherals/interfaces/IShareToken.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.30; + +import '../interfaces/ISecurityPool.sol'; +import '../../Zoltar.sol'; + +/** +* @title IShareToken +* @notice Interface for the ShareToken contract +*/ +interface IShareToken { + + // Read-only metadata + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function zoltar() external view returns (Zoltar); + + // Security pool registration + function authorize(ISecurityPool _securityPoolCandidate) external; + + // Question operations + function mintCompleteSets(uint192 _universeId, address _account, uint256 _cashAmount) external payable; + function burnCompleteSets(uint192 _universeId, address _owner, uint256 _amount) external; + function burnTokenId(uint256 _tokenId, address _owner) external returns (uint256); + + // TokenId information helpers + function getUniverse(uint256 _tokenId) external pure returns (uint256); + function getOutcome(uint256 _tokenId) external pure returns (Zoltar.Outcome); + + // Balance and supply queries + function totalSupplyForOutcome(uint192 _universeId, Zoltar.Outcome _outcome) external view returns (uint256); + function totalSupplyForUniverse(uint192 _universeId) external view returns (uint256); + function balanceOfOutcome(uint192 _universeId, Zoltar.Outcome _outcome, address _account) external view returns (uint256); + function balanceOfShares(uint192 _universeId, address _account) external view returns (uint256[3] memory balances); + + // Token ID encoding/decoding + function getTokenId(uint192 _universeId, Zoltar.Outcome _outcome) external pure returns (uint256 _tokenId); + function getTokenIds(uint192 _universeId, Zoltar.Outcome[] memory _outcomes) external pure returns (uint256[] memory _tokenIds); + function unpackTokenId(uint256 _tokenId) external pure returns (uint256 _universe, Zoltar.Outcome _outcome); +} diff --git a/solidity/contracts/peripherals/CompleteSet.sol b/solidity/contracts/peripherals/tokens/CompleteSet.sol similarity index 96% rename from solidity/contracts/peripherals/CompleteSet.sol rename to solidity/contracts/peripherals/tokens/CompleteSet.sol index 64f5ae2..e1fa181 100644 --- a/solidity/contracts/peripherals/CompleteSet.sol +++ b/solidity/contracts/peripherals/tokens/CompleteSet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNICENSE pragma solidity 0.8.30; -import '../ERC20.sol'; +import '../../ERC20.sol'; contract CompleteSet is ERC20 { diff --git a/solidity/contracts/peripherals/tokens/ERC1155.sol b/solidity/contracts/peripherals/tokens/ERC1155.sol new file mode 100644 index 0000000..1011092 --- /dev/null +++ b/solidity/contracts/peripherals/tokens/ERC1155.sol @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: UNICENSE +pragma solidity 0.8.30; + +import { IERC1155 } from '../interfaces/IERC1155.sol'; + +/** +* @title Standard ERC1155 token +* +* @dev Implementation of the basic standard multi-token. +* See https://eips.ethereum.org/EIPS/eip-1155 +* Originally based on code by Enjin: https://github.com/enjin/erc-1155 +*/ +contract ERC1155 is IERC1155 { + + // Mapping from token ID to account balances + mapping (uint256 => mapping(address => uint256)) public _balances; + + // Mapping from token ID to total supply + mapping (uint256 => uint256) public _supplys; + + // Mapping from account to operator approvals + mapping (address => mapping(address => bool)) public _operatorApprovals; + + constructor() {} + + /** + @dev Get the specified address' balance for token with specified ID. + + Attempting to query the zero account for a balance will result in a revert. + + @param account The address of the token holder + @param id ID of the token + @return The account's balance of the token type requested + */ + function balanceOf(address account, uint256 id) public view virtual returns (uint256) { + require(account != address(0), "ERC1155: balance query for the zero address"); + return _balances[id][account]; + } + + function totalSupply(uint256 id) public view returns (uint256) { + return _supplys[id]; + } + + /** + @dev Get the balance of multiple account/token pairs. + + If any of the query accounts is the zero account, this query will revert. + + @param accounts The addresses of the token holders + @param ids IDs of the tokens + @return Balances for each account and token id pair + */ + function balanceOfBatch( + address[] memory accounts, + uint256[] memory ids + ) + public + view + virtual + returns (uint256[] memory) + { + require(accounts.length == ids.length, "ERC1155: accounts and IDs must have same lengths"); + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + require(accounts[i] != address(0), "ERC1155: some address in batch balance query is zero"); + batchBalances[i] = _balances[ids[i]][accounts[i]]; + } + + return batchBalances; + } + + /** + * @dev Sets or unsets the approval of a given operator. + * + * An operator is allowed to transfer all tokens of the sender on their behalf. + * + * Because an account already has operator privileges for itself, this function will revert + * if the account attempts to set the approval status for itself. + * + * @param operator address to set the approval + * @param approved representing the status of the approval to be set + */ + function setApprovalForAll(address operator, bool approved) external { + require(msg.sender != operator, "ERC1155: cannot set approval status for self"); + _operatorApprovals[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + /** + @notice Queries the approval status of an operator for a given account. + @param account The account of the Tokens + @param operator Address of authorized operator + @return True if the operator is approved, false if not + */ + function isApprovedForAll(address account, address operator) public view returns (bool) { + return operator == address(this) || _operatorApprovals[account][operator]; + } + + /** + @dev Transfers `value` amount of an `id` from the `from` address to the `to` address specified. + Caller must be approved to manage the tokens being transferred out of the `from` account. + If `to` is a smart contract, will call `onERC1155Received` on `to` and act appropriately. + @param from Source address + @param to Target address + @param id ID of the token type + @param value Transfer amount + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 value + ) + external + { + _transferFrom(from, to, id, value); + } + + function _transferFrom( + address from, + address to, + uint256 id, + uint256 value + ) + internal + { + require(to != address(0), "ERC1155: target address must be non-zero"); + require( + from == msg.sender || isApprovedForAll(from, msg.sender) == true, + "ERC1155: need operator approval for 3rd party transfers" + ); + + _internalTransferFrom(from, to, id, value); + } + + function _internalTransferFrom( + address from, + address to, + uint256 id, + uint256 value + ) + internal + virtual + { + _balances[id][from] = _balances[id][from] - value; + _balances[id][to] = _balances[id][to] + value; + + emit TransferSingle(msg.sender, from, to, id, value); + } + + /** + @dev Transfers `values` amount(s) of `ids` from the `from` address to the + `to` address specified. Caller must be approved to manage the tokens being + transferred out of the `from` account. If `to` is a smart contract, will + call `onERC1155BatchReceived` on `to` and act appropriately. + @param from Source address + @param to Target address + @param ids IDs of each token type + @param values Transfer amounts per token type + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata values + ) + external + { + _batchTransferFrom(from, to, ids, values); + } + + function _batchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) + internal + { + require(ids.length == values.length, "ERC1155: IDs and values must have same lengths"); + if (ids.length == 0) { + return; + } + require(to != address(0), "ERC1155: target address must be non-zero"); + require( + from == msg.sender || isApprovedForAll(from, msg.sender) == true, + "ERC1155: need operator approval for 3rd party transfers" + ); + + _internalBatchTransferFrom(from, to, ids, values); + } + + function _internalBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) + internal + virtual + { + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 value = values[i]; + + _balances[id][from] = _balances[id][from] - value; + _balances[id][to] = _balances[id][to] + value; + } + + emit TransferBatch(msg.sender, from, to, ids, values); + } + + /** + * @dev Internal function to mint an amount of a token with the given ID + * @param to The address that will own the minted token + * @param id ID of the token to be minted + * @param value Amount of the token to be minted + */ + function _mint(address to, uint256 id, uint256 value) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + + _balances[id][to] = _balances[id][to] + value; + _supplys[id] = _supplys[id] + value; + + emit TransferSingle(msg.sender, address(0), to, id, value); + } + + /** + * @dev Internal function to batch mint amounts of tokens with the given IDs + * @param to The address that will own the minted token + * @param ids IDs of the tokens to be minted + * @param values Amounts of the tokens to be minted + */ + function _mintBatch(address to, uint256[] memory ids, uint256[] memory values) internal virtual { + require(to != address(0), "ERC1155: batch mint to the zero address"); + require(ids.length == values.length, "ERC1155: minted IDs and values must have same lengths"); + + for (uint i = 0; i < ids.length; i++) { + _balances[ids[i]][to] = values[i] + _balances[ids[i]][to]; + _supplys[ids[i]] = _supplys[ids[i]] + values[i]; + } + + emit TransferBatch(msg.sender, address(0), to, ids, values); + } + + /** + * @dev Internal function to burn an amount of a token with the given ID + * @param account Account which owns the token to be burnt + * @param id ID of the token to be burnt + * @param value Amount of the token to be burnt + */ + function _burn(address account, uint256 id, uint256 value) internal virtual { + require(account != address(0), "ERC1155: attempting to burn tokens on zero account"); + + _balances[id][account] = _balances[id][account] - value; + _supplys[id] = _supplys[id] - value; + emit TransferSingle(msg.sender, account, address(0), id, value); + } + + /** + * @dev Internal function to batch burn an amounts of tokens with the given IDs + * @param account Account which owns the token to be burnt + * @param ids IDs of the tokens to be burnt + * @param values Amounts of the tokens to be burnt + */ + function _burnBatch(address account, uint256[] memory ids, uint256[] memory values) internal virtual { + require(account != address(0), "ERC1155: attempting to burn batch of tokens on zero account"); + require(ids.length == values.length, "ERC1155: burnt IDs and values must have same lengths"); + + for (uint i = 0; i < ids.length; i++) { + _balances[ids[i]][account] = _balances[ids[i]][account] - values[i]; + _supplys[ids[i]] = _supplys[ids[i]] - values[i]; + } + + emit TransferBatch(msg.sender, account, address(0), ids, values); + } +} diff --git a/solidity/contracts/peripherals/tokens/ForkedERC1155.sol b/solidity/contracts/peripherals/tokens/ForkedERC1155.sol new file mode 100644 index 0000000..02d2988 --- /dev/null +++ b/solidity/contracts/peripherals/tokens/ForkedERC1155.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: UNICENSE +pragma solidity 0.8.30; + +import './ERC1155.sol'; +import '../../Constants.sol'; + +abstract contract ForkedERC1155 is ERC1155 { + + constructor() {} + + function universeHasForked(uint192 universeId) internal virtual view returns (bool); + + function getUniverseId(uint256 id) internal virtual pure returns (uint192); + + function getChildId(uint256 originalId, uint192 newUniverse) internal virtual pure returns (uint256); + + // Note: In the event there is a chain of forks 64+ deep where no balance has carried further down this will make the original value innaccesible + // This would take several years and likely a malicious actor very openly burning a large amount of money to do this and a user that has ignored every previous fork so the risk is considered low enough for this to be acceptable + function migrate(uint256 fromId) external { + uint192 universeId = getUniverseId(fromId); + require(universeHasForked(universeId), "Universe has not forked"); + + uint256 fromIdBalance = _balances[fromId][msg.sender]; + _balances[fromId][msg.sender] = 0; + _supplys[fromId] -= fromIdBalance; + + // For each outcome universe + for (uint8 i = 1; i < Constants.NUM_OUTCOMES + 1; i++) { + uint192 childUniverseId = (universeId << 2) + i; + uint256 toId = getChildId(fromId, childUniverseId); + _balances[toId][msg.sender] += fromIdBalance; + _supplys[toId] += fromIdBalance; + } + } +} diff --git a/solidity/contracts/peripherals/tokens/ShareToken.sol b/solidity/contracts/peripherals/tokens/ShareToken.sol new file mode 100644 index 0000000..8f928d2 --- /dev/null +++ b/solidity/contracts/peripherals/tokens/ShareToken.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: UNICENSE +pragma solidity 0.8.30; + +import '../../Constants.sol'; +import './ForkedERC1155.sol'; +import './TokenId.sol'; +import '../../Zoltar.sol'; +import '../interfaces/ISecurityPool.sol'; + +/** +* @title Share Token +* @notice ERC1155 contract to hold all share token balances +*/ +contract ShareToken is ForkedERC1155, IShareToken { + + string constant public name = "Shares"; + string constant public symbol = "SHARE"; + Zoltar public immutable zoltar; + uint56 public immutable questionId; + mapping(address => bool) authorized; + + function universeHasForked(uint192 universeId) internal override view returns (bool) { + (,, uint256 forkTime) = zoltar.universes(universeId); + return forkTime > 0; + } + + constructor(address owner, Zoltar _zoltar, uint56 _questionId) { + zoltar = _zoltar; + questionId = _questionId; + authorized[owner] = true; + } + + function authorize(ISecurityPool _securityPoolCandidate) external { + require(authorized[msg.sender], 'caller is not owner'); + authorized[address(_securityPoolCandidate)] = true; + } + + function getUniverseId(uint256 id) internal override pure returns (uint192 universeId) { + assembly { + universeId := shr(64, and(id, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000)) + } + } + + function getChildId(uint256 originalId, uint192 newUniverse) internal override pure returns (uint256 newId) { + assembly { + newId := or(shr(192, shl(192, originalId)), shl(64, newUniverse)) + } + } + + function mintCompleteSets(uint192 _universeId, address _account, uint256 _cashAmount) external payable { + require(authorized[msg.sender] == true, 'not authorized'); + uint256[] memory _tokenIds = new uint256[](Constants.NUM_OUTCOMES); + uint256[] memory _values = new uint256[](Constants.NUM_OUTCOMES); + + for (uint8 i = 0; i < Constants.NUM_OUTCOMES; i++) { + _tokenIds[i] = TokenId.getTokenId(_universeId, Zoltar.Outcome(i)); + _values[i] = _cashAmount; + } + + _mintBatch(_account, _tokenIds, _values); + } + + function burnCompleteSets(uint192 _universeId, address _owner, uint256 _amount) external { + require(authorized[msg.sender] == true, 'not authorized'); + uint256[] memory _tokenIds = new uint256[](Constants.NUM_OUTCOMES); + uint256[] memory _values = new uint256[](Constants.NUM_OUTCOMES); + + for (uint8 i = 0; i < Constants.NUM_OUTCOMES; i++) { + _tokenIds[i] = TokenId.getTokenId(_universeId, Zoltar.Outcome(i)); + _values[i] = _amount; + } + + _burnBatch(_owner, _tokenIds, _values); + } + + function burnTokenId(uint256 _tokenId, address _owner) external returns (uint256 balance) { + require(authorized[msg.sender] == true, 'not authorized'); + balance = balanceOf(_owner, _tokenId); + _burn(_owner, _tokenId, balance); + } + + function getUniverse(uint256 _tokenId) external pure returns(uint256) { + (uint192 _universe, ) = TokenId.unpackTokenId(_tokenId); + return _universe; + } + + function getOutcome(uint256 _tokenId) external pure returns(Zoltar.Outcome) { + (, Zoltar.Outcome _outcome) = TokenId.unpackTokenId(_tokenId); + return _outcome; + } + + function totalSupplyForOutcome(uint192 _universeId, Zoltar.Outcome _outcome) public view returns (uint256) { + uint256 _tokenId = getTokenId(_universeId, _outcome); + return totalSupply(_tokenId); + } + + function totalSupplyForUniverse(uint192 _universeId) public view returns (uint256) { + // todo, here we might want the getWinningOutcome to just return none if not finalized? + if (zoltar.isFinalized(_universeId, questionId)) { + return totalSupply(getTokenId(_universeId, zoltar.getWinningOutcome(_universeId, questionId))); + } + return totalSupply(getTokenId(_universeId, Zoltar.Outcome.Yes)); + } + + function balanceOfOutcome(uint192 _universeId, Zoltar.Outcome _outcome, address _account) public view returns (uint256) { + uint256 _tokenId = getTokenId(_universeId, _outcome); + return balanceOf(_account, _tokenId); + } + + function balanceOfShares(uint192 _universeId, address _account) public view returns (uint256[3] memory balances) { + balances[0] = balanceOf(_account, getTokenId(_universeId, Zoltar.Outcome.Invalid)); + balances[1] = balanceOf(_account, getTokenId(_universeId, Zoltar.Outcome.Yes)); + balances[2] = balanceOf(_account, getTokenId(_universeId, Zoltar.Outcome.No)); + } + + function getTokenId(uint192 _universeId, Zoltar.Outcome _outcome) public pure returns (uint256 _tokenId) { + return TokenId.getTokenId(_universeId, _outcome); + } + + function getTokenIds(uint192 _universeId, Zoltar.Outcome[] memory _outcomes) public pure returns (uint256[] memory _tokenIds) { + return TokenId.getTokenIds(_universeId, _outcomes); + } + + function unpackTokenId(uint256 _tokenId) public pure returns (uint256 _universe, Zoltar.Outcome _outcome) { + return TokenId.unpackTokenId(_tokenId); + } +} diff --git a/solidity/contracts/peripherals/tokens/TokenId.sol b/solidity/contracts/peripherals/tokens/TokenId.sol new file mode 100644 index 0000000..1bc6a2a --- /dev/null +++ b/solidity/contracts/peripherals/tokens/TokenId.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNICENSE +pragma solidity 0.8.30; + +import '../../Zoltar.sol'; + +library TokenId { + + function getTokenId(uint192 _universeId, Zoltar.Outcome _outcome) internal pure returns (uint256 _tokenId) { + bytes memory _tokenIdBytes = abi.encodePacked(_universeId, _outcome); + assembly { + _tokenId := mload(add(_tokenIdBytes, add(0x20, 0))) + } + } + + function getTokenIds(uint192 _universeId, Zoltar.Outcome[] memory _outcomes) internal pure returns (uint256[] memory _tokenIds) { + _tokenIds = new uint256[](_outcomes.length); + for (uint256 _i = 0; _i < _outcomes.length; _i++) { + _tokenIds[_i] = getTokenId(_universeId, _outcomes[_i]); + } + } + + function unpackTokenId(uint256 _tokenId) internal pure returns (uint192 _universe, Zoltar.Outcome _outcome) { + assembly { + _universe := shr(64, and(_tokenId, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000)) + _outcome := and(_tokenId, 0x00000000000000000000000000000000000000000000000000000000000000FF) + } + } +} diff --git a/solidity/ts/tests/testPeripherals.ts b/solidity/ts/tests/testPeripherals.ts index 5c4b38e..3fdc848 100644 --- a/solidity/ts/tests/testPeripherals.ts +++ b/solidity/ts/tests/testPeripherals.ts @@ -4,21 +4,27 @@ import { createWriteClient, WriteClient } from '../testsuite/simulator/utils/vie import { DAY, GENESIS_REPUTATION_TOKEN, TEST_ADDRESSES } from '../testsuite/simulator/utils/constants.js' import { approximatelyEqual, contractExists, getChildUniverseId, getERC20Balance, getETHBalance, getReportBond, getRepTokenAddress, setupTestAccounts } from '../testsuite/simulator/utils/utilities.js' import { addressString } from '../testsuite/simulator/utils/bigint.js' -import { createCompleteSet, forkSecurityPool, getCompleteSetAddress, getCompleteSetCollateralAmount, getLastPrice, getPriceOracleManagerAndOperatorQueuer, getSecurityBondAllowance, OperationType, redeemCompleteSet, migrateVault, getSecurityPoolAddress, getMigratedRep, getSystemState, startTruthAuction, getCurrentRetentionRate, getTruthAuction, getEthAmountToBuy, participateAuction, finalizeTruthAuction, claimAuctionProceeds, getSecurityVault, getPoolOwnershipDenominator, poolOwnershipToRep } from '../testsuite/simulator/utils/peripherals.js' import assert from 'node:assert' import { SystemState } from '../testsuite/simulator/types/peripheralTypes.js' import { getDeployments } from '../testsuite/simulator/utils/deployments.js' import { createTransactionExplainer } from '../testsuite/simulator/utils/transactionExplainer.js' import { approveAndDepositRep, deployPeripherals, deployZoltarAndCreateMarket, genesisUniverse, MAX_RETENTION_RATE, PRICE_PRECISION, questionId, requestPrice, securityMultiplier, triggerFork } from '../testsuite/simulator/utils/peripheralsTestUtils.js' +import { getSecurityPoolAddresses } from '../testsuite/simulator/utils/deployPeripherals.js' +import { balanceOfShares, claimAuctionProceeds, createCompleteSet, finalizeTruthAuction, forkSecurityPool, getCompleteSetCollateralAmount, getCurrentRetentionRate, getEthAmountToBuy, getLastPrice, getMigratedRep, getPoolOwnershipDenominator, getSecurityBondAllowance, getSecurityVault, getSystemState, migrateVault, OperationType, participateAuction, poolOwnershipToRep, redeemCompleteSet, startTruthAuction } from '../testsuite/simulator/utils/peripherals.js' +import { QuestionOutcome } from '../testsuite/simulator/types/types.js' describe('Peripherals Contract Test Suite', () => { let mockWindow: MockWindowEthereum - let securityPoolAddress: `0x${ string }` let client: WriteClient let startBalance: bigint let reportBond: bigint const repDeposit = 100n * 10n ** 18n - let priceOracleManagerAndOperatorQueuer: `0x${ string }` + let securityPoolAddresses: { + securityPool: `0x${ string }`, + priceOracleManagerAndOperatorQueuer: `0x${ string }`, + shareToken: `0x${ string }`, + truthAuction: `0x${ string }` + } beforeEach(async () => { mockWindow = getMockedEthSimulateWindowEthereum() @@ -31,154 +37,156 @@ describe('Peripherals Contract Test Suite', () => { await deployZoltarAndCreateMarket(client, currentTimestamp + DAY / 2n) await deployPeripherals(client) await approveAndDepositRep(client, repDeposit) - securityPoolAddress = getSecurityPoolAddress(addressString(0x0n), genesisUniverse, questionId, securityMultiplier) + securityPoolAddresses = getSecurityPoolAddresses(addressString(0x0n), genesisUniverse, questionId, securityMultiplier) reportBond = await getReportBond(client) - priceOracleManagerAndOperatorQueuer = await getPriceOracleManagerAndOperatorQueuer(client, securityPoolAddress) }) test('can deposit rep and withdraw it', async () => { - await requestPrice(client, mockWindow, priceOracleManagerAndOperatorQueuer, OperationType.WithdrawRep, client.account.address, repDeposit) - assert.strictEqual(await getLastPrice(client, priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') - assert.strictEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddress), 0n, 'Did not empty security pool of rep') + await requestPrice(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.WithdrawRep, client.account.address, repDeposit) + assert.strictEqual(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + assert.strictEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddresses.securityPool), 0n, 'Did not empty security pool of rep') assert.strictEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address), startBalance - reportBond, 'Did not get rep back') }) test('can set security bonds allowance, mint complete sets and fork happily' , async () => { const securityPoolAllowance = repDeposit / 4n - assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddress), MAX_RETENTION_RATE, 'retention rate was not at max'); - await requestPrice(client, mockWindow, priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) - assert.strictEqual(await getLastPrice(client, priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') - assert.strictEqual(await getSecurityBondAllowance(client, securityPoolAddress), securityPoolAllowance, 'Security pool allowance was not set correctly') + assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max'); + await requestPrice(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) + assert.strictEqual(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + assert.strictEqual(await getSecurityBondAllowance(client, securityPoolAddresses.securityPool), securityPoolAllowance, 'Security pool allowance was not set correctly') const openInterestAmount = 1n * 10n ** 18n const maxGasFees = openInterestAmount /4n const ethBalance = await getETHBalance(client, client.account.address) - await createCompleteSet(client, securityPoolAddress, openInterestAmount) - assert.ok(await getCurrentRetentionRate(client, securityPoolAddress) < MAX_RETENTION_RATE, 'retention rate did not decrease after minting complete sets'); - const completeSetAddress = getCompleteSetAddress(securityPoolAddress) - const completeSetBalance = await getERC20Balance(client, completeSetAddress, client.account.address) - assert.strictEqual(openInterestAmount, completeSetBalance, 'Did not create enough complete sets') + await createCompleteSet(client, securityPoolAddresses.securityPool, openInterestAmount) + assert.ok(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool) < MAX_RETENTION_RATE, 'retention rate did not decrease after minting complete sets'); + const completeSetBalances = await balanceOfShares(client, securityPoolAddresses.shareToken, genesisUniverse, client.account.address) + assert.strictEqual(completeSetBalances[0], completeSetBalances[1], 'yes no and invalid share counts need to match') + assert.strictEqual(completeSetBalances[1], completeSetBalances[2], 'yes no and invalid share counts need to match') + assert.strictEqual(openInterestAmount, completeSetBalances[0], 'Did not create enough complete sets') assert.ok(ethBalance - await getETHBalance(client, client.account.address) > maxGasFees, 'Did not lose eth to create complete sets') - assert.strictEqual(await getCompleteSetCollateralAmount(client, securityPoolAddress), openInterestAmount, 'contract did not record the amount correctly') - await redeemCompleteSet(client, securityPoolAddress, openInterestAmount) + assert.strictEqual(await getCompleteSetCollateralAmount(client, securityPoolAddresses.securityPool), openInterestAmount, 'contract did not record the amount correctly') + await redeemCompleteSet(client, securityPoolAddresses.securityPool, openInterestAmount) assert.ok(ethBalance - await getETHBalance(client, client.account.address) < maxGasFees, 'Did not get ETH back from complete sets') - assert.strictEqual(await getERC20Balance(client, completeSetAddress, client.account.address), 0n, 'Did not lose complete sets') - assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddress), MAX_RETENTION_RATE, 'retention rate was not at max after zero complete sets'); + const newBalanes = await balanceOfShares(client, securityPoolAddresses.shareToken, genesisUniverse, client.account.address) + assert.strictEqual(newBalanes[0], 0n, 'Did not lose complete sets') + assert.strictEqual(newBalanes[1], 0n, 'Did not lose complete sets') + assert.strictEqual(newBalanes[2], 0n, 'Did not lose complete sets') + assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max after zero complete sets'); // forking - await createCompleteSet(client, securityPoolAddress, openInterestAmount) - const repBalance = await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddress) + await createCompleteSet(client, securityPoolAddresses.securityPool, openInterestAmount) + const repBalance = await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddresses.securityPool) await triggerFork(client, mockWindow, questionId) - await forkSecurityPool(client, securityPoolAddress) - assert.strictEqual(await getSystemState(client, securityPoolAddress), SystemState.PoolForked, 'Parent is forked') - assert.strictEqual(0n, await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddress), 'Parents original rep is gone') - await migrateVault(client, securityPoolAddress, QuestionOutcome.Yes) + await forkSecurityPool(client, securityPoolAddresses.securityPool) + assert.strictEqual(await getSystemState(client, securityPoolAddresses.securityPool), SystemState.PoolForked, 'Parent is forked') + assert.strictEqual(0n, await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddresses.securityPool), 'Parents original rep is gone') + await migrateVault(client, securityPoolAddresses.securityPool, QuestionOutcome.Yes) const yesUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.Yes) - const yesSecurityPool = getSecurityPoolAddress(securityPoolAddress, yesUniverse, questionId, securityMultiplier) - assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddress), await getCurrentRetentionRate(client, yesSecurityPool), 'Parent and childs retention rate should be equal') + const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, questionId, securityMultiplier) + assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), await getCurrentRetentionRate(client, yesSecurityPool.securityPool), 'Parent and childs retention rate should be equal') - assert.strictEqual(await getSystemState(client, yesSecurityPool), SystemState.ForkMigration, 'Fork Migration need to start') - const migratedRep = await getMigratedRep(client, yesSecurityPool) + assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkMigration, 'Fork Migration need to start') + const migratedRep = await getMigratedRep(client, yesSecurityPool.securityPool) assert.strictEqual(migratedRep, repBalance, 'correct amount rep migrated') - assert.ok(await contractExists(client, yesSecurityPool), 'Did not create YES security pool') + assert.ok(await contractExists(client, yesSecurityPool.securityPool), 'Did not create YES security pool') await mockWindow.advanceTime(8n * 7n * DAY + DAY) - await startTruthAuction(client, yesSecurityPool) - assert.strictEqual(await getSystemState(client, yesSecurityPool), SystemState.Operational, 'System should be operational again') - assert.strictEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool), openInterestAmount, 'child contract did not record the amount correctly') + await startTruthAuction(client, yesSecurityPool.securityPool) + assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'System should be operational again') + assert.strictEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), openInterestAmount, 'child contract did not record the amount correctly') }) test('two security pools with disagreement', async () => { const openInterestAmount = 1n * 10n ** 18n const securityPoolAllowance = repDeposit / 4n - await requestPrice(client, mockWindow, priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) + await requestPrice(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) const attackerClient = createWriteClient(mockWindow, TEST_ADDRESSES[1], 0) await approveAndDepositRep(attackerClient, repDeposit) - await requestPrice(attackerClient, mockWindow, priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) + await requestPrice(attackerClient, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) - const repBalanceInGenesisPool = await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddress) + const repBalanceInGenesisPool = await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddresses.securityPool) assert.strictEqual(repBalanceInGenesisPool, 2n * repDeposit, 'After two deposits, the system should have 2 x repDeposit worth of REP') - assert.strictEqual(await getSecurityBondAllowance(client, securityPoolAddress), 2n * securityPoolAllowance, 'Security bond allowance should be 2x') - assert.strictEqual(await getPoolOwnershipDenominator(client, securityPoolAddress), repBalanceInGenesisPool * PRICE_PRECISION, 'Pool ownership denominator should equal `pool balance * PRICE_PRECISION` prior fork') + assert.strictEqual(await getSecurityBondAllowance(client, securityPoolAddresses.securityPool), 2n * securityPoolAllowance, 'Security bond allowance should be 2x') + assert.strictEqual(await getPoolOwnershipDenominator(client, securityPoolAddresses.securityPool), repBalanceInGenesisPool * PRICE_PRECISION, 'Pool ownership denominator should equal `pool balance * PRICE_PRECISION` prior fork') const openInterestHolder = createWriteClient(mockWindow, TEST_ADDRESSES[2], 0) - await createCompleteSet(openInterestHolder, securityPoolAddress, openInterestAmount) - const completeSetAddress = getCompleteSetAddress(securityPoolAddress) - const completeSetBalance = await getERC20Balance(client, completeSetAddress, addressString(TEST_ADDRESSES[2])) - assert.strictEqual(openInterestAmount, completeSetBalance, 'Did not create enough complete sets') + await createCompleteSet(openInterestHolder, securityPoolAddresses.securityPool, openInterestAmount) + const completeSetBalance = await balanceOfShares(client, securityPoolAddresses.shareToken, genesisUniverse, addressString(TEST_ADDRESSES[2])) + assert.strictEqual(completeSetBalance[0], openInterestAmount, 'Did not create enough complete sets') + assert.strictEqual(completeSetBalance[1], openInterestAmount, 'Did not create enough complete sets') + assert.strictEqual(completeSetBalance[2], openInterestAmount, 'Did not create enough complete sets') await triggerFork(client, mockWindow, questionId) - await forkSecurityPool(client, securityPoolAddress) + await forkSecurityPool(client, securityPoolAddresses.securityPool) // we migrate to yes const yesUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.Yes) - const yesSecurityPool = getSecurityPoolAddress(securityPoolAddress, yesUniverse, questionId, securityMultiplier) - await migrateVault(client, securityPoolAddress, QuestionOutcome.Yes) - const migratedRepInYes = await getMigratedRep(client, yesSecurityPool) + const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, questionId, securityMultiplier) + await migrateVault(client, securityPoolAddresses.securityPool, QuestionOutcome.Yes) + const migratedRepInYes = await getMigratedRep(client, yesSecurityPool.securityPool) assert.strictEqual(repBalanceInGenesisPool / 2n, migratedRepInYes, 'half migrated to yes') - assert.strictEqual(await getERC20Balance(client, getRepTokenAddress(yesUniverse), yesSecurityPool), repBalanceInGenesisPool, 'yes has all the rep') + assert.strictEqual(await getERC20Balance(client, getRepTokenAddress(yesUniverse), yesSecurityPool.securityPool), repBalanceInGenesisPool, 'yes has all the rep') // attacker migrated to No const noUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.No) - const noSecurityPool = getSecurityPoolAddress(securityPoolAddress, noUniverse, questionId, securityMultiplier) - await migrateVault(attackerClient, securityPoolAddress, QuestionOutcome.No) - const migratedRepInNo = await getMigratedRep(client, noSecurityPool) + const noSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, noUniverse, questionId, securityMultiplier) + await migrateVault(attackerClient, securityPoolAddresses.securityPool, QuestionOutcome.No) + const migratedRepInNo = await getMigratedRep(client, noSecurityPool.securityPool) assert.strictEqual(repBalanceInGenesisPool / 2n, migratedRepInNo, 'half migrated to no') - assert.strictEqual(await getERC20Balance(client, getRepTokenAddress(noUniverse), noSecurityPool), repBalanceInGenesisPool, 'no has all the rep') + assert.strictEqual(await getERC20Balance(client, getRepTokenAddress(noUniverse), noSecurityPool.securityPool), repBalanceInGenesisPool, 'no has all the rep') // auction await mockWindow.advanceTime(8n * 7n * DAY + DAY) - await startTruthAuction(client, yesSecurityPool) - assert.strictEqual(await getSystemState(client, yesSecurityPool), SystemState.ForkTruthAuction, 'Auction started') - await startTruthAuction(client, noSecurityPool) - assert.strictEqual(await getSystemState(client, noSecurityPool), SystemState.ForkTruthAuction, 'Auction started') - const yesAuction = getTruthAuction(yesSecurityPool) - const noAuction = getTruthAuction(noSecurityPool) - - const ethToBuyInYes = await getEthAmountToBuy(client, yesAuction) - const ethToBuyInNo = await getEthAmountToBuy(client, noAuction) + await startTruthAuction(client, yesSecurityPool.securityPool) + assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') + await startTruthAuction(client, noSecurityPool.securityPool) + assert.strictEqual(await getSystemState(client, noSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') + + const ethToBuyInYes = await getEthAmountToBuy(client, yesSecurityPool.truthAuction) + const ethToBuyInNo = await getEthAmountToBuy(client, noSecurityPool.truthAuction) assert.strictEqual(ethToBuyInYes, openInterestAmount / 2n, 'Need to buy half of open interest') assert.strictEqual(ethToBuyInNo, openInterestAmount / 2n, 'Need to buy half of open interest') // participate yes auction by buying quarter of all REP (this is a open interest and rep holder happy case where REP holders win 50%) const yesAuctionParticipant = createWriteClient(mockWindow, TEST_ADDRESSES[3], 0) - await participateAuction(yesAuctionParticipant, yesAuction, repBalanceInGenesisPool / 4n, openInterestAmount / 2n) + await participateAuction(yesAuctionParticipant, yesSecurityPool.truthAuction, repBalanceInGenesisPool / 4n, openInterestAmount / 2n) // participate yes auction by buying 3/4 of all REP (this is a open interest happy case where REP holders lose happy case where REP holders lose 50%) const noAuctionParticipant = createWriteClient(mockWindow, TEST_ADDRESSES[4], 0) - await participateAuction(noAuctionParticipant, noAuction, repBalanceInGenesisPool * 3n / 4n, openInterestAmount / 2n) + await participateAuction(noAuctionParticipant, noSecurityPool.truthAuction, repBalanceInGenesisPool * 3n / 4n, openInterestAmount / 2n) await mockWindow.advanceTime(7n * DAY + DAY) - await finalizeTruthAuction(client, yesSecurityPool) - await finalizeTruthAuction(client, noSecurityPool) + await finalizeTruthAuction(client, yesSecurityPool.securityPool) + await finalizeTruthAuction(client, noSecurityPool.securityPool) - assert.strictEqual(await getSystemState(client, yesSecurityPool), SystemState.Operational, 'Yes System should be operational again') - assert.strictEqual(await getSystemState(client, noSecurityPool), SystemState.Operational, 'No System should be operational again') - assert.strictEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool), openInterestAmount, 'yes child contract did not record the amount correctly') - assert.strictEqual(await getCompleteSetCollateralAmount(client, noSecurityPool), openInterestAmount, 'no child contract did not record the amount correctly') + assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'Yes System should be operational again') + assert.strictEqual(await getSystemState(client, noSecurityPool.securityPool), SystemState.Operational, 'No System should be operational again') + assert.strictEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), openInterestAmount, 'yes child contract did not record the amount correctly') + assert.strictEqual(await getCompleteSetCollateralAmount(client, noSecurityPool.securityPool), openInterestAmount, 'no child contract did not record the amount correctly') - await claimAuctionProceeds(client, yesSecurityPool, yesAuctionParticipant.account.address) - await claimAuctionProceeds(client, noSecurityPool, noAuctionParticipant.account.address) + await claimAuctionProceeds(client, yesSecurityPool.securityPool, yesAuctionParticipant.account.address) + await claimAuctionProceeds(client, noSecurityPool.securityPool, noAuctionParticipant.account.address) // yes status - const yesAuctionParticipantVault = await getSecurityVault(client, yesSecurityPool, yesAuctionParticipant.account.address) + const yesAuctionParticipantVault = await getSecurityVault(client, yesSecurityPool.securityPool, yesAuctionParticipant.account.address) console.log(yesAuctionParticipantVault) - const yesAuctionParticipantRep = await poolOwnershipToRep(client, yesSecurityPool, yesAuctionParticipantVault.repDepositShare) + const yesAuctionParticipantRep = await poolOwnershipToRep(client, yesSecurityPool.securityPool, yesAuctionParticipantVault.repDepositShare) approximatelyEqual(yesAuctionParticipantRep, repBalanceInGenesisPool / 4n, 1000n, 'yes auction participant did not get ownership of rep they bought') - const originalYesVault = await getSecurityVault(client, yesSecurityPool, client.account.address) - const originalYesVaultRep = await poolOwnershipToRep(client, yesSecurityPool, originalYesVault.repDepositShare) + const originalYesVault = await getSecurityVault(client, yesSecurityPool.securityPool, client.account.address) + const originalYesVaultRep = await poolOwnershipToRep(client, yesSecurityPool.securityPool, originalYesVault.repDepositShare) approximatelyEqual(originalYesVaultRep, repBalanceInGenesisPool * 3n / 4n, 1000n, 'original yes vault holder should hold rest 3/4 of rep') - assert.strictEqual((await getSecurityVault(client, yesSecurityPool, attackerClient.account.address)).repDepositShare, 0n, 'attacker should have zero as they did not migrate to yes') + assert.strictEqual((await getSecurityVault(client, yesSecurityPool.securityPool, attackerClient.account.address)).repDepositShare, 0n, 'attacker should have zero as they did not migrate to yes') // no status - const noAuctionParticipantVault = await getSecurityVault(client, noSecurityPool, noAuctionParticipant.account.address) - const noAuctionParticipantRep = await poolOwnershipToRep(client, noSecurityPool, noAuctionParticipantVault.repDepositShare) + const noAuctionParticipantVault = await getSecurityVault(client, noSecurityPool.securityPool, noAuctionParticipant.account.address) + const noAuctionParticipantRep = await poolOwnershipToRep(client, noSecurityPool.securityPool, noAuctionParticipantVault.repDepositShare) approximatelyEqual(noAuctionParticipantRep, repBalanceInGenesisPool * 3n / 4n, 1000n, 'no auction participant did not get ownership of rep they bought') - const originalNoVault = await getSecurityVault(client, noSecurityPool, attackerClient.account.address) - const originalNoVaultRep = await poolOwnershipToRep(client, noSecurityPool, originalNoVault.repDepositShare) + const originalNoVault = await getSecurityVault(client, noSecurityPool.securityPool, attackerClient.account.address) + const originalNoVaultRep = await poolOwnershipToRep(client, noSecurityPool.securityPool, originalNoVault.repDepositShare) approximatelyEqual(originalNoVaultRep, repBalanceInGenesisPool * 1n / 4n, 1000n, 'original no vault holder should hold rest 1/4 of rep') - assert.strictEqual((await getSecurityVault(client, noSecurityPool, client.account.address)).repDepositShare, 0n, 'client should have zero as they did not migrate to no') + assert.strictEqual((await getSecurityVault(client, noSecurityPool.securityPool, client.account.address)).repDepositShare, 0n, 'client should have zero as they did not migrate to no') }) //test('can liquidate', async () => { diff --git a/solidity/ts/testsuite/simulator/utils/deployPeripherals.ts b/solidity/ts/testsuite/simulator/utils/deployPeripherals.ts new file mode 100644 index 0000000..598948b --- /dev/null +++ b/solidity/ts/testsuite/simulator/utils/deployPeripherals.ts @@ -0,0 +1,172 @@ +import 'viem/window' +import { encodeDeployData, getCreate2Address, keccak256, numberToBytes, toHex, encodePacked, zeroAddress } from 'viem' +import { WriteClient } from './viem.js' +import { PROXY_DEPLOYER_ADDRESS } from './constants.js' +import { addressString } from './bigint.js' +import { contractExists, getRepTokenAddress, getZoltarAddress } from './utilities.js' +import { mainnet } from 'viem/chains' +import { peripherals_Auction_Auction, peripherals_AuctionFactory_AuctionFactory, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory, peripherals_SecurityPool_SecurityPool, peripherals_SecurityPoolFactory_SecurityPoolFactory, peripherals_SecurityPoolUtils_SecurityPoolUtils, peripherals_ShareTokenFactory_ShareTokenFactory, peripherals_tokens_ShareToken_ShareToken, Zoltar_Zoltar } from '../../../types/contractArtifact.js' + +export function getSecurityPoolUtilsAddress() { + return getCreate2Address({ bytecode: `0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.bytecode.object }`, from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }) +} + +export const applyLibraries = (bytecode: string): `0x${ string }` => { + const securityPoolUtils = keccak256(toHex('contracts/peripherals/SecurityPoolUtils.sol:SecurityPoolUtils')).slice(2, 36) + return `0x${ bytecode.replaceAll(`__$${ securityPoolUtils }$__`, getSecurityPoolUtilsAddress().slice(2).toLocaleLowerCase()) }` +} + +export const getSecurityPoolFactoryByteCode = (openOracle: `0x${ string }`, zoltar: `0x${ string }`, shareTokenFactory: `0x${ string }`, auctionFactory: `0x${ string }`, priceOracleManagerAndOperatorQueuerFactory: `0x${ string }`) => { + return encodeDeployData({ + abi: peripherals_SecurityPoolFactory_SecurityPoolFactory.abi, + bytecode: applyLibraries(peripherals_SecurityPoolFactory_SecurityPoolFactory.evm.bytecode.object), + args: [ openOracle, zoltar, shareTokenFactory, auctionFactory, priceOracleManagerAndOperatorQueuerFactory ] + }) +} + +export const getSecurityPoolFactoryAddress = (openOracle: `0x${ string }`, zoltar: `0x${ string }`, shareTokenFactory: `0x${ string }`, auctionFactory: `0x${ string }`, priceOracleManagerAndOperatorQueuerFactory: `0x${ string }`) => { + return getCreate2Address({ + from: addressString(PROXY_DEPLOYER_ADDRESS), + salt: numberToBytes(0), + bytecode: getSecurityPoolFactoryByteCode(openOracle, zoltar, shareTokenFactory, auctionFactory, priceOracleManagerAndOperatorQueuerFactory) + }) +} + +export const getShareTokenFactoryByteCode = (zoltar: `0x${ string }`) => { + return encodeDeployData({ + abi: peripherals_ShareTokenFactory_ShareTokenFactory.abi, + bytecode: `0x${ peripherals_ShareTokenFactory_ShareTokenFactory.evm.bytecode.object }`, + args: [ zoltar ] + }) +} + +export function getInfraContractAddresses() { + const contracts = { + securityPoolUtils: getCreate2Address({ bytecode: `0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.bytecode.object }`, from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }), + openOracle: getCreate2Address({ bytecode: `0x${ peripherals_openOracle_OpenOracle_OpenOracle.evm.bytecode.object }`, from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }), + zoltar: getZoltarAddress(), + shareTokenFactory: getCreate2Address({ bytecode: getShareTokenFactoryByteCode(getZoltarAddress()), from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }), + auctionFactory: getCreate2Address({ bytecode: `0x${ peripherals_AuctionFactory_AuctionFactory.evm.bytecode.object }`, from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }), + priceOracleManagerAndOperatorQueuerFactory: getCreate2Address({ bytecode: `0x${ peripherals_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory.evm.bytecode.object }`, from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }), + } + const securityPoolFactory = getSecurityPoolFactoryAddress(contracts.openOracle, contracts.zoltar, contracts.shareTokenFactory, contracts.auctionFactory, contracts.priceOracleManagerAndOperatorQueuerFactory) + return { ...contracts, securityPoolFactory } +} + +export async function getInfraDeployedInformation(client: WriteClient): Promise<{ [key in keyof ReturnType]: boolean }> { + const contractAddresses = getInfraContractAddresses() + type ContractKeys = keyof typeof contractAddresses + + const contractKeys = Object.keys(contractAddresses) as ContractKeys[] + + const contractExistencePairs = await Promise.all( + contractKeys.map(async key => { + const doesExist = await contractExists(client, contractAddresses[key]) + return [key, doesExist] as const + }) + ) + + const contractExistenceObject: { [key in ContractKeys]: boolean } = {} as { [key in ContractKeys]: boolean } + contractExistencePairs.forEach(([key, doesExist]) => { + contractExistenceObject[key] = doesExist + }) + + return contractExistenceObject +} +export async function ensureInfraDeployed(client: WriteClient): Promise { + const contractAddresses = getInfraContractAddresses() + const existence = await getInfraDeployedInformation(client) + if (!existence.securityPoolUtils) { + await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: `0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.bytecode.object }` } as const) + if (!(await contractExists(client, contractAddresses.securityPoolUtils))) throw new Error('Security Pool Utils does not exist eventhought we deployed it') + } + if (!existence.openOracle) { + await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: `0x${ peripherals_openOracle_OpenOracle_OpenOracle.evm.bytecode.object }` } as const) + if (!(await contractExists(client, contractAddresses.openOracle))) throw new Error('Open Oracle does not exist eventhought we deployed it') + } + if (!existence.zoltar) { + await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: `0x${ Zoltar_Zoltar.evm.bytecode.object }` } as const) + if (!(await contractExists(client, contractAddresses.zoltar))) throw new Error('Zoltar does not exist eventhought we deployed it') + } + if (!existence.shareTokenFactory) { + await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: getShareTokenFactoryByteCode(getZoltarAddress()) } as const) + if (!(await contractExists(client, contractAddresses.shareTokenFactory))) throw new Error('Share Token Factory does not exist eventhought we deployed it') + } + if (!existence.auctionFactory) { + await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: `0x${ peripherals_AuctionFactory_AuctionFactory.evm.bytecode.object }` } as const) + if (!(await contractExists(client, contractAddresses.auctionFactory))) throw new Error('auctionFactory does not exist eventhought we deployed it') + } + if (!existence.priceOracleManagerAndOperatorQueuerFactory) { + await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: `0x${ peripherals_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory.evm.bytecode.object }` } as const) + if (!(await contractExists(client, contractAddresses.priceOracleManagerAndOperatorQueuerFactory))) throw new Error('priceOracleManagerAndOperatorQueuerFactory does not exist eventhought we deployed it') + } + if (!existence.securityPoolFactory) { + await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: getSecurityPoolFactoryByteCode(contractAddresses.openOracle, contractAddresses.zoltar, contractAddresses.shareTokenFactory, contractAddresses.auctionFactory, contractAddresses.priceOracleManagerAndOperatorQueuerFactory) } as const) + if (!(await contractExists(client, contractAddresses.securityPoolFactory))) throw new Error('priceOracleManagerAndOperatorQueuerFactory does not exist eventhought we deployed it') + } +} + +const computeSecurityPoolSalt = (parent: `0x${ string }`, universeId: bigint, questionId: bigint, securityMultiplier: bigint) => { + const types = ['address', 'uint192', 'uint56', 'uint256'] as const + const values = [parent, universeId, questionId, securityMultiplier] as const + return keccak256(encodePacked(types, values)) +} + +const computeShareTokenSalt = (securityMultiplier: bigint) => { + const types = ['uint256'] as const + const values = [securityMultiplier] as const + return keccak256(encodePacked(types, values)) +} + +export const getSecurityPoolAddresses = (parent: `0x${ string }`, universeId: bigint, questionId: bigint, securityMultiplier: bigint) => { + const securityPoolSalt = computeSecurityPoolSalt(parent, universeId, questionId, securityMultiplier) + const infraContracts = getInfraContractAddresses() + const securityPoolSaltWithMsgSender = keccak256(encodePacked(['address', 'bytes32'] as const, [infraContracts.securityPoolFactory, securityPoolSalt])) + + const contracts = { + priceOracleManagerAndOperatorQueuer: getCreate2Address({ + bytecode: encodeDeployData({ + abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, + bytecode: `0x${ peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.evm.bytecode.object }`, + args: [ infraContracts.openOracle, getRepTokenAddress(universeId) ] + }), + from: infraContracts.priceOracleManagerAndOperatorQueuerFactory, + salt: securityPoolSaltWithMsgSender + }), + shareToken: getCreate2Address({ + bytecode: encodeDeployData({ + abi: peripherals_tokens_ShareToken_ShareToken.abi, + bytecode: `0x${ peripherals_tokens_ShareToken_ShareToken.evm.bytecode.object }`, + args: [ infraContracts.securityPoolFactory, infraContracts.zoltar, questionId ] + }), + from: infraContracts.shareTokenFactory, + salt: computeShareTokenSalt(securityMultiplier) + }), + truthAuction: BigInt(parent) == 0n ? zeroAddress : getCreate2Address({ + bytecode: `0x${ peripherals_Auction_Auction.evm.bytecode.object }`, + from: infraContracts.auctionFactory, + salt: securityPoolSaltWithMsgSender + }), + } + const securityPool = getCreate2Address({ + bytecode: encodeDeployData({ + abi: peripherals_SecurityPool_SecurityPool.abi, + bytecode: applyLibraries(peripherals_SecurityPool_SecurityPool.evm.bytecode.object), + args: [ infraContracts.securityPoolFactory, contracts.truthAuction, contracts.priceOracleManagerAndOperatorQueuer, contracts.shareToken, infraContracts.openOracle, parent, infraContracts.zoltar, universeId, questionId, securityMultiplier] as const + }), + from: infraContracts.securityPoolFactory, + salt: numberToBytes(0) + }) + return { ...contracts, securityPool } +} + +export const deployOriginSecurityPool = async (client: WriteClient, universeId: bigint, questionId: bigint, securityMultiplier: bigint, startingRetentionRate: bigint, startingRepEthPrice: bigint, completeSetCollateralAmount: bigint) => { + const infraAddresses = getInfraContractAddresses() + return await client.writeContract({ + chain: mainnet, + abi: peripherals_SecurityPoolFactory_SecurityPoolFactory.abi, + functionName: 'deployOriginSecurityPool', + address: infraAddresses.securityPoolFactory, + args: [universeId, questionId, securityMultiplier, startingRetentionRate, startingRepEthPrice, completeSetCollateralAmount] + }) +} diff --git a/solidity/ts/testsuite/simulator/utils/deployments.ts b/solidity/ts/testsuite/simulator/utils/deployments.ts index 5fa2e44..d81f639 100644 --- a/solidity/ts/testsuite/simulator/utils/deployments.ts +++ b/solidity/ts/testsuite/simulator/utils/deployments.ts @@ -1,12 +1,13 @@ -import { peripherals_interfaces_IAugur_IAugur, IERC20_IERC20, peripherals_interfaces_IWeth9_IWeth9, peripherals_Auction_Auction, peripherals_CompleteSet_CompleteSet, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_SecurityPool_SecurityPool, peripherals_SecurityPoolFactory_SecurityPoolFactory, ReputationToken_ReputationToken, Zoltar_Zoltar, peripherals_SecurityPoolUtils_SecurityPoolUtils } from '../../../types/contractArtifact.js' +import { peripherals_interfaces_IAugur_IAugur, IERC20_IERC20, peripherals_interfaces_IWeth9_IWeth9, peripherals_Auction_Auction, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_SecurityPool_SecurityPool, peripherals_SecurityPoolFactory_SecurityPoolFactory, ReputationToken_ReputationToken, Zoltar_Zoltar, peripherals_SecurityPoolUtils_SecurityPoolUtils, peripherals_tokens_ShareToken_ShareToken, peripherals_AuctionFactory_AuctionFactory, peripherals_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory, peripherals_ShareTokenFactory_ShareTokenFactory } from '../../../types/contractArtifact.js' import { QuestionOutcome } from '../types/types.js' import { addressString } from './bigint.js' -import { ETHEREUM_LOGS_LOGGER_ADDRESS, GENESIS_REPUTATION_TOKEN, TEST_ADDRESSES, WETH_ADDRESS } from './constants.js' +import { ETHEREUM_LOGS_LOGGER_ADDRESS, TEST_ADDRESSES, WETH_ADDRESS } from './constants.js' import { Deployment } from './logExplaining.js' -import { getCompleteSetAddress, getOpenOracleAddress, getPriceOracleManagerAndOperatorQueuerAddress, getSecurityPoolAddress, getSecurityPoolFactoryAddress, getSecurityPoolUtilsAddress, getTruthAuction } from './peripherals.js' -import { getChildUniverseId, getRepTokenAddress, getZoltarAddress } from './utilities.js' +import { getInfraContractAddresses, getSecurityPoolAddresses } from './deployPeripherals.js' +import { getChildUniverseId, getRepTokenAddress } from './utilities.js' +import { zeroAddress } from 'viem' -const getDeploymentsForUniverse = (universeId: bigint, securityPoolAddress: `0x${ string }`, repTokenAddress: `0x${ string }`, priceOracleManagerAndOperatorQueuerAddress: `0x${ string }`, completeSetAddress: `0x${ string }`, auction: `0x${ string }`): Deployment[] => [ +const getDeploymentsForUniverse = (universeId: bigint, securityPoolAddress: `0x${ string }`, repTokenAddress: `0x${ string }`, priceOracleManagerAndOperatorQueuerAddress: `0x${ string }`, shareTokenAddress: `0x${ string }`, auction: `0x${ string }`): Deployment[] => [ { abi: ReputationToken_ReputationToken.abi, deploymentName: `RepV2-U${ universeId }`, @@ -20,9 +21,9 @@ const getDeploymentsForUniverse = (universeId: bigint, securityPoolAddress: `0x$ deploymentName: `ETH SecurityPool U${ universeId }`, address: securityPoolAddress }, { - abi: peripherals_CompleteSet_CompleteSet.abi, + abi: peripherals_tokens_ShareToken_ShareToken.abi, deploymentName: `CompleteSet U${ universeId }`, - address: completeSetAddress + address: shareTokenAddress }, { abi: peripherals_Auction_Auction.abi, deploymentName: `Truth Auction U${ universeId }`, @@ -31,41 +32,47 @@ const getDeploymentsForUniverse = (universeId: bigint, securityPoolAddress: `0x$ ] as const export const getDeployments = (genesisUniverse: bigint, questionId: bigint, securityMultiplier: bigint): Deployment[] => { - const securityPoolAddress = getSecurityPoolAddress(addressString(0x0n), genesisUniverse, questionId, securityMultiplier) - const repToken = addressString(GENESIS_REPUTATION_TOKEN) - const priceOracleManagerAndOperatorQueuerAddress = getPriceOracleManagerAndOperatorQueuerAddress(securityPoolAddress, repToken) - const completeSetAddress = getCompleteSetAddress(securityPoolAddress) - const truthAuction = getTruthAuction(securityPoolAddress) + const infraAddresses = getInfraContractAddresses() + const originAddresses = getSecurityPoolAddresses(zeroAddress, genesisUniverse, questionId, securityMultiplier) const oucomes = [QuestionOutcome.Invalid, QuestionOutcome.No, QuestionOutcome.Yes] const getChildAddresses = (parentSecurityPoolAddress: `0x${ string }`, parentUniverseId: bigint): Deployment[] => { return oucomes.flatMap((outcome) => { const universeId = getChildUniverseId(parentUniverseId, outcome) - const securityPoolAddress = getSecurityPoolAddress(parentSecurityPoolAddress, universeId, questionId, securityMultiplier) - const priceOracleManagerAndOperatorQueuerAddress = getPriceOracleManagerAndOperatorQueuerAddress(securityPoolAddress, getRepTokenAddress(universeId)) - const completeSetAddress = getCompleteSetAddress(securityPoolAddress) - const truthAuction = getTruthAuction(securityPoolAddress) - return getDeploymentsForUniverse(universeId, securityPoolAddress, getRepTokenAddress(universeId), priceOracleManagerAndOperatorQueuerAddress, completeSetAddress, truthAuction) + const childAddresses = getSecurityPoolAddresses(parentSecurityPoolAddress, universeId, questionId, securityMultiplier) + return getDeploymentsForUniverse(universeId, childAddresses.securityPool, getRepTokenAddress(universeId), childAddresses.priceOracleManagerAndOperatorQueuer, childAddresses.shareToken, childAddresses.truthAuction) }) } - return [ - ...getDeploymentsForUniverse(genesisUniverse, securityPoolAddress, getRepTokenAddress(genesisUniverse), priceOracleManagerAndOperatorQueuerAddress, completeSetAddress, truthAuction), - ...getChildAddresses(securityPoolAddress, genesisUniverse), // children - ...oucomes.flatMap((outcome) => getChildAddresses(getSecurityPoolAddress(securityPoolAddress, genesisUniverse, questionId, securityMultiplier), getChildUniverseId(genesisUniverse, outcome))), // grand children + return ([ + ...getDeploymentsForUniverse(genesisUniverse, originAddresses.securityPool, getRepTokenAddress(genesisUniverse), originAddresses.priceOracleManagerAndOperatorQueuer, originAddresses.shareToken, originAddresses.truthAuction), + ...getChildAddresses(originAddresses.securityPool, genesisUniverse), // children + ...oucomes.flatMap((outcome) => getChildAddresses(getSecurityPoolAddresses(originAddresses.securityPool, genesisUniverse, questionId, securityMultiplier).securityPool, getChildUniverseId(genesisUniverse, outcome))), // grand children { abi: Zoltar_Zoltar.abi, deploymentName: 'Zoltar', - address: getZoltarAddress(), + address: infraAddresses.zoltar, }, { abi: peripherals_SecurityPoolFactory_SecurityPoolFactory.abi, - deploymentName: 'SecurityPoolFactory', - address: getSecurityPoolFactoryAddress() + deploymentName: 'Security Pool Factory', + address: infraAddresses.securityPoolFactory + }, { + abi: peripherals_AuctionFactory_AuctionFactory.abi, + deploymentName: 'Auction Factory', + address: infraAddresses.auctionFactory + }, { + abi: peripherals_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory.abi, + deploymentName: 'Price Oracle Manager And Operator Queuer Factory', + address: infraAddresses.priceOracleManagerAndOperatorQueuerFactory + }, { + abi: peripherals_ShareTokenFactory_ShareTokenFactory.abi, + deploymentName: 'Share Token Factory', + address: infraAddresses.shareTokenFactory }, { abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, - deploymentName: 'OpenOracle', - address: getOpenOracleAddress() + deploymentName: 'Open Oracle', + address: infraAddresses.openOracle }, { abi: peripherals_interfaces_IWeth9_IWeth9.abi, deploymentName: 'WETH', @@ -85,7 +92,7 @@ export const getDeployments = (genesisUniverse: bigint, questionId: bigint, secu }, { abi: peripherals_SecurityPoolUtils_SecurityPoolUtils.abi, deploymentName: 'Security Pool Utils', - address: getSecurityPoolUtilsAddress() + address: infraAddresses.securityPoolUtils }, { abi: undefined, deploymentName: 'Augur V2 Genesis', @@ -96,5 +103,5 @@ export const getDeployments = (genesisUniverse: bigint, questionId: bigint, secu deploymentName: `Test EOA(${ index + 1 })`, address: addressString(testAddress) } as const)) - ] as const + ] as const).filter((entry) => BigInt(entry.address) !== 0n) } diff --git a/solidity/ts/testsuite/simulator/utils/peripherals.ts b/solidity/ts/testsuite/simulator/utils/peripherals.ts index ff72855..ba5dbc3 100644 --- a/solidity/ts/testsuite/simulator/utils/peripherals.ts +++ b/solidity/ts/testsuite/simulator/utils/peripherals.ts @@ -1,94 +1,11 @@ import 'viem/window' -import { encodeDeployData, getContractAddress, getCreate2Address, keccak256, numberToBytes, ReadContractReturnType, toHex } from 'viem' +import { ReadContractReturnType } from 'viem' import { ReadClient, WriteClient } from './viem.js' -import { PROXY_DEPLOYER_ADDRESS, WETH_ADDRESS } from './constants.js' -import { addressString, bytes32String } from './bigint.js' -import { getZoltarAddress } from './utilities.js' -import { mainnet } from 'viem/chains' +import { WETH_ADDRESS } from './constants.js' import { SystemState } from '../types/peripheralTypes.js' -import { peripherals_Auction_Auction, peripherals_CompleteSet_CompleteSet, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_SecurityPool_SecurityPool, peripherals_SecurityPoolFactory_SecurityPoolFactory, peripherals_SecurityPoolUtils_SecurityPoolUtils } from '../../../types/contractArtifact.js' +import { peripherals_Auction_Auction, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_SecurityPool_SecurityPool, peripherals_tokens_ShareToken_ShareToken } from '../../../types/contractArtifact.js' import { QuestionOutcome } from '../types/types.js' - -export async function ensureProxyDeployerDeployed(client: WriteClient): Promise { - const deployerBytecode = await client.getCode({ address: addressString(PROXY_DEPLOYER_ADDRESS)}) - if (deployerBytecode === '0x60003681823780368234f58015156014578182fd5b80825250506014600cf3') return - const ethSendHash = await client.sendTransaction({ to: '0x4c8d290a1b368ac4728d83a9e8321fc3af2b39b1', amount: 10000000000000000n }) - await client.waitForTransactionReceipt({ hash: ethSendHash }) - const deployHash = await client.sendRawTransaction({ serializedTransaction: '0xf87e8085174876e800830186a08080ad601f80600e600039806000f350fe60003681823780368234f58015156014578182fd5b80825250506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222' }) - await client.waitForTransactionReceipt({ hash: deployHash }) -} - -export function getOpenOracleAddress() { - const bytecode: `0x${ string }` = `0x${ peripherals_openOracle_OpenOracle_OpenOracle.evm.bytecode.object }` - return getContractAddress({ bytecode, from: addressString(PROXY_DEPLOYER_ADDRESS), opcode: 'CREATE2', salt: numberToBytes(0) }) -} - -export const isOpenOracleDeployed = async (client: ReadClient) => { - const expectedDeployedBytecode: `0x${ string }` = `0x${ peripherals_openOracle_OpenOracle_OpenOracle.evm.deployedBytecode.object }` - const address = getOpenOracleAddress() - const deployedBytecode = await client.getCode({ address }) - return deployedBytecode === expectedDeployedBytecode -} - -export function getSecurityPoolUtilsAddress() { - const bytecode: `0x${ string }` = `0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.bytecode.object }` - return getContractAddress({ bytecode, from: addressString(PROXY_DEPLOYER_ADDRESS), opcode: 'CREATE2', salt: numberToBytes(0) }) -} - -export const isSecurityPoolUtilsDeployed = async (client: ReadClient) => { - const expectedDeployedBytecode: `0x${ string }` = `0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.deployedBytecode.object }` - const address = getOpenOracleAddress() - const deployedBytecode = await client.getCode({ address }) - return deployedBytecode === expectedDeployedBytecode -} - -export const ensureOpenOracleDeployed = async (client: WriteClient) => { - await ensureProxyDeployerDeployed(client) - if (await isOpenOracleDeployed(client)) return - const bytecode: `0x${ string }` = `0x${ peripherals_openOracle_OpenOracle_OpenOracle.evm.bytecode.object }` - const hash = await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: bytecode } as const) - await client.waitForTransactionReceipt({ hash }) -} - -export const ensureSecurityPoolUtilsDeployed = async (client: WriteClient) => { - await ensureProxyDeployerDeployed(client) - if (await isSecurityPoolUtilsDeployed(client)) return - const bytecode: `0x${ string }` = `0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.bytecode.object }` - const hash = await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: bytecode } as const) - await client.waitForTransactionReceipt({ hash }) -} - -export const applyLibraries = (bytecode: string): `0x${ string }` => { - const securityPoolUtils = keccak256(toHex('contracts/peripherals/SecurityPoolUtils.sol:SecurityPoolUtils')).slice(2, 36) - return `0x${ bytecode.replaceAll(`__$${ securityPoolUtils }$__`, getSecurityPoolUtilsAddress().slice(2).toLocaleLowerCase()) }` -} - -export const isSecurityPoolFactoryDeployed = async (client: ReadClient) => { - const address = getSecurityPoolFactoryAddress() - return await client.getCode({ address }) === applyLibraries(peripherals_SecurityPoolFactory_SecurityPoolFactory.evm.deployedBytecode.object) -} - -export function getSecurityPoolFactoryAddress() { - return getContractAddress({ bytecode: applyLibraries(peripherals_SecurityPoolFactory_SecurityPoolFactory.evm.bytecode.object), from: addressString(PROXY_DEPLOYER_ADDRESS), opcode: 'CREATE2', salt: numberToBytes(0) }) -} - -export const ensureSecurityPoolFactoryDeployed = async (client: WriteClient) => { - await ensureProxyDeployerDeployed(client) - await ensureSecurityPoolUtilsDeployed(client) - if (await isSecurityPoolFactoryDeployed(client)) return - const hash = await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: applyLibraries(peripherals_SecurityPoolFactory_SecurityPoolFactory.evm.bytecode.object) } as const) - await client.waitForTransactionReceipt({ hash }) -} - -export const deploySecurityPool = async (client: WriteClient, openOracle: `0x${ string }`, universeId: bigint, questionId: bigint, securityMultiplier: bigint, startingRetentionRate: bigint, startingRepEthPrice: bigint, completeSetCollateralAmount: bigint) => { - return await client.writeContract({ - chain: mainnet, - abi: peripherals_SecurityPoolFactory_SecurityPoolFactory.abi, - functionName: 'deploySecurityPool', - address: getSecurityPoolFactoryAddress(), - args: [openOracle, addressString(0x0n), getZoltarAddress(), universeId, questionId, securityMultiplier, startingRetentionRate, startingRepEthPrice, completeSetCollateralAmount] - }) -} +import { getInfraContractAddresses } from './deployPeripherals.js' export const depositRep = async (client: WriteClient, securityPoolAddress: `0x${ string }`, amount: bigint) => { return await client.writeContract({ @@ -99,15 +16,6 @@ export const depositRep = async (client: WriteClient, securityPoolAddress: `0x${ }) } -export const getPriceOracleManagerAndOperatorQueuer = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'priceOracleManagerAndOperatorQueuer', - address: securityPoolAddress, - args: [] - }) as `0x${ string }` -} - export enum OperationType { Liquidation = 0, WithdrawRep = 1, @@ -150,7 +58,7 @@ export const getOpenOracleExtraData = async (client: ReadClient, extraDataId: bi const result = await client.readContract({ abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, functionName: 'extraData', - address: getOpenOracleAddress(), + address: getInfraContractAddresses().openOracle, args: [extraDataId] }) as ReadContractReturnType @@ -193,7 +101,7 @@ export const openOracleSubmitInitialReport = async (client: WriteClient, reportI return await client.writeContract({ abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, functionName: 'submitInitialReport', - address: getOpenOracleAddress(), + address: getInfraContractAddresses().openOracle, args: [reportId, amount1, amount2, stateHash] }) } @@ -202,7 +110,7 @@ export const openOracleSettle = async (client: WriteClient, reportId: bigint) => return await client.writeContract({ abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, functionName: 'settle', - address: getOpenOracleAddress(), + address: getInfraContractAddresses().openOracle, gas: 10000000n, //needed because of gas() opcode being used args: [reportId] }) @@ -252,7 +160,7 @@ export const getOpenOracleReportMeta = async (client: ReadClient, reportId: bigi const reportMetaData = await client.readContract({ abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, functionName: 'reportMeta', - address: getOpenOracleAddress(), + address: getInfraContractAddresses().openOracle, args: [reportId] }) @@ -378,47 +286,6 @@ export const claimAuctionProceeds = async (client: WriteClient, securityPoolAddr }) } -export function getSecurityPoolAddress( - parent: `0x${ string }`, - universeId: bigint, - questionId: bigint, - securityMultiplier: bigint, -) : `0x${ string }` { - const initCode = encodeDeployData({ - abi: peripherals_SecurityPool_SecurityPool.abi, - bytecode: applyLibraries(peripherals_SecurityPool_SecurityPool.evm.bytecode.object), - args: [getSecurityPoolFactoryAddress(), getOpenOracleAddress(), parent, getZoltarAddress(), universeId, questionId, securityMultiplier] - }) - return getCreate2Address({ from: getSecurityPoolFactoryAddress(), salt: bytes32String(1n), bytecodeHash: keccak256(initCode) }) -} - -export function getPriceOracleManagerAndOperatorQueuerAddress(securityPool: `0x${ string }`, repToken: `0x${ string }`): `0x${ string }` { - const initCode = encodeDeployData({ - abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, - bytecode: `0x${ peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.evm.bytecode.object }`, - args: [getOpenOracleAddress(), securityPool, repToken] - }) - return getCreate2Address({ from: securityPool, salt: bytes32String(1n), bytecodeHash: keccak256(initCode) }) -} - -export function getCompleteSetAddress(securityPool: `0x${ string }`): `0x${ string }` { - const initCode = encodeDeployData({ - abi: peripherals_CompleteSet_CompleteSet.abi, - bytecode: `0x${ peripherals_CompleteSet_CompleteSet.evm.bytecode.object }`, - args: [securityPool] - }) - return getCreate2Address({ from: securityPool, salt: bytes32String(1n), bytecodeHash: keccak256(initCode) }) -} - -export function getTruthAuction(securityPool: `0x${ string }`): `0x${ string }` { - const initCode = encodeDeployData({ - abi: peripherals_Auction_Auction.abi, - bytecode: `0x${ peripherals_Auction_Auction.evm.bytecode.object }`, - args: [securityPool] - }) - return getCreate2Address({ from: securityPool, salt: bytes32String(1n), bytecodeHash: keccak256(initCode) }) -} - export const participateAuction = async (client: WriteClient, auctionAddress: `0x${ string }`, repToBuy: bigint, ethToInvest: bigint) => { return await client.writeContract({ abi: peripherals_Auction_Auction.abi, @@ -428,7 +295,7 @@ export const participateAuction = async (client: WriteClient, auctionAddress: `0 value: ethToInvest }) } -export const getEthAmountToBuy = async (client: WriteClient, auctionAddress: `0x${ string }`) => { +export const getEthAmountToBuy = async (client: ReadClient, auctionAddress: `0x${ string }`) => { return await client.readContract({ abi: peripherals_Auction_Auction.abi, functionName: 'ethAmountToBuy', @@ -437,7 +304,7 @@ export const getEthAmountToBuy = async (client: WriteClient, auctionAddress: `0x }) } -export const getMigratedRep = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { +export const getMigratedRep = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { return await client.readContract({ abi: peripherals_SecurityPool_SecurityPool.abi, functionName: 'migratedRep', @@ -446,7 +313,7 @@ export const getMigratedRep = async (client: WriteClient, securityPoolAddress: ` }) } -export const getSystemState = async (client: WriteClient, securityPoolAddress: `0x${ string }`): Promise => { +export const getSystemState = async (client: ReadClient, securityPoolAddress: `0x${ string }`): Promise => { return await client.readContract({ abi: peripherals_SecurityPool_SecurityPool.abi, functionName: 'systemState', @@ -455,7 +322,7 @@ export const getSystemState = async (client: WriteClient, securityPoolAddress: ` }) } -export const getCurrentRetentionRate = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { +export const getCurrentRetentionRate = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { return await client.readContract({ abi: peripherals_SecurityPool_SecurityPool.abi, functionName: 'currentRetentionRate', @@ -464,7 +331,7 @@ export const getCurrentRetentionRate = async (client: WriteClient, securityPoolA }) } -export const getSecurityVault = async (client: WriteClient, securityPoolAddress: `0x${ string }`, securityVault: `0x${ string }`) => { +export const getSecurityVault = async (client: ReadClient, securityPoolAddress: `0x${ string }`, securityVault: `0x${ string }`) => { const vault = await client.readContract({ abi: peripherals_SecurityPool_SecurityPool.abi, functionName: 'securityVaults', @@ -486,7 +353,7 @@ export const getSecurityVault = async (client: WriteClient, securityPoolAddress: } } -export const getPoolOwnershipDenominator = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { +export const getPoolOwnershipDenominator = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { return await client.readContract({ abi: peripherals_SecurityPool_SecurityPool.abi, functionName: 'poolOwnershipDenominator', @@ -495,7 +362,7 @@ export const getPoolOwnershipDenominator = async (client: WriteClient, securityP }) } -export const poolOwnershipToRep = async (client: WriteClient, securityPoolAddress: `0x${ string }`, poolOwnership: bigint) => { +export const poolOwnershipToRep = async (client: ReadClient, securityPoolAddress: `0x${ string }`, poolOwnership: bigint) => { return await client.readContract({ abi: peripherals_SecurityPool_SecurityPool.abi, functionName: 'poolOwnershipToRep', @@ -504,7 +371,7 @@ export const poolOwnershipToRep = async (client: WriteClient, securityPoolAddres }) } -export const repToPoolOwnership = async (client: WriteClient, securityPoolAddress: `0x${ string }`, repAmount: bigint) => { +export const repToPoolOwnership = async (client: ReadClient, securityPoolAddress: `0x${ string }`, repAmount: bigint) => { return await client.readContract({ abi: peripherals_SecurityPool_SecurityPool.abi, functionName: 'repToPoolOwnership', @@ -512,3 +379,30 @@ export const repToPoolOwnership = async (client: WriteClient, securityPoolAddres args: [repAmount], }) } + +export const totalSupplyForUniverse = async (client: ReadClient, shareTokenAddress: `0x${ string }`, universeId: bigint) => { + return await client.readContract({ + abi: peripherals_tokens_ShareToken_ShareToken.abi, + functionName: 'totalSupplyForUniverse', + address: shareTokenAddress, + args: [universeId], + }) +} + +export const balanceOfOutcome = async (client: ReadClient, shareTokenAddress: `0x${ string }`, universeId: bigint, outcome: QuestionOutcome, account: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_tokens_ShareToken_ShareToken.abi, + functionName: 'balanceOfOutcome', + address: shareTokenAddress, + args: [universeId, outcome, account], + }) +} + +export const balanceOfShares = async (client: ReadClient, shareTokenAddress: `0x${ string }`, universeId: bigint, account: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_tokens_ShareToken_ShareToken.abi, + functionName: 'balanceOfShares', + address: shareTokenAddress, + args: [universeId, account], + }) +} diff --git a/solidity/ts/testsuite/simulator/utils/peripheralsTestUtils.ts b/solidity/ts/testsuite/simulator/utils/peripheralsTestUtils.ts index 8b93b45..ba3caa7 100644 --- a/solidity/ts/testsuite/simulator/utils/peripheralsTestUtils.ts +++ b/solidity/ts/testsuite/simulator/utils/peripheralsTestUtils.ts @@ -1,11 +1,13 @@ +import { zeroAddress } from 'viem' import { MockWindowEthereum } from '../MockWindowEthereum.js' import { QuestionOutcome } from '../types/types.js' import { addressString } from './bigint.js' import { DAY, GENESIS_REPUTATION_TOKEN, WETH_ADDRESS } from './constants.js' -import { deploySecurityPool, depositRep, ensureOpenOracleDeployed, ensureSecurityPoolFactoryDeployed, getOpenOracleAddress, getOpenOracleExtraData, getOpenOracleReportMeta, getPendingReportId, getSecurityPoolAddress, isOpenOracleDeployed, isSecurityPoolFactoryDeployed, openOracleSettle, openOracleSubmitInitialReport, OperationType, requestPriceIfNeededAndQueueOperation, wrapWeth } from './peripherals.js' +import { deployOriginSecurityPool, ensureInfraDeployed, getInfraContractAddresses, getSecurityPoolAddresses } from './deployPeripherals.js' import { approveToken, contractExists, createQuestion, dispute, ensureZoltarDeployed, getERC20Balance, getQuestionData, getUniverseData, getZoltarAddress, isZoltarDeployed, reportOutcome } from './utilities.js' import { WriteClient } from './viem.js' import assert from 'node:assert' +import { depositRep, getOpenOracleExtraData, getOpenOracleReportMeta, getPendingReportId, openOracleSettle, openOracleSubmitInitialReport, OperationType, requestPriceIfNeededAndQueueOperation, wrapWeth } from './peripherals.js' export const genesisUniverse = 0n export const questionId = 1n @@ -26,17 +28,14 @@ export const deployZoltarAndCreateMarket = async (client: WriteClient, questionE } export const deployPeripherals = async (client: WriteClient) => { - await ensureOpenOracleDeployed(client) - assert.ok(await isOpenOracleDeployed(client), 'Open Oracle Not Deployed!') - const openOracle = getOpenOracleAddress() - await ensureSecurityPoolFactoryDeployed(client) - assert.ok(await isSecurityPoolFactoryDeployed(client), 'Security Pool Factory Not Deployed!') - await deploySecurityPool(client, openOracle, genesisUniverse, questionId, securityMultiplier, MAX_RETENTION_RATE, startingRepEthPrice, completeSetCollateralAmount) - assert.ok(await contractExists(client, getSecurityPoolAddress(addressString(0x0n), genesisUniverse, questionId, securityMultiplier)), 'security pool not deployed') + await ensureInfraDeployed(client); + await deployOriginSecurityPool(client, genesisUniverse, questionId, securityMultiplier, MAX_RETENTION_RATE, startingRepEthPrice, completeSetCollateralAmount) + const securityPoolAddress = getSecurityPoolAddresses(zeroAddress, genesisUniverse, questionId, securityMultiplier).securityPool + assert.ok(await contractExists(client, securityPoolAddress), 'security pool not deployed') } export const approveAndDepositRep = async (client: WriteClient, repDeposit: bigint) => { - const securityPoolAddress = getSecurityPoolAddress(addressString(0x0n), genesisUniverse, questionId, securityMultiplier) + const securityPoolAddress = getSecurityPoolAddresses(zeroAddress, genesisUniverse, questionId, securityMultiplier).securityPool assert.ok(await contractExists(client, securityPoolAddress), 'security pool not deployed') const startBalance = await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddress) @@ -80,8 +79,9 @@ export const requestPrice = async(client: WriteClient, mockWindow: MockWindowEth const amount1 = reportMeta.exactToken1Report const amount2 = amount1 - await approveToken(client, addressString(GENESIS_REPUTATION_TOKEN), getOpenOracleAddress()) - await approveToken(client, WETH_ADDRESS, getOpenOracleAddress()) + const openOracle = getInfraContractAddresses().openOracle + await approveToken(client, addressString(GENESIS_REPUTATION_TOKEN), openOracle) + await approveToken(client, WETH_ADDRESS, openOracle) await wrapWeth(client, amount2) const wethBalance = await getERC20Balance(client, WETH_ADDRESS, client.account.address) assert.strictEqual(wethBalance, amount2, 'Did not wrap weth')