diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..5196e21 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,59 @@ +name: Linter + +on: push + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: cargo fmt --all --check + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: cargo-build-${{ hashFiles('Cargo.*') }} + + - run: cargo build + + build-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: cargo-build-release-${{ hashFiles('Cargo.*') }} + + - run: rustup target add wasm32-unknown-unknown + + - run: cargo build --target wasm32-unknown-unknown --release + + check-warnings: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: cargo-warnings-${{ hashFiles('Cargo.*') }} + + - run: cargo build + env: + RUSTFLAGS: "-D warnings" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..2663a66 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +name: Tests + +on: push + +jobs: + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: cargo-unittests-${{ hashFiles('Cargo.*') }} + + - run: cargo test diff --git a/README.md b/README.md index e90a9a9..209f1e0 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,71 @@ - Claiming all account's lockups in a single transaction. - Ability to add new lockups. - Whitelist for the accounts that can create new lockups. + + +# Usage + +### setup variables +```shell +export OWNER_ID=owner_account.testnet +export USER_ID=user_account.testnet +export TOKEN_CONTRACT_ID=wrap.testnet +export LOCKUP_CONTRACT_ID=lockup.$OWNER_ID +``` + +### create subaccount for lockup contract +```shell +near create-account $LOCKUP_CONTRACT_ID --masterAccount $OWNER_ID +``` + +### build +```shell +./build.sh +``` + +### deploy and initialize +```shell +near deploy --accountId $LOCKUP_CONTRACT_ID --wasmFile ./res/ft_lockup.wasm --initFunction new --initArgs '{"token_account_id": "'$TOKEN_CONTRACT_ID'", "deposit_whitelist": ["'$OWNER_ID'"]}' +``` + +### register lockup contract in token contract +```shell +near call $TOKEN_CONTRACT_ID storage_deposit '{"account_id": "'$LOCKUP_CONTRACT_ID'"}' --accountId $OWNER_ID --amount .00125 +``` + +### register owner/user in token contract (if needed) +```shell +near call $TOKEN_CONTRACT_ID storage_deposit '{"account_id": "'$OWNER_ID'"}' --accountId $OWNER_ID --amount .00125 +near call $TOKEN_CONTRACT_ID storage_deposit '{"account_id": "'$USER_ID'"}' --accountId $USER_ID --amount .00125 +``` + +### be sure owner has enough tokens on his wallet. in case of wNEAR use: +```shell +near call $TOKEN_CONTRACT_ID near_deposit '' --accountId $OWNER_ID --amount 10 +``` + +### add 1 wNEAR (24 decimals) with linear lockup for one year +```shell +TIMESTAMP=$(date +%s) +ONE_YEAR_LATER=$((TIMESTAMP+365*24*60*60)) +AMOUNT=1000000000000000000000000 +ONE_YEAR_LINEAR_LOCKUP='{"account_id":"'$USER_ID'","schedule":[{"timestamp":'$TIMESTAMP',"balance":"0"},{"timestamp":'$ONE_YEAR_LATER',"balance":"'$AMOUNT'"}],"claimed_balance":"0"}' +ONE_YEAR_LINEAR_LOCKUP_ESC=$(echo $ONE_YEAR_LINEAR_LOCKUP | perl -pe 's/\"/\\"/g') + +near call $TOKEN_CONTRACT_ID ft_transfer_call '{"receiver_id": "'$LOCKUP_CONTRACT_ID'","amount": "'$AMOUNT'","msg":"'$ONE_YEAR_LINEAR_LOCKUP_ESC'"}' --account-id $OWNER_ID --gas 300000000000000 --amount .000000000000000000000001 +``` + +### check user lockups +```shell +near view $LOCKUP_CONTRACT_ID get_account_lockups '{"account_id": "'$USER_ID'"}' +``` + +### check token balance +```shell +near view $TOKEN_CONTRACT_ID ft_balance_of '{"account_id": "'$USER_ID'"}' +``` + +### claim all user lockups +```shell +near call $LOCKUP_CONTRACT_ID claim '' --account-id $USER_ID --gas 300000000000000 +``` diff --git a/res/ft_lockup.wasm b/res/ft_lockup.wasm index 1884da2..728c5d7 100755 Binary files a/res/ft_lockup.wasm and b/res/ft_lockup.wasm differ diff --git a/src/lib.rs b/src/lib.rs index 96e3ad9..3501ff3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use near_contract_standards::fungible_token::core_impl::ext_fungible_token; use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver; +use near_contract_standards::upgrade::Ownable; use near_sdk::borsh::maybestd::collections::HashSet; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::collections::{LookupMap, UnorderedSet, Vector}; @@ -15,6 +16,7 @@ pub mod callbacks; pub mod ft_token_receiver; pub mod internal; pub mod lockup; +pub mod migrate; pub mod schedule; pub mod termination; pub mod util; @@ -66,6 +68,10 @@ pub struct Contract { /// Account IDs that can create new lockups. pub deposit_whitelist: UnorderedSet, + /// Account IDs that can't claim for some reason + pub blacklist: UnorderedSet, + + pub owner_id: AccountId, } #[derive(BorshStorageKey, BorshSerialize)] @@ -73,6 +79,7 @@ pub(crate) enum StorageKey { Lockups, AccountLockups, DepositWhitelist, + Blacklist, } #[near_bindgen] @@ -86,6 +93,8 @@ impl Contract { account_lockups: LookupMap::new(StorageKey::AccountLockups), token_account_id: token_account_id.into(), deposit_whitelist: deposit_whitelist_set, + blacklist: UnorderedSet::new(StorageKey::Blacklist), + owner_id: env::predecessor_account_id(), } } @@ -93,6 +102,10 @@ impl Contract { let account_id = env::predecessor_account_id(); let lockups = self.internal_get_account_lockups(&account_id); + if self.blacklist.contains(&account_id) { + panic!("Your wallet is facing issues with the tokens claim. To claim your tokens contact us via hq@pembrock.finance"); + } + if lockups.is_empty() { return PromiseOrValue::Value(0.into()); } @@ -187,4 +200,18 @@ impl Contract { self.assert_deposit_whitelist(&env::predecessor_account_id()); self.deposit_whitelist.remove(account_id.as_ref()); } + + #[payable] + pub fn add_to_blacklist(&mut self, account_id: ValidAccountId) { + assert_one_yocto(); + self.assert_owner(); + self.blacklist.insert(account_id.as_ref()); + } + + #[payable] + pub fn remove_to_blacklist(&mut self, account_id: ValidAccountId) { + assert_one_yocto(); + self.assert_owner(); + self.blacklist.remove(account_id.as_ref()); + } } diff --git a/src/migrate.rs b/src/migrate.rs new file mode 100644 index 0000000..a6d9c52 --- /dev/null +++ b/src/migrate.rs @@ -0,0 +1,41 @@ +use crate::*; +use near_contract_standards::upgrade::Ownable; + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct OldContract { + pub token_account_id: TokenAccountId, + pub lockups: Vector, + pub account_lockups: LookupMap>, + pub deposit_whitelist: UnorderedSet, +} + +#[near_bindgen] +impl Ownable for Contract { + fn get_owner(&self) -> AccountId { + self.owner_id.clone() + } + + fn set_owner(&mut self, owner: AccountId) { + self.assert_owner(); + self.owner_id = owner; + } +} + +#[near_bindgen] +impl Contract { + /// Migration function for contract upgrade + #[init(ignore_state)] + #[private] + pub fn migrate(owner_id: Option) -> Self { + let contract: OldContract = env::state_read().unwrap_or_else(|| panic!("Not initialized")); + + Self { + token_account_id: contract.token_account_id, + lockups: contract.lockups, + account_lockups: contract.account_lockups, + deposit_whitelist: contract.deposit_whitelist, + blacklist: UnorderedSet::new(StorageKey::Blacklist), + owner_id: owner_id.unwrap_or_else(|| env::predecessor_account_id()), + } + } +} diff --git a/src/view.rs b/src/view.rs index b42ee06..57ea869 100644 --- a/src/view.rs +++ b/src/view.rs @@ -103,4 +103,8 @@ impl Contract { schedule.assert_valid_termination_schedule(&termination_schedule); } } + + pub fn get_blacklist(&self) -> Vec { + self.blacklist.to_vec() + } }