Skip to content

Commit 893abe2

Browse files
feat: implement v2 settlement and forfeiture distribution (#87)
* feat: implement v2 settlement and forfeiture distribution * feat: emit DustCredited event when settlement routes leftover dust
1 parent 20da0cb commit 893abe2

44 files changed

Lines changed: 18192 additions & 15 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

contracts/tholos-v2/src/lib.rs

Lines changed: 333 additions & 15 deletions
Large diffs are not rendered by default.

contracts/tholos-v2/src/test.rs

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,3 +1818,336 @@ fn test_optimistic_timeout_when_neither_side_reaches_majority_by_deadline() {
18181818
// on the challenger" case V2_RESOLUTION.md calls out.
18191819
assert_eq!(assertion.final_outcome, Some(true));
18201820
}
1821+
1822+
#[test]
1823+
fn test_settle_strict_majority_conserves_pool_and_pays_dust_to_asserter() {
1824+
let f = Fixture::new();
1825+
let asserter = f.funded_address();
1826+
let disputer = f.funded_address();
1827+
let voter_x = f.funded_address();
1828+
let voter_y = f.funded_address();
1829+
1830+
let id = f.asserted(&asserter);
1831+
f.client.dispute(&disputer, &id);
1832+
let policy_hash = f.client.get_assertion(&id).policy_hash;
1833+
1834+
let x_salt = salt(&f.env, 1);
1835+
let x_commitment = compute_commitment(
1836+
&f.env,
1837+
&f.client.address,
1838+
&policy_hash,
1839+
id,
1840+
&voter_x,
1841+
true,
1842+
&x_salt,
1843+
);
1844+
f.client.register(&voter_x, &id, &300, &x_commitment);
1845+
let y_salt = salt(&f.env, 2);
1846+
let y_commitment = compute_commitment(
1847+
&f.env,
1848+
&f.client.address,
1849+
&policy_hash,
1850+
id,
1851+
&voter_y,
1852+
true,
1853+
&y_salt,
1854+
);
1855+
f.client.register(&voter_y, &id, &150, &y_commitment);
1856+
1857+
f.advance_past_registration_deadline(id);
1858+
f.client.reveal(&voter_x, &id, &true, &x_salt);
1859+
f.client.reveal(&voter_y, &id, &true, &y_salt);
1860+
1861+
let assertion = f.client.get_assertion(&id);
1862+
assert_eq!(assertion.phase, PhaseV2::Resolved);
1863+
assert_eq!(assertion.terminal_cause, TerminalCause::StrictMajorityFor);
1864+
1865+
// eligible_total = 100 + 100 + 300 + 150 = 650. recipient_weight (agree)
1866+
// = 100 + 300 + 150 = 550. forfeited_pool = 650 - 550 = 100 (the
1867+
// disputer's forfeited fixed bond).
1868+
// reward_asserter = floor(100 * 100 / 550) = 18
1869+
// reward_x = floor(300 * 100 / 550) = 54
1870+
// reward_y = floor(150 * 100 / 550) = 27
1871+
// sum = 99, dust = 1, credited to the asserter.
1872+
f.client.settle(&id, &asserter);
1873+
f.client.settle(&id, &voter_x);
1874+
f.client.settle(&id, &voter_y);
1875+
f.client.settle(&id, &disputer);
1876+
1877+
assert_eq!(f.client.get_credit(&id, &asserter), 100 + 18 + 1);
1878+
assert_eq!(f.client.get_credit(&id, &voter_x), 300 + 54);
1879+
assert_eq!(f.client.get_credit(&id, &voter_y), 150 + 27);
1880+
assert_eq!(f.client.get_credit(&id, &disputer), 0);
1881+
1882+
let total = f.client.get_credit(&id, &asserter)
1883+
+ f.client.get_credit(&id, &voter_x)
1884+
+ f.client.get_credit(&id, &voter_y)
1885+
+ f.client.get_credit(&id, &disputer);
1886+
assert_eq!(total, 650);
1887+
}
1888+
1889+
#[test]
1890+
fn test_settle_strict_majority_order_independent() {
1891+
// Same scenario as
1892+
// test_settle_strict_majority_conserves_pool_and_pays_dust_to_asserter,
1893+
// but settled in a different order (loser first, dust recipient last),
1894+
// to confirm every payout is identical regardless of call order.
1895+
let f = Fixture::new();
1896+
let asserter = f.funded_address();
1897+
let disputer = f.funded_address();
1898+
let voter_x = f.funded_address();
1899+
let voter_y = f.funded_address();
1900+
1901+
let id = f.asserted(&asserter);
1902+
f.client.dispute(&disputer, &id);
1903+
let policy_hash = f.client.get_assertion(&id).policy_hash;
1904+
1905+
let x_salt = salt(&f.env, 1);
1906+
let x_commitment = compute_commitment(
1907+
&f.env,
1908+
&f.client.address,
1909+
&policy_hash,
1910+
id,
1911+
&voter_x,
1912+
true,
1913+
&x_salt,
1914+
);
1915+
f.client.register(&voter_x, &id, &300, &x_commitment);
1916+
let y_salt = salt(&f.env, 2);
1917+
let y_commitment = compute_commitment(
1918+
&f.env,
1919+
&f.client.address,
1920+
&policy_hash,
1921+
id,
1922+
&voter_y,
1923+
true,
1924+
&y_salt,
1925+
);
1926+
f.client.register(&voter_y, &id, &150, &y_commitment);
1927+
1928+
f.advance_past_registration_deadline(id);
1929+
f.client.reveal(&voter_x, &id, &true, &x_salt);
1930+
f.client.reveal(&voter_y, &id, &true, &y_salt);
1931+
1932+
f.client.settle(&id, &disputer);
1933+
f.client.settle(&id, &voter_y);
1934+
f.client.settle(&id, &voter_x);
1935+
f.client.settle(&id, &asserter);
1936+
1937+
assert_eq!(f.client.get_credit(&id, &asserter), 100 + 18 + 1);
1938+
assert_eq!(f.client.get_credit(&id, &voter_x), 300 + 54);
1939+
assert_eq!(f.client.get_credit(&id, &voter_y), 150 + 27);
1940+
assert_eq!(f.client.get_credit(&id, &disputer), 0);
1941+
}
1942+
1943+
#[test]
1944+
fn test_settle_optimistic_timeout_conserves_pool_and_pays_dust_to_asserter() {
1945+
let f = Fixture::new();
1946+
let asserter = f.funded_address();
1947+
let disputer = f.funded_address();
1948+
let voter_a = f.funded_address();
1949+
let voter_b = f.funded_address();
1950+
let never_revealed = f.funded_address();
1951+
1952+
let id = f.asserted(&asserter);
1953+
f.client.dispute(&disputer, &id);
1954+
let policy_hash = f.client.get_assertion(&id).policy_hash;
1955+
1956+
let a_salt = salt(&f.env, 1);
1957+
let a_commitment = compute_commitment(
1958+
&f.env,
1959+
&f.client.address,
1960+
&policy_hash,
1961+
id,
1962+
&voter_a,
1963+
true,
1964+
&a_salt,
1965+
);
1966+
f.client.register(&voter_a, &id, &300, &a_commitment);
1967+
let b_salt = salt(&f.env, 2);
1968+
let b_commitment = compute_commitment(
1969+
&f.env,
1970+
&f.client.address,
1971+
&policy_hash,
1972+
id,
1973+
&voter_b,
1974+
false,
1975+
&b_salt,
1976+
);
1977+
f.client.register(&voter_b, &id, &200, &b_commitment);
1978+
f.client
1979+
.register(&never_revealed, &id, &150, &commitment(&f.env, 9));
1980+
1981+
f.advance_past_registration_deadline(id);
1982+
f.client.reveal(&voter_a, &id, &true, &a_salt);
1983+
f.client.reveal(&voter_b, &id, &false, &b_salt);
1984+
1985+
f.advance_past_reveal_deadline(id);
1986+
let cause = f.client.resolve_outcome(&id);
1987+
assert_eq!(cause, TerminalCause::OptimisticTimeout);
1988+
1989+
// eligible_total = 100 + 100 + 300 + 200 + 150 = 850. recipient_weight
1990+
// (revealed_weight) = 100 + 100 + 300 + 200 = 700. forfeited_pool =
1991+
// 850 - 700 = 150 (never_revealed's stake).
1992+
// reward_asserter = floor(100 * 150 / 700) = 21
1993+
// reward_disputer = floor(100 * 150 / 700) = 21
1994+
// reward_a = floor(300 * 150 / 700) = 64
1995+
// reward_b = floor(200 * 150 / 700) = 42
1996+
// sum = 148, dust = 2, credited to the asserter (timeout default).
1997+
f.client.settle(&id, &asserter);
1998+
f.client.settle(&id, &disputer);
1999+
f.client.settle(&id, &voter_a);
2000+
f.client.settle(&id, &voter_b);
2001+
f.client.settle(&id, &never_revealed);
2002+
2003+
assert_eq!(f.client.get_credit(&id, &asserter), 100 + 21 + 2);
2004+
assert_eq!(f.client.get_credit(&id, &disputer), 100 + 21);
2005+
assert_eq!(f.client.get_credit(&id, &voter_a), 300 + 64);
2006+
assert_eq!(f.client.get_credit(&id, &voter_b), 200 + 42);
2007+
assert_eq!(f.client.get_credit(&id, &never_revealed), 0);
2008+
2009+
let total = f.client.get_credit(&id, &asserter)
2010+
+ f.client.get_credit(&id, &disputer)
2011+
+ f.client.get_credit(&id, &voter_a)
2012+
+ f.client.get_credit(&id, &voter_b)
2013+
+ f.client.get_credit(&id, &never_revealed);
2014+
assert_eq!(total, 850);
2015+
}
2016+
2017+
#[test]
2018+
fn test_settle_optimistic_timeout_fully_revealed_skips_dust_step() {
2019+
let f = Fixture::new();
2020+
let asserter = f.funded_address();
2021+
let disputer = f.funded_address();
2022+
2023+
let id = f.asserted(&asserter);
2024+
f.client.dispute(&disputer, &id);
2025+
2026+
f.advance_past_registration_deadline(id);
2027+
let cause = f.client.resolve_outcome(&id);
2028+
assert_eq!(cause, TerminalCause::OptimisticTimeout);
2029+
2030+
// eligible_total = 200, recipient_weight = 200 (both fixed positions
2031+
// auto-revealed), forfeited_pool = 0: nothing forfeited, so the dust
2032+
// step never runs, and every position just recovers its own principal.
2033+
f.client.settle(&id, &asserter);
2034+
f.client.settle(&id, &disputer);
2035+
2036+
assert_eq!(f.client.get_credit(&id, &asserter), 100);
2037+
assert_eq!(f.client.get_credit(&id, &disputer), 100);
2038+
}
2039+
2040+
#[test]
2041+
fn test_settle_strict_majority_against_dust_goes_to_disputer() {
2042+
let f = Fixture::new();
2043+
let asserter = f.funded_address();
2044+
let disputer = f.funded_address();
2045+
let voter = f.funded_address();
2046+
2047+
let id = f.asserted(&asserter);
2048+
f.client.dispute(&disputer, &id);
2049+
let policy_hash = f.client.get_assertion(&id).policy_hash;
2050+
2051+
let voter_salt = salt(&f.env, 1);
2052+
let voter_commitment = compute_commitment(
2053+
&f.env,
2054+
&f.client.address,
2055+
&policy_hash,
2056+
id,
2057+
&voter,
2058+
false,
2059+
&voter_salt,
2060+
);
2061+
f.client.register(&voter, &id, &301, &voter_commitment);
2062+
2063+
f.advance_past_registration_deadline(id);
2064+
f.client.reveal(&voter, &id, &false, &voter_salt);
2065+
2066+
let assertion = f.client.get_assertion(&id);
2067+
assert_eq!(
2068+
assertion.terminal_cause,
2069+
TerminalCause::StrictMajorityAgainst
2070+
);
2071+
2072+
// eligible_total = 100 + 100 + 301 = 501. recipient_weight (disagree) =
2073+
// 100 + 301 = 401. forfeited_pool = 501 - 401 = 100 (asserter's fixed
2074+
// bond, the losing side here).
2075+
// reward_disputer = floor(100 * 100 / 401) = 24
2076+
// reward_voter = floor(301 * 100 / 401) = 75
2077+
// sum = 99, dust = 1, credited to the disputer (the winning party).
2078+
f.client.settle(&id, &asserter);
2079+
f.client.settle(&id, &voter);
2080+
f.client.settle(&id, &disputer);
2081+
2082+
assert_eq!(f.client.get_credit(&id, &asserter), 0);
2083+
assert_eq!(f.client.get_credit(&id, &voter), 301 + 75);
2084+
assert_eq!(f.client.get_credit(&id, &disputer), 100 + 24 + 1);
2085+
}
2086+
2087+
#[test]
2088+
fn test_settle_before_resolved_fails() {
2089+
let f = Fixture::new();
2090+
let asserter = f.funded_address();
2091+
let disputer = f.funded_address();
2092+
2093+
let id = f.asserted(&asserter);
2094+
f.client.dispute(&disputer, &id);
2095+
2096+
let result = f.client.try_settle(&id, &asserter);
2097+
assert_eq!(result, Err(Ok(Error::NotResolved)));
2098+
}
2099+
2100+
#[test]
2101+
fn test_settle_on_uncontested_finalize_fails() {
2102+
let f = Fixture::new();
2103+
let asserter = f.funded_address();
2104+
2105+
let id = f.asserted(&asserter);
2106+
f.advance_past_window();
2107+
f.client.finalize(&asserter, &id);
2108+
2109+
let result = f.client.try_settle(&id, &asserter);
2110+
assert_eq!(result, Err(Ok(Error::NotResolved)));
2111+
}
2112+
2113+
#[test]
2114+
fn test_settle_twice_fails() {
2115+
let f = Fixture::new();
2116+
let asserter = f.funded_address();
2117+
let disputer = f.funded_address();
2118+
2119+
let id = f.asserted(&asserter);
2120+
f.client.dispute(&disputer, &id);
2121+
f.advance_past_registration_deadline(id);
2122+
f.client.resolve_outcome(&id);
2123+
2124+
f.client.settle(&id, &asserter);
2125+
let result = f.client.try_settle(&id, &asserter);
2126+
assert_eq!(result, Err(Ok(Error::AlreadySettled)));
2127+
}
2128+
2129+
#[test]
2130+
fn test_settle_nonexistent_position_fails() {
2131+
let f = Fixture::new();
2132+
let asserter = f.funded_address();
2133+
let disputer = f.funded_address();
2134+
let stranger = f.funded_address();
2135+
2136+
let id = f.asserted(&asserter);
2137+
f.client.dispute(&disputer, &id);
2138+
f.advance_past_registration_deadline(id);
2139+
f.client.resolve_outcome(&id);
2140+
2141+
let result = f.client.try_settle(&id, &stranger);
2142+
assert_eq!(result, Err(Ok(Error::AssertionNotFound)));
2143+
}
2144+
2145+
#[test]
2146+
fn test_get_credit_returns_zero_for_unknown_address() {
2147+
let f = Fixture::new();
2148+
let asserter = f.funded_address();
2149+
let stranger = f.funded_address();
2150+
2151+
let id = f.asserted(&asserter);
2152+
assert_eq!(f.client.get_credit(&id, &stranger), 0);
2153+
}

0 commit comments

Comments
 (0)