Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions shared/ts/addressDerivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type RepTokenAddressConfig = {
}

type SecurityPoolAddressConfig = {
getEscalationGameInitCode: (securityPool: Address) => Hex
getEscalationGameInitCode: (securityPool: Address, repToken: Address) => Hex
getInfraContracts: () => SecurityPoolCoreAddresses
getPriceOracleManagerAndOperatorQueuerInitCode: (openOracle: Address, repToken: Address) => Hex
getRepTokenAddress: (universeId: bigint) => Address
Expand Down Expand Up @@ -123,7 +123,7 @@ export function createSecurityPoolAddressHelper(config: SecurityPoolAddressConfi
salt: numberToBytes(0, { size: 32 }),
})
const escalationGame = getCreate2Address({
bytecode: config.getEscalationGameInitCode(securityPool),
bytecode: config.getEscalationGameInitCode(securityPool, repToken),
from: infraContracts.escalationGameFactory,
salt: numberToBytes(0, { size: 32 }),
})
Expand Down
183 changes: 170 additions & 13 deletions solidity/contracts/peripherals/EscalationGame.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.35;
import { ISecurityPool } from './interfaces/ISecurityPool.sol';
import { BinaryOutcomes } from './BinaryOutcomes.sol';
import { MerkleMountainRange } from './MerkleMountainRange.sol';
import { ReputationToken } from '../ReputationToken.sol';

uint256 constant ESCALATION_TIME_LENGTH = 4233600; // 7 weeks
uint256 constant SCALE = 1e6;
Expand All @@ -29,6 +30,12 @@ struct CarryLeafView {
uint256 sourceNodeId;
}

struct ExportedDeposit {
address depositor;
BinaryOutcomes.BinaryOutcome outcome;
uint256 amount;
}

