@@ -1662,6 +1662,267 @@ mod tests {
16621662 ) ;
16631663 }
16641664
1665+ #[ test]
1666+ fn test_unlocking_schedule_with_termination ( ) {
1667+ // https://wiki.near.org/getting-started/near-token/lockups#termination-of-vesting
1668+ // This test checks that termination of vesting
1669+ // fixes the amount of tokens that finally should become liquid (*),
1670+ // but does not change the schedule of unlocking the tokens.
1671+ // The contract should act as there was no termination at all,
1672+ // until we do not reach the amount (*).
1673+ // After we reach this amount, the unlocking process should stop.
1674+
1675+ // Taking bigger lockup amount because we compare the balances further
1676+ // and there is a comparison delta.
1677+ // We want to be sure that this delta does not grow on bigger numbers
1678+ let lockup_amount = to_yocto ( LOCKUP_NEAR * 1000 ) ;
1679+ let mut context = basic_context ( ) ;
1680+ context. account_balance = lockup_amount;
1681+ testing_env ! ( context. clone( ) ) ;
1682+
1683+ let vesting_cliff_offset = 100 ;
1684+ let vesting_schedule = new_vesting_schedule ( vesting_cliff_offset) ;
1685+ let mut contract = new_contract (
1686+ true ,
1687+ Some ( vesting_schedule. clone ( ) ) ,
1688+ Some ( to_nanos ( YEAR * 4 ) . into ( ) ) ,
1689+ true ,
1690+ ) ;
1691+
1692+ // Vesting starts at day 235
1693+ let ts_vesting_started = to_ts ( GENESIS_TIME_IN_DAYS - YEAR + vesting_cliff_offset) ;
1694+ assert_eq ! ( vesting_schedule. start_timestamp. 0 , ts_vesting_started) ;
1695+
1696+ // We don't use lockup_timestamp,
1697+ // it means that lockup starts from the transfers enabled moment
1698+ assert_eq ! ( contract. lockup_information. lockup_timestamp, None ) ;
1699+
1700+ // Transfers are enabled at day 500, lockup also starts here
1701+ if let TransfersInformation :: TransfersEnabled {
1702+ transfers_timestamp
1703+ } = & contract. lockup_information . transfers_information
1704+ {
1705+ assert_eq ! ( transfers_timestamp. 0 , to_ts( GENESIS_TIME_IN_DAYS ) ) ;
1706+ } else {
1707+ assert ! ( false , "Transfers should be enabled" ) ;
1708+ }
1709+
1710+ // Vesting cliff ends at day 600
1711+ assert_eq ! ( vesting_schedule. cliff_timestamp. 0 , to_ts( GENESIS_TIME_IN_DAYS + vesting_cliff_offset) ) ;
1712+
1713+ // Lockup cliff ends at day 865
1714+ assert_eq ! ( contract. lockup_information. lockup_duration, to_nanos( YEAR ) ) ;
1715+
1716+ // Everything is locked and unvested in the beginning
1717+ assert_eq ! (
1718+ contract. get_vesting_information( ) ,
1719+ VestingInformation :: VestingHash (
1720+ VestingScheduleWithSalt {
1721+ vesting_schedule: vesting_schedule. clone( ) ,
1722+ salt: SALT . to_vec( ) . into( ) ,
1723+ }
1724+ . hash( )
1725+ . into( )
1726+ )
1727+ ) ;
1728+ assert_eq ! ( contract. get_owners_balance( ) . 0 , 0 ) ;
1729+ assert_eq ! ( contract. get_liquid_owners_balance( ) . 0 , 0 ) ;
1730+ assert_eq ! ( contract. get_locked_amount( ) . 0 , lockup_amount) ;
1731+ assert_eq ! (
1732+ contract. get_unvested_amount( vesting_schedule. clone( ) ) . 0 ,
1733+ lockup_amount
1734+ ) ;
1735+ assert_eq ! (
1736+ contract
1737+ . get_locked_vested_amount( vesting_schedule. clone( ) )
1738+ . 0 ,
1739+ 0
1740+ ) ;
1741+
1742+
1743+ // *** day 599: day before vesting cliff is passed ***
1744+ let ts_1_day_before_vesting_cliff = to_ts ( GENESIS_TIME_IN_DAYS + vesting_cliff_offset - 1 ) ;
1745+ assert_eq ! ( ts_1_day_before_vesting_cliff, vesting_schedule. cliff_timestamp. 0 - to_nanos( 1 ) ) ;
1746+ context. block_timestamp = ts_1_day_before_vesting_cliff;
1747+ testing_env ! ( context. clone( ) ) ;
1748+
1749+ // Everything is still locked and unvested
1750+ assert_eq ! ( contract. get_owners_balance( ) . 0 , 0 ) ;
1751+ assert_eq ! ( contract. get_liquid_owners_balance( ) . 0 , 0 ) ;
1752+ assert_eq ! ( contract. get_locked_amount( ) . 0 , lockup_amount) ;
1753+ assert_eq ! (
1754+ contract. get_unvested_amount( vesting_schedule. clone( ) ) . 0 ,
1755+ lockup_amount
1756+ ) ;
1757+ assert_eq ! (
1758+ contract
1759+ . get_locked_vested_amount( vesting_schedule. clone( ) )
1760+ . 0 ,
1761+ 0
1762+ ) ;
1763+
1764+
1765+ // *** day 600: day of vesting cliff ***
1766+ let ts_day_of_vesting_cliff = to_ts ( GENESIS_TIME_IN_DAYS + vesting_cliff_offset) ;
1767+ assert_eq ! ( ts_day_of_vesting_cliff, vesting_schedule. cliff_timestamp. 0 ) ;
1768+ context. block_timestamp = ts_day_of_vesting_cliff;
1769+ testing_env ! ( context. clone( ) ) ;
1770+
1771+ // 25% is vested
1772+ let vesting_nanos_passed = ( ts_day_of_vesting_cliff - ts_vesting_started) as u128 ;
1773+ let vesting_nanos_total = ( vesting_schedule. end_timestamp . 0 - vesting_schedule. start_timestamp . 0 ) as u128 ;
1774+
1775+ let expected_vested_amount_at_cliff_day = lockup_amount / vesting_nanos_total * vesting_nanos_passed;
1776+ let vested_amount_at_cliff_day = contract
1777+ . get_locked_vested_amount ( vesting_schedule. clone ( ) )
1778+ . 0 ;
1779+ assert_almost_eq_with_max_delta ( expected_vested_amount_at_cliff_day, vested_amount_at_cliff_day, to_yocto ( 1 ) ) ;
1780+ assert_eq ! ( to_yocto( 250000 ) , vested_amount_at_cliff_day) ;
1781+
1782+ let expected_unvested_amount_at_cliff_day = lockup_amount - expected_vested_amount_at_cliff_day;
1783+ let unvested_amount_at_cliff_day = contract. get_unvested_amount ( vesting_schedule. clone ( ) ) . 0 ;
1784+ assert_almost_eq_with_max_delta ( expected_unvested_amount_at_cliff_day, unvested_amount_at_cliff_day, to_yocto ( 1 ) ) ;
1785+ assert_eq ! ( to_yocto( 750000 ) , unvested_amount_at_cliff_day) ;
1786+
1787+ // But tokens are still locked due to lockup
1788+ assert_eq ! ( contract. get_owners_balance( ) . 0 , 0 ) ;
1789+ assert_eq ! ( contract. get_liquid_owners_balance( ) . 0 , 0 ) ;
1790+ assert_eq ! ( contract. get_locked_amount( ) . 0 , lockup_amount) ;
1791+
1792+
1793+ // *** day 800: day of termination ***
1794+ let ts_termination_day = to_ts ( GENESIS_TIME_IN_DAYS + vesting_cliff_offset + 200 ) ;
1795+ context. block_timestamp = ts_termination_day;
1796+ testing_env ! ( context. clone( ) ) ;
1797+
1798+ assert_eq ! (
1799+ contract. get_vesting_information( ) ,
1800+ VestingInformation :: VestingHash (
1801+ VestingScheduleWithSalt {
1802+ vesting_schedule: vesting_schedule. clone( ) ,
1803+ salt: SALT . to_vec( ) . into( ) ,
1804+ }
1805+ . hash( )
1806+ . into( )
1807+ )
1808+ ) ;
1809+
1810+ // Some tokens are vested
1811+ let vesting_nanos_passed = ( ts_termination_day - ts_vesting_started) as u128 ;
1812+ let expected_vested_amount_at_termination_day = lockup_amount / vesting_nanos_total * vesting_nanos_passed;
1813+ let locked_vested_amount_at_termination_day = contract
1814+ . get_locked_vested_amount ( vesting_schedule. clone ( ) )
1815+ . 0 ;
1816+
1817+ assert_almost_eq_with_max_delta ( expected_vested_amount_at_termination_day, locked_vested_amount_at_termination_day, to_yocto ( 1 ) ) ;
1818+ assert_almost_eq_with_max_delta ( to_yocto ( 386986 ) , locked_vested_amount_at_termination_day, to_yocto ( 1 ) ) ;
1819+
1820+ let expected_unvested_amount_at_termination_day = lockup_amount - expected_vested_amount_at_termination_day;
1821+ let unvested_amount_at_termination_day =
1822+ contract. get_unvested_amount ( vesting_schedule. clone ( ) ) . 0 ;
1823+ assert_almost_eq_with_max_delta ( expected_unvested_amount_at_termination_day, unvested_amount_at_termination_day, to_yocto ( 1 ) ) ;
1824+ assert_almost_eq_with_max_delta ( to_yocto ( 613014 ) , unvested_amount_at_termination_day, to_yocto ( 1 ) ) ;
1825+
1826+ // But tokens are still locked due to lockup
1827+ assert_eq ! ( contract. get_owners_balance( ) . 0 , 0 ) ;
1828+ assert_eq ! ( contract. get_liquid_owners_balance( ) . 0 , 0 ) ;
1829+ assert_eq ! ( contract. get_locked_amount( ) . 0 , lockup_amount) ;
1830+
1831+ // Terminate the vesting
1832+ context. predecessor_account_id = account_foundation ( ) ;
1833+ context. signer_account_pk = public_key ( 3 ) . into ( ) ;
1834+ context. is_view = false ;
1835+ testing_env ! ( context. clone( ) ) ;
1836+ contract. terminate_vesting ( Some ( VestingScheduleWithSalt {
1837+ vesting_schedule : vesting_schedule. clone ( ) ,
1838+ salt : SALT . to_vec ( ) . into ( ) ,
1839+ } ) ) ;
1840+
1841+ context. is_view = true ;
1842+ testing_env ! ( context. clone( ) ) ;
1843+ assert_eq ! (
1844+ contract. get_vesting_information( ) ,
1845+ VestingInformation :: Terminating ( TerminationInformation {
1846+ unvested_amount: unvested_amount_at_termination_day. into( ) ,
1847+ status: TerminationStatus :: ReadyToWithdraw ,
1848+ } )
1849+ ) ;
1850+
1851+
1852+ // *** day 801: 1 day after termination ***
1853+ let ts_1_day_after_termination = to_ts ( GENESIS_TIME_IN_DAYS + vesting_cliff_offset + 201 ) ;
1854+ context. block_timestamp = ts_1_day_after_termination;
1855+ testing_env ! ( context. clone( ) ) ;
1856+
1857+ // All the tokens are still locked
1858+ assert_eq ! ( contract. get_owners_balance( ) . 0 , 0 ) ;
1859+ assert_eq ! ( contract. get_liquid_owners_balance( ) . 0 , 0 ) ;
1860+ assert_eq ! ( contract. get_locked_amount( ) . 0 , lockup_amount) ;
1861+
1862+ // Nothing new is vested since termination
1863+ let unvested_amount_1_day_after_termination =
1864+ contract. get_unvested_amount ( vesting_schedule. clone ( ) ) . 0 ;
1865+ assert_eq ! ( unvested_amount_1_day_after_termination, unvested_amount_at_termination_day) ;
1866+
1867+
1868+ // *** day 864: 1 day before lockup cliff is passed ***
1869+ let ts_day_before_lockup_cliff = to_ts ( GENESIS_TIME_IN_DAYS + YEAR - 1 ) ;
1870+ context. block_timestamp = ts_day_before_lockup_cliff;
1871+ testing_env ! ( context. clone( ) ) ;
1872+
1873+ // All the tokens are still locked
1874+ assert_eq ! ( contract. get_owners_balance( ) . 0 , 0 ) ;
1875+ assert_eq ! ( contract. get_liquid_owners_balance( ) . 0 , 0 ) ;
1876+ assert_eq ! ( contract. get_locked_amount( ) . 0 , lockup_amount) ;
1877+
1878+ // Nothing new is vested since termination
1879+ let unvested_amount_day_before_lockup_cliff =
1880+ contract. get_unvested_amount ( vesting_schedule. clone ( ) ) . 0 ;
1881+ assert_eq ! ( unvested_amount_day_before_lockup_cliff, unvested_amount_at_termination_day) ;
1882+
1883+
1884+ // *** day 865: day of lockup cliff ***
1885+ let ts_day_of_lockup_cliff = to_ts ( GENESIS_TIME_IN_DAYS + YEAR ) ;
1886+ context. block_timestamp = ts_day_of_lockup_cliff;
1887+ testing_env ! ( context. clone( ) ) ;
1888+
1889+ let unlocked_amount_day_of_lockup_cliff = contract. get_liquid_owners_balance ( ) . 0 ;
1890+ let expected_unlocked_amount_day_of_lockup_cliff = lockup_amount / ( contract. lockup_information . release_duration . unwrap ( ) as u128 ) * ( to_nanos ( YEAR ) as u128 ) ;
1891+ assert_almost_eq_with_max_delta ( expected_unlocked_amount_day_of_lockup_cliff, unlocked_amount_day_of_lockup_cliff, to_yocto ( 1 ) ) ;
1892+
1893+
1894+ // *** day 1064: 1 day before unlock stopped ***
1895+ let ts_1_day_before_unlock_stop = to_ts ( GENESIS_TIME_IN_DAYS + YEAR + 199 ) ;
1896+ context. block_timestamp = ts_1_day_before_unlock_stop;
1897+ testing_env ! ( context. clone( ) ) ;
1898+
1899+ let locked_amount_1_day_before_unlock_stop = contract. get_locked_amount ( ) . 0 ;
1900+
1901+ // *** day 1065, unlock stopped ***
1902+ let ts_day_unlock_stopped = to_ts ( GENESIS_TIME_IN_DAYS + YEAR + 200 ) ;
1903+ context. block_timestamp = ts_day_unlock_stopped;
1904+ testing_env ! ( context. clone( ) ) ;
1905+
1906+ let locked_amount_day_unlock_stopped = contract. get_locked_amount ( ) . 0 ;
1907+ assert ! (
1908+ locked_amount_day_unlock_stopped < locked_amount_1_day_before_unlock_stop,
1909+ "Locked amount should decrease"
1910+ ) ;
1911+ assert_eq ! ( locked_amount_day_unlock_stopped, unvested_amount_at_termination_day) ;
1912+
1913+ // We unlock 684 tokens in one day,
1914+ // it means that our delta (1 token) is small enough to detect any issue
1915+ assert_almost_eq_with_max_delta ( to_yocto ( 684 ) , locked_amount_1_day_before_unlock_stop - locked_amount_day_unlock_stopped, to_yocto ( 1 ) ) ;
1916+
1917+
1918+ // *** day 1066, 1 day after unlock stopped ***
1919+ let ts_1_day_after_unlock_stopped = to_ts ( GENESIS_TIME_IN_DAYS + YEAR + 201 ) ;
1920+ context. block_timestamp = ts_1_day_after_unlock_stopped;
1921+ testing_env ! ( context. clone( ) ) ;
1922+
1923+ assert_eq ! ( contract. get_locked_amount( ) . 0 , unvested_amount_at_termination_day) ;
1924+ }
1925+
16651926 #[ test]
16661927 fn test_termination_with_staking ( ) {
16671928 let lockup_amount = to_yocto ( 1000 ) ;
0 commit comments