编译器
0.8.10+commit.fc410830
文件 1 的 6:IArbitrable.sol
pragma solidity ^0.8.0;
import "./IArbitrator.sol";
interface IArbitrable {
event Ruling(IArbitrator indexed _arbitrator, uint256 indexed _disputeID, uint256 _ruling);
function rule(uint256 _disputeID, uint256 _ruling) external;
}
文件 2 的 6:IArbitrator.sol
pragma solidity ^0.8.0;
import "./IArbitrable.sol";
interface IArbitrator {
enum DisputeStatus {
Waiting,
Appealable,
Solved
}
event DisputeCreation(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID);
function arbitrationCost(bytes calldata _extraData) external view returns (uint256 cost);
function appeal(uint256 _disputeID, bytes calldata _extraData) external payable;
function appealCost(uint256 _disputeID, bytes calldata _extraData) external view returns (uint256 cost);
function appealPeriod(uint256 _disputeID) external view returns (uint256 start, uint256 end);
function disputeStatus(uint256 _disputeID) external view returns (DisputeStatus status);
function currentRuling(uint256 _disputeID) external view returns (uint256 ruling);
}
文件 3 的 6:IDisputeResolver.sol
pragma solidity ^0.8.0;
import "@kleros/erc-792/contracts/IArbitrable.sol";
import "@kleros/erc-792/contracts/erc-1497/IEvidence.sol";
import "@kleros/erc-792/contracts/IArbitrator.sol";
abstract contract IDisputeResolver is IArbitrable, IEvidence {
string public constant VERSION = "2.0.0";
event Contribution(uint256 indexed _localDisputeID, uint256 indexed _round, uint256 ruling, address indexed _contributor, uint256 _amount);
event Withdrawal(uint256 indexed _localDisputeID, uint256 indexed _round, uint256 _ruling, address indexed _contributor, uint256 _reward);
event RulingFunded(uint256 indexed _localDisputeID, uint256 indexed _round, uint256 indexed _ruling);
function externalIDtoLocalID(uint256 _externalDisputeID) external virtual returns (uint256 localDisputeID);
function numberOfRulingOptions(uint256 _localDisputeID) external view virtual returns (uint256 count);
function submitEvidence(uint256 _localDisputeID, string calldata _evidenceURI) external virtual;
function fundAppeal(uint256 _localDisputeID, uint256 _ruling) external payable virtual returns (bool fullyFunded);
function getMultipliers()
external
view
virtual
returns (
uint256 winnerStakeMultiplier,
uint256 loserStakeMultiplier,
uint256 loserAppealPeriodMultiplier,
uint256 denominator
);
function withdrawFeesAndRewards(
uint256 _localDisputeID,
address payable _contributor,
uint256 _round,
uint256 _ruling
) external virtual returns (uint256 sum);
function withdrawFeesAndRewardsForAllRounds(
uint256 _localDisputeID,
address payable _contributor,
uint256 _ruling
) external virtual;
function getTotalWithdrawableAmount(
uint256 _localDisputeID,
address payable _contributor,
uint256 _ruling
) external view virtual returns (uint256 sum);
}
文件 4 的 6:IEvidence.sol
pragma solidity ^0.8.0;
import "../IArbitrator.sol";
interface IEvidence {
event MetaEvidence(uint256 indexed _metaEvidenceID, string _evidence);
event Evidence(
IArbitrator indexed _arbitrator,
uint256 indexed _evidenceGroupID,
address indexed _party,
string _evidence
);
event Dispute(
IArbitrator indexed _arbitrator,
uint256 indexed _disputeID,
uint256 _metaEvidenceID,
uint256 _evidenceGroupID
);
}
文件 5 的 6:ITruthPost.sol
pragma solidity ^0.8.10;
abstract contract ITruthPost {
string public constant VERSION = "1.2.0";
enum RulingOptions {
Tied,
ChallengeFailed,
Debunked
}
bool isPublishingEnabled = true;
address payable public TREASURY;
uint256 public treasuryBalance;
uint256 public constant NUMBER_OF_RULING_OPTIONS = 2;
uint256 public constant MULTIPLIER_DENOMINATOR = 1024;
uint256 public LOSER_APPEAL_PERIOD_MULTIPLIER = 512;
uint256 public ARTICLE_WITHDRAWAL_TIMELOCK;
uint256 public WINNER_STAKE_MULTIPLIER;
uint256 public LOSER_STAKE_MULTIPLIER;
uint256 public challengeTaxRate = 16;
constructor(
uint256 _articleWithdrawalTimelock,
uint256 _winnerStakeMultiplier,
uint256 _loserStakeMultiplier,
address payable _treasury
) {
ARTICLE_WITHDRAWAL_TIMELOCK = _articleWithdrawalTimelock;
WINNER_STAKE_MULTIPLIER = _winnerStakeMultiplier;
LOSER_STAKE_MULTIPLIER = _loserStakeMultiplier;
TREASURY = _treasury;
}
event NewArticle(string articleID, uint8 category, uint256 articleAddress);
event Debunked(uint256 articleAddress);
event ArticleWithdrawn(uint256 articleAddress);
event BalanceUpdate(uint256 articleAddress, uint256 newTotal);
event TimelockStarted(uint256 articleAddress);
event Challenge(uint256 indexed articleAddress, address challanger, uint256 disputeID);
event Contribution(
uint256 indexed disputeId,
uint256 indexed round,
RulingOptions ruling,
address indexed contributor,
uint256 amount
);
event Withdrawal(
uint256 indexed disputeId,
uint256 indexed round,
RulingOptions ruling,
address indexed contributor,
uint256 reward
);
event RulingFunded(uint256 indexed disputeId, uint256 indexed round, RulingOptions indexed ruling);
event OwnershipTransfer(address indexed _newOwner);
event AdminUpdate(address indexed _newAdmin);
event WinnerStakeMultiplierUpdate(uint256 indexed _newWinnerStakeMultiplier);
event LoserStakeMultiplierUpdate(uint256 indexed _newLoserStakeMultiplier);
event LoserAppealPeriodMultiplierUpdate(uint256 indexed _newLoserAppealPeriodMultiplier);
event ArticleWithdrawalTimelockUpdate(uint256 indexed _newWithdrawalTimelock);
event ChallengeTaxRateUpdate(uint256 indexed _newTaxRate);
event TreasuryUpdate(address indexed _newTreasury);
event TreasuryBalanceUpdate(uint256 indexed _byAmount);
function submitEvidence(uint256 _disputeID, string calldata _evidenceURI) external virtual;
function fundAppeal(uint256 _disputeID, RulingOptions _ruling) external payable virtual returns (bool fullyFunded);
function initializeArticle(
string calldata _articleID,
uint8 _category,
uint80 _searchPointer
) external payable virtual;
function increaseBounty(uint80 _articleStorageAddress) external payable virtual;
function initiateWithdrawal(uint80 _articleStorageAddress) external virtual;
function withdraw(uint80 _articleStorageAddress) external virtual;
function challenge(uint80 _articleStorageAddress) external payable virtual;
function transferOwnership(uint80 _articleStorageAddress, address payable _newOwner) external virtual;
function changeWinnerStakeMultiplier(uint256 _newWinnerStakeMultiplier) external virtual;
function changeLoserStakeMultiplier(uint256 _newLoserStakeMultiplier) external virtual;
function changeLoserAppealPeriodMultiplier(uint256 _newLoserAppealPeriodMultiplier) external virtual;
function changeArticleWithdrawalTimelock(uint256 _newArticleWithdrawalTimelock) external virtual;
function findVacantStorageSlot(uint80 _searchPointer) external view virtual returns (uint256 vacantSlotIndex);
function challengeFee(uint80 _articleStorageAddress) public view virtual returns (uint256 challengeFee);
function appealFee(uint256 _disputeID) external view virtual returns (uint256 arbitrationFee);
function withdrawFeesAndRewards(
uint256 _disputeID,
address payable _contributor,
uint256 _round,
RulingOptions _ruling
) external virtual returns (uint256 sum);
function withdrawFeesAndRewardsForAllRounds(
uint256 _disputeID,
address payable _contributor,
RulingOptions _ruling
) external virtual;
function withdrawFeesAndRewardsForGivenPositions(
uint256 _disputeID,
address payable _contributor,
uint256[][] calldata positions
) external virtual;
function withdrawFeesAndRewardsForAllRoundsAndAllRulings(uint256 _disputeID, address payable _contributor)
external
virtual;
function getTotalWithdrawableAmount(uint256 _disputeID, address payable _contributor)
external
view
virtual
returns (uint256 sum, uint256[][] memory positions);
function getRoundInfo(uint256 _disputeID, uint256 _round)
external
view
virtual
returns (
bool[NUMBER_OF_RULING_OPTIONS + 1] memory hasPaid,
uint256[NUMBER_OF_RULING_OPTIONS + 1] memory totalPerRuling,
uint256 totalClaimableAfterExpenses
);
function getAmountRemainsToBeRaised(uint256 _disputeID, RulingOptions _ruling)
external
view
virtual
returns (uint256);
function getReturnOfInvestmentRatio(RulingOptions _ruling, RulingOptions _lastRoundWinner)
external
view
virtual
returns (uint256);
function getAppealPeriod(uint256 _disputeID, RulingOptions _ruling)
external
view
virtual
returns (uint256, uint256);
function getLastRoundWinner(uint256 _disputeID) public view virtual returns (uint256);
function switchPublishingLock() public virtual;
}
文件 6 的 6:TruthPost.sol
pragma solidity ^0.8.10;
import "@kleros/dispute-resolver-interface-contract/contracts/IDisputeResolver.sol";
import "./ITruthPost.sol";
contract TruthPost is ITruthPost, IArbitrable, IEvidence {
IArbitrator public immutable ARBITRATOR;
uint256 public constant NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE = 32;
uint8 public categoryCounter = 0;
address payable public admin = payable(msg.sender);
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
struct DisputeData {
address payable challenger;
RulingOptions outcome;
uint8 articleCategory;
bool resolved;
uint80 articleStorageAddress;
Round[] rounds;
}
struct Round {
mapping(address => uint256[NUMBER_OF_RULING_OPTIONS + 1]) contributions;
bool[NUMBER_OF_RULING_OPTIONS + 1] hasPaid;
uint256[NUMBER_OF_RULING_OPTIONS + 1] totalPerRuling;
uint256 totalClaimableAfterExpenses;
}
struct Article {
address payable owner;
uint32 withdrawalPermittedAt;
uint56 bountyAmount;
uint8 category;
}
bytes[64] public categoryToArbitratorExtraData;
mapping(uint80 => Article) public articleStorage;
mapping(uint256 => DisputeData) public disputes;
constructor(
IArbitrator _arbitrator,
bytes memory _arbitratorExtraData,
string memory _metaevidenceIpfsUri,
uint256 _articleWithdrawalTimelock,
uint256 _winnerStakeMultiplier,
uint256 _loserStakeMultiplier,
address payable _treasury
) ITruthPost(_articleWithdrawalTimelock, _winnerStakeMultiplier, _loserStakeMultiplier, _treasury) {
ARBITRATOR = _arbitrator;
newCategory(_metaevidenceIpfsUri, _arbitratorExtraData);
}
function initializeArticle(
string calldata _articleID,
uint8 _category,
uint80 _searchPointer
) external payable override {
require(_category < categoryCounter, "This category does not exist");
Article storage article;
do {
article = articleStorage[_searchPointer++];
} while (article.bountyAmount != 0);
article.owner = payable(msg.sender);
article.bountyAmount = uint56(msg.value >> NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE);
article.category = _category;
require(article.bountyAmount > 0, "You can't initialize an article without putting a bounty.");
uint256 articleStorageAddress = _searchPointer - 1;
emit NewArticle(_articleID, _category, articleStorageAddress);
emit BalanceUpdate(
articleStorageAddress,
uint256(article.bountyAmount) << NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE
);
}
function submitEvidence(uint256 _disputeID, string calldata _evidenceURI) external override {
emit Evidence(ARBITRATOR, _disputeID, msg.sender, _evidenceURI);
}
function increaseBounty(uint80 _articleStorageAddress) external payable override {
Article storage article = articleStorage[_articleStorageAddress];
require(msg.sender == article.owner, "Only author can increase bounty of an article.");
article.bountyAmount += uint56(msg.value >> NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE);
emit BalanceUpdate(
_articleStorageAddress,
uint256(article.bountyAmount) << NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE
);
}
function initiateWithdrawal(uint80 _articleStorageAddress) external override {
Article storage article = articleStorage[_articleStorageAddress];
require(msg.sender == article.owner, "Only author can withdraw an article.");
require(article.withdrawalPermittedAt == 0, "Withdrawal already initiated or there is a challenge.");
article.withdrawalPermittedAt = uint32(block.timestamp + ARTICLE_WITHDRAWAL_TIMELOCK);
emit TimelockStarted(_articleStorageAddress);
}
function withdraw(uint80 _articleStorageAddress) external override {
Article storage article = articleStorage[_articleStorageAddress];
require(msg.sender == article.owner, "Only author can withdraw an article.");
require(article.withdrawalPermittedAt != 0, "You need to initiate withdrawal first.");
require(
article.withdrawalPermittedAt <= block.timestamp,
"You need to wait for timelock or wait until the challenge ends."
);
uint256 withdrawal = uint96(article.bountyAmount) << NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE;
article.bountyAmount = 0;
article.withdrawalPermittedAt = 0;
payable(msg.sender).transfer(withdrawal);
emit ArticleWithdrawn(_articleStorageAddress);
}
function challenge(uint80 _articleStorageAddress) external payable override {
Article storage article = articleStorage[_articleStorageAddress];
require(article.bountyAmount > 0, "Nothing to challenge.");
require(article.withdrawalPermittedAt != type(uint32).max, "There is an ongoing challenge.");
article.withdrawalPermittedAt = type(uint32).max;
require(msg.value >= challengeFee(_articleStorageAddress), "Insufficient funds to challenge.");
uint256 taxAmount = ((uint96(article.bountyAmount) << NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE) *
challengeTaxRate) / MULTIPLIER_DENOMINATOR;
treasuryBalance += taxAmount;
uint256 disputeID = ARBITRATOR.createDispute{value: msg.value - taxAmount}(
NUMBER_OF_RULING_OPTIONS,
categoryToArbitratorExtraData[article.category]
);
disputes[disputeID].challenger = payable(msg.sender);
disputes[disputeID].rounds.push();
disputes[disputeID].articleStorageAddress = uint80(_articleStorageAddress);
disputes[disputeID].articleCategory = article.category;
emit Dispute(ARBITRATOR, disputeID, article.category, disputeID);
emit Challenge(_articleStorageAddress, msg.sender, disputeID);
}
function fundAppeal(uint256 _disputeID, RulingOptions _supportedRuling)
external
payable
override
returns (bool fullyFunded)
{
DisputeData storage dispute = disputes[_disputeID];
RulingOptions currentRuling = RulingOptions(ARBITRATOR.currentRuling(_disputeID));
uint256 basicCost;
uint256 totalCost;
{
(uint256 appealWindowStart, uint256 appealWindowEnd) = ARBITRATOR.appealPeriod(_disputeID);
uint256 multiplier;
if (_supportedRuling == currentRuling) {
require(block.timestamp < appealWindowEnd, "Funding must be made within the appeal period.");
multiplier = WINNER_STAKE_MULTIPLIER;
} else {
require(
block.timestamp <
(appealWindowStart +
(((appealWindowEnd - appealWindowStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) /
MULTIPLIER_DENOMINATOR)),
"Funding must be made within the first half appeal period."
);
multiplier = LOSER_STAKE_MULTIPLIER;
}
basicCost = ARBITRATOR.appealCost(_disputeID, categoryToArbitratorExtraData[dispute.articleCategory]);
totalCost = basicCost + ((basicCost * (multiplier)) / MULTIPLIER_DENOMINATOR);
}
RulingOptions supportedRulingOutcome = RulingOptions(_supportedRuling);
uint256 lastRoundIndex = dispute.rounds.length - 1;
Round storage lastRound = dispute.rounds[lastRoundIndex];
require(!lastRound.hasPaid[uint256(supportedRulingOutcome)], "Appeal fee has already been paid.");
uint256 contribution;
{
uint256 paidSoFar = lastRound.totalPerRuling[uint256(supportedRulingOutcome)];
if (paidSoFar >= totalCost) {
contribution = 0;
} else {
contribution = totalCost - paidSoFar > msg.value ? msg.value : totalCost - paidSoFar;
}
}
emit Contribution(_disputeID, lastRoundIndex, _supportedRuling, msg.sender, contribution);
lastRound.contributions[msg.sender][uint256(supportedRulingOutcome)] += contribution;
lastRound.totalPerRuling[uint256(supportedRulingOutcome)] += contribution;
if (lastRound.totalPerRuling[uint256(supportedRulingOutcome)] >= totalCost) {
lastRound.totalClaimableAfterExpenses += lastRound.totalPerRuling[uint256(supportedRulingOutcome)];
lastRound.hasPaid[uint256(supportedRulingOutcome)] = true;
emit RulingFunded(_disputeID, lastRoundIndex, _supportedRuling);
}
if (
lastRound.hasPaid[uint256(RulingOptions.ChallengeFailed)] &&
lastRound.hasPaid[uint256(RulingOptions.Debunked)]
) {
dispute.rounds.push();
lastRound.totalClaimableAfterExpenses -= basicCost;
ARBITRATOR.appeal{value: basicCost}(_disputeID, categoryToArbitratorExtraData[dispute.articleCategory]);
}
if (msg.value - contribution > 0) payable(msg.sender).send(msg.value - contribution);
return lastRound.hasPaid[uint256(supportedRulingOutcome)];
}
function rule(uint256 _disputeID, uint256 _ruling) external override {
require(IArbitrator(msg.sender) == ARBITRATOR);
DisputeData storage dispute = disputes[_disputeID];
Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
RulingOptions wonByDefault;
if (lastRound.hasPaid[uint256(RulingOptions.ChallengeFailed)]) {
wonByDefault = RulingOptions.ChallengeFailed;
} else if (lastRound.hasPaid[uint256(RulingOptions.ChallengeFailed)]) {
wonByDefault = RulingOptions.Debunked;
}
RulingOptions actualRuling = wonByDefault != RulingOptions.Tied ? wonByDefault : RulingOptions(_ruling);
dispute.outcome = actualRuling;
uint80 articleStorageAddress = dispute.articleStorageAddress;
Article storage article = articleStorage[articleStorageAddress];
if (actualRuling == RulingOptions.Debunked) {
uint256 bounty = uint96(article.bountyAmount) << NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE;
article.bountyAmount = 0;
emit Debunked(articleStorageAddress);
disputes[_disputeID].challenger.send(bounty);
}
article.withdrawalPermittedAt = 0;
dispute.resolved = true;
emit Ruling(IArbitrator(msg.sender), _disputeID, _ruling);
}
function withdrawFeesAndRewardsForAllRoundsAndAllRulings(uint256 _disputeID, address payable _contributor)
external
override
{
DisputeData storage dispute = disputes[_disputeID];
uint256 noOfRounds = dispute.rounds.length;
for (uint256 roundNumber = 0; roundNumber < noOfRounds; roundNumber++) {
for (uint256 rulingOption = 0; rulingOption <= NUMBER_OF_RULING_OPTIONS; rulingOption++)
withdrawFeesAndRewards(_disputeID, _contributor, roundNumber, RulingOptions(rulingOption));
}
}
function withdrawFeesAndRewardsForAllRounds(
uint256 _disputeID,
address payable _contributor,
RulingOptions _ruling
) external override {
DisputeData storage dispute = disputes[_disputeID];
uint256 noOfRounds = dispute.rounds.length;
for (uint256 roundNumber = 0; roundNumber < noOfRounds; roundNumber++) {
withdrawFeesAndRewards(_disputeID, _contributor, roundNumber, _ruling);
}
}
function withdrawFeesAndRewardsForGivenPositions(
uint256 _disputeID,
address payable _contributor,
uint256[][] calldata positions
) external override {
for (uint256 roundNumber = 0; roundNumber < positions.length; roundNumber++) {
for (uint256 rulingOption = 0; rulingOption < positions[roundNumber].length; rulingOption++) {
if (positions[roundNumber][rulingOption] > 0) {
withdrawFeesAndRewards(_disputeID, _contributor, roundNumber, RulingOptions(rulingOption));
}
}
}
}
function withdrawFeesAndRewards(
uint256 _disputeID,
address payable _contributor,
uint256 _roundNumber,
RulingOptions _ruling
) public override returns (uint256 amount) {
DisputeData storage dispute = disputes[_disputeID];
require(dispute.resolved, "There is no ruling yet.");
Round storage round = dispute.rounds[_roundNumber];
amount = getWithdrawableAmount(round, _contributor, _ruling, dispute.outcome);
if (amount != 0) {
round.contributions[_contributor][uint256(RulingOptions(_ruling))] = 0;
_contributor.send(amount);
emit Withdrawal(_disputeID, _roundNumber, _ruling, _contributor, amount);
}
}
function updateChallengeTaxRate(uint256 _newChallengeTaxRate) external onlyAdmin {
require(_newChallengeTaxRate <= 256, "The tax rate can only be increased by a maximum of 25%");
challengeTaxRate = _newChallengeTaxRate;
emit ChallengeTaxRateUpdate(_newChallengeTaxRate);
}
function transferBalanceToTreasury() public {
uint256 amount = treasuryBalance;
treasuryBalance = 0;
TREASURY.send(amount);
emit TreasuryBalanceUpdate(amount);
}
function switchPublishingLock() public override onlyAdmin {
isPublishingEnabled = !isPublishingEnabled;
}
function changeAdmin(address payable _newAdmin) external onlyAdmin {
admin = _newAdmin;
emit AdminUpdate(_newAdmin);
}
function changeTreasury(address payable _newTreasury) external onlyAdmin {
TREASURY = _newTreasury;
emit TreasuryUpdate(_newTreasury);
}
function changeWinnerStakeMultiplier(uint256 _newWinnerStakeMultiplier) external override onlyAdmin {
WINNER_STAKE_MULTIPLIER = _newWinnerStakeMultiplier;
emit WinnerStakeMultiplierUpdate(_newWinnerStakeMultiplier);
}
function changeLoserStakeMultiplier(uint256 _newLoserStakeMultiplier) external override onlyAdmin {
LOSER_STAKE_MULTIPLIER = _newLoserStakeMultiplier;
emit LoserStakeMultiplierUpdate(_newLoserStakeMultiplier);
}
function changeLoserAppealPeriodMultiplier(uint256 _newLoserAppealPeriodMultiplier) external override onlyAdmin {
LOSER_APPEAL_PERIOD_MULTIPLIER = _newLoserAppealPeriodMultiplier;
emit LoserAppealPeriodMultiplierUpdate(_newLoserAppealPeriodMultiplier);
}
function changeArticleWithdrawalTimelock(uint256 _newArticleWithdrawalTimelock) external override onlyAdmin {
ARTICLE_WITHDRAWAL_TIMELOCK = _newArticleWithdrawalTimelock;
emit ArticleWithdrawalTimelockUpdate(_newArticleWithdrawalTimelock);
}
function newCategory(string memory _metaevidenceIpfsUri, bytes memory _arbitratorExtraData) public {
require(categoryCounter + 1 != 0, "No space left for a new category");
emit MetaEvidence(categoryCounter, _metaevidenceIpfsUri);
categoryToArbitratorExtraData[categoryCounter] = _arbitratorExtraData;
categoryCounter++;
}
function transferOwnership(uint80 _articleStorageAddress, address payable _newOwner) external override {
Article storage article = articleStorage[_articleStorageAddress];
require(msg.sender == article.owner, "Only author can transfer ownership.");
article.owner = _newOwner;
emit OwnershipTransfer(_newOwner);
}
function challengeFee(uint80 _articleStorageAddress) public view override returns (uint256) {
Article storage article = articleStorage[_articleStorageAddress];
uint256 arbitrationFee = ARBITRATOR.arbitrationCost(categoryToArbitratorExtraData[article.category]);
uint256 challengeTax = ((uint96(article.bountyAmount) << NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE) *
challengeTaxRate) / MULTIPLIER_DENOMINATOR;
return arbitrationFee + challengeTax;
}
function appealFee(uint256 _disputeID) external view override returns (uint256 arbitrationFee) {
DisputeData storage dispute = disputes[_disputeID];
arbitrationFee = ARBITRATOR.appealCost(_disputeID, categoryToArbitratorExtraData[dispute.articleCategory]);
}
function findVacantStorageSlot(uint80 _searchPointer) external view override returns (uint256 vacantSlotIndex) {
Article storage article;
do {
article = articleStorage[_searchPointer++];
} while (article.bountyAmount != 0);
return _searchPointer - 1;
}
function getTotalWithdrawableAmount(uint256 _disputeID, address payable _contributor)
external
view
override
returns (uint256 sum, uint256[][] memory amounts)
{
DisputeData storage dispute = disputes[_disputeID];
if (!dispute.resolved) return (uint256(0), amounts);
uint256 noOfRounds = dispute.rounds.length;
RulingOptions finalRuling = dispute.outcome;
amounts = new uint256[][](noOfRounds);
for (uint256 roundNumber = 0; roundNumber < noOfRounds; roundNumber++) {
amounts[roundNumber] = new uint256[](NUMBER_OF_RULING_OPTIONS + 1);
Round storage round = dispute.rounds[roundNumber];
for (uint256 rulingOption = 0; rulingOption <= NUMBER_OF_RULING_OPTIONS; rulingOption++) {
uint256 currentAmount = getWithdrawableAmount(
round,
_contributor,
RulingOptions(rulingOption),
finalRuling
);
if (currentAmount > 0) {
sum += getWithdrawableAmount(round, _contributor, RulingOptions(rulingOption), finalRuling);
amounts[roundNumber][rulingOption] = currentAmount;
}
}
}
}
function getWithdrawableAmount(
Round storage _round,
address _contributor,
RulingOptions _ruling,
RulingOptions _finalRuling
) internal view returns (uint256 amount) {
RulingOptions givenRuling = RulingOptions(_ruling);
if (!_round.hasPaid[uint256(givenRuling)]) {
amount = _round.contributions[_contributor][uint256(givenRuling)];
} else {
if (_ruling == _finalRuling) {
amount = _round.totalPerRuling[uint256(givenRuling)] > 0
? (_round.contributions[_contributor][uint256(givenRuling)] * _round.totalClaimableAfterExpenses) /
_round.totalPerRuling[uint256(givenRuling)]
: 0;
} else if (!_round.hasPaid[uint256(RulingOptions(_finalRuling))]) {
amount =
(_round.contributions[_contributor][uint256(givenRuling)] * _round.totalClaimableAfterExpenses) /
(_round.totalPerRuling[uint256(RulingOptions.ChallengeFailed)] +
_round.totalPerRuling[uint256(RulingOptions.Debunked)]);
}
}
}
function getRoundInfo(uint256 _disputeID, uint256 _round)
external
view
override
returns (
bool[NUMBER_OF_RULING_OPTIONS + 1] memory hasPaid,
uint256[NUMBER_OF_RULING_OPTIONS + 1] memory totalPerRuling,
uint256 totalClaimableAfterExpenses
)
{
Round storage round = disputes[_disputeID].rounds[_round];
return (round.hasPaid, round.totalPerRuling, round.totalClaimableAfterExpenses);
}
function getLastRoundWinner(uint256 _disputeID) public view override returns (uint256) {
return ARBITRATOR.currentRuling(_disputeID);
}
function getAppealPeriod(uint256 _disputeID, RulingOptions _ruling)
external
view
override
returns (uint256, uint256)
{
(uint256 appealWindowStart, uint256 appealWindowEnd) = ARBITRATOR.appealPeriod(_disputeID);
uint256 loserAppealWindowEnd = appealWindowStart +
(((appealWindowEnd - appealWindowStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / MULTIPLIER_DENOMINATOR);
bool isWinner = RulingOptions(getLastRoundWinner(_disputeID)) == _ruling;
return isWinner ? (appealWindowStart, appealWindowEnd) : (appealWindowStart, loserAppealWindowEnd);
}
function getReturnOfInvestmentRatio(RulingOptions _ruling, RulingOptions _lastRoundWinner)
external
view
override
returns (uint256)
{
bool isWinner = _lastRoundWinner == _ruling;
uint256 DECIMAL_PRECISION = 1000;
uint256 multiplier = isWinner ? WINNER_STAKE_MULTIPLIER : LOSER_STAKE_MULTIPLIER;
return (((WINNER_STAKE_MULTIPLIER + LOSER_STAKE_MULTIPLIER + MULTIPLIER_DENOMINATOR) * DECIMAL_PRECISION) /
(multiplier + MULTIPLIER_DENOMINATOR));
}
function getAmountRemainsToBeRaised(uint256 _disputeID, RulingOptions _ruling)
external
view
override
returns (uint256)
{
DisputeData storage dispute = disputes[_disputeID];
uint256 lastRoundIndex = dispute.rounds.length - 1;
Round storage lastRound = dispute.rounds[lastRoundIndex];
bool isWinner = RulingOptions(getLastRoundWinner(_disputeID)) == _ruling;
uint256 multiplier = isWinner ? WINNER_STAKE_MULTIPLIER : LOSER_STAKE_MULTIPLIER;
uint256 raisedSoFar = lastRound.totalPerRuling[uint256(_ruling)];
uint256 basicCost = ARBITRATOR.appealCost(_disputeID, categoryToArbitratorExtraData[dispute.articleCategory]);
uint256 totalCost = basicCost + ((basicCost * (multiplier)) / MULTIPLIER_DENOMINATOR);
return totalCost - raisedSoFar;
}
}
{
"compilationTarget": {
"contracts/TruthPost.sol": "TruthPost"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": []
}
[{"inputs":[{"internalType":"contract IArbitrator","name":"_arbitrator","type":"address"},{"internalType":"bytes","name":"_arbitratorExtraData","type":"bytes"},{"internalType":"string","name":"_metaevidenceIpfsUri","type":"string"},{"internalType":"uint256","name":"_articleWithdrawalTimelock","type":"uint256"},{"internalType":"uint256","name":"_winnerStakeMultiplier","type":"uint256"},{"internalType":"uint256","name":"_loserStakeMultiplier","type":"uint256"},{"internalType":"address payable","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_newAdmin","type":"address"}],"name":"AdminUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_newWithdrawalTimelock","type":"uint256"}],"name":"ArticleWithdrawalTimelockUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"articleAddress","type":"uint256"}],"name":"ArticleWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"articleAddress","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTotal","type":"uint256"}],"name":"BalanceUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"articleAddress","type":"uint256"},{"indexed":false,"internalType":"address","name":"challanger","type":"address"},{"indexed":false,"internalType":"uint256","name":"disputeID","type":"uint256"}],"name":"Challenge","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_newTaxRate","type":"uint256"}],"name":"ChallengeTaxRateUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"enum ITruthPost.RulingOptions","name":"ruling","type":"uint8"},{"indexed":true,"internalType":"address","name":"contributor","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Contribution","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"articleAddress","type":"uint256"}],"name":"Debunked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IArbitrator","name":"_arbitrator","type":"address"},{"indexed":true,"internalType":"uint256","name":"_disputeID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_metaEvidenceID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_evidenceGroupID","type":"uint256"}],"name":"Dispute","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IArbitrator","name":"_arbitrator","type":"address"},{"indexed":true,"internalType":"uint256","name":"_evidenceGroupID","type":"uint256"},{"indexed":true,"internalType":"address","name":"_party","type":"address"},{"indexed":false,"internalType":"string","name":"_evidence","type":"string"}],"name":"Evidence","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_newLoserAppealPeriodMultiplier","type":"uint256"}],"name":"LoserAppealPeriodMultiplierUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_newLoserStakeMultiplier","type":"uint256"}],"name":"LoserStakeMultiplierUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_metaEvidenceID","type":"uint256"},{"indexed":false,"internalType":"string","name":"_evidence","type":"string"}],"name":"MetaEvidence","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"articleID","type":"string"},{"indexed":false,"internalType":"uint8","name":"category","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"articleAddress","type":"uint256"}],"name":"NewArticle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_newOwner","type":"address"}],"name":"OwnershipTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IArbitrator","name":"_arbitrator","type":"address"},{"indexed":true,"internalType":"uint256","name":"_disputeID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_ruling","type":"uint256"}],"name":"Ruling","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"enum ITruthPost.RulingOptions","name":"ruling","type":"uint8"}],"name":"RulingFunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"articleAddress","type":"uint256"}],"name":"TimelockStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_byAmount","type":"uint256"}],"name":"TreasuryBalanceUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_newTreasury","type":"address"}],"name":"TreasuryUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_newWinnerStakeMultiplier","type":"uint256"}],"name":"WinnerStakeMultiplierUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"disputeId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"enum ITruthPost.RulingOptions","name":"ruling","type":"uint8"},{"indexed":true,"internalType":"address","name":"contributor","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"Withdrawal","type":"event"},{"inputs":[],"name":"ARBITRATOR","outputs":[{"internalType":"contract IArbitrator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ARTICLE_WITHDRAWAL_TIMELOCK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOSER_APPEAL_PERIOD_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOSER_STAKE_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MULTIPLIER_DENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NUMBER_OF_LEAST_SIGNIFICANT_BITS_TO_IGNORE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NUMBER_OF_RULING_OPTIONS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WINNER_STAKE_MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"}],"name":"appealFee","outputs":[{"internalType":"uint256","name":"arbitrationFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"","type":"uint80"}],"name":"articleStorage","outputs":[{"internalType":"address payable","name":"owner","type":"address"},{"internalType":"uint32","name":"withdrawalPermittedAt","type":"uint32"},{"internalType":"uint56","name":"bountyAmount","type":"uint56"},{"internalType":"uint8","name":"category","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"categoryCounter","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"categoryToArbitratorExtraData","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"_articleStorageAddress","type":"uint80"}],"name":"challenge","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint80","name":"_articleStorageAddress","type":"uint80"}],"name":"challengeFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"challengeTaxRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_newAdmin","type":"address"}],"name":"changeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newArticleWithdrawalTimelock","type":"uint256"}],"name":"changeArticleWithdrawalTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newLoserAppealPeriodMultiplier","type":"uint256"}],"name":"changeLoserAppealPeriodMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newLoserStakeMultiplier","type":"uint256"}],"name":"changeLoserStakeMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_newTreasury","type":"address"}],"name":"changeTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newWinnerStakeMultiplier","type":"uint256"}],"name":"changeWinnerStakeMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"disputes","outputs":[{"internalType":"address payable","name":"challenger","type":"address"},{"internalType":"enum ITruthPost.RulingOptions","name":"outcome","type":"uint8"},{"internalType":"uint8","name":"articleCategory","type":"uint8"},{"internalType":"bool","name":"resolved","type":"bool"},{"internalType":"uint80","name":"articleStorageAddress","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"_searchPointer","type":"uint80"}],"name":"findVacantStorageSlot","outputs":[{"internalType":"uint256","name":"vacantSlotIndex","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"enum ITruthPost.RulingOptions","name":"_supportedRuling","type":"uint8"}],"name":"fundAppeal","outputs":[{"internalType":"bool","name":"fullyFunded","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"enum ITruthPost.RulingOptions","name":"_ruling","type":"uint8"}],"name":"getAmountRemainsToBeRaised","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"enum ITruthPost.RulingOptions","name":"_ruling","type":"uint8"}],"name":"getAppealPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"}],"name":"getLastRoundWinner","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum ITruthPost.RulingOptions","name":"_ruling","type":"uint8"},{"internalType":"enum ITruthPost.RulingOptions","name":"_lastRoundWinner","type":"uint8"}],"name":"getReturnOfInvestmentRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"uint256","name":"_round","type":"uint256"}],"name":"getRoundInfo","outputs":[{"internalType":"bool[3]","name":"hasPaid","type":"bool[3]"},{"internalType":"uint256[3]","name":"totalPerRuling","type":"uint256[3]"},{"internalType":"uint256","name":"totalClaimableAfterExpenses","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"address payable","name":"_contributor","type":"address"}],"name":"getTotalWithdrawableAmount","outputs":[{"internalType":"uint256","name":"sum","type":"uint256"},{"internalType":"uint256[][]","name":"amounts","type":"uint256[][]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"_articleStorageAddress","type":"uint80"}],"name":"increaseBounty","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"_articleID","type":"string"},{"internalType":"uint8","name":"_category","type":"uint8"},{"internalType":"uint80","name":"_searchPointer","type":"uint80"}],"name":"initializeArticle","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint80","name":"_articleStorageAddress","type":"uint80"}],"name":"initiateWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_metaevidenceIpfsUri","type":"string"},{"internalType":"bytes","name":"_arbitratorExtraData","type":"bytes"}],"name":"newCategory","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"uint256","name":"_ruling","type":"uint256"}],"name":"rule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"string","name":"_evidenceURI","type":"string"}],"name":"submitEvidence","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"switchPublishingLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"transferBalanceToTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint80","name":"_articleStorageAddress","type":"uint80"},{"internalType":"address payable","name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"treasuryBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newChallengeTaxRate","type":"uint256"}],"name":"updateChallengeTaxRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint80","name":"_articleStorageAddress","type":"uint80"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"address payable","name":"_contributor","type":"address"},{"internalType":"uint256","name":"_roundNumber","type":"uint256"},{"internalType":"enum ITruthPost.RulingOptions","name":"_ruling","type":"uint8"}],"name":"withdrawFeesAndRewards","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"address payable","name":"_contributor","type":"address"},{"internalType":"enum ITruthPost.RulingOptions","name":"_ruling","type":"uint8"}],"name":"withdrawFeesAndRewardsForAllRounds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"address payable","name":"_contributor","type":"address"}],"name":"withdrawFeesAndRewardsForAllRoundsAndAllRulings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_disputeID","type":"uint256"},{"internalType":"address payable","name":"_contributor","type":"address"},{"internalType":"uint256[][]","name":"positions","type":"uint256[][]"}],"name":"withdrawFeesAndRewardsForGivenPositions","outputs":[],"stateMutability":"nonpayable","type":"function"}]