struct OutcomeState {
// Snapshot fields are the inherited proof baseline for this outcome.
// currentNullifierRoot tracks which inherited proof indexes have been consumed in this instance.
Expand Down Expand Up @@ -94,6 +101,7 @@ contract EscalationGame {
uint256 public constant activationDelay = 3 days;
uint256 public activationTime;
ISecurityPool public immutable securityPool;
ReputationToken public immutable repToken;
uint256 public nonDecisionThreshold;
uint256 public startBond;
uint256 public lnRatioScaled;
Expand All @@ -107,6 +115,7 @@ contract EscalationGame {
OutcomeState[3] private outcomeState;
uint256 public nextNodeId = 1;
mapping(uint256 => Node) public nodes;
mapping(address => uint256) public escrowedRepByVault;

event GameStarted(uint256 activationTime, uint256 startBond, uint256 nonDecisionThreshold);
event GameContinuedFromFork(uint256 startBond, uint256 nonDecisionThreshold, uint256 elapsedAtFork);
Expand All @@ -117,6 +126,8 @@ contract EscalationGame {
event ClaimDeposit(uint256 amountToWithdraw, uint256 burnAmount);
event LocalDepositAppended(uint256 indexed nodeId, BinaryOutcomes.BinaryOutcome outcome, address depositor, uint256 amount, uint256 parentDepositIndex, uint256 cumulativeAmount);
event CarriedDepositClaimed(BinaryOutcomes.BinaryOutcome outcome, address depositor, uint256 amount, uint256 parentDepositIndex, uint256 sourceNodeId, bytes32 leafHash);
event ResidualRepSweptToSecurityPool(uint256 amount);
event ForkedDepositImported(address depositor, BinaryOutcomes.BinaryOutcome outcome, uint256 amount, uint256 depositIndex, uint256 cumulativeAmount);

modifier onlySecurityPoolOrForker() {
require(
Expand All @@ -126,8 +137,9 @@ contract EscalationGame {
_;
}

constructor(ISecurityPool _securityPool) {
constructor(ISecurityPool _securityPool, ReputationToken _repToken) {
securityPool = _securityPool;
repToken = _repToken;
owner = msg.sender;
EMPTY_NULLIFIER_ROOT = _computeEmptyNullifierRoot();
}
Expand Down Expand Up @@ -348,7 +360,19 @@ contract EscalationGame {
return outcomeState[2].balance;
}

function depositOnOutcome(address depositor, BinaryOutcomes.BinaryOutcome outcome, uint256 amount) external returns (uint256 depositAmount) {
function previewDepositOnOutcome(BinaryOutcomes.BinaryOutcome outcome, uint256 amount) external view returns (uint256 acceptedAmount, uint256 resultingCumulativeAmount) {
require(nonDecisionTimestamp == 0, 'System has already reached a non-decision');
require(outcome != BinaryOutcomes.BinaryOutcome.None, 'Outcome must not be None');
require(getQuestionResolution() == BinaryOutcomes.BinaryOutcome.None, 'System has already timed out');
require(outcomeState[uint8(outcome)].balance < nonDecisionThreshold, 'Already full');
require(amount >= startBond, 'all amounts need to be bigger or equal to start deposit');
uint256 outcomeIndex = uint256(outcome);
uint256 currentBalance = outcomeState[outcomeIndex].balance;
uint256 room = nonDecisionThreshold - currentBalance;
(acceptedAmount, resultingCumulativeAmount) = _getAcceptedDepositAmount(outcomeIndex, amount, currentBalance, room);
}

function recordDepositFromSecurityPool(address depositor, BinaryOutcomes.BinaryOutcome outcome, uint256 amount, uint256 expectedCumulativeAmount) external returns (uint256 parentDepositIndex) {
require(nonDecisionTimestamp == 0, 'System has already reached a non-decision');
require(msg.sender == address(securityPool), 'Only Security Pool can deposit');
require(outcome != BinaryOutcomes.BinaryOutcome.None, 'Outcome must not be None');
Expand All @@ -360,29 +384,32 @@ contract EscalationGame {
uint256 currentBalance = selectedOutcomeState.balance;
uint256 room = nonDecisionThreshold - currentBalance;
(uint256 effectiveDeposit, uint256 newBalance) = _getAcceptedDepositAmount(outcomeIndex, amount, currentBalance, room);
require(effectiveDeposit == amount, 'deposit preview stale');
require(newBalance == expectedCumulativeAmount, 'cumulative preview stale');

selectedOutcomeState.balance += effectiveDeposit;
depositAmount = effectiveDeposit;
escrowedRepByVault[depositor] += effectiveDeposit;

Deposit memory deposit;
deposit.depositor = depositor;
deposit.amount = depositAmount;
deposit.amount = effectiveDeposit;
deposit.cumulativeAmount = newBalance;
selectedOutcomeState.deposits.push(deposit);
uint256 depositIndex = selectedOutcomeState.deposits.length - 1;
uint256 stableParentDepositIndex = _getStableLocalParentDepositIndex(depositIndex);
parentDepositIndex = stableParentDepositIndex;
uint256 nodeId = nextNodeId;
nextNodeId += 1;
Node storage node = nodes[nodeId];
node.parentNodeId = selectedOutcomeState.localHeadNodeId;
node.depositor = depositor;
node.outcome = outcome;
node.amount = depositAmount;
node.amount = effectiveDeposit;
node.parentDepositIndex = stableParentDepositIndex;
node.cumulativeAmount = deposit.cumulativeAmount;
selectedOutcomeState.localHeadNodeId = nodeId;
selectedOutcomeState.localUnresolvedTotal += depositAmount;
emit LocalDepositAppended(nodeId, outcome, depositor, depositAmount, stableParentDepositIndex, deposit.cumulativeAmount);
selectedOutcomeState.localUnresolvedTotal += effectiveDeposit;
emit LocalDepositAppended(nodeId, outcome, depositor, effectiveDeposit, stableParentDepositIndex, deposit.cumulativeAmount);
emit DepositOnOutcome(depositor, outcome, deposit.amount, depositIndex, deposit.cumulativeAmount);
if (hasReachedNonDecision()) {
nonDecisionTimestamp = block.timestamp;
Expand All @@ -391,13 +418,14 @@ contract EscalationGame {

function claimDepositForWinning(uint256 depositIndex, BinaryOutcomes.BinaryOutcome outcome) public onlySecurityPoolOrForker returns (address depositor, uint256 amountToWithdraw, uint256 originalDepositAmount) {
require(outcome != BinaryOutcomes.BinaryOutcome.None, 'Outcome must not be None');
Deposit memory deposit = _consumeLocalDeposit(uint8(outcome), depositIndex);
depositor = deposit.depositor;
originalDepositAmount = deposit.amount;
uint256 burnAmount;
(amountToWithdraw, burnAmount) = _computeWinningWithdrawal(uint8(outcome), deposit.amount, deposit.cumulativeAmount);
(depositor, amountToWithdraw, originalDepositAmount) = _claimDepositForWinning(depositIndex, outcome);
if (amountToWithdraw > 0) {
repToken.transfer(depositor, amountToWithdraw);
}
}

emit ClaimDeposit(amountToWithdraw, burnAmount);
function claimDepositForWinningWithoutTransfer(uint256 depositIndex, BinaryOutcomes.BinaryOutcome outcome) public onlySecurityPoolOrForker returns (address depositor, uint256 amountToWithdraw, uint256 originalDepositAmount) {
return _claimDepositForWinning(depositIndex, outcome);
}

function exportUnresolvedDeposit(uint256 depositIndex, BinaryOutcomes.BinaryOutcome outcome) public onlySecurityPoolOrForker returns (address depositor, uint256 amount, uint256 parentDepositIndex) {
Expand All @@ -406,6 +434,7 @@ contract EscalationGame {
Deposit memory deposit = _consumeLocalDeposit(outcomeIndex, depositIndex);
depositor = deposit.depositor;
amount = deposit.amount;
escrowedRepByVault[depositor] -= amount;
parentDepositIndex = _getStableLocalParentDepositIndex(depositIndex);
}

Expand All @@ -420,6 +449,9 @@ contract EscalationGame {
if (outcome == questionResolution) {
uint256 burnAmount;
(amountToWithdraw, burnAmount) = _computeWinningWithdrawal(outcomeIndex, proof.amount, proof.cumulativeAmount);
if (amountToWithdraw > 0) {
repToken.transfer(depositor, amountToWithdraw);
}
emit ClaimDeposit(amountToWithdraw, burnAmount);
emit WithdrawDeposit(depositor, outcome, amountToWithdraw, proof.parentDepositIndex);
return (depositor, amountToWithdraw, originalDepositAmount);
Expand Down Expand Up @@ -502,9 +534,95 @@ contract EscalationGame {
Deposit memory deposit = _consumeLocalDeposit(uint8(outcome), depositIndex);
depositor = deposit.depositor;
originalDepositAmount = deposit.amount;
escrowedRepByVault[depositor] -= originalDepositAmount;
emit WithdrawDeposit(depositor, outcome, 0, depositIndex);
}

function importForkedDeposits(ExportedDeposit[] calldata deposits) external onlySecurityPoolOrForker {
for (uint256 index = 0; index < deposits.length; index++) {
ExportedDeposit calldata deposit = deposits[index];
_requireImportableDeposit(deposit);
uint8 outcomeIndex = uint8(deposit.outcome);
OutcomeState storage selectedOutcomeState = outcomeState[outcomeIndex];
uint256 newBalance = selectedOutcomeState.balance + deposit.amount;
selectedOutcomeState.balance = newBalance;
escrowedRepByVault[deposit.depositor] += deposit.amount;

selectedOutcomeState.deposits.push(Deposit({
depositor: deposit.depositor,
amount: deposit.amount,
cumulativeAmount: newBalance
}));
uint256 depositIndex = selectedOutcomeState.deposits.length - 1;
uint256 stableParentDepositIndex = _getStableLocalParentDepositIndex(depositIndex);
uint256 nodeId = nextNodeId;
nextNodeId += 1;
Node storage node = nodes[nodeId];
node.parentNodeId = selectedOutcomeState.localHeadNodeId;
node.depositor = deposit.depositor;
node.outcome = deposit.outcome;
node.amount = deposit.amount;
node.parentDepositIndex = stableParentDepositIndex;
node.cumulativeAmount = newBalance;
selectedOutcomeState.localHeadNodeId = nodeId;
selectedOutcomeState.localUnresolvedTotal += deposit.amount;
emit LocalDepositAppended(nodeId, deposit.outcome, deposit.depositor, deposit.amount, stableParentDepositIndex, newBalance);
emit ForkedDepositImported(deposit.depositor, deposit.outcome, deposit.amount, depositIndex, newBalance);
}
}

function exportVaultUnresolvedDeposits(address vault, address repReceiver) external onlySecurityPoolOrForker returns (ExportedDeposit[] memory exportedDeposits, uint256 principalToTransfer) {
require(repReceiver != address(0x0), 'invalid receiver');
return _exportVaultUnresolvedDeposits(vault, repReceiver, true);
}

function exportVaultUnresolvedDepositsWithoutTransfer(address vault) external onlySecurityPoolOrForker returns (ExportedDeposit[] memory exportedDeposits, uint256 principalToTransfer) {
return _exportVaultUnresolvedDeposits(vault, address(0x0), false);
}

function _exportVaultUnresolvedDeposits(address vault, address repReceiver, bool transferRep) private returns (ExportedDeposit[] memory exportedDeposits, uint256 principalToTransfer) {
exportedDeposits = new ExportedDeposit[](_countVaultUnresolvedDeposits(vault));
uint256 writeIndex = 0;
for (uint8 outcomeIndex = 0; outcomeIndex < 3; outcomeIndex++) {
OutcomeState storage state = outcomeState[outcomeIndex];
for (uint256 depositIndex = 0; depositIndex < state.deposits.length; depositIndex++) {
Deposit memory deposit = state.deposits[depositIndex];
if (deposit.amount == 0 || deposit.depositor != vault) continue;
Deposit memory consumedDeposit = _consumeLocalDeposit(outcomeIndex, depositIndex);
exportedDeposits[writeIndex] = ExportedDeposit({
depositor: consumedDeposit.depositor,
outcome: BinaryOutcomes.BinaryOutcome(outcomeIndex),
amount: consumedDeposit.amount
});
principalToTransfer += consumedDeposit.amount;
writeIndex += 1;
}
}
require(writeIndex == exportedDeposits.length, 'export count mismatch');
if (principalToTransfer == 0) return (exportedDeposits, 0);
escrowedRepByVault[vault] -= principalToTransfer;
if (transferRep) {
repToken.transfer(repReceiver, principalToTransfer);
}
}

function sweepResidualRepToSecurityPool() external {
require(getQuestionResolution() != BinaryOutcomes.BinaryOutcome.None, 'question not final');
require(_totalUnresolvedPrincipal() == 0, 'unsettled deposits remain');
uint256 amount = repToken.balanceOf(address(this));
require(amount > 0, 'no residual rep');
repToken.transfer(address(securityPool), amount);
emit ResidualRepSweptToSecurityPool(amount);
}

function drainAllRep(address receiver) external onlySecurityPoolOrForker returns (uint256 amount) {
require(nonDecisionTimestamp > 0, 'non-decision not reached');
require(receiver != address(0x0), 'invalid receiver');
amount = repToken.balanceOf(address(this));
if (amount == 0) return 0;
repToken.transfer(receiver, amount);
}

function getDepositsByOutcome(BinaryOutcomes.BinaryOutcome outcome, uint256 startIndex, uint256 numberOfEntries) external view returns (Deposit[] memory returnDeposits) {
if (outcome == BinaryOutcomes.BinaryOutcome.None) return new Deposit[](0);
Deposit[] storage outcomeDeposits = outcomeState[uint8(outcome)].deposits;
Expand All @@ -516,6 +634,45 @@ contract EscalationGame {
}
}

function getStableLocalParentDepositIndex(uint256 depositIndex) external view returns (uint256) {
return _getStableLocalParentDepositIndex(depositIndex);
}

function _requireImportableDeposit(ExportedDeposit calldata deposit) private pure {
require(deposit.depositor != address(0x0), 'invalid depositor');
require(deposit.outcome != BinaryOutcomes.BinaryOutcome.None, 'invalid outcome');
require(deposit.amount > 0, 'invalid amount');
}

function _countVaultUnresolvedDeposits(address vault) private view returns (uint256 count) {
for (uint8 outcomeIndex = 0; outcomeIndex < 3; outcomeIndex++) {
OutcomeState storage state = outcomeState[outcomeIndex];
for (uint256 depositIndex = 0; depositIndex < state.deposits.length; depositIndex++) {
Deposit storage deposit = state.deposits[depositIndex];
if (deposit.amount > 0 && deposit.depositor == vault) {
count += 1;
}
}
}
}

function _claimDepositForWinning(uint256 depositIndex, BinaryOutcomes.BinaryOutcome outcome) private returns (address depositor, uint256 amountToWithdraw, uint256 originalDepositAmount) {
Deposit memory deposit = _consumeLocalDeposit(uint8(outcome), depositIndex);
depositor = deposit.depositor;
originalDepositAmount = deposit.amount;
uint256 burnAmount;
(amountToWithdraw, burnAmount) = _computeWinningWithdrawal(uint8(outcome), deposit.amount, deposit.cumulativeAmount);
escrowedRepByVault[depositor] -= originalDepositAmount;
emit ClaimDeposit(amountToWithdraw, burnAmount);
}

function _totalUnresolvedPrincipal() private view returns (uint256 unresolvedPrincipal) {
for (uint8 outcomeIndex = 0; outcomeIndex < 3; outcomeIndex++) {
OutcomeState storage state = outcomeState[outcomeIndex];
unresolvedPrincipal += state.inheritedUnresolvedTotal + state.localUnresolvedTotal;
}
}

function _computeLnRatioScaled(uint256 lowValue, uint256 highValue) internal pure returns (uint256) {
uint256 normalizedLow = lowValue;
uint256 log2Count = 0;
Expand Down
Loading
Loading