diff --git a/solidity/contracts/peripherals/Auction.sol b/solidity/contracts/peripherals/Auction.sol index 265c981..f62d18c 100644 --- a/solidity/contracts/peripherals/Auction.sol +++ b/solidity/contracts/peripherals/Auction.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; uint256 constant AUCTION_TIME = 1 weeks; @@ -39,12 +39,12 @@ contract Auction { emit AuctionStarted(ethAmountToBuy, repAvailable); } - function finalizeAuction() public { + function finalizeAuction(address receiver) public { //require(block.timestamp > auctionStarted + AUCTION_TIME, 'Auction needs to have ended first'); // caller checks require(msg.sender == owner, 'Only owner can finalize'); require(!finalized, 'Already finalized'); finalized = true; - (bool sent, ) = payable(owner).call{value: address(this).balance}(''); + (bool sent, ) = payable(receiver).call{value: address(this).balance}(''); require(sent, 'Failed to send Ether'); } } diff --git a/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol b/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol index bfc8d9e..d49fa84 100644 --- a/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol +++ b/solidity/contracts/peripherals/PriceOracleManagerAndOperatorQueuer.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import { IWeth9 } from './interfaces/IWeth9.sol'; @@ -37,7 +37,7 @@ contract PriceOracleManagerAndOperatorQueuer { OpenOracle public immutable openOracle; event PriceReported(uint256 reportId, uint256 price); - event ExecutetedQueuedOperation(uint256 operationId, OperationType operation, bool success, string errorMessage); + event ExecutedQueuedOperation(uint256 operationId, OperationType operation, bool success, string errorMessage); // operation queuing uint256 public previousQueuedOperationId; @@ -125,32 +125,42 @@ contract PriceOracleManagerAndOperatorQueuer { queuedPendingOperationId = previousQueuedOperationId; requestPrice(); } + // send rest of the eth back + (bool sent, ) = payable(msg.sender).call{ value: address(this).balance }(''); + require(sent, 'Failed to return eth'); } function executeQueuedOperation(uint256 operationId) public { require(queuedOperations[operationId].amount > 0, 'no such operation or already executed'); require(isPriceValid(), 'price is not valid to execute'); + uint256 amount = queuedOperations[operationId].amount; + queuedOperations[operationId].amount = 0; // todo, we should allow these operations here to fail, but solidity try catch doesnt work inside the same contract if (queuedOperations[operationId].operation == OperationType.Liquidation) { - try securityPool.performLiquidation(queuedOperations[operationId].initiatorVault, queuedOperations[operationId].targetVault, queuedOperations[operationId].amount) { - emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, true, ''); + try securityPool.performLiquidation(queuedOperations[operationId].initiatorVault, queuedOperations[operationId].targetVault, amount) { + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, true, ''); } catch Error(string memory reason) { - emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, false, reason); + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, false, reason); + } catch (bytes memory lowLevelData) { + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, false, 'Unknown error'); } } else if(queuedOperations[operationId].operation == OperationType.WithdrawRep) { - try securityPool.performWithdrawRep(queuedOperations[operationId].initiatorVault, queuedOperations[operationId].amount) { - emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, true, ''); + try securityPool.performWithdrawRep(queuedOperations[operationId].initiatorVault, amount) { + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, true, ''); } catch Error(string memory reason) { - emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, false, reason); + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, false, reason); + } catch (bytes memory lowLevelData) { + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, false, 'Unknown error'); } } else { - try securityPool.performSetSecurityBondsAllowance(queuedOperations[operationId].initiatorVault, queuedOperations[operationId].amount) { - emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, true, ''); + try securityPool.performSetSecurityBondsAllowance(queuedOperations[operationId].initiatorVault, amount) { + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, true, ''); } catch Error(string memory reason) { - emit ExecutetedQueuedOperation(operationId, queuedOperations[operationId].operation, false, reason); + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, false, reason); + } catch (bytes memory lowLevelData) { + emit ExecutedQueuedOperation(operationId, queuedOperations[operationId].operation, false, 'Unknown error'); } } - queuedOperations[operationId].amount = 0; } function getQueuedOperation() public view returns (QueuedOperation memory) { diff --git a/solidity/contracts/peripherals/SecurityPool.sol b/solidity/contracts/peripherals/SecurityPool.sol index 1ab9b5a..1b19a48 100644 --- a/solidity/contracts/peripherals/SecurityPool.sol +++ b/solidity/contracts/peripherals/SecurityPool.sol @@ -1,94 +1,103 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import { Auction } from './Auction.sol'; -import { Zoltar } from '../Zoltar.sol'; +import { Zoltar, FORK_THRESHOLD_DIVISOR } from '../Zoltar.sol'; import { ReputationToken } from '../ReputationToken.sol'; import { IShareToken } from './interfaces/IShareToken.sol'; import { PriceOracleManagerAndOperatorQueuer, QueuedOperation } from './PriceOracleManagerAndOperatorQueuer.sol'; import { ISecurityPool, SecurityVault, SystemState, QuestionOutcome, ISecurityPoolFactory } from './interfaces/ISecurityPool.sol'; import { OpenOracle } from './openOracle/OpenOracle.sol'; import { SecurityPoolUtils } from './SecurityPoolUtils.sol'; +import { EscalationGameFactory } from './factories/EscalationGameFactory.sol'; +import { EscalationGame } from './EscalationGame.sol'; +import { YesNoMarkets } from './YesNoMarkets.sol'; +import { SecurityPoolForker } from './SecurityPoolForker.sol'; +import { ISecurityPoolForker } from './interfaces/ISecurityPoolForker.sol'; + +uint256 constant TODO_INITIAL_ESCALATION_GAME_DEPOSIT = 1 ether; // todo, how to get this value? // Security pool for one question, one universe, one denomination (ETH) contract SecurityPool is ISecurityPool { - uint56 public immutable questionId; - uint192 public immutable universeId; + uint256 public immutable marketId; + uint248 public immutable universeId; Zoltar public immutable zoltar; ISecurityPool immutable public parent; IShareToken public immutable shareToken; - Auction public immutable truthAuction; - ISecurityPoolFactory public immutable securityPoolFactory; ReputationToken public immutable repToken; PriceOracleManagerAndOperatorQueuer public immutable priceOracleManagerAndOperatorQueuer; OpenOracle public immutable openOracle; + EscalationGameFactory public immutable escalationGameFactory; + EscalationGame public escalationGame; + YesNoMarkets public yesNoMarkets; + address public securityPoolForker; + ISecurityPoolFactory public securityPoolFactory; - uint256 public securityBondAllowance; - uint256 public auctionedSecurityBondAllowance; + uint256 public totalSecurityBondAllowance; uint256 public completeSetCollateralAmount; // amount of eth that is backing complete sets, `address(this).balance - completeSetCollateralAmount` are the fees belonging to REP pool holders uint256 public poolOwnershipDenominator; - uint256 public repAtFork; - uint256 public migratedRep; uint256 public securityMultiplier; uint256 public shareTokenSupply; - uint256 public totalFeesOvedToVaults; + uint256 public totalFeesOwedToVaults; uint256 public lastUpdatedFeeAccumulator; uint256 public feeIndex; uint256 public currentRetentionRate; mapping(address => SecurityVault) public securityVaults; - mapping(address => bool) public claimedAuctionProceeds; - - ISecurityPool[3] public children; - uint256 public truthAuctionStarted; SystemState public systemState; event SecurityBondAllowanceChange(address vault, uint256 from, uint256 to); event PerformWithdrawRep(address vault, uint256 amount); event PoolRetentionRateChanged(uint256 retentionRate); - event ForkSecurityPool(uint256 repAtFork); - event MigrateVault(address vault, QuestionOutcome outcome, uint256 poolOwnership, uint256 securityBondAllowance); - event TruthAuctionStarted(uint256 completeSetCollateralAmount, uint256 repMigrated, uint256 repAtFork); - event TruthAuctionFinalized(); - event ClaimAuctionProceeds(address vault, uint256 amount, uint256 poolOwnershipAmount, uint256 poolOwnershipDenominator); - event MigrateRepFromParent(address vault, uint256 parentSecurityBondAllowance, uint256 parentpoolOwnership); event DepositRep(address vault, uint256 repAmount, uint256 poolOwnership); event RedeemShares(address redeemer, uint256 sharesAmount, uint256 ethValue); - event FinalizeAuction(uint256 repAvailable, uint256 migratedRep, uint256 repPurchased, uint256 poolOwnershipDenominator); event UpdateVaultFees(address vault, uint256 feeIndex, uint256 unpaidEthFees); event RedeemFees(address vault, uint256 fees); - event UpdateCollateralAmount(uint256 totalFeesOvedToVaults, uint256 completeSetCollateralAmount); + event UpdateCollateralAmount(uint256 totalFeesOwedToVaults, uint256 completeSetCollateralAmount); event CreateCompleteSet(uint256 shareTokenSupply, uint256 completeSetsToMint, uint256 completeSetCollateralAmount); event PerformLiquidation(address callerVault, address targetVaultAddress, uint256 debtAmount, uint256 debtToMove, uint256 repToMove); event RedeemRep(address caller, address vault, uint256 repAmount); - modifier isOperational { - (,, uint256 forkTime) = zoltar.universes(universeId); - require(forkTime == 0, 'Zoltar has forked'); + modifier isOperational { // todo, system can be operational if the fork has happened after this market has finalized + require(zoltar.getForkTime(universeId) == 0, 'Zoltar has forked'); require(systemState == SystemState.Operational, 'System is not operational'); _; } - constructor(ISecurityPoolFactory _securityPoolFactory, Auction _truthAuction, PriceOracleManagerAndOperatorQueuer _priceOracleManagerAndOperatorQueuer, IShareToken _shareToken, OpenOracle _openOracle, ISecurityPool _parent, Zoltar _zoltar, uint192 _universeId, uint56 _questionId, uint256 _securityMultiplier) { + modifier onlyValidOracle { + require(msg.sender == address(priceOracleManagerAndOperatorQueuer), 'OnlyOracle'); + require(priceOracleManagerAndOperatorQueuer.isPriceValid(), 'Stale price'); + _; + } + + modifier onlyForker { + require(msg.sender == securityPoolForker, 'Only Forker'); + _; + } + + constructor(address _securityPoolForker, ISecurityPoolFactory _securityPoolFactory, YesNoMarkets _yesNoMarkets, EscalationGameFactory _escalationGameFactory, PriceOracleManagerAndOperatorQueuer _priceOracleManagerAndOperatorQueuer, IShareToken _shareToken, OpenOracle _openOracle, ISecurityPool _parent, Zoltar _zoltar, uint248 _universeId, uint256 _marketId, uint256 _securityMultiplier) { universeId = _universeId; securityPoolFactory = _securityPoolFactory; - questionId = _questionId; + marketId = _marketId; securityMultiplier = _securityMultiplier; zoltar = _zoltar; parent = _parent; openOracle = _openOracle; - truthAuction = _truthAuction; + escalationGameFactory = _escalationGameFactory; priceOracleManagerAndOperatorQueuer = _priceOracleManagerAndOperatorQueuer; + securityPoolForker = _securityPoolForker; + yesNoMarkets = _yesNoMarkets; if (address(parent) == address(0x0)) { // origin universe never does truthAuction systemState = SystemState.Operational; } else { systemState = SystemState.ForkMigration; } shareToken = _shareToken; - (repToken, , ) = zoltar.universes(universeId); + repToken = zoltar.getRepToken(universeId); + repToken.approve(address(zoltar), type(uint256).max); } function setStartingParams(uint256 _currentRetentionRate, uint256 _repEthPrice, uint256 _completeSetCollateralAmount) public { @@ -100,29 +109,29 @@ contract SecurityPool is ISecurityPool { } function updateCollateralAmount() public { - if (securityBondAllowance == 0) return; - uint256 forkTime; - (,,forkTime) = zoltar.universes(universeId); - (uint64 endTime,,,) = zoltar.questions(questionId); + if (totalSecurityBondAllowance == 0) return; + uint256 forkTime = zoltar.getForkTime(universeId); + uint256 endTime = yesNoMarkets.getMarketEndDate(marketId); uint256 feeEndDate = forkTime == 0 ? endTime : forkTime; uint256 clampedCurrentTimestamp = block.timestamp > feeEndDate ? feeEndDate : block.timestamp; + if (lastUpdatedFeeAccumulator > clampedCurrentTimestamp) return; uint256 timeDelta = clampedCurrentTimestamp - lastUpdatedFeeAccumulator; if (timeDelta == 0) return; uint256 newCompleteSetCollateralAmount = completeSetCollateralAmount * SecurityPoolUtils.rpow(currentRetentionRate, timeDelta, SecurityPoolUtils.PRICE_PRECISION) / SecurityPoolUtils.PRICE_PRECISION; uint256 delta = completeSetCollateralAmount - newCompleteSetCollateralAmount; - totalFeesOvedToVaults += delta; - feeIndex += delta * SecurityPoolUtils.PRICE_PRECISION / securityBondAllowance; + totalFeesOwedToVaults += delta; + feeIndex += delta * SecurityPoolUtils.PRICE_PRECISION / totalSecurityBondAllowance; completeSetCollateralAmount = newCompleteSetCollateralAmount; lastUpdatedFeeAccumulator = feeEndDate < block.timestamp ? feeEndDate : block.timestamp; - emit UpdateCollateralAmount(totalFeesOvedToVaults, completeSetCollateralAmount); + emit UpdateCollateralAmount(totalFeesOwedToVaults, completeSetCollateralAmount); } function updateRetentionRate() public { - if (securityBondAllowance == 0) return; + if (totalSecurityBondAllowance == 0) return; if (systemState != SystemState.Operational) return; // if system state is not operational do not change fees - currentRetentionRate = SecurityPoolUtils.calculateRetentionRate(completeSetCollateralAmount, securityBondAllowance); + currentRetentionRate = SecurityPoolUtils.calculateRetentionRate(completeSetCollateralAmount, totalSecurityBondAllowance); emit PoolRetentionRateChanged(currentRetentionRate); } @@ -137,7 +146,7 @@ contract SecurityPool is ISecurityPool { function redeemFees(address vault) public { uint256 fees = securityVaults[vault].unpaidEthFees; securityVaults[vault].unpaidEthFees = 0; - totalFeesOvedToVaults -= fees; + totalFeesOwedToVaults -= fees; (bool sent, ) = payable(vault).call{ value: fees }(''); require(sent, 'Failed to send Ether'); emit RedeemFees(vault, fees); @@ -147,16 +156,14 @@ contract SecurityPool is ISecurityPool { // withdrawing rep //////////////////////////////////////// - function performWithdrawRep(address vault, uint256 repAmount) public isOperational { - require(msg.sender == address(priceOracleManagerAndOperatorQueuer), 'only priceOracleManagerAndOperatorQueuer can call'); - require(priceOracleManagerAndOperatorQueuer.isPriceValid(), 'no valid price'); + function performWithdrawRep(address vault, uint256 repAmount) public isOperational onlyValidOracle { uint256 ownershipToWithdraw = repToPoolOwnership(repAmount); uint256 withdrawOwnership = ownershipToWithdraw + repToPoolOwnership(SecurityPoolUtils.MIN_REP_DEPOSIT) > securityVaults[vault].poolOwnership ? securityVaults[vault].poolOwnership : ownershipToWithdraw; uint256 withdrawRepAmount = poolOwnershipToRep(withdrawOwnership); uint256 oldRep = poolOwnershipToRep(securityVaults[vault].poolOwnership); require((oldRep - withdrawRepAmount) * SecurityPoolUtils.PRICE_PRECISION >= securityVaults[vault].securityBondAllowance * priceOracleManagerAndOperatorQueuer.lastPrice(), 'Local Security Bond Allowance broken'); - require((repToken.balanceOf(address(this)) - withdrawRepAmount) * SecurityPoolUtils.PRICE_PRECISION >= securityBondAllowance * priceOracleManagerAndOperatorQueuer.lastPrice(), 'Global Security Bond Allowance broken'); + require((repToken.balanceOf(address(this)) - withdrawRepAmount) * SecurityPoolUtils.PRICE_PRECISION >= totalSecurityBondAllowance * priceOracleManagerAndOperatorQueuer.lastPrice(), 'Global Security Bond Allowance broken'); securityVaults[vault].poolOwnership -= withdrawOwnership; poolOwnershipDenominator -= withdrawOwnership; @@ -182,8 +189,6 @@ contract SecurityPool is ISecurityPool { } function depositRep(uint256 repAmount) public isOperational { - QueuedOperation memory queuedOperation = priceOracleManagerAndOperatorQueuer.getQueuedOperation(); - require(queuedOperation.amount == 0 || queuedOperation.targetVault != msg.sender, 'operation pending'); // prevents owner from saving their vault when liquidation is pending uint256 poolOwnership = repToPoolOwnership(repAmount); repToken.transferFrom(msg.sender, address(this), repAmount); securityVaults[msg.sender].poolOwnership += poolOwnership; @@ -195,14 +200,12 @@ contract SecurityPool is ISecurityPool { //////////////////////////////////////// // liquidating vault //////////////////////////////////////// - + // TODO, currently liquidator can be blocked by someone by depositing rep to vault while the deposit is pending. We dont want to block depositReps for this duration thought as we want to allow people to participate escalation game using external rep. I feel after liquidation is triggered we should store a snapshot rep balance of the vault that is then used for liquidation calculations //price = (amount1 * PRICE_PRECISION) / amount2; // price = REP * PRICE_PRECISION / ETH // liquidation moves share of debt and rep to another pool which need to remain non-liquidable // this is currently very harsh, as we steal all the rep and debt from the pool - function performLiquidation(address callerVault, address targetVaultAddress, uint256 debtAmount) public isOperational { - require(msg.sender == address(priceOracleManagerAndOperatorQueuer), 'only priceOracleManagerAndOperatorQueuer can call'); - require(priceOracleManagerAndOperatorQueuer.isPriceValid(), 'no valid price'); + function performLiquidation(address callerVault, address targetVaultAddress, uint256 debtAmount) public isOperational onlyValidOracle { updateVaultFees(targetVaultAddress); updateVaultFees(callerVault); uint256 vaultsSecurityBondAllowance = securityVaults[targetVaultAddress].securityBondAllowance; @@ -214,7 +217,7 @@ contract SecurityPool is ISecurityPool { require(debtToMove > 0, 'no debt to move'); uint256 repToMove = debtToMove * vaultsRepDeposit / securityVaults[targetVaultAddress].securityBondAllowance; uint256 ownershipToMove = repToPoolOwnership(repToMove); - require((securityVaults[callerVault].securityBondAllowance + debtToMove) * securityMultiplier * repEthPrice <= (poolOwnershipToRep(securityVaults[callerVault].poolOwnership) + repToMove) * SecurityPoolUtils.PRICE_PRECISION, 'New pool would be liquidable!'); + require((securityVaults[callerVault].securityBondAllowance + debtToMove) * securityMultiplier * repEthPrice <= poolOwnershipToRep(securityVaults[callerVault].poolOwnership + ownershipToMove) * SecurityPoolUtils.PRICE_PRECISION, 'New pool would be liquidable!'); securityVaults[targetVaultAddress].securityBondAllowance -= debtToMove; securityVaults[targetVaultAddress].poolOwnership -= ownershipToMove; securityVaults[callerVault].securityBondAllowance += debtToMove; @@ -233,19 +236,17 @@ contract SecurityPool is ISecurityPool { // set security bond allowance //////////////////////////////////////// - function performSetSecurityBondsAllowance(address callerVault, uint256 amount) public isOperational { + function performSetSecurityBondsAllowance(address callerVault, uint256 amount) public isOperational onlyValidOracle { updateVaultFees(callerVault); - require(msg.sender == address(priceOracleManagerAndOperatorQueuer), 'only priceOracleManagerAndOperatorQueuer can call'); - require(priceOracleManagerAndOperatorQueuer.isPriceValid(), 'no valid price'); uint256 oldAllowance = securityVaults[callerVault].securityBondAllowance; - securityBondAllowance += amount; - securityBondAllowance -= oldAllowance; + totalSecurityBondAllowance += amount; + totalSecurityBondAllowance -= oldAllowance; securityVaults[callerVault].securityBondAllowance = amount; require(poolOwnershipToRep(securityVaults[callerVault].poolOwnership) * SecurityPoolUtils.PRICE_PRECISION > amount * priceOracleManagerAndOperatorQueuer.lastPrice()); - require(repToken.balanceOf(address(this)) * SecurityPoolUtils.PRICE_PRECISION > securityBondAllowance * priceOracleManagerAndOperatorQueuer.lastPrice()); - require(securityBondAllowance >= completeSetCollateralAmount, 'minted too many complete sets to allow this'); + require(repToken.balanceOf(address(this)) * SecurityPoolUtils.PRICE_PRECISION > totalSecurityBondAllowance * priceOracleManagerAndOperatorQueuer.lastPrice()); + require(totalSecurityBondAllowance >= completeSetCollateralAmount, 'minted too many complete sets to allow this'); require(securityVaults[callerVault].securityBondAllowance >= SecurityPoolUtils.MIN_SECURITY_BOND_DEBT || securityVaults[callerVault].securityBondAllowance == 0, 'min deposit requirement'); emit SecurityBondAllowanceChange(callerVault, oldAllowance, amount); updateRetentionRate(); @@ -254,11 +255,10 @@ contract SecurityPool is ISecurityPool { //////////////////////////////////////// // Complete Sets //////////////////////////////////////// - function createCompleteSet() payable public isOperational { + function createCompleteSet() payable public isOperational { // todo, we want to be able to create complete sets in the children right away, figure accounting out require(msg.value > 0, 'need to send eth'); - 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 >= msg.value + completeSetCollateralAmount, 'no capacity to create that many sets'); + require(totalSecurityBondAllowance >= msg.value + completeSetCollateralAmount, 'no capacity to create that many sets'); uint256 completeSetsToMint = cashToShares(msg.value); shareToken.mintCompleteSets(universeId, msg.sender, completeSetsToMint); shareTokenSupply += completeSetsToMint; @@ -267,8 +267,7 @@ contract SecurityPool is ISecurityPool { updateRetentionRate(); } - function redeemCompleteSet(uint256 completeSetAmount) public isOperational { - 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? + function redeemCompleteSet(uint256 completeSetAmount) public isOperational { // 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 = sharesToCash(completeSetAmount); @@ -281,8 +280,8 @@ contract SecurityPool is ISecurityPool { } function redeemShares() isOperational external { - Zoltar.Outcome outcome = zoltar.finalizeQuestion(universeId, questionId); - require(outcome != Zoltar.Outcome.None, 'Question has not finalized!'); + YesNoMarkets.Outcome outcome = ISecurityPoolForker(securityPoolForker).getMarketOutcome(this); + require(outcome != YesNoMarkets.Outcome.None, 'Market has not finalized!'); uint256 tokenId = shareToken.getTokenId(universeId, outcome); uint256 amount = shareToken.burnTokenId(tokenId, msg.sender); uint256 ethValue = sharesToCash(amount); @@ -292,147 +291,99 @@ contract SecurityPool is ISecurityPool { } function redeemRep(address vault) public { - Zoltar.Outcome outcome = zoltar.finalizeQuestion(universeId, questionId); - require(outcome != Zoltar.Outcome.None, 'Question has not finalized!'); + require(ISecurityPoolForker(securityPoolForker).getMarketOutcome(this) != YesNoMarkets.Outcome.None, 'Market has not finalized!'); updateVaultFees(vault); - uint256 repAmount = poolOwnershipToRep(securityVaults[vault].poolOwnership); + uint256 repAmount = poolOwnershipToRep(securityVaults[vault].poolOwnership) - securityVaults[vault].lockedRepInEscalationGame; securityVaults[vault].poolOwnership = 0; repToken.transfer(vault, repAmount); emit RedeemRep(msg.sender, vault, repAmount); } //////////////////////////////////////// - // FORKING (migrate vault (oi+rep), truth truthAuction) + // Escalation Game (migrate vault (oi+rep), truth truthAuction) //////////////////////////////////////// - function forkSecurityPool() public { - (,, 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(!zoltar.isFinalized(universeId, questionId), 'question has been finalized already'); - systemState = SystemState.PoolForked; - updateCollateralAmount(); - currentRetentionRate = 0; - repAtFork = repToken.balanceOf(address(this)); - emit ForkSecurityPool(repAtFork); - repToken.approve(address(zoltar), repAtFork); - zoltar.splitRep(universeId); - // TODO: we could pay the caller basefee*2 out of Open interest we have to reward caller - } - - function createChildUniverse(QuestionOutcome outcome) public { - (,, uint256 forkTime) = zoltar.universes(universeId); - require(systemState == SystemState.PoolForked, 'Pool needs to have forked'); - require(block.timestamp <= forkTime + SecurityPoolUtils.MIGRATION_TIME, 'migration time passed'); - _createChildUniverse(outcome); - } - - function _createChildUniverse(QuestionOutcome outcome) private { - // first vault migrater creates new pool and transfers all REP to it - uint192 childUniverseId = (universeId << 2) + uint192(outcome) + 1; - uint256 retentionRate = SecurityPoolUtils.calculateRetentionRate(completeSetCollateralAmount, securityBondAllowance); - children[uint8(outcome)] = securityPoolFactory.deployChildSecurityPool(shareToken, childUniverseId, questionId, securityMultiplier, retentionRate, priceOracleManagerAndOperatorQueuer.lastPrice(), 0); - shareToken.authorize(children[uint8(outcome)]); - ReputationToken childReputationToken = children[uint8(outcome)].repToken(); - childReputationToken.transfer(address(children[uint8(outcome)]), childReputationToken.balanceOf(address(this))); - } - - // migrates vault into outcome universe after fork - function migrateVault(QuestionOutcome outcome) public { // called on parent - (,, uint256 forkTime) = zoltar.universes(universeId); - require(systemState == SystemState.PoolForked, 'Pool needs to have forked'); - require(block.timestamp <= forkTime + SecurityPoolUtils.MIGRATION_TIME , 'migration time passed'); - updateVaultFees(msg.sender); - emit MigrateVault(msg.sender, outcome, securityVaults[msg.sender].poolOwnership, securityVaults[msg.sender].securityBondAllowance); - if (address(children[uint8(outcome)]) == address(0x0)) { - _createChildUniverse(outcome); - } - children[uint256(outcome)].migrateRepFromParent(msg.sender); - // migrate open interest - if (poolOwnershipDenominator > 0 && securityVaults[msg.sender].poolOwnership > 0) { - (bool sent, ) = payable(children[uint256(outcome)]).call{ value: completeSetCollateralAmount * securityVaults[msg.sender].poolOwnership / poolOwnershipDenominator }(''); - require(sent, 'Failed to send Ether'); + function depositToEscalationGame(YesNoMarkets.Outcome outcome, uint256 maxAmount) external isOperational { + if (address(escalationGame) == address(0x0)) { + uint256 endTime = yesNoMarkets.getMarketEndDate(marketId); + require(block.timestamp > endTime, 'market has not ended'); + escalationGame = escalationGameFactory.deployEscalationGame(TODO_INITIAL_ESCALATION_GAME_DEPOSIT, repToken.getTotalTheoreticalSupply() / (FORK_THRESHOLD_DIVISOR * 2)); } - securityVaults[msg.sender].poolOwnership = 0; - securityVaults[msg.sender].securityBondAllowance = 0; + securityVaults[msg.sender].lockedRepInEscalationGame += escalationGame.depositOnOutcome(msg.sender, outcome, maxAmount); + require(poolOwnershipToRep(securityVaults[msg.sender].poolOwnership) >= securityVaults[msg.sender].lockedRepInEscalationGame, 'Not enough REP'); } - function migrateRepFromParent(address vault) public { // called on children - require(msg.sender == address(parent), 'only parent can migrate'); - updateVaultFees(vault); - parent.updateCollateralAmount(); - (uint256 parentPoolOwnership, uint256 parentSecurityBondAllowance, , ) = parent.securityVaults(vault); - emit MigrateRepFromParent(vault, parentSecurityBondAllowance, parentPoolOwnership); - securityVaults[vault].securityBondAllowance = parentSecurityBondAllowance; - securityBondAllowance += parentSecurityBondAllowance; - poolOwnershipDenominator = parent.repAtFork() * SecurityPoolUtils.PRICE_PRECISION; - if (parent.poolOwnershipDenominator() == 0 || repToken.balanceOf(address(this)) == 0) return; - - securityVaults[vault].poolOwnership = repToPoolOwnership(parentPoolOwnership * parent.repAtFork() / parent.poolOwnershipDenominator()); - migratedRep += poolOwnershipToRep(securityVaults[vault].poolOwnership); - securityVaults[vault].feeIndex = feeIndex; - // migrate completeset collateral amount incrementally as we want this portion to start paying fees right away, but stop paying fees in the parent system - } - - function startTruthAuction() public { - (,, uint256 forkTime) = zoltar.universes(universeId); - require(systemState == SystemState.ForkMigration, 'System needs to be in migration'); - require(block.timestamp > forkTime + SecurityPoolUtils.MIGRATION_TIME, 'migration time needs to pass first'); - systemState = SystemState.ForkTruthAuction; - truthAuctionStarted = block.timestamp; - parent.updateCollateralAmount(); - uint256 parentCollateral = parent.completeSetCollateralAmount(); - shareTokenSupply = parent.shareTokenSupply(); - emit TruthAuctionStarted(parentCollateral, migratedRep, parent.repAtFork()); - if (migratedRep >= parent.repAtFork()) { - // we have acquired all the ETH already, no need for truthAuction - _finalizeTruthAuction(0); - } else { - // we need to buy all the collateral that is missing (did not migrate) - uint256 ethToBuy = parentCollateral - parentCollateral * migratedRep / parent.repAtFork(); - // sell all but very small amount of REP for ETH. We cannot sell all for accounting purposes, as `poolOwnershipDenominator` cannot be infinite - // only migratedRep gets this guarrantee that some of their rep never gets sold - truthAuction.startAuction(ethToBuy, parent.repAtFork() - migratedRep / SecurityPoolUtils.MAX_AUCTION_VAULT_HAIRCUT_DIVISOR); + function withdrawFromEscalationGame(uint256[] memory depositIndexes) external isOperational { + require(address(escalationGame) != address(0x0), 'escalation game needs to be deployed'); + YesNoMarkets.Outcome outcome = ISecurityPoolForker(securityPoolForker).getMarketOutcome(this); + require(outcome != YesNoMarkets.Outcome.None, 'Market has not finalized!'); + require(!escalationGame.hasReachedNonDecision(), 'cannot withdraw, escalation game is indecisive'); + for (uint256 index = 0; index < depositIndexes.length; index++) { + (address depositor, uint256 amountToWithdraw) = escalationGame.withdrawDeposit(depositIndexes[index]); + securityVaults[depositor].poolOwnership += repToPoolOwnership(amountToWithdraw); } } - function _finalizeTruthAuction(uint256 repPurchased) private { - require(systemState == SystemState.ForkTruthAuction, 'Auction need to have started'); - truthAuction.finalizeAuction(); // this sends the eth back - systemState = SystemState.Operational; - uint256 repAvailable = parent.repAtFork(); - completeSetCollateralAmount = address(this).balance - totalFeesOvedToVaults; //todo, we might want to reduce fees if we didn't get fully funded? - if (repAvailable > 0) poolOwnershipDenominator = migratedRep * repAvailable * SecurityPoolUtils.PRICE_PRECISION / (repAvailable - repPurchased); - auctionedSecurityBondAllowance = parent.securityBondAllowance() - securityBondAllowance; - securityBondAllowance = parent.securityBondAllowance(); - if (poolOwnershipDenominator == 0) poolOwnershipDenominator = repAvailable * SecurityPoolUtils.PRICE_PRECISION; - emit FinalizeAuction(repAvailable, migratedRep, repPurchased, poolOwnershipDenominator); - updateRetentionRate(); + // todo, cleanup these only forker functions by minimizing amount and adding checks + function setSystemState(SystemState newState) external onlyForker { + systemState = newState; } - function finalizeTruthAuction() public { - require(block.timestamp > truthAuctionStarted + SecurityPoolUtils.AUCTION_TIME, 'truthAuction still ongoing'); - _finalizeTruthAuction(truthAuction.totalRepPurchased()); + function setRetentionRate(uint256 newRetention) external onlyForker { + currentRetentionRate = newRetention; } - receive() external payable { - // needed for Truth Auction to send ETH back + function setVaultOwnership(address vault, uint256 _poolOwnership, uint256 _securityBondAllowance) external onlyForker { + securityVaults[vault].poolOwnership = _poolOwnership; + securityVaults[vault].securityBondAllowance = _securityBondAllowance; + } + + function setVaultSecurityBondAllowance(address vault, uint256 _securityBondAllowance) external onlyForker { + securityVaults[vault].securityBondAllowance = _securityBondAllowance; + + } + function addToTotalSecurityBondAllowance(uint256 securityBondAllowanceDelta) external onlyForker { + totalSecurityBondAllowance += securityBondAllowanceDelta; + } + + function setPoolOwnershipDenominator(uint256 _poolOwnershipDenominator) external onlyForker { + poolOwnershipDenominator = _poolOwnershipDenominator; + } + + function setVaultPoolOwnership(address vault, uint256 poolOwnership) external onlyForker { + securityVaults[vault].poolOwnership = poolOwnership; + } + + function setVaultFeeIndex(address vault, uint256 newFeeIndex) external onlyForker { + securityVaults[vault].feeIndex = newFeeIndex; + } + + function setShareTokenSupply(uint256 newShareTokenSupply) external onlyForker { + shareTokenSupply = newShareTokenSupply; + } + + function setCompleteSetCollateralAmount(uint256 newCompleteSetCollateralAmount) external onlyForker { + completeSetCollateralAmount = newCompleteSetCollateralAmount; + } + + function setTotalSecurityBondAllowance(uint256 newTotalSecurityBondAllowance) external onlyForker { + totalSecurityBondAllowance = newTotalSecurityBondAllowance; + } + + function stealAllRep() external onlyForker() { + repToken.transfer(msg.sender, repToken.balanceOf(address(this))); } - // accounts the purchased REP from truthAuction to the vault - // we should also move a share of bad debt in the system to this vault - // anyone can call these so that we can liquidate them if needed - function claimAuctionProceeds(address vault) public { - require(claimedAuctionProceeds[vault] == false, 'Already Claimed'); - require(truthAuction.finalized(), 'Auction needs to be finalized'); - claimedAuctionProceeds[vault] = true; - uint256 amount = truthAuction.purchasedRep(vault); - require(amount > 0, 'Did not purchase anything'); // not really necessary, but good for testing - uint256 poolOwnershipAmount = repToPoolOwnership(amount); - securityVaults[vault].poolOwnership += poolOwnershipAmount; // no need to add to poolOwnershipDenominator as its already accounted - emit ClaimAuctionProceeds(vault, amount, poolOwnershipAmount, poolOwnershipDenominator); - securityVaults[vault].securityBondAllowance = auctionedSecurityBondAllowance * amount / truthAuction.totalRepPurchased(); + function migrateEth(address payable receiver, uint256 amount) external onlyForker() { + (bool sent, ) = receiver.call{ value: amount }(''); + require(sent, 'Failed to steal ETH'); + } + function authorize(ISecurityPool pool) external onlyForker() { + shareToken.authorize(pool); } - // todo, fee calculation doesn't work yet accross forks + receive() external payable { + // needed for Truth Auction to send ETH back + // TODO, add check that its truth auction sending + } } diff --git a/solidity/contracts/peripherals/SecurityPoolForker.sol b/solidity/contracts/peripherals/SecurityPoolForker.sol new file mode 100644 index 0000000..1e0331d --- /dev/null +++ b/solidity/contracts/peripherals/SecurityPoolForker.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.33; + +import { ReputationToken } from '../ReputationToken.sol'; +import { Zoltar } from '../Zoltar.sol'; +import { Auction } from './Auction.sol'; +import { ISecurityPool, ISecurityPoolFactory, SystemState } from './interfaces/ISecurityPool.sol'; +import { IShareToken } from './interfaces/IShareToken.sol'; +import { EscalationGame } from './EscalationGame.sol'; +import { YesNoMarkets } from './YesNoMarkets.sol'; +import { SecurityPoolUtils } from './SecurityPoolUtils.sol'; +import { ISecurityPoolForker } from './interfaces/ISecurityPoolForker.sol'; + +struct ForkData { + uint256 repAtFork; + mapping(uint8 => ISecurityPool) children; // outcome -> children + Auction truthAuction; + mapping(address => bool) claimedAuctionProceeds; + uint256 truthAuctionStarted; + uint256 migratedRep; + uint256 auctionedSecurityBondAllowance; + + bool ownFork; + uint8 outcomeIndex; +} + +contract SecurityPoolForker is ISecurityPoolForker { + Zoltar public immutable zoltar; + YesNoMarkets public yesNoMarkets; + + mapping(ISecurityPool => ForkData) public forkData; + + event ForkSecurityPool(uint256 repAtFork); + event MigrateVault(address vault, uint8 outcome, uint256 poolOwnership, uint256 securityBondAllowance, uint256 parentLockedRepInEscalationGame); + event TruthAuctionStarted(uint256 completeSetCollateralAmount, uint256 repMigrated, uint256 repAtFork); + event TruthAuctionFinalized(); + event ClaimAuctionProceeds(address vault, uint256 amount, uint256 poolOwnershipAmount, uint256 poolOwnershipDenominator); + event MigrateRepFromParent(address vault, uint256 parentSecurityBondAllowance, uint256 parentpoolOwnership); + event FinalizeAuction(uint256 repAvailable, uint256 migratedRep, uint256 repPurchased, uint256 poolOwnershipDenominator, uint256 completeSetCollateralAmount); + event MigrateFromEscalationGame(ISecurityPool parent, address vault, YesNoMarkets.Outcome outcomeIndex, uint8[] depositIndexes, uint256 totalRep, uint256 newOwnership); + + function repToPoolOwnership(ISecurityPool securityPool, uint256 repAmount) public view returns (uint256) { + if (securityPool.poolOwnershipDenominator() == 0) return repAmount * SecurityPoolUtils.PRICE_PRECISION; + return repAmount * securityPool.poolOwnershipDenominator() / securityPool.repToken().balanceOf(address(securityPool)); + } + + function poolOwnershipToRep(ISecurityPool securityPool, uint256 poolOwnership) public view returns (uint256) { + return poolOwnership * securityPool.repToken().balanceOf(address(securityPool)) / securityPool.poolOwnershipDenominator(); + } + function getMigratedRep(ISecurityPool securityPool) public view returns (uint256) { + return forkData[securityPool].migratedRep; + } + + constructor(Zoltar _zoltar) { + zoltar = _zoltar; + } + + function forkSecurityPool(ISecurityPool securityPool) public { + uint248 universe = securityPool.universeId(); + EscalationGame escalationGame = securityPool.escalationGame(); + require(zoltar.getForkTime(universe) > 0, 'Zoltar needs to have forked before Security Pool can do so'); + require(securityPool.systemState() == SystemState.Operational, 'System is not operational'); + require(address(escalationGame) == address(0x0) || escalationGame.getMarketResolution() == YesNoMarkets.Outcome.None, 'question has been finalized already'); + securityPool.setSystemState(SystemState.PoolForked); + securityPool.updateCollateralAmount(); + securityPool.setRetentionRate(0); + ReputationToken rep = securityPool.repToken(); + securityPool.stealAllRep(); + forkData[securityPool].repAtFork = rep.balanceOf(address(this)); + + uint8[] memory outcomeIndices = new uint8[](4 + 1); + for (uint8 index = 0; index < outcomeIndices.length; index++) { + outcomeIndices[index] = index; + } + rep.approve(address(zoltar), type(uint256).max); + zoltar.splitRep(universe, outcomeIndices); + if (zoltar.getForkedBy(universe) == address(this)) { + forkData[securityPool].repAtFork += zoltar.getForkerDeposit(universe); + zoltar.forkerClaimRep(universe, outcomeIndices); + } + emit ForkSecurityPool(forkData[securityPool].repAtFork); + // TODO: we could pay the caller basefee*2 out of Open interest. We have to reward caller + } + + function createChildUniverse(ISecurityPool parent, uint8 outcomeIndex) public { + require(address(forkData[parent].children[outcomeIndex]) == address(0x0), 'child already created'); + require(parent.systemState() == SystemState.PoolForked, 'Pool needs to have forked'); + require(block.timestamp <= zoltar.getForkTime(parent.universeId()) + SecurityPoolUtils.MIGRATION_TIME , 'migration time passed'); + // first vault migrater creates new pool and transfers all REP to it + uint248 childUniverseId = uint248(uint256(keccak256(abi.encode(parent.universeId(), outcomeIndex)))); + uint256 retentionRate = SecurityPoolUtils.calculateRetentionRate(parent.completeSetCollateralAmount(), parent.totalSecurityBondAllowance()); + (ISecurityPool child, Auction truthAuction) = parent.securityPoolFactory().deployChildSecurityPool(parent, parent.shareToken(), childUniverseId, parent.marketId(), parent.securityMultiplier(), retentionRate, parent.priceOracleManagerAndOperatorQueuer().lastPrice(), 0); + forkData[child].outcomeIndex = outcomeIndex; + forkData[child].truthAuction = truthAuction; + forkData[parent].children[outcomeIndex] = child; + parent.authorize(child); + ReputationToken childReputationToken = child.repToken(); + childReputationToken.transfer(address(child), childReputationToken.balanceOf(address(this))); + + if (forkData[parent].ownFork) { + child.setPoolOwnershipDenominator(parent.poolOwnershipDenominator() * forkData[parent].repAtFork / (forkData[parent].repAtFork + parent.escalationGame().nonDecisionThreshold()*2/5) ); + } else { + child.setPoolOwnershipDenominator(parent.poolOwnershipDenominator()); + } + } + + // todo, atm this needs to be called after migratevault + function migrateFromEscalationGame(ISecurityPool parent, address vault, YesNoMarkets.Outcome outcomeIndex, uint8[] memory depositIndexes) public { + EscalationGame escalationGame = parent.escalationGame(); + if (address(forkData[parent].children[uint8(outcomeIndex)]) == address(0x0)) createChildUniverse(parent, uint8(outcomeIndex)); + ISecurityPool child = forkData[parent].children[uint8(outcomeIndex)]; + require(address(escalationGame) != address(0x0), 'escalation game needs to be deployed'); + uint256 repMigratedFromEscalationGame = 0; + for (uint256 index = 0; index < depositIndexes.length; index++) { + (address depositor, uint256 amountToWithdraw) = escalationGame.claimDepositForWinning(depositIndexes[index], outcomeIndex); + require(depositor == vault, 'deposit was not for this vault'); + repMigratedFromEscalationGame += amountToWithdraw; + } + (uint256 poolOwnership, , , , ) = child.securityVaults(vault); + uint256 ownershipDelta = repToPoolOwnership(child, repMigratedFromEscalationGame); + child.setVaultPoolOwnership(vault, poolOwnership + ownershipDelta); + forkData[child].migratedRep += repMigratedFromEscalationGame; + emit MigrateFromEscalationGame(parent, vault, outcomeIndex, depositIndexes, repMigratedFromEscalationGame, ownershipDelta); + // migrate open interest + parent.migrateEth(payable(child), parent.completeSetCollateralAmount() * repMigratedFromEscalationGame / forkData[parent].repAtFork); + + } + + // migrates vault into outcome universe after fork + function migrateVault(ISecurityPool parent, uint8 outcomeIndex) public { + parent.updateVaultFees(msg.sender); + if (address(forkData[parent].children[outcomeIndex]) == address(0x0)) createChildUniverse(parent, outcomeIndex); + ISecurityPool child = forkData[parent].children[outcomeIndex]; + + child.updateVaultFees(msg.sender); + parent.updateCollateralAmount(); + (uint256 parentPoolOwnership, uint256 parentSecurityBondAllowance, , , uint256 parentLockedRepInEscalationGame) = parent.securityVaults(msg.sender); + emit MigrateRepFromParent(msg.sender, parentSecurityBondAllowance, parentPoolOwnership); + child.setVaultSecurityBondAllowance(msg.sender, parentSecurityBondAllowance); + child.addToTotalSecurityBondAllowance(parentSecurityBondAllowance); + + if (parent.poolOwnershipDenominator() != 0 && child.repToken().balanceOf(address(child)) != 0) { + uint256 ownership = parentPoolOwnership - repToPoolOwnership(child, parentLockedRepInEscalationGame); + child.setVaultPoolOwnership(msg.sender, ownership); + uint256 migratedRep = poolOwnershipToRep(child, ownership); + forkData[child].migratedRep += migratedRep; + child.setVaultFeeIndex(msg.sender, child.feeIndex()); + // migrate open interest + if (ownership > 0) { + parent.migrateEth(payable(child), parent.completeSetCollateralAmount() * migratedRep / forkData[parent].repAtFork); + } + } + + (uint256 poolOwnership, uint256 securityBondAllowance,,,) = parent.securityVaults(msg.sender); + emit MigrateVault(msg.sender, outcomeIndex, poolOwnership, securityBondAllowance, parentLockedRepInEscalationGame); + parent.setVaultOwnership(msg.sender, 0, 0); + } + + function startTruthAuction(ISecurityPool securityPool) public { + require(securityPool.systemState() == SystemState.ForkMigration, 'System needs to be in migration'); + require(block.timestamp > zoltar.getForkTime(securityPool.universeId()) + SecurityPoolUtils.MIGRATION_TIME, 'migration time needs to pass first'); + securityPool.setSystemState(SystemState.ForkTruthAuction); + forkData[securityPool].truthAuctionStarted = block.timestamp; + ISecurityPool parent = securityPool.parent(); + parent.updateCollateralAmount(); + uint256 parentCollateral = parent.completeSetCollateralAmount(); + securityPool.setShareTokenSupply(parent.shareTokenSupply()); + emit TruthAuctionStarted(parentCollateral, forkData[securityPool].migratedRep, forkData[parent].repAtFork); + if (forkData[securityPool].migratedRep >= forkData[parent].repAtFork) { + // we have acquired all the ETH already, no need for truthAuction + _finalizeTruthAuction(securityPool, 0); + } else { + // we need to buy all the collateral that is missing (did not migrate) + uint256 ethToBuy = parentCollateral - parentCollateral * forkData[securityPool].migratedRep / forkData[parent].repAtFork; + // sell all but very small amount of REP for ETH. We cannot sell all for accounting purposes, as `poolOwnershipDenominator` cannot be infinite + // only migratedRep gets this guarantee that some of their rep never gets sold + forkData[securityPool].truthAuction.startAuction(ethToBuy, forkData[parent].repAtFork - forkData[securityPool].migratedRep / SecurityPoolUtils.MAX_AUCTION_VAULT_HAIRCUT_DIVISOR); + } + } + + function _finalizeTruthAuction(ISecurityPool securityPool, uint256 repPurchased) private { + require(securityPool.systemState() == SystemState.ForkTruthAuction, 'Auction needs to have started'); + forkData[securityPool].truthAuction.finalizeAuction(address(securityPool)); // this sends the eth back + securityPool.setSystemState(SystemState.Operational); + ISecurityPool parent = securityPool.parent(); + uint256 repAvailable = forkData[parent].repAtFork; + securityPool.setCompleteSetCollateralAmount(address(securityPool).balance - securityPool.totalFeesOwedToVaults()); //todo, we might want to reduce fees if we didn't get fully funded? + uint256 parentTotalSecurityBondAllowance = parent.totalSecurityBondAllowance(); + forkData[securityPool].auctionedSecurityBondAllowance = parentTotalSecurityBondAllowance - securityPool.totalSecurityBondAllowance(); + securityPool.setTotalSecurityBondAllowance(parentTotalSecurityBondAllowance); + if (repAvailable > 0) { + securityPool.setPoolOwnershipDenominator(forkData[securityPool].migratedRep * repAvailable * SecurityPoolUtils.PRICE_PRECISION / (repAvailable - repPurchased)); + } + if (securityPool.poolOwnershipDenominator() == 0) { // wipe all rep holders in vaults + securityPool.setPoolOwnershipDenominator(repAvailable * SecurityPoolUtils.PRICE_PRECISION); + } + emit FinalizeAuction(repAvailable, forkData[securityPool].migratedRep, repPurchased, securityPool.poolOwnershipDenominator(), securityPool.completeSetCollateralAmount()); + securityPool.updateRetentionRate(); + } + + function finalizeTruthAuction(ISecurityPool securityPool) public { + require(block.timestamp > forkData[securityPool].truthAuctionStarted + SecurityPoolUtils.AUCTION_TIME, 'truthAuction still ongoing'); + _finalizeTruthAuction(securityPool, forkData[securityPool].truthAuction.totalRepPurchased()); + } + + function forkZoltarWithOwnEscalationGame(ISecurityPool securityPool) public { + EscalationGame escalationGame = securityPool.escalationGame(); + require(address(escalationGame) != address(0x0) && escalationGame.nonDecisionTimestamp() > 0, 'escalation game has not triggered fork'); + (string memory extraInfo, string[4] memory outcomes) = securityPool.yesNoMarkets().getForkingData(securityPool.marketId()); + securityPool.stealAllRep(); + forkData[securityPool].ownFork = true; + securityPool.repToken().approve(address(zoltar), type(uint256).max); + zoltar.forkUniverse(securityPool.universeId(), extraInfo, outcomes); + } + + // accounts the purchased REP from truthAuction to the vault + // we should also move a share of bad debt in the system to this vault + // anyone can call these so that we can liquidate them if needed + function claimAuctionProceeds(ISecurityPool securityPool, address vault) public { + require(forkData[securityPool].claimedAuctionProceeds[vault] == false, 'Already Claimed'); + require(forkData[securityPool].truthAuction.finalized(), 'Auction needs to be finalized'); + forkData[securityPool].claimedAuctionProceeds[vault] = true; + uint256 amount = forkData[securityPool].truthAuction.purchasedRep(vault); + require(amount > 0, 'Did not purchase anything'); // not really necessary, but good for testing + uint256 poolOwnershipAmount = repToPoolOwnership(securityPool, amount); + (uint256 poolOwnership,,,,) = securityPool.securityVaults(vault); + securityPool.setVaultOwnership(vault, poolOwnership + poolOwnershipAmount, forkData[securityPool].auctionedSecurityBondAllowance * amount / forkData[securityPool].truthAuction.totalRepPurchased()); + emit ClaimAuctionProceeds(vault, amount, poolOwnershipAmount, securityPool.poolOwnershipDenominator()); + } + + function getMarketOutcome(ISecurityPool securityPool) external returns (YesNoMarkets.Outcome outcome){ + SystemState systemState = securityPool.systemState(); + if (systemState == SystemState.PoolForked) return YesNoMarkets.Outcome.None; + ISecurityPool parent = securityPool.parent(); + if (address(parent) != address(0x0)) { + if (forkData[parent].ownFork) return YesNoMarkets.Outcome(forkData[securityPool].outcomeIndex); + } + if (systemState == SystemState.Operational) { + EscalationGame escalationGame = securityPool.escalationGame(); + uint256 forkTime = zoltar.getForkTime(securityPool.universeId()); + if (address(escalationGame) != address(0x0)) { + uint256 escalationEndDate = escalationGame.getEscalationGameEndDate(); + if (block.timestamp > escalationEndDate && (forkTime == 0 || escalationEndDate < forkTime)) return escalationGame.getMarketResolution(); + } + } + return YesNoMarkets.Outcome.None; + } +} diff --git a/solidity/contracts/peripherals/SecurityPoolUtils.sol b/solidity/contracts/peripherals/SecurityPoolUtils.sol index 2441cf8..c11856f 100644 --- a/solidity/contracts/peripherals/SecurityPoolUtils.sol +++ b/solidity/contracts/peripherals/SecurityPoolUtils.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; library SecurityPoolUtils { @@ -26,7 +26,7 @@ library SecurityPoolUtils { } } - // starts from MAX_RETENTION_RATE, decrases linearly until RETENTION_RATE_DIP% utilization is hit and then caps to MIN_RETENTION_RATE + // starts from MAX_RETENTION_RATE, decreases linearly until RETENTION_RATE_DIP% utilization is hit and then caps to MIN_RETENTION_RATE // TODO: research more on how this should work function calculateRetentionRate(uint256 completeSetCollateralAmount, uint256 securityBondAllowance) external pure returns (uint256 z) { if (securityBondAllowance == 0) return MAX_RETENTION_RATE; diff --git a/solidity/contracts/peripherals/YesNoMarkets.sol b/solidity/contracts/peripherals/YesNoMarkets.sol new file mode 100644 index 0000000..3fe6ee2 --- /dev/null +++ b/solidity/contracts/peripherals/YesNoMarkets.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.33; + +contract YesNoMarkets { + enum Outcome { + Invalid, + Yes, + No, + None + } + + struct MarketData { + string extraInfo; + uint256 marketEndDate; + uint256 marketCreated; + } + + mapping(uint256 => MarketData) markets; + + function createMarket(string memory extraInfo, uint256 marketEndDate, bytes32 salt) external returns (uint256) { + uint256 marketId = uint256(keccak256(abi.encode(msg.sender, extraInfo, marketEndDate, salt))); + require(markets[marketId].marketCreated == 0, 'Market already exists'); + markets[marketId].extraInfo = extraInfo; + markets[marketId].marketCreated = block.timestamp; + markets[marketId].marketEndDate = marketEndDate; + return marketId; + } + + function getMarketEndDate(uint256 marketId) external view returns (uint256) { + return markets[marketId].marketEndDate; + } + + function getForkingData(uint256 marketId) external view returns (string memory extraInfo, string[4] memory outcomes) { + extraInfo = markets[marketId].extraInfo; + outcomes[0] = 'Yes'; + outcomes[1] = 'No'; + } +} diff --git a/solidity/contracts/peripherals/factories/AuctionFactory.sol b/solidity/contracts/peripherals/factories/AuctionFactory.sol index 46e8f94..7c335ba 100644 --- a/solidity/contracts/peripherals/factories/AuctionFactory.sol +++ b/solidity/contracts/peripherals/factories/AuctionFactory.sol @@ -1,9 +1,9 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import { Auction } from '../Auction.sol'; contract AuctionFactory { function deployAuction(bytes32 salt) external returns (Auction) { - return new Auction{ salt: keccak256(abi.encodePacked(msg.sender, salt)) }(); + return new Auction{ salt: keccak256(abi.encode(msg.sender, salt)) }(); } } diff --git a/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol b/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol index 421a572..2ba4c6c 100644 --- a/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol +++ b/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol @@ -1,6 +1,5 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; -import { IShareToken } from '../interfaces/IShareToken.sol'; import { ShareToken } from '../tokens/ShareToken.sol'; import { ISecurityPool } from '../interfaces/ISecurityPool.sol'; import { Zoltar } from '../../Zoltar.sol'; @@ -10,6 +9,6 @@ import { PriceOracleManagerAndOperatorQueuer } from '../PriceOracleManagerAndOpe 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); + return new PriceOracleManagerAndOperatorQueuer{ salt: keccak256(abi.encode(msg.sender, salt)) }(_openOracle, _reputationToken); } } diff --git a/solidity/contracts/peripherals/factories/SecurityPoolFactory.sol b/solidity/contracts/peripherals/factories/SecurityPoolFactory.sol index aba9ba3..043d48e 100644 --- a/solidity/contracts/peripherals/factories/SecurityPoolFactory.sol +++ b/solidity/contracts/peripherals/factories/SecurityPoolFactory.sol @@ -1,6 +1,5 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; -import { IShareToken } from '../interfaces/IShareToken.sol'; import { SecurityPool } from '../SecurityPool.sol'; import { ISecurityPool, ISecurityPoolFactory } from '../interfaces/ISecurityPool.sol'; import { OpenOracle } from '../openOracle/OpenOracle.sol'; @@ -8,10 +7,13 @@ 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 { IShareToken } from '../interfaces/IShareToken.sol'; import { PriceOracleManagerAndOperatorQueuerFactory } from './PriceOracleManagerAndOperatorQueuerFactory.sol'; import { PriceOracleManagerAndOperatorQueuer } from '../PriceOracleManagerAndOperatorQueuer.sol'; import { ReputationToken } from '../../ReputationToken.sol'; +import { EscalationGameFactory } from './EscalationGameFactory.sol'; +import { YesNoMarkets } from '../YesNoMarkets.sol'; +import { ISecurityPoolForker } from '../interfaces/ISecurityPoolForker.sol'; contract SecurityPoolFactory is ISecurityPoolFactory { ShareTokenFactory shareTokenFactory; @@ -19,50 +21,57 @@ contract SecurityPoolFactory is ISecurityPoolFactory { PriceOracleManagerAndOperatorQueuerFactory priceOracleManagerAndOperatorQueuerFactory; Zoltar zoltar; OpenOracle openOracle; + EscalationGameFactory escalationGameFactory; + YesNoMarkets yesNoMarkets; + ISecurityPoolForker securityPoolForker; - event DeploySecurityPool(ISecurityPool securityPool, Auction truthAuction, PriceOracleManagerAndOperatorQueuer priceOracleManagerAndOperatorQueuer, IShareToken shareToken, ISecurityPool parent, uint192 universeId, uint56 questionId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount); + event DeploySecurityPool(ISecurityPool securityPool, Auction truthAuction, PriceOracleManagerAndOperatorQueuer priceOracleManagerAndOperatorQueuer, IShareToken shareToken, ISecurityPool parent, uint248 universeId, uint256 marketId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount); - constructor(OpenOracle _openOracle, Zoltar _zoltar, ShareTokenFactory _shareTokenFactory, AuctionFactory _auctionFactory, PriceOracleManagerAndOperatorQueuerFactory _priceOracleManagerAndOperatorQueuerFactory) { + constructor(ISecurityPoolForker _securityPoolForker, YesNoMarkets _yesNoMarkets, EscalationGameFactory _escalationGameFactory, OpenOracle _openOracle, Zoltar _zoltar, ShareTokenFactory _shareTokenFactory, AuctionFactory _auctionFactory, PriceOracleManagerAndOperatorQueuerFactory _priceOracleManagerAndOperatorQueuerFactory) { + securityPoolForker = _securityPoolForker; shareTokenFactory = _shareTokenFactory; auctionFactory = _auctionFactory; priceOracleManagerAndOperatorQueuerFactory = _priceOracleManagerAndOperatorQueuerFactory; zoltar = _zoltar; openOracle = _openOracle; + escalationGameFactory = _escalationGameFactory; + yesNoMarkets = _yesNoMarkets; } - 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); + function deployChildSecurityPool(ISecurityPool parent, IShareToken shareToken, uint248 universeId, uint256 marketId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPool, Auction truthAuction) { + require(msg.sender == address(securityPoolForker), 'only securityPoolForker'); + bytes32 securityPoolSalt = keccak256(abi.encode(parent, universeId, marketId, securityMultiplier)); + ReputationToken reputationToken = zoltar.getRepToken(universeId); PriceOracleManagerAndOperatorQueuer priceOracleManagerAndOperatorQueuer = priceOracleManagerAndOperatorQueuerFactory.deployPriceOracleManagerAndOperatorQueuer(openOracle, reputationToken, securityPoolSalt); - Auction truthAuction = auctionFactory.deployAuction(securityPoolSalt); + truthAuction = auctionFactory.deployAuction(securityPoolSalt); - securityPool = new SecurityPool{ salt: bytes32(uint256(0x0)) }(this, truthAuction, priceOracleManagerAndOperatorQueuer, shareToken, openOracle, parent, zoltar, universeId, questionId, securityMultiplier); + securityPool = new SecurityPool{ salt: bytes32(uint256(0x0)) }(address(securityPoolForker), this, yesNoMarkets, escalationGameFactory, priceOracleManagerAndOperatorQueuer, shareToken, openOracle, parent, zoltar, universeId, marketId, 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); + truthAuction.setOwner(address(securityPoolForker)); + emit DeploySecurityPool(securityPool, truthAuction, priceOracleManagerAndOperatorQueuer, shareToken, parent, universeId, marketId, 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); + function deployOriginSecurityPool(uint248 universeId, string memory extraInfo, uint256 marketEndDate, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice) external returns (ISecurityPool securityPool) { + uint256 marketId = yesNoMarkets.createMarket(extraInfo, marketEndDate, keccak256(abi.encode(address(this), universeId, securityMultiplier, extraInfo, marketEndDate))); + ReputationToken reputationToken = zoltar.getRepToken(universeId); + bytes32 securityPoolSalt = keccak256(abi.encode(address(0x0), universeId, marketId, securityMultiplier)); 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); + bytes32 shareTokenSalt = keccak256(abi.encode(securityMultiplier, marketId)); + IShareToken shareToken = shareTokenFactory.deployShareToken(shareTokenSalt); - securityPool = new SecurityPool{ salt: bytes32(uint256(0x0)) }(this, Auction(address(0x0)), priceOracleManagerAndOperatorQueuer, shareToken, openOracle, ISecurityPool(payable(0x0)), zoltar, universeId, questionId, securityMultiplier); + securityPool = new SecurityPool{ salt: bytes32(uint256(0x0)) }(address(securityPoolForker), this, yesNoMarkets, escalationGameFactory, priceOracleManagerAndOperatorQueuer, shareToken, openOracle, ISecurityPool(payable(0x0)), zoltar, universeId, marketId, securityMultiplier); priceOracleManagerAndOperatorQueuer.setSecurityPool(securityPool); - securityPool.setStartingParams(currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); + securityPool.setStartingParams(currentRetentionRate, startingRepEthPrice, 0); shareToken.authorize(securityPool); - emit DeploySecurityPool(securityPool, Auction(address(0x0)), priceOracleManagerAndOperatorQueuer, shareToken, ISecurityPool(payable(0x0)), universeId, questionId, securityMultiplier, currentRetentionRate, startingRepEthPrice, completeSetCollateralAmount); + emit DeploySecurityPool(securityPool, Auction(address(0x0)), priceOracleManagerAndOperatorQueuer, shareToken, ISecurityPool(payable(0x0)), universeId, marketId, securityMultiplier, currentRetentionRate, startingRepEthPrice, 0); } } diff --git a/solidity/contracts/peripherals/factories/ShareTokenFactory.sol b/solidity/contracts/peripherals/factories/ShareTokenFactory.sol index 33b5483..3a4ce68 100644 --- a/solidity/contracts/peripherals/factories/ShareTokenFactory.sol +++ b/solidity/contracts/peripherals/factories/ShareTokenFactory.sol @@ -1,6 +1,5 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; -import { IShareToken } from '../interfaces/IShareToken.sol'; import { ShareToken } from '../tokens/ShareToken.sol'; import { ISecurityPool } from '../interfaces/ISecurityPool.sol'; import { Zoltar } from '../../Zoltar.sol'; @@ -12,7 +11,7 @@ contract ShareTokenFactory { zoltar = _zoltar; } - function deployShareToken(uint56 questionId, bytes32 salt) external returns (IShareToken shareToken) { - return new ShareToken{ salt: salt }(msg.sender, zoltar, questionId); + function deployShareToken(bytes32 salt) external returns (ShareToken shareToken) { + return new ShareToken{ salt: salt }(msg.sender, zoltar); } } diff --git a/solidity/contracts/peripherals/interfaces/IAugur.sol b/solidity/contracts/peripherals/interfaces/IAugur.sol index b367b73..842a9d0 100644 --- a/solidity/contracts/peripherals/interfaces/IAugur.sol +++ b/solidity/contracts/peripherals/interfaces/IAugur.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; enum TokenType { @@ -17,7 +17,7 @@ interface IAugur { function createChildUniverse(bytes32 _parentPayoutDistributionHash, uint256[] memory _parentPayoutNumerators) external returns (address); function isKnownUniverse(address _universe) external view returns (bool); function trustedCashTransfer(address _from, address _to, uint256 _amount) external returns (bool); - function isTrustedSender(address _address) external returns (bool); + function isTrustedSender(address _address) external view returns (bool); function onCategoricalMarketCreated(uint256 _endTime, string memory _extraInfo, address _market, address _marketCreator, address _designatedReporter, uint256 _feePerCashInAttoCash, bytes32[] memory _outcomes) external returns (bool); function onYesNoMarketCreated(uint256 _endTime, string memory _extraInfo, address _market, address _marketCreator, address _designatedReporter, uint256 _feePerCashInAttoCash) external returns (bool); function onScalarMarketCreated(uint256 _endTime, string memory _extraInfo, address _market, address _marketCreator, address _designatedReporter, uint256 _feePerCashInAttoCash, int256[] memory _prices, uint256 _numTicks) external returns (bool); @@ -56,7 +56,7 @@ interface IAugur { function isKnownFeeSender(address _feeSender) external view returns (bool); function lookup(bytes32 _key) external view returns (address); function getTimestamp() external view returns (uint256); - function getMaximumMarketEndDate() external returns (uint256); + function getMaximumMarketEndDate() external view returns (uint256); function isKnownMarket(address _market) external view returns (bool); function derivePayoutDistributionHash(uint256[] memory _payoutNumerators, uint256 _numTicks, uint256 numOutcomes) external view returns (bytes32); function logValidityBondChanged(uint256 _validityBond) external returns (bool); diff --git a/solidity/contracts/peripherals/interfaces/ISecurityPool.sol b/solidity/contracts/peripherals/interfaces/ISecurityPool.sol index fa9a90b..1a59497 100644 --- a/solidity/contracts/peripherals/interfaces/ISecurityPool.sol +++ b/solidity/contracts/peripherals/interfaces/ISecurityPool.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import { Zoltar } from '../../Zoltar.sol'; @@ -7,12 +7,15 @@ import { Auction } from "../Auction.sol"; import { IShareToken } from "./IShareToken.sol"; import { ReputationToken } from "../../ReputationToken.sol"; import { PriceOracleManagerAndOperatorQueuer } from "../PriceOracleManagerAndOperatorQueuer.sol"; +import { EscalationGame } from '../EscalationGame.sol'; +import { YesNoMarkets } from '../YesNoMarkets.sol'; struct SecurityVault { uint256 poolOwnership; uint256 securityBondAllowance; uint256 unpaidEthFees; uint256 feeIndex; + uint256 lockedRepInEscalationGame; } enum SystemState { @@ -31,26 +34,20 @@ enum QuestionOutcome { interface ISecurityPool { // -------- View Functions -------- - function questionId() external view returns (uint56); - function universeId() external view returns (uint192); + function marketId() external view returns (uint256); + function universeId() external view returns (uint248); function zoltar() external view returns (Zoltar); - function securityBondAllowance() external view returns (uint256); + function totalSecurityBondAllowance() external view returns (uint256); function completeSetCollateralAmount() external view returns (uint256); function poolOwnershipDenominator() external view returns (uint256); - function repAtFork() external view returns (uint256); - function migratedRep() external view returns (uint256); function securityMultiplier() external view returns (uint256); - function totalFeesOvedToVaults() external view returns (uint256); + function totalFeesOwedToVaults() external view returns (uint256); function lastUpdatedFeeAccumulator() external view returns (uint256); function currentRetentionRate() external view returns (uint256); - function securityVaults(address vault) external view returns (uint256 poolOwnership, uint256 securityBondAllowance, uint256 unpaidEthFees, uint256 feeIndex); - function claimedAuctionProceeds(address vault) external view returns (bool); - function children(uint256 index) external view returns (ISecurityPool); + function securityVaults(address vault) external view returns (uint256 poolOwnership, uint256 securityBondAllowance, uint256 unpaidEthFees, uint256 feeIndex, uint256 lockedRepInEscalationGame); function parent() external view returns (ISecurityPool); - function truthAuctionStarted() external view returns (uint256); function systemState() external view returns (SystemState); function shareToken() external view returns (IShareToken); - function truthAuction() external view returns (Auction); function repToken() external view returns (ReputationToken); function securityPoolFactory() external view returns (ISecurityPoolFactory); function priceOracleManagerAndOperatorQueuer() external view returns (PriceOracleManagerAndOperatorQueuer); @@ -80,20 +77,31 @@ interface ISecurityPool { function createCompleteSet() external payable; function redeemCompleteSet(uint256 amount) external; - function forkSecurityPool() external; - function migrateVault(QuestionOutcome outcome) external; - function migrateRepFromParent(address vault) external; - function startTruthAuction() external; - function finalizeTruthAuction() external; - function claimAuctionProceeds(address vault) external; - function createChildUniverse(QuestionOutcome outcome) external; - - function redeemShares() external; + function escalationGame() external view returns (EscalationGame); + function setRetentionRate(uint256 newRetention) external; + function setSystemState(SystemState newState) external; + function setVaultOwnership(address vault, uint256 _poolOwnership, uint256 _securityBondAllowance) external; + + function setVaultSecurityBondAllowance(address vault, uint256 _securityBondAllowance) external; + function addToTotalSecurityBondAllowance(uint256 securityBondAllowanceDelta) external; + function setPoolOwnershipDenominator(uint256 _poolOwnershipDenominator) external; + function setVaultPoolOwnership(address vault, uint256 poolOwnership) external; + function setVaultFeeIndex(address vault, uint256 newFeeIndex) external; + function feeIndex() external view returns (uint256); + function setShareTokenSupply(uint256 newShareTokenSupply) external; + function setCompleteSetCollateralAmount(uint256 newCompleteSetCollateralAmount) external; + function setTotalSecurityBondAllowance(uint256 newTotalSecurityBondAllowance) external; + function authorize(ISecurityPool pool) external; + function yesNoMarkets() external view returns (YesNoMarkets); + function stealAllRep() external; + function migrateEth(address payable child, uint256 amount) external; + + function securityPoolForker() external view returns (address); receive() external payable; } interface ISecurityPoolFactory { - 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); + function deployChildSecurityPool(ISecurityPool parent, IShareToken shareToken, uint248 universeId, uint256 marketId, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice, uint256 completeSetCollateralAmount) external returns (ISecurityPool securityPool, Auction truthAuction); + function deployOriginSecurityPool(uint248 universeId, string memory extraInfo, uint256 marketEndDate, uint256 securityMultiplier, uint256 currentRetentionRate, uint256 startingRepEthPrice) external returns (ISecurityPool securityPool); } diff --git a/solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol b/solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol new file mode 100644 index 0000000..1be25e3 --- /dev/null +++ b/solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.33; + +import { ISecurityPool } from './ISecurityPool.sol'; +import { YesNoMarkets } from '../YesNoMarkets.sol'; + +interface ISecurityPoolForker { + function forkSecurityPool(ISecurityPool securityPool) external; + function createChildUniverse(ISecurityPool securityPool, uint8 outcomeIndex) external; + function migrateVault(ISecurityPool securityPool, uint8 outcomeIndex) external; + function startTruthAuction(ISecurityPool securityPool) external; + function finalizeTruthAuction(ISecurityPool securityPool) external; + function forkZoltarWithOwnEscalationGame(ISecurityPool securityPool) external; + function claimAuctionProceeds(ISecurityPool securityPool, address vault) external; + function getMarketOutcome(ISecurityPool securityPool) external view returns (YesNoMarkets.Outcome outcome); +} diff --git a/solidity/contracts/peripherals/interfaces/IShareToken.sol b/solidity/contracts/peripherals/interfaces/IShareToken.sol index 398df80..bcef89f 100644 --- a/solidity/contracts/peripherals/interfaces/IShareToken.sol +++ b/solidity/contracts/peripherals/interfaces/IShareToken.sol @@ -1,39 +1,22 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; -import '../interfaces/ISecurityPool.sol'; import '../../Zoltar.sol'; +import '../interfaces/ISecurityPool.sol'; +import '../tokens/TokenId.sol'; +import '../YesNoMarkets.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 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); + function mintCompleteSets(uint248 _universeId, address _account, uint256 _cashAmount) external payable; + function burnCompleteSets(uint248 _universeId, address _owner, uint256 _amount) external; + function burnTokenId(uint256 _tokenId, address _owner) external returns (uint256 balance); + function getUniverse(uint256 _tokenId) external pure returns(uint248); + function getOutcome(uint256 _tokenId) external pure returns(YesNoMarkets.Outcome); + function totalSupplyForOutcome(uint248 _universeId, YesNoMarkets.Outcome _outcome) external view returns (uint256); + function balanceOfOutcome(uint248 _universeId, YesNoMarkets.Outcome _outcome, address _account) external view returns (uint256); + function balanceOfShares(uint248 _universeId, address _account) external view returns (uint256[3] memory balances); + function getTokenId(uint248 _universeId, YesNoMarkets.Outcome _outcome) external pure returns (uint256 _tokenId); + function getTokenIds(uint248 _universeId, YesNoMarkets.Outcome[] memory _outcomes) external pure returns (uint256[] memory _tokenIds); + function unpackTokenId(uint256 _tokenId) external pure returns (uint248 _universe, YesNoMarkets.Outcome _outcome); } diff --git a/solidity/contracts/peripherals/interfaces/IWeth9.sol b/solidity/contracts/peripherals/interfaces/IWeth9.sol index feedd1b..4e78312 100644 --- a/solidity/contracts/peripherals/interfaces/IWeth9.sol +++ b/solidity/contracts/peripherals/interfaces/IWeth9.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; interface IWeth9 { diff --git a/solidity/contracts/peripherals/tokens/ERC1155.sol b/solidity/contracts/peripherals/tokens/ERC1155.sol index 85685dd..53a1635 100644 --- a/solidity/contracts/peripherals/tokens/ERC1155.sol +++ b/solidity/contracts/peripherals/tokens/ERC1155.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import { IERC1155 } from '../interfaces/IERC1155.sol'; @@ -16,7 +16,7 @@ contract ERC1155 is IERC1155 { mapping (uint256 => mapping(address => uint256)) public _balances; // Mapping from token ID to total supply - mapping (uint256 => uint256) public _supplys; + mapping (uint256 => uint256) public _supplies; // Mapping from account to operator approvals mapping (address => mapping(address => bool)) public _operatorApprovals; @@ -38,7 +38,7 @@ contract ERC1155 is IERC1155 { } function totalSupply(uint256 id) public view returns (uint256) { - return _supplys[id]; + return _supplies[id]; } /** @@ -222,7 +222,7 @@ contract ERC1155 is IERC1155 { require(to != address(0), "ERC1155: mint to the zero address"); _balances[id][to] = _balances[id][to] + value; - _supplys[id] = _supplys[id] + value; + _supplies[id] = _supplies[id] + value; emit TransferSingle(msg.sender, address(0), to, id, value); } @@ -239,7 +239,7 @@ contract ERC1155 is IERC1155 { 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]; + _supplies[ids[i]] = _supplies[ids[i]] + values[i]; } emit TransferBatch(msg.sender, address(0), to, ids, values); @@ -255,7 +255,7 @@ contract ERC1155 is IERC1155 { require(account != address(0), "ERC1155: attempting to burn tokens on zero account"); _balances[id][account] = _balances[id][account] - value; - _supplys[id] = _supplys[id] - value; + _supplies[id] = _supplies[id] - value; emit TransferSingle(msg.sender, account, address(0), id, value); } @@ -271,7 +271,7 @@ contract ERC1155 is IERC1155 { 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]; + _supplies[ids[i]] = _supplies[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 index 1bfba57..ae3ded2 100644 --- a/solidity/contracts/peripherals/tokens/ForkedERC1155.sol +++ b/solidity/contracts/peripherals/tokens/ForkedERC1155.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import './ERC1155.sol'; @@ -9,28 +9,27 @@ abstract contract ForkedERC1155 is ERC1155 { event Migrate(address migrator, uint256 fromId, uint256 toId, uint256 fromIdBalance); constructor() {} - function universeHasForked(uint192 universeId) internal virtual view returns (bool); + function universeHasForked(uint248 universeId) internal virtual view returns (bool); - function getUniverseId(uint256 id) internal virtual pure returns (uint192); + function getUniverseId(uint256 id) internal virtual pure returns (uint248); - function getChildId(uint256 originalId, uint192 newUniverse) internal virtual pure returns (uint256); + function getChildId(uint256 originalId, uint248 newUniverse) internal virtual pure returns (uint256); + function getChildUniverseId(uint248 universeId, uint8 outcomeIndex) public virtual pure returns (uint248); - // 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"); + function migrate(uint256 fromId, uint8[] memory outcomes) external { + uint248 universeId = getUniverseId(fromId); + require(universeHasForked(universeId), 'Universe has not forked'); uint256 fromIdBalance = _balances[fromId][msg.sender]; _balances[fromId][msg.sender] = 0; - _supplys[fromId] -= fromIdBalance; + _supplies[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); + // TODO, check that outcomes are unique + // TODO, do we allow people to migrate later to different universes? + for (uint8 i = 0; i < outcomes.length; i++) { + uint256 toId = getChildId(fromId, getChildUniverseId(universeId, outcomes[i])); _balances[toId][msg.sender] += fromIdBalance; - _supplys[toId] += fromIdBalance; + _supplies[toId] += fromIdBalance; emit Migrate(msg.sender, fromId, toId, fromIdBalance); } } diff --git a/solidity/contracts/peripherals/tokens/ShareToken.sol b/solidity/contracts/peripherals/tokens/ShareToken.sol index e417183..f086310 100644 --- a/solidity/contracts/peripherals/tokens/ShareToken.sol +++ b/solidity/contracts/peripherals/tokens/ShareToken.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import '../../Constants.sol'; @@ -6,6 +6,8 @@ import './ForkedERC1155.sol'; import './TokenId.sol'; import '../../Zoltar.sol'; import '../interfaces/ISecurityPool.sol'; +import '../interfaces/IShareToken.sol'; +import '../YesNoMarkets.sol'; /** * @title Share Token @@ -13,60 +15,60 @@ import '../interfaces/ISecurityPool.sol'; */ contract ShareToken is ForkedERC1155, IShareToken { - string constant public name = "Shares"; - string constant public symbol = "SHARE"; + // TODO, rename based on the market they represent + string constant public name = 'Shares'; + string constant public symbol = 'SHARE'; Zoltar public immutable zoltar; - uint56 public immutable questionId; mapping(address => bool) authorized; + event Authorized(address indexed securityPool); - function universeHasForked(uint192 universeId) internal override view returns (bool) { - (,, uint256 forkTime) = zoltar.universes(universeId); - return forkTime > 0; + function universeHasForked(uint248 universeId) internal override view returns (bool) { + return zoltar.getForkTime(universeId) > 0; } - constructor(address owner, Zoltar _zoltar, uint56 _questionId) { + constructor(address owner, Zoltar _zoltar) { zoltar = _zoltar; - questionId = _questionId; authorized[owner] = true; } function authorize(ISecurityPool _securityPoolCandidate) external { - require(authorized[msg.sender], 'caller is not owner'); + require(authorized[msg.sender], 'not authorized'); authorized[address(_securityPoolCandidate)] = true; + emit Authorized(address(_securityPoolCandidate)); } - function getUniverseId(uint256 id) internal override pure returns (uint192 universeId) { + function getUniverseId(uint256 id) internal override pure returns (uint248 universeId) { assembly { - universeId := shr(64, and(id, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000)) + universeId := shr(8, and(id, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00)) } } - function getChildId(uint256 originalId, uint192 newUniverse) internal override pure returns (uint256 newId) { + function getChildId(uint256 originalId, uint248 newUniverse) internal override pure returns (uint256 newId) { assembly { - newId := or(shr(192, shl(192, originalId)), shl(64, newUniverse)) + newId := or(and(originalId, 0xFF), shl(8, and(newUniverse, 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))) } } - function mintCompleteSets(uint192 _universeId, address _account, uint256 _cashAmount) external payable { + function mintCompleteSets(uint248 _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)); + _tokenIds[i] = TokenId.getTokenId(_universeId, YesNoMarkets.Outcome(i)); _values[i] = _cashAmount; } _mintBatch(_account, _tokenIds, _values); } - function burnCompleteSets(uint192 _universeId, address _owner, uint256 _amount) external { + function burnCompleteSets(uint248 _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)); + _tokenIds[i] = TokenId.getTokenId(_universeId, YesNoMarkets.Outcome(i)); _values[i] = _amount; } @@ -79,41 +81,45 @@ contract ShareToken is ForkedERC1155, IShareToken { _burn(_owner, _tokenId, balance); } - function getUniverse(uint256 _tokenId) external pure returns(uint256) { - (uint192 _universe, ) = TokenId.unpackTokenId(_tokenId); + function getChildUniverseId(uint248 universeId, uint8 outcomeIndex) public override pure returns (uint248) { + return uint248(uint256(keccak256(abi.encode(universeId, outcomeIndex)))); + } + + function getUniverse(uint256 _tokenId) external pure returns(uint248) { + (uint248 _universe, ) = TokenId.unpackTokenId(_tokenId); return _universe; } - function getOutcome(uint256 _tokenId) external pure returns(Zoltar.Outcome) { - (, Zoltar.Outcome _outcome) = TokenId.unpackTokenId(_tokenId); + function getOutcome(uint256 _tokenId) external pure returns(YesNoMarkets.Outcome) { + (, YesNoMarkets.Outcome _outcome) = TokenId.unpackTokenId(_tokenId); return _outcome; } - function totalSupplyForOutcome(uint192 _universeId, Zoltar.Outcome _outcome) public view returns (uint256) { + function totalSupplyForOutcome(uint248 _universeId, YesNoMarkets.Outcome _outcome) public view returns (uint256) { uint256 _tokenId = getTokenId(_universeId, _outcome); return totalSupply(_tokenId); } - function balanceOfOutcome(uint192 _universeId, Zoltar.Outcome _outcome, address _account) public view returns (uint256) { + function balanceOfOutcome(uint248 _universeId, YesNoMarkets.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 balanceOfShares(uint248 _universeId, address _account) public view returns (uint256[3] memory balances) { + balances[0] = balanceOf(_account, getTokenId(_universeId, YesNoMarkets.Outcome.Invalid)); + balances[1] = balanceOf(_account, getTokenId(_universeId, YesNoMarkets.Outcome.Yes)); + balances[2] = balanceOf(_account, getTokenId(_universeId, YesNoMarkets.Outcome.No)); } - function getTokenId(uint192 _universeId, Zoltar.Outcome _outcome) public pure returns (uint256 _tokenId) { + function getTokenId(uint248 _universeId, YesNoMarkets.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) { + function getTokenIds(uint248 _universeId, YesNoMarkets.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) { + function unpackTokenId(uint256 _tokenId) public override pure returns (uint248 _universe, YesNoMarkets.Outcome _outcome) { return TokenId.unpackTokenId(_tokenId); } } diff --git a/solidity/contracts/peripherals/tokens/TokenId.sol b/solidity/contracts/peripherals/tokens/TokenId.sol index 874898f..7dc43ae 100644 --- a/solidity/contracts/peripherals/tokens/TokenId.sol +++ b/solidity/contracts/peripherals/tokens/TokenId.sol @@ -1,28 +1,28 @@ -// SPDX-License-Identifier: UNICENSE +// SPDX-License-Identifier: Unlicense pragma solidity 0.8.33; import '../../Zoltar.sol'; +import { YesNoMarkets } from '../YesNoMarkets.sol'; library TokenId { - function getTokenId(uint192 _universeId, Zoltar.Outcome _outcome) internal pure returns (uint256 _tokenId) { - bytes memory _tokenIdBytes = abi.encodePacked(_universeId, uint56(0), _outcome); + function getTokenId(uint248 _universeId, YesNoMarkets.Outcome _outcome) internal pure returns (uint256 _tokenId) { assembly { - _tokenId := mload(add(_tokenIdBytes, add(0x20, 0))) + _tokenId := or(shl(8, and(_universeId, 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)), and(_outcome, 0xFF)) } } - function getTokenIds(uint192 _universeId, Zoltar.Outcome[] memory _outcomes) internal pure returns (uint256[] memory _tokenIds) { + function getTokenIds(uint248 _universeId, YesNoMarkets.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) { + function unpackTokenId(uint256 _tokenId) internal pure returns (uint248 _universe, YesNoMarkets.Outcome _outcome) { assembly { - _universe := shr(64, and(_tokenId, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000)) - _outcome := and(_tokenId, 0x00000000000000000000000000000000000000000000000000000000000000FF) + _universe := shr(8, and(_tokenId, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00)) + _outcome := and(_tokenId, 0xFF) } } } diff --git a/solidity/ts/abi/abis.ts b/solidity/ts/abi/abis.ts index 68b82b7..4f56127 100644 --- a/solidity/ts/abi/abis.ts +++ b/solidity/ts/abi/abis.ts @@ -1,6 +1,6 @@ export const ABIS = { 'mainnet': { 'erc20': [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}], - 'erc1155': [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"value","type":"string"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"URI","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"_balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"_operatorApprovals","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"_supplys","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}], + 'erc1155': [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"value","type":"string"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"URI","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"_balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"_operatorApprovals","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"_supplies","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}], } } as const diff --git a/solidity/ts/tests/testPeripherals.ts b/solidity/ts/tests/testPeripherals.ts index a509a8f..9fe377c 100644 --- a/solidity/ts/tests/testPeripherals.ts +++ b/solidity/ts/tests/testPeripherals.ts @@ -1,289 +1,363 @@ -import { describe, beforeEach, test } from 'node:test' +import test, { describe, beforeEach } from 'node:test' +import assert from 'node:assert' import { getMockedEthSimulateWindowEthereum, MockWindowEthereum } from '../testsuite/simulator/MockWindowEthereum.js' import { createWriteClient, WriteClient } from '../testsuite/simulator/utils/viem.js' import { DAY, GENESIS_REPUTATION_TOKEN, TEST_ADDRESSES } from '../testsuite/simulator/utils/constants.js' -import { approximatelyEqual, contractExists, finalizeQuestion, getChildUniverseId, getERC20Balance, getETHBalance, getQuestionData, getReportBond, getRepTokenAddress, getWinningOutcome, isFinalized, reportOutcome, setupTestAccounts } from '../testsuite/simulator/utils/utilities.js' +import { approveToken, contractExists, getChildUniverseId, getERC20Balance, getETHBalance, setupTestAccounts } from '../testsuite/simulator/utils/utilities.js' import { addressString, dateToBigintSeconds, rpow } from '../testsuite/simulator/utils/bigint.js' -import assert from 'node:assert' -import { SystemState } from '../testsuite/simulator/types/peripheralTypes.js' -import { getDeployments } from '../testsuite/simulator/utils/deployments.js' +import { getDeployments } from '../testsuite/simulator/utils/contracts/deployments.js' import { createTransactionExplainer } from '../testsuite/simulator/utils/transactionExplainer.js' -import { approveAndDepositRep, canLiquidate, deployPeripherals, deployZoltarAndCreateMarket, genesisUniverse, MAX_RETENTION_RATE, questionId, manipulatePriceOracleAndPerformOperation, securityMultiplier, triggerFork, manipulatePriceOracle, handleOracleReporting } from '../testsuite/simulator/utils/peripheralsTestUtils.js' -import { getSecurityPoolAddresses } from '../testsuite/simulator/utils/deployPeripherals.js' -import { balanceOfShares, balanceOfSharesInCash, claimAuctionProceeds, createChildUniverse, createCompleteSet, finalizeTruthAuction, forkSecurityPool, getCompleteSetCollateralAmount, getCurrentRetentionRate, getEthAmountToBuy, getTotalFeesOvedToVaults, getLastPrice, getMigratedRep, getPoolOwnershipDenominator, getSecurityBondAllowance, getSecurityVault, getSystemState, migrateShares, migrateVault, OperationType, participateAuction, poolOwnershipToRep, redeemCompleteSet, redeemFees, redeemShares, sharesToCash, startTruthAuction, updateVaultFees, redeemRep, requestPriceIfNeededAndQueueOperation, depositRep } from '../testsuite/simulator/utils/peripherals.js' +import { approveAndDepositRep, canLiquidate, handleOracleReporting, manipulatePriceOracle, manipulatePriceOracleAndPerformOperation, triggerOwnGameFork } from '../testsuite/simulator/utils/contracts/peripheralsTestUtils.js' +import { deployOriginSecurityPool, ensureInfraDeployed, getInfraContractAddresses, getMarketId, getSecurityPoolAddresses } from '../testsuite/simulator/utils/contracts/deployPeripherals.js' +import { balanceOfShares, balanceOfSharesInCash, getEthAmountToBuy, getLastPrice, getMarketEndDate, migrateShares, OperationType, participateAuction, requestPriceIfNeededAndQueueOperation } from '../testsuite/simulator/utils/contracts/peripherals.js' import { QuestionOutcome } from '../testsuite/simulator/types/types.js' +import { approximatelyEqual, strictEqual18Decimal, strictEqualTypeSafe } from '../testsuite/simulator/utils/testUtils.js' +import { claimAuctionProceeds, createChildUniverse, finalizeTruthAuction, forkSecurityPool, getMarketOutcome, getMigratedRep, getSecurityPoolForkerForkData, migrateFromEscalationGame, migrateVault, startTruthAuction } from '../testsuite/simulator/utils/contracts/securityPoolForker.js' +import { SystemState } from '../testsuite/simulator/types/peripheralTypes.js' +import { getEscalationGameDeposits, getMarketResolution, getnonDecisionThreshold, getStartBond } from '../testsuite/simulator/utils/contracts/escalationGame.js' +import { ensureZoltarDeployed, forkUniverse, getRepTokenAddress, getTotalTheoreticalSupply, getUniverseForkData, getZoltarAddress, getZoltarforkThreshold } from '../testsuite/simulator/utils/contracts/zoltar.js' +import { createCompleteSet, depositRep, depositToEscalationGame, getCompleteSetCollateralAmount, getCurrentRetentionRate, getPoolOwnershipDenominator, getRepToken, getSecurityPoolsEscalationGame, getSecurityVault, getSystemState, getTotalFeesOwedToVaults, getTotalSecurityBondAllowance, poolOwnershipToRep, redeemCompleteSet, redeemFees, redeemRep, redeemShares, sharesToCash, updateVaultFees, withdrawFromEscalationGame } from '../testsuite/simulator/utils/contracts/securityPool.js' describe('Peripherals Contract Test Suite', () => { let mockWindow: MockWindowEthereum let client: WriteClient let startBalance: bigint - let reportBond: bigint + const reportBond = 1n * 10n ** 18n const PRICE_PRECISION = 1n * 10n ** 18n const repDeposit = 1000n * 10n ** 18n const currentTimestamp = dateToBigintSeconds(new Date()) + const marketEndDate = currentTimestamp + 365n * DAY let securityPoolAddresses: { securityPool: `0x${ string }`, priceOracleManagerAndOperatorQueuer: `0x${ string }`, shareToken: `0x${ string }`, - truthAuction: `0x${ string }` + truthAuction: `0x${ string }`, + escalationGame: `0x${ string }`, } + const genesisUniverse = 0n + const securityMultiplier = 2n + const startingRepEthPrice = 10n + const MAX_RETENTION_RATE = 999_999_996_848_000_000n // ≈90% yearly + const EXTRA_INFO = 'test market!' + const marketId = getMarketId(genesisUniverse, securityMultiplier, EXTRA_INFO, marketEndDate) + + const marketText = 'test market' + const outcomes = ['Outcome 1', 'Outcome 2', 'Outcome 3', 'Outcome 4'] as const beforeEach(async () => { mockWindow = getMockedEthSimulateWindowEthereum() - mockWindow.setAfterTransactionSendCallBack(createTransactionExplainer(getDeployments(genesisUniverse, questionId, securityMultiplier))) + mockWindow.setAfterTransactionSendCallBack(createTransactionExplainer(getDeployments(genesisUniverse, marketId, securityMultiplier))) client = createWriteClient(mockWindow, TEST_ADDRESSES[0], 0) //await mockWindow.setStartBLock(mockWindow.getTime) await setupTestAccounts(mockWindow) startBalance = await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address) - await deployZoltarAndCreateMarket(client, currentTimestamp + 365n * DAY) - await deployPeripherals(client) - await approveAndDepositRep(client, repDeposit) - securityPoolAddresses = getSecurityPoolAddresses(addressString(0x0n), genesisUniverse, questionId, securityMultiplier) - reportBond = await getReportBond(client) + await ensureZoltarDeployed(client) + await ensureInfraDeployed(client) + await deployOriginSecurityPool(client, genesisUniverse, EXTRA_INFO, marketEndDate, securityMultiplier, MAX_RETENTION_RATE, startingRepEthPrice) + + await approveAndDepositRep(client, repDeposit, marketId) + securityPoolAddresses = getSecurityPoolAddresses(addressString(0x0n), genesisUniverse, marketId, securityMultiplier) }) test('can deposit rep and withdraw it', async () => { await manipulatePriceOracleAndPerformOperation(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.WithdrawRep, client.account.address, repDeposit) - assert.strictEqual(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + strictEqualTypeSafe(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') approximatelyEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddresses.securityPool), 0n, 100n, 'Did not empty security pool of rep') - approximatelyEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address), startBalance - reportBond, 100n, 'Did not get rep back') + approximatelyEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address), startBalance, 100n, 'Did not get rep back') }) test('can deposit rep and redeem it back after market has ended', async () => { await manipulatePriceOracle(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer) - assert.strictEqual(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + strictEqualTypeSafe(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + const poolOwnershipDenominator = await getPoolOwnershipDenominator(client, securityPoolAddresses.securityPool) + assert.ok(poolOwnershipDenominator > 0n, 'poolOwnershipDenominator was zero') + const endTime = await getMarketEndDate(client, marketId) + await mockWindow.setTime(endTime + 10000n) + await depositToEscalationGame(client, securityPoolAddresses.securityPool, QuestionOutcome.Yes, reportBond) + const escalationGameAddress = await getSecurityPoolsEscalationGame(client, securityPoolAddresses.securityPool) + strictEqualTypeSafe(escalationGameAddress, securityPoolAddresses.escalationGame, 'escalation game addresses do not match') + + assert.ok(await getnonDecisionThreshold(client, securityPoolAddresses.escalationGame) > 10n * reportBond, 'fork treshold need to be big enough') + await mockWindow.advanceTime(10n * DAY) + const yesDeposits = await getEscalationGameDeposits(client, securityPoolAddresses.escalationGame, QuestionOutcome.Yes) + strictEqualTypeSafe(yesDeposits.length, 1, 'there should be one deposit') + strictEqualTypeSafe(yesDeposits[0].depositIndex, 0n, 'index should be zero') + strictEqualTypeSafe(yesDeposits[0].depositor, client.account.address, 'wrong depositor') + strictEqualTypeSafe(yesDeposits[0].cumulativeAmount, reportBond, 'cumulator should be report bond') + strictEqualTypeSafe(yesDeposits[0].amount, reportBond, 'amount should be report bond') + strictEqualTypeSafe(await getStartBond(client, securityPoolAddresses.escalationGame), reportBond, 'report bond matches') + + const ourDeposits = yesDeposits.filter((deposit) => BigInt(deposit.depositor) === BigInt(client.account.address)) + strictEqualTypeSafe(await getMarketResolution(client, securityPoolAddresses.escalationGame), QuestionOutcome.Yes, 'market has resolved') + await withdrawFromEscalationGame(client, securityPoolAddresses.securityPool, ourDeposits.map((deposit) => deposit.depositIndex)) - const questionData = await getQuestionData(client, questionId) - await mockWindow.setTime(questionData.endTime + 10000n) - await reportOutcome(client, genesisUniverse, questionId, QuestionOutcome.Yes) - await mockWindow.advanceTime(2n * DAY) - await finalizeQuestion(client, genesisUniverse, questionId) const repBefore = await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address) await redeemRep(client, securityPoolAddresses.securityPool, client.account.address) const repAfter = await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address) - assert.strictEqual(repAfter-repBefore, repDeposit, 'did not get rep back') - assert.strictEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddresses.securityPool), 0n, 'Did not empty security pool of rep') + strictEqualTypeSafe(repAfter-repBefore, repDeposit, 'did not get rep back') + strictEqualTypeSafe(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddresses.securityPool), 0n, 'Did not empty security pool of rep') }) test('Can Liquidate', async () => { - const questionData = await getQuestionData(client, questionId) - await mockWindow.setTime(questionData.endTime + 10000n) + const endTime = await getMarketEndDate(client, marketId) + await mockWindow.setTime(endTime + 10000n) const securityPoolAllowance = repDeposit / 4n - assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max'); + strictEqualTypeSafe(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max'); await manipulatePriceOracleAndPerformOperation(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) const initialPrice = await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer) - assert.strictEqual(initialPrice, 1n * PRICE_PRECISION, 'Price was not set!') - assert.strictEqual(await getSecurityBondAllowance(client, securityPoolAddresses.securityPool), securityPoolAllowance, 'Security pool allowance was not set correctly') + strictEqualTypeSafe(initialPrice, 1n * PRICE_PRECISION, 'Price was not set!') + strictEqualTypeSafe(await getTotalSecurityBondAllowance(client, securityPoolAddresses.securityPool), securityPoolAllowance, 'Security pool allowance was not set correctly') + const liquidatorClient = createWriteClient(mockWindow, TEST_ADDRESSES[1], 0) + await approveToken(liquidatorClient, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddresses.securityPool) + await depositRep(liquidatorClient, securityPoolAddresses.securityPool, repDeposit * 10n) const openInterestAmount = 100n * 10n ** 18n await createCompleteSet(client, securityPoolAddresses.securityPool, openInterestAmount) await mockWindow.advanceTime(100000n) - const liquidatorClient = createWriteClient(mockWindow, TEST_ADDRESSES[1], 0) - - assert.strictEqual(canLiquidate(initialPrice, securityPoolAllowance, repDeposit, 2n), false, 'Should not be able to liquidate yet') + strictEqualTypeSafe(canLiquidate(initialPrice, securityPoolAllowance, repDeposit, 2n), false, 'Should not be able to liquidate yet') // REP/ETH increases to 10x, 10 REP = 1 ETH (rep drops in value) const forcedPrice = PRICE_PRECISION * 10n await requestPriceIfNeededAndQueueOperation(liquidatorClient, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.Liquidation, client.account.address, securityPoolAllowance) - assert.rejects(depositRep(client, securityPoolAddresses.securityPool, repDeposit * 10n), 'operation pending') + + // TODO: this should reject as we should not allow user to block liquidation like this + //assert.rejects(depositRep(client, securityPoolAddresses.securityPool, repDeposit * 10n), 'operation pending') await handleOracleReporting(liquidatorClient, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, forcedPrice) const currentPrice = await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer) - assert.strictEqual(currentPrice, PRICE_PRECISION * 10n, 'Price did not increase!') + strictEqualTypeSafe(currentPrice, PRICE_PRECISION * 10n, 'Price did not increase!') - assert.strictEqual(canLiquidate(currentPrice, securityPoolAllowance, repDeposit, 2n), true, 'Should be able to liquidate now') + strictEqualTypeSafe(canLiquidate(currentPrice, securityPoolAllowance, repDeposit, 2n), true, 'Should be able to liquidate now') // liquidator should have all the assets now const originalVault = await getSecurityVault(client, securityPoolAddresses.securityPool, client.account.address) const liquidatorVault = await getSecurityVault(client, securityPoolAddresses.securityPool, liquidatorClient.account.address) - assert.strictEqual(originalVault.securityBondAllowance, 0n, 'original vault should not have any security bonds') - assert.strictEqual(originalVault.repDepositShare, 0n, 'original vault should not have any rep') - assert.strictEqual(liquidatorVault.securityBondAllowance, securityPoolAllowance, 'liquidator doesnt have all the security pool allowances') - assert.strictEqual(liquidatorVault.repDepositShare / PRICE_PRECISION, repDeposit+(repDeposit * 10n), 'liquidator should have all the rep in the pool') + strictEqualTypeSafe(originalVault.securityBondAllowance, 0n, 'original vault should not have any security bonds') + strictEqualTypeSafe(originalVault.repDepositShare, 0n, 'original vault should not have any rep') + strictEqualTypeSafe(liquidatorVault.securityBondAllowance, securityPoolAllowance, 'liquidator doesnt have all the security pool allowances') + strictEqualTypeSafe(liquidatorVault.repDepositShare / PRICE_PRECISION, repDeposit+(repDeposit * 10n), 'liquidator should have all the rep in the pool') }) test('Open Interest Fees (non forking)', async () => { - const questionData = await getQuestionData(client, questionId) - assert.strictEqual(questionData.endTime > dateToBigintSeconds(new Date), true, 'market has already ended') + const endTime = await getMarketEndDate(client, marketId) + strictEqualTypeSafe(endTime > dateToBigintSeconds(new Date), true, 'market has already ended') const securityPoolAllowance = repDeposit / 4n const aMonthFromNow = currentTimestamp + 2628000n - assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max') + strictEqualTypeSafe(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max') await manipulatePriceOracleAndPerformOperation(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') + strictEqualTypeSafe(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + strictEqualTypeSafe(await getTotalSecurityBondAllowance(client, securityPoolAddresses.securityPool), securityPoolAllowance, 'Security pool allowance was not set correctly') const openInterestAmount = 100n * 10n ** 18n await mockWindow.setTime(aMonthFromNow) await createCompleteSet(client, securityPoolAddresses.securityPool, openInterestAmount) const retentionRate = await getCurrentRetentionRate(client, securityPoolAddresses.securityPool) - await mockWindow.setTime(questionData.endTime + 10000n) + await mockWindow.setTime(endTime + 10000n) await updateVaultFees(client, securityPoolAddresses.securityPool, client.account.address) - const feesAccrued = await getTotalFeesOvedToVaults(client, securityPoolAddresses.securityPool) + const feesAccrued = await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool) const ethBalanceBefore = await getETHBalance(client, client.account.address) const securityVault = await getSecurityVault(client, securityPoolAddresses.securityPool, client.account.address) await redeemFees(client, securityPoolAddresses.securityPool, client.account.address) - assert.strictEqual(securityVault.securityBondAllowance, securityPoolAllowance, 'securityPoolAllowance is all ours') + strictEqualTypeSafe(securityVault.securityBondAllowance, securityPoolAllowance, 'securityPoolAllowance is all ours') const ethBalanceAfter = await getETHBalance(client, client.account.address) - assert.strictEqual(ethBalanceAfter - ethBalanceBefore, securityVault.unpaidEthFees, 'eth gained should be fees accrued') - assert.strictEqual(feesAccrued / 1000n, securityVault.unpaidEthFees / 1000n, 'eth gained should be fees accrued (minus rounding issues') + strictEqualTypeSafe(ethBalanceAfter - ethBalanceBefore, securityVault.unpaidEthFees, 'eth gained should be fees accrued') + strictEqualTypeSafe(feesAccrued / 1000n, securityVault.unpaidEthFees / 1000n, 'eth gained should be fees accrued (minus rounding issues)') const completeSetCollateralAmount = await getCompleteSetCollateralAmount(client, securityPoolAddresses.securityPool) - assert.strictEqual(feesAccrued + completeSetCollateralAmount, openInterestAmount, 'no eth lost') - const timePassed = questionData.endTime - aMonthFromNow - assert.strictEqual(timePassed / 8640n, 3345n, 'not enough time passed') - assert.strictEqual(retentionRate, 999999987364000000n, 'retention rate did not match') + strictEqualTypeSafe(feesAccrued + completeSetCollateralAmount, openInterestAmount, 'no eth lost') + const timePassed = endTime - aMonthFromNow + strictEqualTypeSafe(timePassed / 8640n, 3345n, 'not enough time passed') + strictEqualTypeSafe(retentionRate, 999999987364000000n, 'retention rate did not match') const completeSetCollateralAmountPercentage = Number(completeSetCollateralAmount * 1000n / openInterestAmount) / 10 const expected = Number(1000n * rpow(retentionRate, timePassed, PRICE_PRECISION) / PRICE_PRECISION) / 10 - assert.strictEqual(completeSetCollateralAmountPercentage, expected, 'return amount did not match') + strictEqualTypeSafe(completeSetCollateralAmountPercentage, expected, 'return amount did not match') const contractBalance = await getETHBalance(client, securityPoolAddresses.securityPool) - assert.strictEqual(contractBalance + ethBalanceAfter - ethBalanceBefore, openInterestAmount, 'contract balance+ fees should equal initial open interest') + strictEqualTypeSafe(contractBalance + ethBalanceAfter - ethBalanceBefore, openInterestAmount, 'contract balance + fees should equal initial open interest') }) test('can set security bonds allowance, mint complete sets and fork happily' , async () => { - const questionData = await getQuestionData(client, questionId) - await mockWindow.setTime(questionData.endTime + 10000n) + const endTime = await getMarketEndDate(client, marketId) + await mockWindow.setTime(endTime + 10000n) const securityPoolAllowance = repDeposit / 4n - assert.strictEqual(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max'); + strictEqualTypeSafe(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max'); await manipulatePriceOracleAndPerformOperation(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') + strictEqualTypeSafe(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + strictEqualTypeSafe(await getTotalSecurityBondAllowance(client, securityPoolAddresses.securityPool), securityPoolAllowance, 'Security pool allowance was not set correctly') + + const forkThreshold = (await getTotalTheoreticalSupply(client, await getRepToken(client, securityPoolAddresses.securityPool))) / 20n + await depositRep(client, securityPoolAddresses.securityPool, forkThreshold * 2n) const openInterestAmount = 100n * 10n ** 18n - const maxGasFees = openInterestAmount /4n + const maxGasFees = openInterestAmount / 4n const ethBalance = await getETHBalance(client, client.account.address) 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, await sharesToCash(client, securityPoolAddresses.securityPool, completeSetBalances[0]), 'Did not create enough complete sets') + strictEqualTypeSafe(completeSetBalances[0], completeSetBalances[1], 'yes no and invalid share counts need to match') + strictEqualTypeSafe(completeSetBalances[1], completeSetBalances[2], 'yes no and invalid share counts need to match') + strictEqualTypeSafe(await sharesToCash(client, securityPoolAddresses.securityPool, completeSetBalances[0]), openInterestAmount, '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, securityPoolAddresses.securityPool), openInterestAmount, 'contract did not record the amount correctly') + strictEqualTypeSafe(await getCompleteSetCollateralAmount(client, securityPoolAddresses.securityPool), openInterestAmount, 'contract did not record the amount correctly') await redeemCompleteSet(client, securityPoolAddresses.securityPool, completeSetBalances[0]) assert.ok(ethBalance - await getETHBalance(client, client.account.address) < maxGasFees, 'Did not get ETH back from complete sets') const newCompleteSetBalances = await balanceOfShares(client, securityPoolAddresses.shareToken, genesisUniverse, client.account.address) - assert.strictEqual(newCompleteSetBalances[0], 0n, 'Did not lose complete sets') - assert.strictEqual(newCompleteSetBalances[1], 0n, 'Did not lose complete sets') - assert.strictEqual(newCompleteSetBalances[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 + strictEqualTypeSafe(newCompleteSetBalances[0], 0n, 'Did not lose complete sets') + strictEqualTypeSafe(newCompleteSetBalances[1], 0n, 'Did not lose complete sets') + strictEqualTypeSafe(newCompleteSetBalances[2], 0n, 'Did not lose complete sets') + strictEqualTypeSafe(await getCurrentRetentionRate(client, securityPoolAddresses.securityPool), MAX_RETENTION_RATE, 'retention rate was not at max after zero complete sets'); + await createCompleteSet(client, securityPoolAddresses.securityPool, openInterestAmount) const repBalance = await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddresses.securityPool) - await triggerFork(client, mockWindow, questionId) + // forking + const zoltarforkThreshold = await getZoltarforkThreshold (client, genesisUniverse) + const burnAmount = zoltarforkThreshold / 5n + await triggerOwnGameFork(client, securityPoolAddresses.securityPool) + const forkerRepBalance = await getERC20Balance(client, getRepTokenAddress(genesisUniverse), getInfraContractAddresses().securityPoolForker) + const zoltarForkData = await getUniverseForkData(client, genesisUniverse) + strictEqualTypeSafe(zoltarForkData.forkerRepDeposit + forkerRepBalance + burnAmount, repBalance, 'forkerRepDeposit + forkerRepBalance + burnAmount should equal deposit') + await forkSecurityPool(client, securityPoolAddresses.securityPool) - const totalFeesOvedToVaultsRightAfterFork = await getTotalFeesOvedToVaults(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') + + const forkData = await getSecurityPoolForkerForkData(client, securityPoolAddresses.securityPool) + strictEqualTypeSafe(forkData.repAtFork, repBalance - burnAmount, 'rep at fork does not match deposit rep') + strictEqualTypeSafe(forkData.migratedRep, 0n, 'migrated rep should be 0 so far') + strictEqualTypeSafe(forkData.outcomeIndex, 0, 'there should be no outcome') + strictEqualTypeSafe(forkData.ownFork, true, 'should be own fork') + const totalFeesOwedToVaultsRightAfterFork = await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool) + strictEqualTypeSafe(await getSystemState(client, securityPoolAddresses.securityPool), SystemState.PoolForked, 'Parent is forked') + strictEqualTypeSafe(0n, await getERC20Balance(client, getRepTokenAddress(genesisUniverse), securityPoolAddresses.securityPool), 'Parents original rep is gone') await migrateVault(client, securityPoolAddresses.securityPool, QuestionOutcome.Yes) + await migrateFromEscalationGame(client, securityPoolAddresses.securityPool, client.account.address, QuestionOutcome.Yes, [0n]) const yesUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.Yes) - const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, questionId, securityMultiplier) + const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, marketId, securityMultiplier) - assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkMigration, 'Fork Migration need to start') + strictEqualTypeSafe(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkMigration, 'Fork Migration need to start') const migratedRep = await getMigratedRep(client, yesSecurityPool.securityPool) - approximatelyEqual(migratedRep, repBalance, 10n, 'correct amount rep migrated') + approximatelyEqual(migratedRep, repBalance - burnAmount, 10n, 'correct amount rep migrated') assert.ok(await contractExists(client, yesSecurityPool.securityPool), 'Did not create YES security pool') await mockWindow.advanceTime(8n * 7n * DAY + DAY) await startTruthAuction(client, yesSecurityPool.securityPool) - assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'yes System should be operational right away') - assert.strictEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), openInterestAmount, 'child contract did not record the amount correctly') + strictEqualTypeSafe(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'yes System should be operational right away') + + const totalFees = await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool) + await getTotalFeesOwedToVaults(client, yesSecurityPool.securityPool) + approximatelyEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), openInterestAmount - totalFees, 10n, 'child contract did not record the amount correctly') - const totalFeesOvedToVaultsAfterFork = await getTotalFeesOvedToVaults(client, securityPoolAddresses.securityPool) - assert.strictEqual(totalFeesOvedToVaultsRightAfterFork, totalFeesOvedToVaultsAfterFork, 'parents fees should be frozen') + const totalFeesOwedToVaultsAfterFork = await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool) + strictEqualTypeSafe(totalFeesOwedToVaultsRightAfterFork, totalFeesOwedToVaultsAfterFork, 'parents fees should be frozen') }) test('two security pools with disagreement', async () => { - const questionData = await getQuestionData(client, questionId) - await mockWindow.setTime(questionData.endTime + 10000n) + const endTime = await getMarketEndDate(client, marketId) + await mockWindow.setTime(endTime + 10000n) const openInterestAmount = 10n * 10n ** 18n const openInterestArray = [openInterestAmount, openInterestAmount, openInterestAmount] const securityPoolAllowance = repDeposit / 4n await manipulatePriceOracleAndPerformOperation(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) const attackerClient = createWriteClient(mockWindow, TEST_ADDRESSES[1], 0) - await approveAndDepositRep(attackerClient, repDeposit) + await approveAndDepositRep(attackerClient, repDeposit, marketId) await manipulatePriceOracleAndPerformOperation(attackerClient, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.SetSecurityBondsAllowance, client.account.address, securityPoolAllowance) + const forkThreshold = (await getTotalTheoreticalSupply(client, await getRepToken(client, securityPoolAddresses.securityPool))) / 20n + + const zoltarforkThreshold = await getZoltarforkThreshold (client, genesisUniverse) + const burnAmount = zoltarforkThreshold / 5n + await depositRep(client, securityPoolAddresses.securityPool, 2n * forkThreshold ) 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, 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') + strictEqual18Decimal(repBalanceInGenesisPool, 2n * repDeposit + 2n * forkThreshold , 'After two deposits, the system should have 2 x repDeposit worth of REP + 2x fork') + strictEqual18Decimal(await getTotalSecurityBondAllowance(client, securityPoolAddresses.securityPool), 2n * securityPoolAllowance, 'Security bond allowance should be 2x') + strictEqual18Decimal(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, securityPoolAddresses.securityPool, openInterestAmount) assert.deepStrictEqual(await balanceOfSharesInCash(client, securityPoolAddresses.securityPool, securityPoolAddresses.shareToken, genesisUniverse, addressString(TEST_ADDRESSES[2])), openInterestArray, 'Did not create enough complete sets') - - await triggerFork(client, mockWindow, questionId) + await triggerOwnGameFork(client, securityPoolAddresses.securityPool) await forkSecurityPool(client, securityPoolAddresses.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, securityPoolAddresses.securityPool, securityPoolAddresses.shareToken, genesisUniverse, addressString(TEST_ADDRESSES[2])), openInterestArray, 'Shares exist after fork') - await migrateShares(openInterestHolder, securityPoolAddresses.shareToken, genesisUniverse, QuestionOutcome.Yes) - await migrateShares(openInterestHolder, securityPoolAddresses.shareToken, genesisUniverse, QuestionOutcome.No) - await migrateShares(openInterestHolder, securityPoolAddresses.shareToken, genesisUniverse, QuestionOutcome.Invalid) + const yesUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.Yes) + const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, marketId, securityMultiplier) // we migrate to yes - const yesUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.Yes) - assert.ok(await isFinalized(client, yesUniverse, questionId), 'yes is finalized') - assert.strictEqual(await getWinningOutcome(client, yesUniverse, questionId), QuestionOutcome.Yes, 'finalized as yes') - const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, questionId, securityMultiplier) await migrateVault(client, securityPoolAddresses.securityPool, QuestionOutcome.Yes) + await migrateFromEscalationGame(client, securityPoolAddresses.securityPool, client.account.address, QuestionOutcome.Yes, [0n]) + const yesVault = await getSecurityVault(client, yesSecurityPool.securityPool, client.account.address) + const yesPoolBalance = await getERC20Balance(client, await getRepToken(client, yesSecurityPool.securityPool), yesSecurityPool.securityPool) + strictEqual18Decimal(await poolOwnershipToRep(client, yesSecurityPool.securityPool, yesVault.repDepositShare), yesPoolBalance-repDeposit, 'we should account for all the rep in yes pool (except attackers rep)') const migratedRepInYes = await getMigratedRep(client, yesSecurityPool.securityPool) - approximatelyEqual(repBalanceInGenesisPool / 2n, migratedRepInYes, 10n, 'half migrated to yes') - assert.strictEqual(await getERC20Balance(client, getRepTokenAddress(yesUniverse), yesSecurityPool.securityPool), repBalanceInGenesisPool, 'yes has all the rep') + strictEqual18Decimal(yesPoolBalance-repDeposit, migratedRepInYes, 'yes pool has the same rep as migrated rep') + strictEqualTypeSafe(await getMarketOutcome(client, yesSecurityPool.securityPool), QuestionOutcome.Yes, 'yes is finalized') + strictEqualTypeSafe(await getERC20Balance(client, getRepTokenAddress(yesUniverse), yesSecurityPool.securityPool), repBalanceInGenesisPool - burnAmount, 'yes has all the rep') + + assert.ok(await contractExists(client, yesSecurityPool.securityPool), 'yes security pool exist') + const feesOwed= await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool) + await getTotalFeesOwedToVaults(client, yesSecurityPool.securityPool) // attacker migrated to No const noUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.No) - assert.ok(await isFinalized(client, noUniverse, questionId), 'no is finalized') - assert.strictEqual(await getWinningOutcome(client, noUniverse, questionId), QuestionOutcome.No, 'finalized as yes') - const noSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, noUniverse, questionId, securityMultiplier) + const noSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, noUniverse, marketId, securityMultiplier) await migrateVault(attackerClient, securityPoolAddresses.securityPool, QuestionOutcome.No) + strictEqualTypeSafe(await getMarketOutcome(client, noSecurityPool.securityPool), QuestionOutcome.No, 'finalized as no') const migratedRepInNo = await getMigratedRep(client, noSecurityPool.securityPool) - approximatelyEqual(repBalanceInGenesisPool / 2n, migratedRepInNo, 10n, 'half migrated to no') - assert.strictEqual(await getERC20Balance(client, getRepTokenAddress(noUniverse), noSecurityPool.securityPool), repBalanceInGenesisPool, 'no has all the rep') + approximatelyEqual(migratedRepInNo, repDeposit, 10n, 'other side migrated to no') + strictEqualTypeSafe(await getERC20Balance(client, getRepTokenAddress(noUniverse), noSecurityPool.securityPool), repBalanceInGenesisPool - burnAmount, 'no has all the rep') + + approximatelyEqual(await getETHBalance(client, securityPoolAddresses.securityPool), await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool), 10n, 'there should be only fees left in old security pool') // invalid, no one migrated here await createChildUniverse(client, securityPoolAddresses.securityPool, QuestionOutcome.Invalid) // no one migrated, we need to create the universe as rep holders did not const invalidUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.Invalid) - const invalidSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, invalidUniverse, questionId, securityMultiplier) - assert.ok(await isFinalized(client, invalidUniverse, questionId), 'invalid is finalized') - assert.strictEqual(await getWinningOutcome(client, invalidUniverse, questionId), QuestionOutcome.Invalid, 'finalized as invalid') + const invalidSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, invalidUniverse, marketId, securityMultiplier) await mockWindow.advanceTime(8n * 7n * DAY + DAY) + const getCurrentOpenInterestArray = async () => { + const currentFees = await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool) + await getTotalFeesOwedToVaults(client, yesSecurityPool.securityPool) + return openInterestArray.map((x) => x - currentFees) + } + // auction yes + const repAtFork = (await getSecurityPoolForkerForkData(client, securityPoolAddresses.securityPool)).repAtFork + const completeSetAmount = await getCompleteSetCollateralAmount(client, securityPoolAddresses.securityPool) + const auctionedEthInYes = completeSetAmount-completeSetAmount * migratedRepInYes / repAtFork await startTruthAuction(client, yesSecurityPool.securityPool) - assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') - approximatelyEqual(await getEthAmountToBuy(client, yesSecurityPool.truthAuction), openInterestAmount / 2n, 10n, 'Need to buy half of open interest') + strictEqualTypeSafe(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') + approximatelyEqual(await getEthAmountToBuy(client, yesSecurityPool.truthAuction), auctionedEthInYes, 10n, 'Need to buy half of open interest on yes') // 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, yesSecurityPool.truthAuction, repBalanceInGenesisPool / 4n, openInterestAmount / 2n) + await participateAuction(yesAuctionParticipant, yesSecurityPool.truthAuction, repBalanceInGenesisPool / 4n, auctionedEthInYes) // auction no + const auctionedEthInNo = completeSetAmount-completeSetAmount * migratedRepInNo / repAtFork await startTruthAuction(client, noSecurityPool.securityPool) - assert.strictEqual(await getSystemState(client, noSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') - approximatelyEqual(await getEthAmountToBuy(client, noSecurityPool.truthAuction), openInterestAmount / 2n, 10n, 'Need to buy half of open interest') + strictEqualTypeSafe(await getSystemState(client, noSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') + approximatelyEqual(await getEthAmountToBuy(client, noSecurityPool.truthAuction), auctionedEthInNo, 10n, 'Need to buy half of open interest on no') // participate no auction by buying 3/4 of all REP (this is a open interest happy case where REP holders lose 50%) const noAuctionParticipant = createWriteClient(mockWindow, TEST_ADDRESSES[4], 0) - await participateAuction(noAuctionParticipant, noSecurityPool.truthAuction, repBalanceInGenesisPool * 3n / 4n, openInterestAmount / 2n) + await participateAuction(noAuctionParticipant, noSecurityPool.truthAuction, repBalanceInGenesisPool * 3n / 4n, auctionedEthInNo) // auction invalid await startTruthAuction(client, invalidSecurityPool.securityPool) - assert.strictEqual(await getSystemState(client, invalidSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') - approximatelyEqual(await getEthAmountToBuy(client, invalidSecurityPool.truthAuction), openInterestAmount, 10n, 'Need to buy all of open interest') - // participate no auction by buying 3/4 of all REP (this is a open interest happy case where REP holders lose 50%) + strictEqualTypeSafe(await getSystemState(client, invalidSecurityPool.securityPool), SystemState.ForkTruthAuction, 'Auction started') + approximatelyEqual(await getEthAmountToBuy(client, invalidSecurityPool.truthAuction), completeSetAmount, 10n, 'Need to buy all of open interest on invalid') const invalidAuctionParticipant = createWriteClient(mockWindow, TEST_ADDRESSES[5], 0) // buy half of the open interest for 3/4 of everything - await participateAuction(invalidAuctionParticipant, invalidSecurityPool.truthAuction, repBalanceInGenesisPool - repBalanceInGenesisPool / 1_000_000n, openInterestAmount / 2n) + await participateAuction(invalidAuctionParticipant, invalidSecurityPool.truthAuction, repBalanceInGenesisPool - burnAmount - repBalanceInGenesisPool / 1_000_000n, completeSetAmount / 2n) await mockWindow.advanceTime(7n * DAY + DAY) // yes status: auction fully funds, 1/4 of rep balance is sold for eth await finalizeTruthAuction(client, yesSecurityPool.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, yesSecurityPool.securityPool, yesSecurityPool.shareToken, yesUniverse, addressString(TEST_ADDRESSES[2])), openInterestArray, 'Not enough shares in yes') - approximatelyEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), openInterestAmount, 10n, 'yes child contract did not record the amount correctly') - assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'Yes System should be operational again') + assert.deepStrictEqual(await balanceOfSharesInCash(client, securityPoolAddresses.securityPool, securityPoolAddresses.shareToken, genesisUniverse, addressString(TEST_ADDRESSES[2])), openInterestArray.map((x) => x - feesOwed), 'Shares exist after fork') + await migrateShares(openInterestHolder, securityPoolAddresses.shareToken, genesisUniverse, QuestionOutcome.Yes, [0n, 1n, 2n]) + await migrateShares(openInterestHolder, securityPoolAddresses.shareToken, genesisUniverse, QuestionOutcome.No, [0n, 1n, 2n]) + await migrateShares(openInterestHolder, securityPoolAddresses.shareToken, genesisUniverse, QuestionOutcome.Invalid, [0n, 1n, 2n]) + + assert.deepStrictEqual(await balanceOfSharesInCash(client, yesSecurityPool.securityPool, yesSecurityPool.shareToken, yesUniverse, addressString(TEST_ADDRESSES[2])), [completeSetAmount, completeSetAmount, completeSetAmount], 'Not enough shares in yes') + + approximatelyEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), (await getCurrentOpenInterestArray())[0], 10n, 'yes child contract did not record the amount correctly') + strictEqualTypeSafe(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'Yes System should be operational again') await claimAuctionProceeds(client, yesSecurityPool.securityPool, yesAuctionParticipant.account.address) const yesAuctionParticipantVault = await getSecurityVault(client, yesSecurityPool.securityPool, yesAuctionParticipant.account.address) @@ -292,21 +366,22 @@ describe('Peripherals Contract Test Suite', () => { const originalYesVault = await getSecurityVault(client, yesSecurityPool.securityPool, client.account.address) const originalYesVaultRep = await poolOwnershipToRep(client, yesSecurityPool.securityPool, originalYesVault.repDepositShare) - approximatelyEqual(originalYesVaultRep, repBalanceInGenesisPool * 3n / 4n, 10000n, 'original yes vault holder should hold rest 3/4 of rep') - assert.strictEqual((await getSecurityVault(client, yesSecurityPool.securityPool, attackerClient.account.address)).repDepositShare, 0n, 'attacker should have zero as they did not migrate to yes') + approximatelyEqual(originalYesVaultRep, repBalanceInGenesisPool * 3n / 4n-burnAmount, 10000n, 'original yes vault holder should hold rest 3/4 of rep') + strictEqualTypeSafe((await getSecurityVault(client, yesSecurityPool.securityPool, attackerClient.account.address)).repDepositShare, 0n, 'attacker should have zero as they did not migrate to yes') const balancePriorYesRedeemal = await getETHBalance(client, addressString(TEST_ADDRESSES[2])) await redeemShares(openInterestHolder, yesSecurityPool.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, yesSecurityPool.securityPool, securityPoolAddresses.shareToken, yesUniverse, addressString(TEST_ADDRESSES[2])), [openInterestAmount, 0n, openInterestAmount], 'Not enough shares') - const fees = await getTotalFeesOvedToVaults(client, securityPoolAddresses.securityPool) + await getTotalFeesOvedToVaults(client, yesSecurityPool.securityPool) + const currentShares = await getCurrentOpenInterestArray() + assert.deepStrictEqual(await balanceOfSharesInCash(client, yesSecurityPool.securityPool, securityPoolAddresses.shareToken, yesUniverse, addressString(TEST_ADDRESSES[2])), [currentShares[0], 0n, currentShares[2]], 'Not enough shares 1') + const fees = await getTotalFeesOwedToVaults(client, securityPoolAddresses.securityPool) + await getTotalFeesOwedToVaults(client, yesSecurityPool.securityPool) approximatelyEqual(await getETHBalance(client, addressString(TEST_ADDRESSES[2])), balancePriorYesRedeemal + openInterestAmount - fees, 10n ** 15n, 'did not gain eth after redeeming yes shares') // no status: auction fully funds, 3/4 of rep balance is sold for eth await finalizeTruthAuction(client, noSecurityPool.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, noSecurityPool.securityPool, noSecurityPool.shareToken, noUniverse, addressString(TEST_ADDRESSES[2])), openInterestArray, 'Not enough shares in no') + assert.deepStrictEqual(await balanceOfSharesInCash(client, noSecurityPool.securityPool, noSecurityPool.shareToken, noUniverse, addressString(TEST_ADDRESSES[2])), currentShares, 'Not enough shares in no') - assert.strictEqual(await getSystemState(client, noSecurityPool.securityPool), SystemState.Operational, 'No System should be operational again') - assert.strictEqual(await getCompleteSetCollateralAmount(client, noSecurityPool.securityPool), openInterestAmount, 'no child contract did not record the amount correctly') + strictEqualTypeSafe(await getSystemState(client, noSecurityPool.securityPool), SystemState.Operational, 'No System should be operational again') + strictEqualTypeSafe(await getCompleteSetCollateralAmount(client, noSecurityPool.securityPool), currentShares[0], 'no child contract did not record the amount correctly') await claimAuctionProceeds(client, noSecurityPool.securityPool, noAuctionParticipant.account.address) const noAuctionParticipantVault = await getSecurityVault(client, noSecurityPool.securityPool, noAuctionParticipant.account.address) @@ -315,62 +390,66 @@ describe('Peripherals Contract Test Suite', () => { const originalNoVault = await getSecurityVault(client, noSecurityPool.securityPool, attackerClient.account.address) const originalNoVaultRep = await poolOwnershipToRep(client, noSecurityPool.securityPool, originalNoVault.repDepositShare) - approximatelyEqual(originalNoVaultRep, repBalanceInGenesisPool * 1n / 4n, 10000n, 'original no vault holder should hold rest 1/4 of rep') - assert.strictEqual((await getSecurityVault(client, noSecurityPool.securityPool, client.account.address)).repDepositShare, 0n, 'client should have zero as they did not migrate to no') + approximatelyEqual(originalNoVaultRep, repBalanceInGenesisPool * 1n / 4n - burnAmount, 10000n, 'original no vault holder should hold rest 1/4 of rep') + strictEqualTypeSafe((await getSecurityVault(client, noSecurityPool.securityPool, client.account.address)).repDepositShare, 0n, 'client should have zero as they did not migrate to no') const balancePriorNoRedeemal = await getETHBalance(client, addressString(TEST_ADDRESSES[2])) await redeemShares(openInterestHolder, noSecurityPool.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, noSecurityPool.securityPool, securityPoolAddresses.shareToken, noUniverse, addressString(TEST_ADDRESSES[2])), [openInterestAmount, openInterestAmount, 0n], 'Not enough shares') + assert.deepStrictEqual(await balanceOfSharesInCash(client, noSecurityPool.securityPool, noSecurityPool.shareToken, noUniverse, addressString(TEST_ADDRESSES[2])), [currentShares[0], currentShares[1], 0n], 'Not enough shares 2') approximatelyEqual(await getETHBalance(client, addressString(TEST_ADDRESSES[2])), balancePriorNoRedeemal + openInterestAmount-fees, 10n ** 15n, 'did not gain eth after redeeming no shares') // invalid status: auction 3/4 funds for all REP (minus 1/100 000). Open interest holders lose 50% await finalizeTruthAuction(client, invalidSecurityPool.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, invalidSecurityPool.securityPool, securityPoolAddresses.shareToken, invalidUniverse, addressString(TEST_ADDRESSES[2])), openInterestArray.map((x) => x / 2n), 'Not enough shares in invalid') - assert.strictEqual(await getSystemState(client, invalidSecurityPool.securityPool), SystemState.Operational, 'Invalid System should be operational again') - assert.strictEqual(await getCompleteSetCollateralAmount(client, invalidSecurityPool.securityPool), openInterestAmount / 2n, 'Invalid child contract did not record the amount correctly') + assert.deepStrictEqual(await balanceOfSharesInCash(client, invalidSecurityPool.securityPool, invalidSecurityPool.shareToken, invalidUniverse, addressString(TEST_ADDRESSES[2])), currentShares.map((x) => x / 2n), 'Not enough shares in invalid') + strictEqualTypeSafe(await getSystemState(client, invalidSecurityPool.securityPool), SystemState.Operational, 'Invalid System should be operational again') + approximatelyEqual(await getCompleteSetCollateralAmount(client, invalidSecurityPool.securityPool), currentShares[0] / 2n, 10n, 'Invalid child contract did not record the amount correctly') await claimAuctionProceeds(client, invalidSecurityPool.securityPool, invalidAuctionParticipant.account.address) const invalidAuctionParticipantVault = await getSecurityVault(client, invalidSecurityPool.securityPool, invalidAuctionParticipant.account.address) const invalidAuctionParticipantRep = await poolOwnershipToRep(client, invalidSecurityPool.securityPool, invalidAuctionParticipantVault.repDepositShare) - approximatelyEqual(invalidAuctionParticipantRep, repBalanceInGenesisPool - repBalanceInGenesisPool / 1_000_000n, 10000n, 'Invalid auction participant did not get ownership of rep they bought') + approximatelyEqual(invalidAuctionParticipantRep, repBalanceInGenesisPool - repBalanceInGenesisPool / 1_000_000n - burnAmount, 10000n, 'Invalid auction participant did not get ownership of rep they bought') + // try creating new complete sets const openInterestHolder2 = createWriteClient(mockWindow, TEST_ADDRESSES[4], 0) - await createCompleteSet(openInterestHolder2, invalidSecurityPool.securityPool, openInterestAmount) + await createCompleteSet(openInterestHolder2, invalidSecurityPool.securityPool, currentShares[0]) const balancePriorInvalidRedeemal = await getETHBalance(client, addressString(TEST_ADDRESSES[2])) await redeemShares(openInterestHolder, invalidSecurityPool.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, invalidSecurityPool.securityPool, invalidSecurityPool.shareToken, invalidUniverse, addressString(TEST_ADDRESSES[2])), [0n, openInterestAmount, openInterestAmount].map((x) => x / 2n), 'Not enough shares after redeeming invalid 1') + assert.deepStrictEqual(await balanceOfSharesInCash(client, invalidSecurityPool.securityPool, invalidSecurityPool.shareToken, invalidUniverse, addressString(TEST_ADDRESSES[2])), [0n, currentShares[1], currentShares[2]].map((x) => x / 2n), 'Not enough shares after redeeming invalid 1') approximatelyEqual(await getETHBalance(client, addressString(TEST_ADDRESSES[2])), balancePriorInvalidRedeemal + (openInterestAmount - fees) / 2n, 10n ** 15n, 'did not gain eth after redeeming invalid shares') const balancePriorInvalidRedeemal2 = await getETHBalance(client, addressString(TEST_ADDRESSES[4])) await redeemShares(openInterestHolder2, invalidSecurityPool.securityPool) - assert.deepStrictEqual(await balanceOfSharesInCash(client, invalidSecurityPool.securityPool, invalidSecurityPool.shareToken, invalidUniverse, addressString(TEST_ADDRESSES[4])), [0n, openInterestAmount, openInterestAmount], 'Not enough shares after redeeming invalid 2') - approximatelyEqual(await getETHBalance(client, addressString(TEST_ADDRESSES[4])), balancePriorInvalidRedeemal2 + openInterestAmount, 10n ** 15n, 'did not gain eth after redeeming invalid shares') + assert.deepStrictEqual(await balanceOfSharesInCash(client, invalidSecurityPool.securityPool, invalidSecurityPool.shareToken, invalidUniverse, addressString(TEST_ADDRESSES[4])), [0n, currentShares[1], currentShares[2]], 'Not enough shares after redeeming invalid 2') + approximatelyEqual(await getETHBalance(client, addressString(TEST_ADDRESSES[4])), balancePriorInvalidRedeemal2 + currentShares[0], 10n ** 15n, 'did not gain eth after redeeming invalid shares') }) test('can fork zero rep pools', async () => { - const questionData = await getQuestionData(client, questionId) - await mockWindow.setTime(questionData.endTime + 10000n) + const endTime = await getMarketEndDate(client, marketId) + await mockWindow.setTime(endTime + 10000n) await manipulatePriceOracleAndPerformOperation(client, mockWindow, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.WithdrawRep, client.account.address, repDeposit) - assert.strictEqual(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') + strictEqualTypeSafe(await getLastPrice(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer), 1n * PRICE_PRECISION, 'Price was not set!') approximatelyEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddresses.securityPool), 0n, 100n, 'Did not empty security pool of rep') - approximatelyEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address), startBalance - reportBond, 100n, 'Did not get rep back') - await triggerFork(client, mockWindow, questionId) + approximatelyEqual(await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), client.account.address), startBalance, 100n, 'Did not get rep back') + + await approveToken(client, addressString(GENESIS_REPUTATION_TOKEN), getZoltarAddress()) + await forkUniverse(client, genesisUniverse, marketText, outcomes) await forkSecurityPool(client, securityPoolAddresses.securityPool) - assert.strictEqual(await getSystemState(client, securityPoolAddresses.securityPool), SystemState.PoolForked, 'Parent is forked') + strictEqualTypeSafe(await getSystemState(client, securityPoolAddresses.securityPool), SystemState.PoolForked, 'Parent is forked') await migrateVault(client, securityPoolAddresses.securityPool, QuestionOutcome.Yes) const yesUniverse = getChildUniverseId(genesisUniverse, QuestionOutcome.Yes) - const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, questionId, securityMultiplier) + const yesSecurityPool = getSecurityPoolAddresses(securityPoolAddresses.securityPool, yesUniverse, marketId, securityMultiplier) - assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkMigration, 'Fork Migration need to start') + strictEqualTypeSafe(await getSystemState(client, yesSecurityPool.securityPool), SystemState.ForkMigration, 'Fork Migration need to start') const migratedRep = await getMigratedRep(client, yesSecurityPool.securityPool) - assert.strictEqual(migratedRep, 0n, 'correct amount rep migrated') + strictEqualTypeSafe(migratedRep, 0n, 'correct amount rep migrated') assert.ok(await contractExists(client, yesSecurityPool.securityPool), 'Did not create YES security pool') await mockWindow.advanceTime(8n * 7n * DAY + DAY) await startTruthAuction(client, yesSecurityPool.securityPool) - assert.strictEqual(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'yes System should be operational right away') - assert.strictEqual(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), 0n, 'child contract did not record the amount correctly') + strictEqualTypeSafe(await getSystemState(client, yesSecurityPool.securityPool), SystemState.Operational, 'yes System should be operational right away') + strictEqualTypeSafe(await getCompleteSetCollateralAmount(client, yesSecurityPool.securityPool), 0n, 'child contract did not record the amount correctly') }) // - todo test that users can claim their stuff (shares+rep) even if zoltar forks after market ends }) + diff --git a/solidity/ts/testsuite/simulator/EthereumClientService.ts b/solidity/ts/testsuite/simulator/EthereumClientService.ts index d65d1cd..5c9596e 100644 --- a/solidity/ts/testsuite/simulator/EthereumClientService.ts +++ b/solidity/ts/testsuite/simulator/EthereumClientService.ts @@ -163,7 +163,7 @@ export class EthereumClientService { return EthereumQuantity.parse(response) } - public readonly getGasPrice = async(requestAbortController: AbortController | undefined) => { + public readonly getGasPrice = async (requestAbortController: AbortController | undefined) => { const response = await this.requestHandler.jsonRpcRequest({ method: 'eth_gasPrice' }, requestAbortController) return EthereumQuantity.parse(response) } diff --git a/solidity/ts/testsuite/simulator/SimulationModeEthereumClientService.ts b/solidity/ts/testsuite/simulator/SimulationModeEthereumClientService.ts index 8b55c3d..e2f04ae 100644 --- a/solidity/ts/testsuite/simulator/SimulationModeEthereumClientService.ts +++ b/solidity/ts/testsuite/simulator/SimulationModeEthereumClientService.ts @@ -188,7 +188,7 @@ export const appendTransaction = async (ethereumClientService: EthereumClientSer return await createSimulationState(ethereumClientService, requestAbortController, simulationStateInput) } -export const getNonceFixedSimulatedTransactions = async(ethereumClientService: EthereumClientService, requestAbortController: AbortController | undefined, simulatedTransactions: readonly SimulatedTransaction[]) => { +export const getNonceFixedSimulatedTransactions = async (ethereumClientService: EthereumClientService, requestAbortController: AbortController | undefined, simulatedTransactions: readonly SimulatedTransaction[]) => { const isFixableNonceError = (transaction: SimulatedTransaction) => { return transaction.ethSimulateV1CallResult.status === 'failure' && transaction.ethSimulateV1CallResult.error.message === 'wrong transaction nonce' //TODO, change to error code diff --git a/solidity/ts/testsuite/simulator/utils/contracts/deployPeripherals.ts b/solidity/ts/testsuite/simulator/utils/contracts/deployPeripherals.ts new file mode 100644 index 0000000..b1b39d8 --- /dev/null +++ b/solidity/ts/testsuite/simulator/utils/contracts/deployPeripherals.ts @@ -0,0 +1,225 @@ +import 'viem/window' +import { encodeDeployData, getCreate2Address, keccak256, numberToBytes, toHex, zeroAddress, encodeAbiParameters } from 'viem' +import { WriteClient } from '../viem.js' +import { PROXY_DEPLOYER_ADDRESS } from '../constants.js' +import { addressString } from '../bigint.js' +import { contractExists } from '../utilities.js' +import { mainnet } from 'viem/chains' +import { peripherals_Auction_Auction, peripherals_EscalationGame_EscalationGame, peripherals_factories_AuctionFactory_AuctionFactory, peripherals_factories_EscalationGameFactory_EscalationGameFactory, peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory, peripherals_factories_SecurityPoolFactory_SecurityPoolFactory, peripherals_factories_ShareTokenFactory_ShareTokenFactory, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_SecurityPool_SecurityPool, peripherals_SecurityPoolForker_SecurityPoolForker, peripherals_SecurityPoolUtils_SecurityPoolUtils, peripherals_tokens_ShareToken_ShareToken, peripherals_YesNoMarkets_YesNoMarkets, Zoltar_Zoltar } from '../../../../types/contractArtifact.js' +import { objectEntries } from '../typescript.js' +import { getRepTokenAddress, getZoltarAddress } from './zoltar.js' + +export const getSecurityPoolUtilsAddress = () => 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) + const replaceLib = (bytecode: string, hash: string, replaceWithAddress: `0x${ string }`) => bytecode.replaceAll(`__$${ hash }$__`, replaceWithAddress.slice(2).toLocaleLowerCase()) + return `0x${ replaceLib(bytecode, securityPoolUtils, getSecurityPoolUtilsAddress()) }` +} + +export const getSecurityPoolForkerByteCode = (zoltar: `0x${ string }`) => { + return encodeDeployData({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + bytecode: applyLibraries(peripherals_SecurityPoolForker_SecurityPoolForker.evm.bytecode.object), + args: [ zoltar ] + }) +} + +export const getSecurityPoolFactoryByteCode = (securityPoolForker: `0x${ string }`, yesNoMarkets: `0x${ string }`, escalationGameFactory: `0x${ string }`, openOracle: `0x${ string }`, zoltar: `0x${ string }`, shareTokenFactory: `0x${ string }`, auctionFactory: `0x${ string }`, priceOracleManagerAndOperatorQueuerFactory: `0x${ string }`) => { + return encodeDeployData({ + abi: peripherals_factories_SecurityPoolFactory_SecurityPoolFactory.abi, + bytecode: applyLibraries(peripherals_factories_SecurityPoolFactory_SecurityPoolFactory.evm.bytecode.object), + args: [ securityPoolForker, yesNoMarkets, escalationGameFactory, openOracle, zoltar, shareTokenFactory, auctionFactory, priceOracleManagerAndOperatorQueuerFactory ] + }) +} + +export const getSecurityPoolFactoryAddress = (securityPoolForker: `0x${ string }`, yesNoMarkets: `0x${ string }`, escalationGameFactory: `0x${ string }`, 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(securityPoolForker, yesNoMarkets, escalationGameFactory, openOracle, zoltar, shareTokenFactory, auctionFactory, priceOracleManagerAndOperatorQueuerFactory) + }) +} + +export const getShareTokenFactoryByteCode = (zoltar: `0x${ string }`) => { + return encodeDeployData({ + abi: peripherals_factories_ShareTokenFactory_ShareTokenFactory.abi, + bytecode: `0x${ peripherals_factories_ShareTokenFactory_ShareTokenFactory.evm.bytecode.object }`, + args: [ zoltar ] + }) +} + +export const getYesNoMarketsByteCode = () => { + return encodeDeployData({ + abi: peripherals_YesNoMarkets_YesNoMarkets.abi, + bytecode: `0x${ peripherals_YesNoMarkets_YesNoMarkets.evm.bytecode.object }` + }) +} +export const getEscalationGameFactoryByteCode = () => { + return encodeDeployData({ + abi: peripherals_factories_EscalationGameFactory_EscalationGameFactory.abi, + bytecode: `0x${ peripherals_factories_EscalationGameFactory_EscalationGameFactory.evm.bytecode.object }` + }) +} + +export function getInfraContractAddresses() { + const getAddress = (bytecode: `0x${ string }`) => getCreate2Address({ bytecode, from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }) + + const contracts = { + securityPoolUtils: getAddress(`0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.bytecode.object }`), + openOracle: getAddress(`0x${ peripherals_openOracle_OpenOracle_OpenOracle.evm.bytecode.object }`), + zoltar: getZoltarAddress(), + shareTokenFactory: getAddress(getShareTokenFactoryByteCode(getZoltarAddress())), + auctionFactory: getAddress(`0x${ peripherals_factories_AuctionFactory_AuctionFactory.evm.bytecode.object }`), + priceOracleManagerAndOperatorQueuerFactory: getAddress(`0x${ peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory.evm.bytecode.object }`), + securityPoolForker: getAddress(getSecurityPoolForkerByteCode(getZoltarAddress())), + yesNoMarkets: getAddress(getYesNoMarketsByteCode()), + escalationGameFactory: getAddress(getEscalationGameFactoryByteCode()), + } + const securityPoolFactory = getSecurityPoolFactoryAddress(contracts.securityPoolForker, contracts.yesNoMarkets, contracts.escalationGameFactory, 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) + + const deployBytecode = async (bytecode: `0x${ string }`) => await client.sendTransaction({ to: addressString(PROXY_DEPLOYER_ADDRESS), data: bytecode }) + + if (!existence.securityPoolUtils) await deployBytecode(`0x${ peripherals_SecurityPoolUtils_SecurityPoolUtils.evm.bytecode.object }`) + if (!existence.openOracle) await deployBytecode(`0x${ peripherals_openOracle_OpenOracle_OpenOracle.evm.bytecode.object }`) + if (!existence.zoltar) await deployBytecode(`0x${ Zoltar_Zoltar.evm.bytecode.object }`) + if (!existence.shareTokenFactory) await deployBytecode(getShareTokenFactoryByteCode(getZoltarAddress())) + if (!existence.auctionFactory) await deployBytecode(`0x${ peripherals_factories_AuctionFactory_AuctionFactory.evm.bytecode.object }`) + if (!existence.priceOracleManagerAndOperatorQueuerFactory) await deployBytecode(`0x${ peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory.evm.bytecode.object }`) + if (!existence.securityPoolFactory) await deployBytecode(getSecurityPoolFactoryByteCode(contractAddresses.securityPoolForker, contractAddresses.yesNoMarkets, contractAddresses.escalationGameFactory, contractAddresses.openOracle, contractAddresses.zoltar, contractAddresses.shareTokenFactory, contractAddresses.auctionFactory, contractAddresses.priceOracleManagerAndOperatorQueuerFactory)) + if (!existence.yesNoMarkets) await deployBytecode(getYesNoMarketsByteCode()) + if (!existence.escalationGameFactory) await deployBytecode(getEscalationGameFactoryByteCode()) + if (!existence.securityPoolForker) await deployBytecode(getSecurityPoolForkerByteCode(contractAddresses.zoltar)) + + for (const [name, contractAddress] of objectEntries(contractAddresses)) { + if (!(await contractExists(client, contractAddress))) throw new Error(`${ name } does not exist even though we deployed it`) + } +} + +const computeSecurityPoolSalt = (parent: `0x${ string }`, universeId: bigint, marketId: bigint, securityMultiplier: bigint) => { + const values = [parent, universeId, marketId, securityMultiplier] as const + return keccak256(encodeAbiParameters([ + { name: 'parent', type: 'address' }, + { name: 'universeId', type: 'uint248' }, + { name: 'marketId', type: 'uint256' }, + { name: 'securityMultiplier', type: 'uint256' }, + ], values)) +} + +const computeShareTokenSalt = (securityMultiplier: bigint, marketId: bigint) => { + const values = [securityMultiplier, marketId] as const + return keccak256(encodeAbiParameters([ + { name: 'securityMultiplier', type: 'uint256' }, + { name: 'marketId', type: 'uint256' }, + ], values)) +} + +export const getMarketId = (universeId: bigint, securityMultiplier: bigint, extraInfo: string, marketEndDate: bigint) => { + const securityPoolfactory = getInfraContractAddresses().securityPoolFactory + const marketCreationTypes = [ + { name: 'securityPoolfactory', type: 'address' }, + { name: 'universeId', type: 'uint248' }, + { name: 'securityMultiplier', type: 'uint256' }, + { name: 'extraInfo', type: 'string' }, + { name: 'marketEndDate', type: 'uint256' }, + ] + const salt = keccak256(encodeAbiParameters(marketCreationTypes, [securityPoolfactory, universeId, securityMultiplier, extraInfo, marketEndDate])) + const saltTypes = [ + { name: 'securityPoolfactory', type: 'address' }, + { name: 'extraInfo', type: 'string' }, + { name: 'marketEndDate', type: 'uint256' }, + { name: 'salt', type: 'bytes32' }, + ] + return BigInt(keccak256(encodeAbiParameters(saltTypes, [securityPoolfactory, extraInfo, marketEndDate, salt]))); +} + +export const getSecurityPoolAddresses = (parent: `0x${ string }`, universeId: bigint, marketId: bigint, securityMultiplier: bigint) => { + const securityPoolSalt = computeSecurityPoolSalt(parent, universeId, marketId, securityMultiplier) + const infraContracts = getInfraContractAddresses() + const securityPoolTypes = [ + { name: 'securityPoolfactory', type: 'address' }, + { name: 'securityPoolSalt', type: 'bytes32' }, + ] + const securityPoolSaltWithMsgSender = keccak256(encodeAbiParameters(securityPoolTypes, [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 ] + }), + from: infraContracts.shareTokenFactory, + salt: computeShareTokenSalt(securityMultiplier, marketId) + }), + 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.securityPoolForker, infraContracts.securityPoolFactory, infraContracts.yesNoMarkets, infraContracts.escalationGameFactory, contracts.priceOracleManagerAndOperatorQueuer, contracts.shareToken, infraContracts.openOracle, parent, infraContracts.zoltar, universeId, marketId, securityMultiplier] as const + }), + from: infraContracts.securityPoolFactory, + salt: numberToBytes(0) + }) + const escalationGame = getCreate2Address({ + bytecode: encodeDeployData({ + abi: peripherals_EscalationGame_EscalationGame.abi, + bytecode: `0x${ peripherals_EscalationGame_EscalationGame.evm.bytecode.object }`, + args: [ securityPool ] + }), + from: infraContracts.escalationGameFactory, + salt: numberToBytes(0) + }) + return { ...contracts, securityPool, escalationGame } +} + +export const deployOriginSecurityPool = async (client: WriteClient, universeId: bigint, extraInfo: string, marketEndDate: bigint, securityMultiplier: bigint, startingRetentionRate: bigint, startingRepEthPrice: bigint) => { + const infraAddresses = getInfraContractAddresses() + return await client.writeContract({ + abi: peripherals_factories_SecurityPoolFactory_SecurityPoolFactory.abi, + functionName: 'deployOriginSecurityPool', + address: infraAddresses.securityPoolFactory, + args: [universeId, extraInfo, marketEndDate, securityMultiplier, startingRetentionRate, startingRepEthPrice] + }) +} diff --git a/solidity/ts/testsuite/simulator/utils/deployments.ts b/solidity/ts/testsuite/simulator/utils/contracts/deployments.ts similarity index 73% rename from solidity/ts/testsuite/simulator/utils/deployments.ts rename to solidity/ts/testsuite/simulator/utils/contracts/deployments.ts index 2e8c761..713fbbc 100644 --- a/solidity/ts/testsuite/simulator/utils/deployments.ts +++ b/solidity/ts/testsuite/simulator/utils/contracts/deployments.ts @@ -1,13 +1,17 @@ -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_factories_AuctionFactory_AuctionFactory, ReputationToken_ReputationToken, Zoltar_Zoltar, peripherals_SecurityPoolUtils_SecurityPoolUtils, peripherals_tokens_ShareToken_ShareToken, peripherals_factories_SecurityPoolFactory_SecurityPoolFactory, peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory, peripherals_factories_ShareTokenFactory_ShareTokenFactory } from '../../../types/contractArtifact.js' -import { QuestionOutcome } from '../types/types.js' -import { addressString } from './bigint.js' -import { ETHEREUM_LOGS_LOGGER_ADDRESS, TEST_ADDRESSES, WETH_ADDRESS } from './constants.js' -import { Deployment } from './logExplaining.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_factories_AuctionFactory_AuctionFactory, ReputationToken_ReputationToken, Zoltar_Zoltar, peripherals_SecurityPoolUtils_SecurityPoolUtils, peripherals_tokens_ShareToken_ShareToken, peripherals_factories_SecurityPoolFactory_SecurityPoolFactory, peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory, peripherals_factories_ShareTokenFactory_ShareTokenFactory, peripherals_factories_EscalationGameFactory_EscalationGameFactory, peripherals_YesNoMarkets_YesNoMarkets, peripherals_SecurityPoolForker_SecurityPoolForker, peripherals_EscalationGame_EscalationGame } from '../../../../types/contractArtifact.js' +import { QuestionOutcome } from '../../types/types.js' +import { addressString } from '../bigint.js' +import { ETHEREUM_LOGS_LOGGER_ADDRESS, TEST_ADDRESSES, WETH_ADDRESS } from '../constants.js' +import { Deployment } from '../logExplaining.js' import { getInfraContractAddresses, getSecurityPoolAddresses } from './deployPeripherals.js' -import { getChildUniverseId, getRepTokenAddress } from './utilities.js' import { zeroAddress } from 'viem' +import { getChildUniverseId } from '../utilities.js' +import { getRepTokenAddress } from './zoltar.js' const getUniverseName = (universeId: bigint): string => { + if (universeId === 0n) return 'Genesis' + return `Universe-${ universeId }` + /* const path: string[] = [] let currentUniverseId = universeId while (currentUniverseId > 0n) { @@ -18,9 +22,10 @@ const getUniverseName = (universeId: bigint): string => { } if (path.length === 0) return 'U-Genesis' return `U-Genesis-${ path.join('-') }` + */ } -const getDeploymentsForUniverse = (universeId: bigint, securityPoolAddress: `0x${ string }`, repTokenAddress: `0x${ string }`, priceOracleManagerAndOperatorQueuerAddress: `0x${ string }`, shareTokenAddress: `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 }`, escalationGame: `0x${ string }` ): Deployment[] => [ { abi: ReputationToken_ReputationToken.abi, deploymentName: `RepV2 ${ getUniverseName(universeId) }`, @@ -33,6 +38,10 @@ const getDeploymentsForUniverse = (universeId: bigint, securityPoolAddress: `0x$ abi: peripherals_SecurityPool_SecurityPool.abi, deploymentName: `ETH SecurityPool ${ getUniverseName(universeId) }`, address: securityPoolAddress + }, { + abi: peripherals_EscalationGame_EscalationGame.abi, + deploymentName: `Escalation Game`, + address: escalationGame }, { abi: peripherals_tokens_ShareToken_ShareToken.abi, deploymentName: `ShareToken ${ getUniverseName(universeId) }`, @@ -48,20 +57,20 @@ export const getDeployments = (genesisUniverse: bigint, questionId: bigint, secu const infraAddresses = getInfraContractAddresses() const originAddresses = getSecurityPoolAddresses(zeroAddress, genesisUniverse, questionId, securityMultiplier) - const oucomes = [QuestionOutcome.Invalid, QuestionOutcome.No, QuestionOutcome.Yes] + const outcomes = [QuestionOutcome.Invalid, QuestionOutcome.No, QuestionOutcome.Yes] const getChildAddresses = (parentSecurityPoolAddress: `0x${ string }`, parentUniverseId: bigint): Deployment[] => { - return oucomes.flatMap((outcome) => { - const universeId = getChildUniverseId(parentUniverseId, outcome) + return outcomes.flatMap((outcome) => { + const universeId = getChildUniverseId(parentUniverseId, BigInt(outcome)) const childAddresses = getSecurityPoolAddresses(parentSecurityPoolAddress, universeId, questionId, securityMultiplier) - return getDeploymentsForUniverse(universeId, childAddresses.securityPool, getRepTokenAddress(universeId), childAddresses.priceOracleManagerAndOperatorQueuer, childAddresses.shareToken, childAddresses.truthAuction) + return getDeploymentsForUniverse(universeId, childAddresses.securityPool, getRepTokenAddress(universeId), childAddresses.priceOracleManagerAndOperatorQueuer, childAddresses.shareToken, childAddresses.truthAuction, childAddresses.escalationGame) }) } return ([ - ...getDeploymentsForUniverse(genesisUniverse, originAddresses.securityPool, getRepTokenAddress(genesisUniverse), originAddresses.priceOracleManagerAndOperatorQueuer, originAddresses.shareToken, originAddresses.truthAuction), + ...getDeploymentsForUniverse(genesisUniverse, originAddresses.securityPool, getRepTokenAddress(genesisUniverse), originAddresses.priceOracleManagerAndOperatorQueuer, originAddresses.shareToken, originAddresses.truthAuction, originAddresses.escalationGame), ...getChildAddresses(originAddresses.securityPool, genesisUniverse), // children - ...oucomes.flatMap((outcome) => getChildAddresses(getSecurityPoolAddresses(originAddresses.securityPool, genesisUniverse, questionId, securityMultiplier).securityPool, getChildUniverseId(genesisUniverse, outcome))), // grand children + ...outcomes.flatMap((outcome) => getChildAddresses(getSecurityPoolAddresses(originAddresses.securityPool, genesisUniverse, questionId, securityMultiplier).securityPool, getChildUniverseId(genesisUniverse, BigInt(outcome)))), // grand children { abi: Zoltar_Zoltar.abi, deploymentName: 'Zoltar', @@ -106,6 +115,18 @@ export const getDeployments = (genesisUniverse: bigint, questionId: bigint, secu abi: peripherals_SecurityPoolUtils_SecurityPoolUtils.abi, deploymentName: 'Security Pool Utils', address: infraAddresses.securityPoolUtils + }, { + abi: peripherals_factories_EscalationGameFactory_EscalationGameFactory.abi, + deploymentName: 'Escalation Game Factory', + address: infraAddresses.escalationGameFactory + }, { + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + deploymentName: 'Security Pool Forker', + address: infraAddresses.securityPoolForker + }, { + abi: peripherals_YesNoMarkets_YesNoMarkets.abi, + deploymentName: 'Yes No Markets', + address: infraAddresses.yesNoMarkets }, { abi: undefined, deploymentName: 'Augur V2 Genesis', diff --git a/solidity/ts/testsuite/simulator/utils/contracts/peripherals.ts b/solidity/ts/testsuite/simulator/utils/contracts/peripherals.ts new file mode 100644 index 0000000..6f56125 --- /dev/null +++ b/solidity/ts/testsuite/simulator/utils/contracts/peripherals.ts @@ -0,0 +1,277 @@ +import 'viem/window' +import { ReadContractReturnType } from 'viem' +import { ReadClient, WriteClient } from '../viem.js' +import { WETH_ADDRESS } from '../constants.js' +import { peripherals_Auction_Auction, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_tokens_ShareToken_ShareToken, peripherals_YesNoMarkets_YesNoMarkets } from '../../../../types/contractArtifact.js' +import { QuestionOutcome } from '../../types/types.js' +import { getInfraContractAddresses } from './deployPeripherals.js' +import { shareArrayToCash } from './securityPool.js' + +export enum OperationType { + Liquidation = 0, + WithdrawRep = 1, + SetSecurityBondsAllowance = 2 +} + +export const requestPriceIfNeededAndQueueOperation = async (client: WriteClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`, operation: OperationType, targetVault: `0x${ string }`, amount: bigint) => { + const ethCost = await getRequestPriceEthCost(client, priceOracleManagerAndOperatorQueuer) * 2n; + return await client.writeContract({ + abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, + functionName: 'requestPriceIfNeededAndQueueOperation', + address: priceOracleManagerAndOperatorQueuer, + args: [operation, targetVault, amount], + value: ethCost, + }) +} + +export const requestPrice = async (client: WriteClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { + const ethCost = await getRequestPriceEthCost(client, priceOracleManagerAndOperatorQueuer) * 2n; + return await client.writeContract({ + abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, + functionName: 'requestPrice', + address: priceOracleManagerAndOperatorQueuer, + args: [], + value: ethCost, + }) +} + +export const getPendingReportId = async (client: ReadClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, + functionName: 'pendingReportId', + address: priceOracleManagerAndOperatorQueuer, + args: [] + }) as bigint +} + +interface ExtraReportData { + stateHash: `0x${ string }` + callbackContract: `0x${ string }` + numReports: number + callbackGasLimit: number + callbackSelector: `0x${ string }` + protocolFeeRecipient: `0x${ string }` + trackDisputes: boolean + keepFee: boolean + feeToken: boolean +} + +export const getOpenOracleExtraData = async (client: ReadClient, extraDataId: bigint): Promise => { + const result = await client.readContract({ + abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, + functionName: 'extraData', + address: getInfraContractAddresses().openOracle, + args: [extraDataId] + }) as ReadContractReturnType + + const [ + stateHash, + callbackContract, + numReports, + callbackGasLimit, + callbackSelector, + protocolFeeRecipient, + trackDisputes, + keepFee, + feeToken + ] = result as [ + `0x${ string }`, + `0x${ string }`, + bigint, + bigint, + `0x${ string }`, + `0x${ string }`, + boolean, + boolean, + boolean + ] + + return { + stateHash, + callbackContract, + numReports: Number(numReports), + callbackGasLimit: Number(callbackGasLimit), + callbackSelector, + protocolFeeRecipient, + trackDisputes, + keepFee, + feeToken + } +} + +export const openOracleSubmitInitialReport = async (client: WriteClient, reportId: bigint, amount1: bigint, amount2: bigint, stateHash: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, + functionName: 'submitInitialReport', + address: getInfraContractAddresses().openOracle, + args: [reportId, amount1, amount2, stateHash] + }) +} + +export const openOracleSettle = async (client: WriteClient, reportId: bigint) => { + return await client.writeContract({ + abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, + functionName: 'settle', + address: getInfraContractAddresses().openOracle, + gas: 5_000_000n, //needed because of gas() opcode being used + args: [reportId] + }) +} + +export const getRequestPriceEthCost = async (client: ReadClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, + functionName: 'getRequestPriceEthCost', + address: priceOracleManagerAndOperatorQueuer, + args: [] + }) as bigint +} + +export const wrapWeth = async (client: WriteClient, amount: bigint) => { + const wethAbi = [{ + type: 'function', + name: 'deposit', + stateMutability: 'payable', + inputs: [], + outputs: [] + }] + return await client.writeContract({ + abi: wethAbi, + address: WETH_ADDRESS, + functionName: 'deposit', + value: amount + }) +} + +export interface ReportMeta { + exactToken1Report: bigint + escalationHalt: bigint + fee: bigint + settlerReward: bigint + token1: `0x${ string }` + settlementTime: number + token2: `0x${ string }` + timeType: boolean + feePercentage: number + protocolFee: number + multiplier: number + disputeDelay: number +} + +export const getOpenOracleReportMeta = async (client: ReadClient, reportId: bigint): Promise => { + const reportMetaData = await client.readContract({ + abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, + functionName: 'reportMeta', + address: getInfraContractAddresses().openOracle, + args: [reportId] + }) + + const [ + exactToken1Report, + escalationHalt, + fee, + settlerReward, + token1, + settlementTime, + token2, + timeType, + feePercentage, + protocolFee, + multiplier, + disputeDelay + ] = reportMetaData + + return { + exactToken1Report, + escalationHalt, + fee, + settlerReward, + token1, + settlementTime, + token2, + timeType, + feePercentage, + protocolFee, + multiplier, + disputeDelay + } +} + +export const getLastPrice = async (client: ReadClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, + functionName: 'lastPrice', + address: priceOracleManagerAndOperatorQueuer, + args: [] + }) as bigint +} + +export const participateAuction = async (client: WriteClient, auctionAddress: `0x${ string }`, repToBuy: bigint, ethToInvest: bigint) => { + return await client.writeContract({ + abi: peripherals_Auction_Auction.abi, + functionName: 'participate', + address: auctionAddress, + args: [repToBuy], + value: ethToInvest + }) +} +export const getEthAmountToBuy = async (client: ReadClient, auctionAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_Auction_Auction.abi, + functionName: 'ethAmountToBuy', + address: auctionAddress, + args: [], + }) +} + +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], + }) +} + +export const balanceOfSharesInCash = async (client: ReadClient, seucurityPoolAddress: `0x${ string }`, shareTokenAddress: `0x${ string }`, universeId: bigint, account: `0x${ string }`) => { + const array = await client.readContract({ + abi: peripherals_tokens_ShareToken_ShareToken.abi, + functionName: 'balanceOfShares', + address: shareTokenAddress, + args: [universeId, account], + }) + return await shareArrayToCash(client, seucurityPoolAddress, array) +} + +export const getTokenId = (universeId: bigint, outcome: QuestionOutcome) => { + const universeMask = (1n << 248n) - 1n + return ((universeId & universeMask) << 8n) | (BigInt(outcome) & 255n) +} +export const unpackTokenId = (tokenId: bigint): { universe: bigint, outcome: QuestionOutcome } => ({ universe: tokenId >> 8n, outcome: Number(tokenId & 0xFFn) }) + +export const migrateShares = async (client: WriteClient, shareTokenAddress: `0x${ string }`, fromUniverseId: bigint, outcome: QuestionOutcome, outcomes: bigint[]) => { + return await client.writeContract({ + abi: peripherals_tokens_ShareToken_ShareToken.abi, + functionName: 'migrate', + address: shareTokenAddress, + args: [getTokenId(fromUniverseId, outcome), outcomes.map((x) => Number(x))], + }) +} + +export const getMarketEndDate = async(client: ReadClient, marketId: bigint) => { + return await client.readContract({ + abi: peripherals_YesNoMarkets_YesNoMarkets.abi, + functionName: 'getMarketEndDate', + address: getInfraContractAddresses().yesNoMarkets, + args: [marketId], + }) +} diff --git a/solidity/ts/testsuite/simulator/utils/contracts/peripheralsTestUtils.ts b/solidity/ts/testsuite/simulator/utils/contracts/peripheralsTestUtils.ts new file mode 100644 index 0000000..bab8b29 --- /dev/null +++ b/solidity/ts/testsuite/simulator/utils/contracts/peripheralsTestUtils.ts @@ -0,0 +1,86 @@ +import { zeroAddress } from 'viem' +import { MockWindowEthereum } from '../../MockWindowEthereum.js' +import { addressString } from '../bigint.js' +import { DAY, GENESIS_REPUTATION_TOKEN, WETH_ADDRESS } from '../constants.js' +import { getInfraContractAddresses, getSecurityPoolAddresses } from './deployPeripherals.js' +import { approveToken, contractExists, getERC20Balance } from '../utilities.js' +import { WriteClient } from '../viem.js' +import assert from 'node:assert' +import { getOpenOracleExtraData, getOpenOracleReportMeta, getPendingReportId, openOracleSettle, openOracleSubmitInitialReport, OperationType, requestPrice, requestPriceIfNeededAndQueueOperation, wrapWeth } from './peripherals.js' +import { QuestionOutcome } from '../../types/types.js' +import { forkZoltarWithOwnEscalationGame } from './securityPoolForker.js' +import { getTotalTheoreticalSupply } from './zoltar.js' +import { depositRep, depositToEscalationGame, getRepToken, getSecurityVault, poolOwnershipToRep } from './securityPool.js' + +export const genesisUniverse = 0n +export const securityMultiplier = 2n +export const startingRepEthPrice = 1n +export const completeSetCollateralAmount = 0n +export const PRICE_PRECISION = 10n ** 18n +export const MAX_RETENTION_RATE = 999_999_996_848_000_000n // ≈90% yearly +export const EXTRA_INFO = 'test market!' + +export const approveAndDepositRep = async (client: WriteClient, repDeposit: bigint, marketId: bigint) => { + const securityPoolAddress = getSecurityPoolAddresses(zeroAddress, genesisUniverse, marketId, securityMultiplier).securityPool + assert.ok(await contractExists(client, securityPoolAddress), 'security pool not deployed') + + const startBalance = await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddress) + await approveToken(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddress) + await depositRep(client, securityPoolAddress, repDeposit) + + const newBalance = await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddress) + assert.strictEqual(newBalance, startBalance + repDeposit, 'Did not deposit rep') +} + +export const triggerOwnGameFork = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { + const repToken = await getRepToken(client, securityPoolAddress) + const forkThreshold = (await getTotalTheoreticalSupply(client, repToken)) / 20n /2n + const vault = await getSecurityVault(client, securityPoolAddress, client.account.address) + const repAmount = await poolOwnershipToRep(client, securityPoolAddress, vault.repDepositShare) + assert.ok(repAmount >= 2n * forkThreshold, 'not enough rep in vault to fork') + await depositToEscalationGame(client, securityPoolAddress, QuestionOutcome.Yes, forkThreshold) + await depositToEscalationGame(client, securityPoolAddress, QuestionOutcome.No, forkThreshold) + await forkZoltarWithOwnEscalationGame(client, securityPoolAddress) +} + +export const handleOracleReporting = async (client: WriteClient, mockWindow: MockWindowEthereum, priceOracleManagerAndOperatorQueuer: `0x${ string }`, forceRepEthPriceTo: bigint) => { + const pendingReportId = await getPendingReportId(client, priceOracleManagerAndOperatorQueuer) + if (pendingReportId === 0n) { + // operation already executed + return + } + assert.ok(pendingReportId > 0, 'Operation is not queued') + + const reportMeta = await getOpenOracleReportMeta(client, pendingReportId) + + // initial report + const amount1 = reportMeta.exactToken1Report + const amount2 = amount1 * PRICE_PRECISION / forceRepEthPriceTo + + 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') + + const stateHash = (await getOpenOracleExtraData(client, pendingReportId)).stateHash + await openOracleSubmitInitialReport(client, pendingReportId, amount1, amount2, stateHash) + + await mockWindow.advanceTime(DAY) + + await openOracleSettle(client, pendingReportId) +} + +export const manipulatePriceOracleAndPerformOperation = async (client: WriteClient, mockWindow: MockWindowEthereum, priceOracleManagerAndOperatorQueuer: `0x${ string }`, operation: OperationType, targetVault: `0x${ string }`, amount: bigint, forceRepEthPriceTo: bigint = PRICE_PRECISION) => { + await requestPriceIfNeededAndQueueOperation(client, priceOracleManagerAndOperatorQueuer, operation, targetVault, amount) + await handleOracleReporting(client, mockWindow, priceOracleManagerAndOperatorQueuer, forceRepEthPriceTo) +} + +export const manipulatePriceOracle = async (client: WriteClient, mockWindow: MockWindowEthereum, priceOracleManagerAndOperatorQueuer: `0x${ string }`, forceRepEthPriceTo: bigint = PRICE_PRECISION) => { + await requestPrice(client, priceOracleManagerAndOperatorQueuer) + await handleOracleReporting(client, mockWindow, priceOracleManagerAndOperatorQueuer, forceRepEthPriceTo) +} + +export const canLiquidate = (lastPrice: bigint, securityBondAllowance: bigint, stakedRep: bigint, securityMultiplier: bigint) => securityBondAllowance * lastPrice * securityMultiplier > stakedRep * PRICE_PRECISION + diff --git a/solidity/ts/testsuite/simulator/utils/contracts/securityPool.ts b/solidity/ts/testsuite/simulator/utils/contracts/securityPool.ts new file mode 100644 index 0000000..2080dbc --- /dev/null +++ b/solidity/ts/testsuite/simulator/utils/contracts/securityPool.ts @@ -0,0 +1,218 @@ +import { peripherals_SecurityPool_SecurityPool } from '../../../../types/contractArtifact.js' +import { SystemState } from '../../types/peripheralTypes.js' +import { QuestionOutcome } from '../../types/types.js' +import { ReadClient, WriteClient } from '../viem.js' + +export const depositToEscalationGame = async (client: WriteClient, securityPoolAddress: `0x${ string }`, outcome: QuestionOutcome, amount: bigint) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'depositToEscalationGame', + address: securityPoolAddress, + args: [outcome, amount], + }) +} + +export const withdrawFromEscalationGame = async (client: WriteClient, securityPoolAddress: `0x${ string }`, depositIndexes: bigint[]) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'withdrawFromEscalationGame', + address: securityPoolAddress, + args: [depositIndexes], + }) +} + +export const depositRep = async (client: WriteClient, securityPoolAddress: `0x${ string }`, amount: bigint) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'depositRep', + address: securityPoolAddress, + args: [amount] + }) +} + +export const createCompleteSet = async (client: WriteClient, securityPoolAddress: `0x${ string }`, completeSetsToCreate: bigint) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'createCompleteSet', + address: securityPoolAddress, + args: [], + value: completeSetsToCreate, + }) +} + +export const redeemCompleteSet = async (client: WriteClient, securityPoolAddress: `0x${ string }`, completeSetsToRedeem: bigint) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'redeemCompleteSet', + address: securityPoolAddress, + args: [completeSetsToRedeem], + }) +} + +export const getTotalSecurityBondAllowance = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'totalSecurityBondAllowance', + address: securityPoolAddress, + args: [] + }) as bigint +} + +export const getCompleteSetCollateralAmount = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'completeSetCollateralAmount', + address: securityPoolAddress, + args: [] + }) as bigint +} + +export const getSystemState = async (client: ReadClient, securityPoolAddress: `0x${ string }`): Promise => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'systemState', + address: securityPoolAddress, + args: [], + }) +} + +export const getCurrentRetentionRate = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'currentRetentionRate', + address: securityPoolAddress, + args: [], + }) +} + +export const getSecurityVault = async (client: ReadClient, securityPoolAddress: `0x${ string }`, securityVault: `0x${ string }`) => { + const [repDepositShare, securityBondAllowance, unpaidEthFees, feeIndex] = await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'securityVaults', + address: securityPoolAddress, + args: [securityVault], + }) + return { repDepositShare, securityBondAllowance, unpaidEthFees, feeIndex } +} + +export const getSecurityPoolsEscalationGame = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'escalationGame', + address: securityPoolAddress, + args: [], + }) +} + +export const getPoolOwnershipDenominator = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'poolOwnershipDenominator', + address: securityPoolAddress, + args: [], + }) +} + +export const poolOwnershipToRep = async (client: ReadClient, securityPoolAddress: `0x${ string }`, poolOwnership: bigint) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'poolOwnershipToRep', + address: securityPoolAddress, + args: [poolOwnership], + }) +} + +export const repToPoolOwnership = async (client: ReadClient, securityPoolAddress: `0x${ string }`, repAmount: bigint) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'repToPoolOwnership', + address: securityPoolAddress, + args: [repAmount], + }) +} + +export const redeemShares = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'redeemShares', + address: securityPoolAddress, + args: [], + }) +} + + +export const getTotalFeesOwedToVaults = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'totalFeesOwedToVaults', + address: securityPoolAddress, + args: [], + }) +} + +export const sharesToCash = async (client: ReadClient, securityPoolAddress: `0x${ string }`, completeSetAmount: bigint) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'sharesToCash', + address: securityPoolAddress, + args: [completeSetAmount], + }) +} + +export const cashToShares = async (client: ReadClient, securityPoolAddress: `0x${ string }`, eth: bigint) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'cashToShares', + address: securityPoolAddress, + args: [eth], + }) +} + +export const getShareTokenSupply = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'shareTokenSupply', + address: securityPoolAddress, + args: [], + }) +} + +export const shareArrayToCash = async (client: ReadClient, securityPoolAddress: `0x${ string }`, shares: readonly bigint[]) => { + return await Promise.all(shares.map((shares) => sharesToCash(client, securityPoolAddress, shares))) +} + +export const updateVaultFees = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'updateVaultFees', + address: securityPoolAddress, + args: [vault], + }) +} + +export const redeemFees = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'redeemFees', + address: securityPoolAddress, + args: [vault], + }) +} + +export const redeemRep = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'redeemRep', + address: securityPoolAddress, + args: [vault], + }) +} + +export const getRepToken = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'repToken', + address: securityPoolAddress, + args: [], + }) +} diff --git a/solidity/ts/testsuite/simulator/utils/contracts/securityPoolForker.ts b/solidity/ts/testsuite/simulator/utils/contracts/securityPoolForker.ts new file mode 100644 index 0000000..e93cab4 --- /dev/null +++ b/solidity/ts/testsuite/simulator/utils/contracts/securityPoolForker.ts @@ -0,0 +1,134 @@ +import { peripherals_SecurityPool_SecurityPool, peripherals_SecurityPoolForker_SecurityPoolForker } from '../../../../types/contractArtifact.js' +import { QuestionOutcome } from '../../types/types.js' +import { getInfraContractAddresses } from './deployPeripherals.js' +import { contractExists } from '../utilities.js' +import { ReadClient, WriteClient } from '../viem.js' + +export const forkSecurityPool = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'forkSecurityPool', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress], + }) +} + +export const migrateVault = async (client: WriteClient, securityPoolAddress: `0x${ string }`, outcome: bigint | QuestionOutcome) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'migrateVault', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress, Number(outcome)], + }) +} + +export const startTruthAuction = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'startTruthAuction', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress], + }) +} + +export const finalizeTruthAuction = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'finalizeTruthAuction', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress], + }) +} + +export const claimAuctionProceeds = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'claimAuctionProceeds', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress, vault], + }) +} + +export const forkZoltarWithOwnEscalationGame = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'forkZoltarWithOwnEscalationGame', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress], + }) +} + +export const getMigratedRep = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'getMigratedRep', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress], + }) +} + +export const getMarketOutcome = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + if(!(await contractExists(client, securityPoolAddress))) return QuestionOutcome.None + return await client.readContract({ + abi: [{ + "inputs": [ + { + "internalType": "contract ISecurityPool", + "name": "securityPool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "getMarketOutcome", + "outputs": [ + { + "internalType": "enum YesNoMarkets.Outcome", + "name": "outcome", + "type": "uint8" + } + ] + }] as const, // typescript limitation on types... + functionName: 'getMarketOutcome', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress], + }) +} + +export const createChildUniverse = async (client: WriteClient, securityPoolAddress: `0x${ string }`, outcome: QuestionOutcome) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'createChildUniverse', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress, Number(outcome)], + }) +} + +export const getSecurityPoolForkerForkData = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + const data = await client.readContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'forkData', + address: getInfraContractAddresses().securityPoolForker, + args: [securityPoolAddress], + }) + const [ repAtFork, truthAuction, truthAuctionStarted, migratedRep, auctionedSecurityBondAllowance, ownFork, outcomeIndex] = data + return { repAtFork, truthAuction, truthAuctionStarted, migratedRep, auctionedSecurityBondAllowance, ownFork, outcomeIndex } +} + +export const migrateFromEscalationGame = async (client: WriteClient, parentSecurityPool: `0x${ string }`, vault: `0x${ string }`, outcomeIndex: QuestionOutcome, depositIndexes: bigint[]) => { + return await client.writeContract({ + abi: peripherals_SecurityPoolForker_SecurityPoolForker.abi, + functionName: 'migrateFromEscalationGame', + address: getInfraContractAddresses().securityPoolForker, + args: [parentSecurityPool, vault, outcomeIndex, depositIndexes.map((x) => Number(x))], + }) +} + +export const getCompleteSetCollateralAmount = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { + return await client.readContract({ + abi: peripherals_SecurityPool_SecurityPool.abi, + functionName: 'completeSetCollateralAmount', + address: securityPoolAddress, + args: [], + }) +} diff --git a/solidity/ts/testsuite/simulator/utils/deployPeripherals.ts b/solidity/ts/testsuite/simulator/utils/deployPeripherals.ts deleted file mode 100644 index a7a9f53..0000000 --- a/solidity/ts/testsuite/simulator/utils/deployPeripherals.ts +++ /dev/null @@ -1,172 +0,0 @@ -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_factories_AuctionFactory_AuctionFactory, peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory, peripherals_factories_SecurityPoolFactory_SecurityPoolFactory, peripherals_factories_ShareTokenFactory_ShareTokenFactory, peripherals_openOracle_OpenOracle_OpenOracle, peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer, peripherals_SecurityPool_SecurityPool, peripherals_SecurityPoolUtils_SecurityPoolUtils, 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_factories_SecurityPoolFactory_SecurityPoolFactory.abi, - bytecode: applyLibraries(peripherals_factories_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_factories_ShareTokenFactory_ShareTokenFactory.abi, - bytecode: `0x${ peripherals_factories_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_factories_AuctionFactory_AuctionFactory.evm.bytecode.object }`, from: addressString(PROXY_DEPLOYER_ADDRESS), salt: numberToBytes(0) }), - priceOracleManagerAndOperatorQueuerFactory: getCreate2Address({ bytecode: `0x${ peripherals_factories_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_factories_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_factories_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_factories_SecurityPoolFactory_SecurityPoolFactory.abi, - functionName: 'deployOriginSecurityPool', - address: infraAddresses.securityPoolFactory, - args: [universeId, questionId, securityMultiplier, startingRetentionRate, startingRepEthPrice, completeSetCollateralAmount] - }) -} diff --git a/solidity/ts/testsuite/simulator/utils/logExplaining.ts b/solidity/ts/testsuite/simulator/utils/logExplaining.ts index 50aeb98..611a4b9 100644 --- a/solidity/ts/testsuite/simulator/utils/logExplaining.ts +++ b/solidity/ts/testsuite/simulator/utils/logExplaining.ts @@ -1,6 +1,6 @@ import { Abi, decodeEventLog, GetLogsReturnType } from 'viem' -import { isUnknownAnAddress } from './utilities.js' +import { isUnknownAddress } from './utilities.js' export type Deployment = { deploymentName: string @@ -71,7 +71,7 @@ export const printLogs = (rawLogs: GetLogsReturnType, deployments: Deployment[]) console.log(`${ padding }${ head }(`) for (const [paramName, paramValue] of Object.entries(log.args)) { let formattedValue = paramValue - if (isUnknownAnAddress(paramValue)) { + if (isUnknownAddress(paramValue)) { const matchingDeployment = deployments.find((deploymentItem) => deploymentItem.address.toLowerCase() === paramValue.toLowerCase()) if (matchingDeployment) { formattedValue = `${ matchingDeployment.deploymentName } (${ paramValue })` diff --git a/solidity/ts/testsuite/simulator/utils/peripherals.ts b/solidity/ts/testsuite/simulator/utils/peripherals.ts deleted file mode 100644 index 6b82878..0000000 --- a/solidity/ts/testsuite/simulator/utils/peripherals.ts +++ /dev/null @@ -1,505 +0,0 @@ -import 'viem/window' -import { ReadContractReturnType } from 'viem' -import { ReadClient, WriteClient } from './viem.js' -import { WETH_ADDRESS } from './constants.js' -import { SystemState } from '../types/peripheralTypes.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' -import { getInfraContractAddresses } from './deployPeripherals.js' - -export const depositRep = async (client: WriteClient, securityPoolAddress: `0x${ string }`, amount: bigint) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'depositRep', - address: securityPoolAddress, - args: [amount] - }) -} - -export enum OperationType { - Liquidation = 0, - WithdrawRep = 1, - SetSecurityBondsAllowance = 2 -} - -export const requestPriceIfNeededAndQueueOperation = async (client: WriteClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`, operation: OperationType, targetVault: `0x${ string }`, amount: bigint) => { - const ethCost = await getRequestPriceEthCost(client, priceOracleManagerAndOperatorQueuer) * 2n; - return await client.writeContract({ - abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, - functionName: 'requestPriceIfNeededAndQueueOperation', - address: priceOracleManagerAndOperatorQueuer, - args: [operation, targetVault, amount], - value: ethCost, - }) -} - -export const requestPrice = async (client: WriteClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { - const ethCost = await getRequestPriceEthCost(client, priceOracleManagerAndOperatorQueuer) * 2n; - return await client.writeContract({ - abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, - functionName: 'requestPrice', - address: priceOracleManagerAndOperatorQueuer, - args: [], - value: ethCost, - }) -} - -export const getPendingReportId = async (client: ReadClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, - functionName: 'pendingReportId', - address: priceOracleManagerAndOperatorQueuer, - args: [] - }) as bigint -} - -interface ExtraReportData { - stateHash: `0x${ string }` - callbackContract: `0x${ string }` - numReports: number - callbackGasLimit: number - callbackSelector: `0x${ string }` - protocolFeeRecipient: `0x${ string }` - trackDisputes: boolean - keepFee: boolean - feeToken: boolean -} - -export const getOpenOracleExtraData = async (client: ReadClient, extraDataId: bigint): Promise => { - const result = await client.readContract({ - abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, - functionName: 'extraData', - address: getInfraContractAddresses().openOracle, - args: [extraDataId] - }) as ReadContractReturnType - - const [ - stateHash, - callbackContract, - numReports, - callbackGasLimit, - callbackSelector, - protocolFeeRecipient, - trackDisputes, - keepFee, - feeToken - ] = result as [ - `0x${ string }`, - `0x${ string }`, - bigint, - bigint, - `0x${ string }`, - `0x${ string }`, - boolean, - boolean, - boolean - ] - - return { - stateHash, - callbackContract, - numReports: Number(numReports), - callbackGasLimit: Number(callbackGasLimit), - callbackSelector, - protocolFeeRecipient, - trackDisputes, - keepFee, - feeToken - } -} - -export const openOracleSubmitInitialReport = async (client: WriteClient, reportId: bigint, amount1: bigint, amount2: bigint, stateHash: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, - functionName: 'submitInitialReport', - address: getInfraContractAddresses().openOracle, - args: [reportId, amount1, amount2, stateHash] - }) -} - -export const openOracleSettle = async (client: WriteClient, reportId: bigint) => { - return await client.writeContract({ - abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, - functionName: 'settle', - address: getInfraContractAddresses().openOracle, - gas: 5_000_000n, //needed because of gas() opcode being used - args: [reportId] - }) -} - -export const getRequestPriceEthCost = async (client: ReadClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, - functionName: 'getRequestPriceEthCost', - address: priceOracleManagerAndOperatorQueuer, - args: [] - }) as bigint -} - -export const wrapWeth = async (client: WriteClient, amount: bigint) => { - const wethAbi = [{ - type: 'function', - name: 'deposit', - stateMutability: 'payable', - inputs: [], - outputs: [] - }] - return await client.writeContract({ - abi: wethAbi, - address: WETH_ADDRESS, - functionName: 'deposit', - value: amount - }) -} - -export interface ReportMeta { - exactToken1Report: bigint - escalationHalt: bigint - fee: bigint - settlerReward: bigint - token1: `0x${ string }` - settlementTime: number - token2: `0x${ string }` - timeType: boolean - feePercentage: number - protocolFee: number - multiplier: number - disputeDelay: number -} - -export const getOpenOracleReportMeta = async (client: ReadClient, reportId: bigint): Promise => { - const reportMetaData = await client.readContract({ - abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, - functionName: 'reportMeta', - address: getInfraContractAddresses().openOracle, - args: [reportId] - }) - - const [ - exactToken1Report, - escalationHalt, - fee, - settlerReward, - token1, - settlementTime, - token2, - timeType, - feePercentage, - protocolFee, - multiplier, - disputeDelay - ] = reportMetaData - - return { - exactToken1Report, - escalationHalt, - fee, - settlerReward, - token1, - settlementTime, - token2, - timeType, - feePercentage, - protocolFee, - multiplier, - disputeDelay - } -} - -export const createCompleteSet = async (client: WriteClient, securityPoolAddress: `0x${ string }`, completeSetsToCreate: bigint) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'createCompleteSet', - address: securityPoolAddress, - args: [], - value: completeSetsToCreate, - }) -} - -export const redeemCompleteSet = async (client: WriteClient, securityPoolAddress: `0x${ string }`, completeSetsToRedeem: bigint) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'redeemCompleteSet', - address: securityPoolAddress, - args: [completeSetsToRedeem], - }) -} - -export const getSecurityBondAllowance = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'securityBondAllowance', - address: securityPoolAddress, - args: [] - }) as bigint -} - -export const getCompleteSetCollateralAmount = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'completeSetCollateralAmount', - address: securityPoolAddress, - args: [] - }) as bigint -} - -export const getLastPrice = async (client: ReadClient, priceOracleManagerAndOperatorQueuer: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_PriceOracleManagerAndOperatorQueuer_PriceOracleManagerAndOperatorQueuer.abi, - functionName: 'lastPrice', - address: priceOracleManagerAndOperatorQueuer, - args: [] - }) as bigint -} - -export const forkSecurityPool = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'forkSecurityPool', - address: securityPoolAddress, - args: [], - }) -} - -export const migrateVault = async (client: WriteClient, securityPoolAddress: `0x${ string }`, outcome: QuestionOutcome) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'migrateVault', - address: securityPoolAddress, - args: [Number(outcome)], - }) -} - -export const startTruthAuction = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'startTruthAuction', - address: securityPoolAddress, - args: [], - }) -} - -export const finalizeTruthAuction = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'finalizeTruthAuction', - address: securityPoolAddress, - args: [], - }) -} - -export const claimAuctionProceeds = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'claimAuctionProceeds', - address: securityPoolAddress, - args: [vault], - }) -} - -export const participateAuction = async (client: WriteClient, auctionAddress: `0x${ string }`, repToBuy: bigint, ethToInvest: bigint) => { - return await client.writeContract({ - abi: peripherals_Auction_Auction.abi, - functionName: 'participate', - address: auctionAddress, - args: [repToBuy], - value: ethToInvest - }) -} -export const getEthAmountToBuy = async (client: ReadClient, auctionAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_Auction_Auction.abi, - functionName: 'ethAmountToBuy', - address: auctionAddress, - args: [], - }) -} - -export const getMigratedRep = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'migratedRep', - address: securityPoolAddress, - args: [], - }) -} - -export const getSystemState = async (client: ReadClient, securityPoolAddress: `0x${ string }`): Promise => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'systemState', - address: securityPoolAddress, - args: [], - }) -} - -export const getCurrentRetentionRate = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'currentRetentionRate', - address: securityPoolAddress, - args: [], - }) -} - -export const getSecurityVault = async (client: ReadClient, securityPoolAddress: `0x${ string }`, securityVault: `0x${ string }`) => { - const vault = await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'securityVaults', - address: securityPoolAddress, - args: [securityVault], - }) - const [repDepositShare, securityBondAllowance, unpaidEthFees, feeIndex ] = vault - return { repDepositShare, securityBondAllowance, unpaidEthFees, feeIndex } -} - -export const getPoolOwnershipDenominator = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'poolOwnershipDenominator', - address: securityPoolAddress, - args: [], - }) -} - -export const poolOwnershipToRep = async (client: ReadClient, securityPoolAddress: `0x${ string }`, poolOwnership: bigint) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'poolOwnershipToRep', - address: securityPoolAddress, - args: [poolOwnership], - }) -} - -export const repToPoolOwnership = async (client: ReadClient, securityPoolAddress: `0x${ string }`, repAmount: bigint) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'repToPoolOwnership', - address: securityPoolAddress, - args: [repAmount], - }) -} - -export const redeemShares = async (client: WriteClient, securityPoolAddress: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'redeemShares', - address: securityPoolAddress, - args: [], - }) -} - -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], - }) -} -export const balanceOfSharesInCash = async (client: ReadClient, seucurityPoolAddress: `0x${ string }`, shareTokenAddress: `0x${ string }`, universeId: bigint, account: `0x${ string }`) => { - const array = await client.readContract({ - abi: peripherals_tokens_ShareToken_ShareToken.abi, - functionName: 'balanceOfShares', - address: shareTokenAddress, - args: [universeId, account], - }) - return await shareArrayToCash(client, seucurityPoolAddress, array) -} - -export const getTokenId = (universeId: bigint, outcome: QuestionOutcome) => (universeId << 8n) + BigInt(outcome) -export const unpackTokenId = (tokenId: bigint): { universe: bigint, outcome: QuestionOutcome } => ({ universe: tokenId >> 8n, outcome: Number(tokenId & 0xFFn) }) - -export const migrateShares = async (client: WriteClient, shareTokenAddress: `0x${ string }`, universeId: bigint, outcome: QuestionOutcome) => { - return await client.writeContract({ - abi: peripherals_tokens_ShareToken_ShareToken.abi, - functionName: 'migrate', - address: shareTokenAddress, - args: [getTokenId(universeId, outcome)], - }) -} - -export const createChildUniverse = async (client: WriteClient, securityPoolAddress: `0x${ string }`, outcome: QuestionOutcome) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'createChildUniverse', - address: securityPoolAddress, - args: [outcome], - }) -} - -export const getTotalFeesOvedToVaults = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'totalFeesOvedToVaults', - address: securityPoolAddress, - args: [], - }) -} - -export const sharesToCash = async (client: ReadClient, securityPoolAddress: `0x${ string }`, completeSetAmount: bigint) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'sharesToCash', - address: securityPoolAddress, - args: [completeSetAmount], - }) -} - -export const cashToShares = async (client: ReadClient, securityPoolAddress: `0x${ string }`, eth: bigint) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'cashToShares', - address: securityPoolAddress, - args: [eth], - }) -} - -export const getShareTokenSupply = async (client: ReadClient, securityPoolAddress: `0x${ string }`) => { - return await client.readContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'shareTokenSupply', - address: securityPoolAddress, - args: [], - }) -} - -export const shareArrayToCash = async (client: ReadClient, securityPoolAddress: `0x${ string }`, shares: readonly bigint[]) => { - return await Promise.all(shares.map((shares) => sharesToCash(client, securityPoolAddress, shares))) -} - -export const updateVaultFees = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'updateVaultFees', - address: securityPoolAddress, - args: [vault], - }) -} - -export const redeemFees = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'redeemFees', - address: securityPoolAddress, - args: [vault], - }) -} - -export const redeemRep = async (client: WriteClient, securityPoolAddress: `0x${ string }`, vault: `0x${ string }`) => { - return await client.writeContract({ - abi: peripherals_SecurityPool_SecurityPool.abi, - functionName: 'redeemRep', - address: securityPoolAddress, - args: [vault], - }) -} diff --git a/solidity/ts/testsuite/simulator/utils/peripheralsTestUtils.ts b/solidity/ts/testsuite/simulator/utils/peripheralsTestUtils.ts deleted file mode 100644 index 09dce00..0000000 --- a/solidity/ts/testsuite/simulator/utils/peripheralsTestUtils.ts +++ /dev/null @@ -1,106 +0,0 @@ -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 { 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, requestPrice, requestPriceIfNeededAndQueueOperation, wrapWeth } from './peripherals.js' - -export const genesisUniverse = 0n -export const questionId = 1n -export const securityMultiplier = 2n -export const startingRepEthPrice = 1n -export const completeSetCollateralAmount = 0n -export const PRICE_PRECISION = 10n ** 18n -export const MAX_RETENTION_RATE = 999_999_996_848_000_000n // ≈90% yearly - -export const deployZoltarAndCreateMarket = async (client: WriteClient, questionEndTime: bigint) => { - await ensureZoltarDeployed(client) - const isDeployed = await isZoltarDeployed(client) - assert.ok(isDeployed, `Zoltar Not Deployed!`) - const zoltar = getZoltarAddress() - await approveToken(client, addressString(GENESIS_REPUTATION_TOKEN), zoltar) - await createQuestion(client, genesisUniverse, questionEndTime, 'test') - return await getQuestionData(client, questionId) -} - -export const deployPeripherals = async (client: WriteClient) => { - 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 = 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) - await approveToken(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddress) - await depositRep(client, securityPoolAddress, repDeposit) - - const newBalance = await getERC20Balance(client, addressString(GENESIS_REPUTATION_TOKEN), securityPoolAddress) - assert.strictEqual(newBalance, startBalance + repDeposit, 'Did not deposit rep') -} - -export const triggerFork = async(client: WriteClient, mockWindow: MockWindowEthereum, questionId: bigint) => { - await ensureZoltarDeployed(client) - await mockWindow.advanceTime(DAY) - const initialOutcome = QuestionOutcome.Yes - await reportOutcome(client, genesisUniverse, questionId, initialOutcome) - const disputeOutcome = QuestionOutcome.No - await dispute(client, genesisUniverse, questionId, disputeOutcome) - const invalidUniverseId = 1n - const yesUniverseId = 2n - const noUniverseId = 3n - return { - invalidUniverseData: await getUniverseData(client, invalidUniverseId), - yesUniverseData: await getUniverseData(client, yesUniverseId), - noUniverseData: await getUniverseData(client, noUniverseId) - } -} - -export const handleOracleReporting = async(client: WriteClient, mockWindow: MockWindowEthereum, priceOracleManagerAndOperatorQueuer: `0x${ string }`, forceRepEthPriceTo: bigint) => { - const pendingReportId = await getPendingReportId(client, priceOracleManagerAndOperatorQueuer) - if (pendingReportId === 0n) { - // operation already executed - return - } - assert.ok(pendingReportId > 0, 'Operation is not queued') - - const reportMeta = await getOpenOracleReportMeta(client, pendingReportId) - - // initial report - const amount1 = reportMeta.exactToken1Report - const amount2 = amount1 * PRICE_PRECISION / forceRepEthPriceTo - - 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') - - const stateHash = (await getOpenOracleExtraData(client, pendingReportId)).stateHash - await openOracleSubmitInitialReport(client, pendingReportId, amount1, amount2, stateHash) - - await mockWindow.advanceTime(DAY) - - await openOracleSettle(client, pendingReportId) -} - -export const manipulatePriceOracleAndPerformOperation = async(client: WriteClient, mockWindow: MockWindowEthereum, priceOracleManagerAndOperatorQueuer: `0x${ string }`, operation: OperationType, targetVault: `0x${ string }`, amount: bigint, forceRepEthPriceTo: bigint = PRICE_PRECISION) => { - await requestPriceIfNeededAndQueueOperation(client, priceOracleManagerAndOperatorQueuer, operation, targetVault, amount) - await handleOracleReporting(client, mockWindow, priceOracleManagerAndOperatorQueuer, forceRepEthPriceTo) -} - -export const manipulatePriceOracle = async(client: WriteClient, mockWindow: MockWindowEthereum, priceOracleManagerAndOperatorQueuer: `0x${ string }`, forceRepEthPriceTo: bigint = PRICE_PRECISION) => { - await requestPrice(client, priceOracleManagerAndOperatorQueuer) - await handleOracleReporting(client, mockWindow, priceOracleManagerAndOperatorQueuer, forceRepEthPriceTo) -} - -export const canLiquidate = (lastPrice: bigint, securityBondAllowance: bigint, stakedRep: bigint, securityMultiplier: bigint) => securityBondAllowance * lastPrice * securityMultiplier > stakedRep * PRICE_PRECISION - diff --git a/solidity/ts/testsuite/simulator/utils/testUtils.ts b/solidity/ts/testsuite/simulator/utils/testUtils.ts new file mode 100644 index 0000000..6843b1e --- /dev/null +++ b/solidity/ts/testsuite/simulator/utils/testUtils.ts @@ -0,0 +1,18 @@ +import assert from 'node:assert' +import { abs, bigintToDecimalString } from './bigint.js' + +export const strictEqualTypeSafe = (actual: Type, expected: Type, errorMessage?: string | Error | undefined) => assert.strictEqual(actual, expected, errorMessage) + +export const strictEqual18Decimal = (actual: bigint, expected: bigint, errorMessage?: string | Error | undefined) => assert.strictEqual(bigintToDecimalString(actual, 18n), bigintToDecimalString(expected, 18n), errorMessage) + +export const approximatelyEqual = (actual: bigint, expected: bigint, errorDelta: bigint, errorMessage?: string | undefined) => { + if (errorDelta < 0n) throw new RangeError('errorDelta must be non-negative') + const diff = abs(actual - expected) + if (diff > errorDelta) { + throw new assert.AssertionError({ + message: errorMessage || `Expected values to be within ${ errorDelta }, but difference was ${ diff }`, + actual, + expected + }) + } +} diff --git a/solidity/ts/testsuite/simulator/utils/typescript.ts b/solidity/ts/testsuite/simulator/utils/typescript.ts index eb093a3..9070ef5 100644 --- a/solidity/ts/testsuite/simulator/utils/typescript.ts +++ b/solidity/ts/testsuite/simulator/utils/typescript.ts @@ -1,36 +1,38 @@ -export type UnionToIntersection = (T extends unknown ? (k: T) => void : never) extends (k: infer I) => void ? I : never - -export function assertNever(value: never): never { - throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`) -} - -export type DistributedOmit> = T extends unknown ? Pick> : never - -export type DistributiveOmit = T extends unknown ? Omit : never - -export function assertUnreachable(value: never): never { - throw new Error(`Unreachable! (${ value })`) -} - -function isObject(maybe: unknown): maybe is Object { - return typeof maybe === 'object' && maybe !== null && !Array.isArray(maybe) -} - -export function assertIsObject(maybe: unknown): asserts maybe is Object { - if (!isObject(maybe)) throw new Error(`Expected object but got ${ typeof maybe }`) -} - -export function createGuard(check: (maybe: T) => U | undefined): (maybe: T) => maybe is U { - return (maybe: T): maybe is U => check(maybe) !== undefined -} - -export function getWithDefault(map: Map, key: Key, defaultValue: Value) { - const previousValue = map.get(key) - if (previousValue === undefined) return defaultValue - return previousValue -} - -type Split = { [K in keyof T]: { [P in K]: T[P] } }[keyof T] | Record -export function modifyObject(original: T, subObject: NoInfer>): T { - return {...original, ...subObject } -} +export type UnionToIntersection = (T extends unknown ? (k: T) => void : never) extends (k: infer I) => void ? I : never + +export function assertNever(value: never): never { + throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`) +} + +export type DistributedOmit> = T extends unknown ? Pick> : never + +export type DistributiveOmit = T extends unknown ? Omit : never + +export function assertUnreachable(value: never): never { + throw new Error(`Unreachable! (${ value })`) +} + +function isObject(maybe: unknown): maybe is Object { + return typeof maybe === 'object' && maybe !== null && !Array.isArray(maybe) +} + +export function assertIsObject(maybe: unknown): asserts maybe is Object { + if (!isObject(maybe)) throw new Error(`Expected object but got ${ typeof maybe }`) +} + +export function createGuard(check: (maybe: T) => U | undefined): (maybe: T) => maybe is U { + return (maybe: T): maybe is U => check(maybe) !== undefined +} + +export function getWithDefault(map: Map, key: Key, defaultValue: Value) { + const previousValue = map.get(key) + if (previousValue === undefined) return defaultValue + return previousValue +} + +type Split = { [K in keyof T]: { [P in K]: T[P] } }[keyof T] | Record +export function modifyObject(original: T, subObject: NoInfer>): T { + return {...original, ...subObject } +} + +export const objectEntries = (obj: T) => Object.entries(obj) as [string, T[keyof T & string]][] diff --git a/solidity/ts/testsuite/simulator/utils/utilities.ts b/solidity/ts/testsuite/simulator/utils/utilities.ts index e359afc..7b97738 100644 --- a/solidity/ts/testsuite/simulator/utils/utilities.ts +++ b/solidity/ts/testsuite/simulator/utils/utilities.ts @@ -1,17 +1,15 @@ import 'viem/window' -import { getContractAddress, numberToBytes, encodeAbiParameters, keccak256, encodeDeployData, getCreate2Address } from 'viem' +import { encodeAbiParameters, keccak256 } from 'viem' import { mainnet } from 'viem/chains' import { ReadClient, WriteClient } from './viem.js' import { GENESIS_REPUTATION_TOKEN, PROXY_DEPLOYER_ADDRESS, TEST_ADDRESSES } from './constants.js' -import { abs, addressString, bytes32String } from './bigint.js' +import { addressString } from './bigint.js' import { Address } from 'viem' import { ABIS } from '../../../abi/abis.js' import { MockWindowEthereum } from '../MockWindowEthereum.js' -import { ReputationToken_ReputationToken, Zoltar_Zoltar } from '../../../types/contractArtifact.js' import { QuestionOutcome } from '../types/types.js' -import assert from 'node:assert' -export const initialTokenBalance = 1000000n * 10n**18n +export const TOKEN_AMOUNT_TO_MINT = 100000000n * 10n ** 18n export async function sleep(milliseconds: number) { await new Promise(resolve => setTimeout(resolve, milliseconds)) @@ -210,9 +208,7 @@ export const getETHBalance = async (client: ReadClient, address: Address) => { } export const setupTestAccounts = async (mockWindowEthereum: MockWindowEthereum) => { - const accountValues = TEST_ADDRESSES.map((address) => { - return { address: addressString(address), amount: initialTokenBalance} - }) + const accountValues = TEST_ADDRESSES.map((address) => ({ address: addressString(address), amount: TOKEN_AMOUNT_TO_MINT})) await mintETH(mockWindowEthereum, accountValues) await mintERC20(mockWindowEthereum, addressString(GENESIS_REPUTATION_TOKEN), accountValues, 1n) } @@ -226,163 +222,11 @@ export async function ensureProxyDeployerDeployed(client: WriteClient): Promise< await client.waitForTransactionReceipt({ hash: deployHash }) } -export function getZoltarAddress() { - const bytecode: `0x${ string }` = `0x${ Zoltar_Zoltar.evm.bytecode.object }` - return getContractAddress({ bytecode, from: addressString(PROXY_DEPLOYER_ADDRESS), opcode: 'CREATE2', salt: numberToBytes(0) }) -} - -export const isZoltarDeployed = async (client: ReadClient) => { - const expectedDeployedBytecode: `0x${ string }` = `0x${ Zoltar_Zoltar.evm.deployedBytecode.object }` - const address = getZoltarAddress() - const deployedBytecode = await client.getCode({ address }) - return deployedBytecode === expectedDeployedBytecode -} - -export const deployZoltarTransaction = () => { - const bytecode: `0x${ string }` = `0x${ Zoltar_Zoltar.evm.bytecode.object }` - return { to: addressString(PROXY_DEPLOYER_ADDRESS), data: bytecode } as const -} - -export const ensureZoltarDeployed = async (client: WriteClient) => { - await ensureProxyDeployerDeployed(client) - if (await isZoltarDeployed(client)) return - const hash = await client.sendTransaction(deployZoltarTransaction()) - await client.waitForTransactionReceipt({ hash }) -} - -export const getUniverseData = async (client: ReadClient, universeId: bigint) => { - const universeData = await client.readContract({ - abi: Zoltar_Zoltar.abi, - functionName: 'universes', - address: getZoltarAddress(), - args: [universeId] - }) - const [reputationToken, forkingQuestion, forkTime] = universeData - return { reputationToken, forkingQuestion, forkTime } -} - -export const createQuestion = async (client: WriteClient, universe: bigint, endTime: bigint, extraInfo: string) => { - return await client.writeContract({ - chain: mainnet, - abi: Zoltar_Zoltar.abi, - functionName: 'createQuestion', - address: getZoltarAddress(), - args: [universe, endTime, client.account.address, extraInfo] - }) -} - -export const getQuestionData = async (client: ReadClient, questionId: bigint) => { - const questionData = await client.readContract({ - abi: Zoltar_Zoltar.abi, - functionName: 'questions', - address: getZoltarAddress(), - args: [questionId] - }) - const [endTime, originUniverse, designatedReporter, extraInfo] = questionData - - return { - endTime, - originUniverse, - designatedReporter, - extraInfo - } -} - -export const reportOutcome = async (client: WriteClient, universe: bigint, question: bigint, outcome: QuestionOutcome) => { - return await client.writeContract({ - chain: mainnet, - abi: Zoltar_Zoltar.abi, - functionName: 'reportOutcome', - address: getZoltarAddress(), - args: [universe, question, Number(outcome)] - }) -} - -export const finalizeQuestion = async (client: WriteClient, universe: bigint, question: bigint) => { - return await client.writeContract({ - chain: mainnet, - abi: Zoltar_Zoltar.abi, - functionName: 'finalizeQuestion', - address: getZoltarAddress(), - args: [universe, question] - }) -} - -export const dispute = async (client: WriteClient, universe: bigint, question: bigint, outcome: QuestionOutcome) => { - return await client.writeContract({ - chain: mainnet, - abi: Zoltar_Zoltar.abi, - functionName: 'dispute', - address: getZoltarAddress(), - args: [universe, question, Number(outcome)] - }) -} - -export const splitRep = async (client: WriteClient, universe: bigint) => { - return await client.writeContract({ - chain: mainnet, - abi: Zoltar_Zoltar.abi, - functionName: 'splitRep', - address: getZoltarAddress(), - args: [universe] - }) -} - -export const splitStakedRep = async (client: WriteClient, universe: bigint, question: bigint) => { - return await client.writeContract({ - chain: mainnet, - abi: Zoltar_Zoltar.abi, - functionName: 'splitStakedRep', - address: getZoltarAddress(), - args: [universe, question] - }) -} - -export const isFinalized = async (client: ReadClient, universe: bigint, questionId: bigint) => { - return await client.readContract({ - abi: Zoltar_Zoltar.abi, - functionName: 'isFinalized', - address: getZoltarAddress(), - args: [universe, questionId] - }) -} - -export const getWinningOutcome = async (client: ReadClient, universe: bigint, questionId: bigint): Promise => { - return await client.readContract({ - abi: Zoltar_Zoltar.abi, - functionName: 'getWinningOutcome', - address: getZoltarAddress(), - args: [universe, questionId] - }) -} - -export const getReportBond = async (client: ReadClient) => { - return await client.readContract({ - abi: Zoltar_Zoltar.abi, - functionName: 'REP_BOND', - address: getZoltarAddress(), - args: [] - }) -} - -export function getChildUniverseId(parentUniverseId: bigint, outcome: QuestionOutcome): bigint { - return (parentUniverseId << 2n) + BigInt(outcome) + 1n -} - -export function getRepTokenAddress(universeId: bigint): `0x${ string }` { - if (universeId === 0n) return addressString(GENESIS_REPUTATION_TOKEN) - const initCode = encodeDeployData({ - abi: ReputationToken_ReputationToken.abi, - bytecode: `0x${ ReputationToken_ReputationToken.evm.bytecode.object }`, - args: [getZoltarAddress()] - }) - return getCreate2Address({ from: getZoltarAddress(), salt: bytes32String(universeId), bytecodeHash: keccak256(initCode) }) -} - export const contractExists = async (client: ReadClient, contract: `0x${ string }`) => await client.getCode({ address: contract }) !== undefined -export const approximatelyEqual = (actual: bigint, expected: bigint, errorDelta: bigint, message?: string | Error | undefined) => { - if (abs(actual - expected) > errorDelta) assert.strictEqual(actual, expected, message) -} +export const isUnknownAddress = (maybeAddress: unknown): maybeAddress is `0x${ string }` => typeof maybeAddress === 'string' && /^0x[a-fA-F0-9]{40}$/.test(maybeAddress) -export const isUnknownAnAddress = (maybeAddress: unknown): maybeAddress is `0x${ string }` => typeof maybeAddress === 'string' && /^0x[a-fA-F0-9]{40}$/.test(maybeAddress) +const uint248BitMask = (1n << 248n) - 1n +export function getChildUniverseId(parentUniverseId: bigint, outcome: bigint | QuestionOutcome): bigint { + return BigInt(keccak256(encodeAbiParameters([{ type: 'uint248' }, { type: 'uint8' }], [parentUniverseId, Number(outcome)]))) & uint248BitMask +}