Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -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"
19 changes: 19 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Binary file modified res/ft_lockup.wasm
Binary file not shown.
27 changes: 27 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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;
Expand Down Expand Up @@ -66,13 +68,18 @@ pub struct Contract {

/// Account IDs that can create new lockups.
pub deposit_whitelist: UnorderedSet<AccountId>,
/// Account IDs that can't claim for some reason
pub blacklist: UnorderedSet<AccountId>,

pub owner_id: AccountId,
}

#[derive(BorshStorageKey, BorshSerialize)]
pub(crate) enum StorageKey {
Lockups,
AccountLockups,
DepositWhitelist,
Blacklist,
}

#[near_bindgen]
Expand All @@ -86,13 +93,19 @@ 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(),
}
}

pub fn claim(&mut self) -> PromiseOrValue<WrappedBalance> {
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());
}
Expand Down Expand Up @@ -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());
}
}
41 changes: 41 additions & 0 deletions src/migrate.rs
Original file line number Diff line number Diff line change
@@ -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<Lockup>,
pub account_lockups: LookupMap<AccountId, HashSet<LockupIndex>>,
pub deposit_whitelist: UnorderedSet<AccountId>,
}

#[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<AccountId>) -> 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()),
}
}
}
4 changes: 4 additions & 0 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,8 @@ impl Contract {
schedule.assert_valid_termination_schedule(&termination_schedule);
}
}

pub fn get_blacklist(&self) -> Vec<AccountId> {
self.blacklist.to_vec()
}
}