-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8f46e1f
commit bdd6592
Showing
8 changed files
with
583 additions
and
16 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
use std::time::Duration; | ||
|
||
use anyhow::Context; | ||
|
||
use near_jsonrpc_client::{methods, JsonRpcClient}; | ||
use near_jsonrpc_primitives::types::query::QueryResponseKind; | ||
use near_primitives::hash::CryptoHash; | ||
use near_primitives::types::{AccountId, BlockHeight, BlockId, BlockReference}; | ||
use near_primitives::views::QueryRequest; | ||
use near_sdk::borsh::BorshDeserialize; | ||
use near_sdk::json_types::{U128, U64}; | ||
use tracing::info; | ||
|
||
use super::lockup_types::{ | ||
LockupContract, TransfersInformation, VestingInformation, VestingSchedule, WrappedBalance, U256, | ||
}; | ||
|
||
// The timestamp (nanos) when transfers were enabled in the Mainnet after community voting | ||
// Tuesday, 13 October 2020 18:38:58.293 | ||
pub(super) const TRANSFERS_ENABLED: Duration = Duration::from_nanos(1602614338293769340); | ||
const CIRCULATING_SUPPLY: &str = "circulating_supply"; | ||
|
||
pub(crate) async fn get_lockup_contract_state( | ||
rpc_client: &JsonRpcClient, | ||
account_id: &AccountId, | ||
block_height: &BlockHeight, | ||
) -> anyhow::Result<LockupContract> { | ||
let block_reference = BlockReference::BlockId(BlockId::Height(*block_height)); | ||
let request = QueryRequest::ViewState { | ||
account_id: account_id.clone(), | ||
prefix: vec![].into(), | ||
include_proof: false, | ||
}; | ||
let query = methods::query::RpcQueryRequest { | ||
block_reference, | ||
request, | ||
}; | ||
|
||
let state_response = rpc_client.call(query).await.with_context(|| { | ||
format!( | ||
"Failed to deliver ViewState for lockup contract {}, block_height {}", | ||
account_id, block_height | ||
) | ||
})?; | ||
|
||
let view_state_result = match state_response.kind { | ||
QueryResponseKind::ViewState(state) => state, | ||
_ => { | ||
anyhow::bail!( | ||
"Failed to extract ViewState response for lockup contract {}, block_height {}", | ||
account_id, | ||
block_height | ||
) | ||
} | ||
}; | ||
|
||
let view_state = view_state_result.values.get(0).with_context(|| { | ||
format!( | ||
"Failed to find encoded lockup contract for {}, block_height {}", | ||
account_id, block_height | ||
) | ||
})?; | ||
|
||
let mut state = LockupContract::try_from_slice(&view_state.value) | ||
.with_context(|| format!("Failed to construct LockupContract for {}", account_id))?; | ||
|
||
// If owner of the lockup account didn't call the | ||
// `check_transfers_vote` contract method we won't be able to | ||
// get proper information based on timestamp, that's why we inject | ||
// the `transfer_timestamp` which is phase2 timestamp | ||
state.lockup_information.transfers_information = TransfersInformation::TransfersEnabled { | ||
transfers_timestamp: U64(TRANSFERS_ENABLED.as_nanos() as u64), | ||
}; | ||
Ok(state) | ||
} | ||
|
||
// The lockup contract implementation had a bug that affected lockup start date. | ||
// https://github.com/near/core-contracts/pull/136 | ||
// For each contract, we should choose the logic based on the binary version of the contract | ||
pub(crate) fn is_bug_inside_contract(code_hash: &CryptoHash, account_id: &AccountId) -> bool { | ||
match &*code_hash.to_string() { | ||
// The first implementation, with the bug | ||
"3kVY9qcVRoW3B5498SMX6R3rtSLiCdmBzKs7zcnzDJ7Q" => true, | ||
// We have 6 lockups created at 6th of April 2021, assume it's buggy | ||
"DiC9bKCqUHqoYqUXovAnqugiuntHWnM3cAc7KrgaHTu" => true, | ||
// Another 5 lockups created in May/June 2021, assume they are OK | ||
"Cw7bnyp4B6ypwvgZuMmJtY6rHsxP2D4PC8deqeJ3HP7D" => false, | ||
// Most recent contracts | ||
"4Pfw2RU6e35dUsHQQoFYfwX8KFFvSRNwMSNLXuSFHXrC" => false, | ||
"3skHaUtj85RPdUZwx6M4Jp4PfC9qJHqnsyuWLtuq2xBT" => false, | ||
_ => { | ||
info!( | ||
target: CIRCULATING_SUPPLY, | ||
"Assuming contract {} for account {} is not buggy", code_hash, account_id | ||
); | ||
false | ||
} | ||
} | ||
} | ||
|
||
// This is almost a copy of https://github.com/near/core-contracts/blob/master/lockup/src/getters.rs#L64 | ||
impl LockupContract { | ||
/// Returns the amount of tokens that are locked in the account due to lockup or vesting. | ||
pub fn get_locked_amount(&self, timestamp: u64, has_bug: bool) -> WrappedBalance { | ||
let lockup_amount = self.lockup_information.lockup_amount; | ||
if let TransfersInformation::TransfersEnabled { | ||
transfers_timestamp, | ||
} = &self.lockup_information.transfers_information | ||
{ | ||
let lockup_timestamp = std::cmp::max( | ||
transfers_timestamp | ||
.0 | ||
.saturating_add(self.lockup_information.lockup_duration), | ||
self.lockup_information.lockup_timestamp.unwrap_or(0), | ||
); | ||
let block_timestamp = timestamp; | ||
if lockup_timestamp <= block_timestamp { | ||
let unreleased_amount = | ||
if let Some(release_duration) = self.lockup_information.release_duration { | ||
let start_lockup = if has_bug { | ||
transfers_timestamp.0 | ||
} else { | ||
lockup_timestamp | ||
}; | ||
let end_timestamp = start_lockup.saturating_add(release_duration); | ||
if block_timestamp >= end_timestamp { | ||
// Everything is released | ||
0 | ||
} else { | ||
let time_left = U256::from(end_timestamp - block_timestamp); | ||
let unreleased_amount = U256::from(lockup_amount) * time_left | ||
/ U256::from(release_duration); | ||
// The unreleased amount can't be larger than lockup_amount because the | ||
// time_left is smaller than total_time. | ||
unreleased_amount.as_u128() | ||
} | ||
} else { | ||
0 | ||
}; | ||
|
||
let unvested_amount = match &self.vesting_information { | ||
VestingInformation::VestingSchedule(vs) => { | ||
self.get_unvested_amount(vs.clone(), block_timestamp) | ||
} | ||
VestingInformation::Terminating(terminating) => terminating.unvested_amount, | ||
// Vesting is private, so we can assume the vesting started before lockup date. | ||
_ => U128(0), | ||
}; | ||
return std::cmp::max( | ||
unreleased_amount | ||
.saturating_sub(self.lockup_information.termination_withdrawn_tokens), | ||
unvested_amount.0, | ||
) | ||
.into(); | ||
} | ||
} | ||
// The entire balance is still locked before the lockup timestamp. | ||
(lockup_amount - self.lockup_information.termination_withdrawn_tokens).into() | ||
} | ||
|
||
/// Returns the amount of tokens that are locked in this account due to vesting schedule. | ||
/// Takes raw vesting schedule, in case the internal vesting schedule is private. | ||
pub fn get_unvested_amount( | ||
&self, | ||
vesting_schedule: VestingSchedule, | ||
block_timestamp: u64, | ||
) -> WrappedBalance { | ||
let lockup_amount = self.lockup_information.lockup_amount; | ||
match &self.vesting_information { | ||
VestingInformation::Terminating(termination_information) => { | ||
termination_information.unvested_amount | ||
} | ||
VestingInformation::None => U128::from(0), | ||
_ => { | ||
if block_timestamp < vesting_schedule.cliff_timestamp.0 { | ||
// Before the cliff, nothing is vested | ||
lockup_amount.into() | ||
} else if block_timestamp >= vesting_schedule.end_timestamp.0 { | ||
// After the end, everything is vested | ||
0.into() | ||
} else { | ||
// cannot overflow since block_timestamp < vesting_schedule.end_timestamp | ||
let time_left = U256::from(vesting_schedule.end_timestamp.0 - block_timestamp); | ||
// The total time is positive. Checked at the contract initialization. | ||
let total_time = U256::from( | ||
vesting_schedule.end_timestamp.0 - vesting_schedule.start_timestamp.0, | ||
); | ||
let unvested_amount = U256::from(lockup_amount) * time_left / total_time; | ||
// The unvested amount can't be larger than lockup_amount because the | ||
// time_left is smaller than total_time. | ||
unvested_amount.as_u128().into() | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.