Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2409 fetch stake balance from contract #2412

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
"reselect": "4.0.0",
"sanitize-html": "1.20.0",
"scrypt": "6.0.3",
"selfkey-ui": "https://github.com/SelfKeyFoundation/selfkey-ui.git#e510a0b3e2c69b33a5b48dee0bf2b1ab1052bf3f",
"selfkey-ui": "https://github.com/SelfKeyFoundation/selfkey-ui.git#2101efc22f7139b62fb745568fd87b9b21ff5e11",
"selfkey.js": "1.0.24",
"serialize-error": "3.0.0",
"snyk": "1.235.0",
Expand Down Expand Up @@ -255,7 +255,7 @@
"react": "16.13.1",
"react-dom": "16.13.1",
"react-jsonschema-form-material-theme": "https://github.com/SelfKeyFoundation/react-jsonschema-form-material-theme.git#e8356d2037b527f7b0a8433f20c476b6e501d70e",
"selfkey-ui": "https://github.com/SelfKeyFoundation/selfkey-ui.git#2ed1702ce863d1f077639ea3893beecda78915d2",
"selfkey-ui": "https://github.com/SelfKeyFoundation/selfkey-ui.git#2101efc22f7139b62fb745568fd87b9b21ff5e11",
"@material-ui/styles": "4.9.6"
},
"migrations": {
Expand Down
97 changes: 97 additions & 0 deletions src/common/staking/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { getGlobalContext } from 'common/context';
import BN from 'bignumber.js';
import moment from 'moment';
import { createAliasedAction } from 'electron-redux';
import { getWallet } from '../wallet/selectors';

export const initialState = {
stakeBalance: '0',
rewardBalance: '0',
timelockStart: null,
timelockEnd: null,
initialized: false,
minStakePeriod: null,
minStakeAmount: '0'
};
export const stakingTypes = {
STAKING_SET: 'staking/SET',
STAKING_FETCH: 'staking/operations/FETCH'
};
export const stakingActions = {
setStakingAction: payload => ({ type: stakingTypes.STAKING_SET, payload })
};

const operations = {
...stakingActions,
fetchStakeOperation: () => async (dispatch, getState) => {
const wallet = getWallet(getState());
const stake = await getGlobalContext().stakingService.fetchStake(wallet.id);

await dispatch(
stakingActions.setStakingAction({
...stake,
initialized: true
})
);
}
};

export const stakingOperations = {
...stakingActions,
fetchStakeOperation: createAliasedAction(
stakingTypes.STAKING_FETCH,
operations.fetchStakeOperation
)
};

export const stakingSelectors = {
selectStakingTree(state) {
return state.staking;
},
selectStakingInfo(state) {
const stakingInfo = this.selectStakingTree(state);
const now = moment().utc();
const canStake = stakingInfo.initialized;
const canWithdrawReward = new BN(stakingInfo.rewardBalance).gt(0);
const hasStaked = new BN(stakingInfo.stakeBalance).gt(0);
const endDate = moment.utc(stakingInfo.timelockEnd || 0);
let minStakeDate = null;
let canWithdrawStake = false;

if (!hasStaked && canStake) {
minStakeDate = now.add(stakingInfo.minStakePeriod || 0).valueOf();
}
if (canStake && endDate) {
minStakeDate = moment.max(now, endDate).valueOf();
}

if (hasStaked && endDate.isBefore(now)) {
canWithdrawStake = true;
}

return {
...stakingInfo,
hasStaked,
canStake,
canWithdrawStake,
canWithdrawReward,
minStakeDate
};
}
};

export const stakingReducers = {
setStakeReducer: (state, { payload }) => {
return { ...payload };
}
};

export default (state = initialState, action) => {
switch (action.type) {
case stakingTypes.STAKING_SET:
return stakingReducers.setStakeReducer(state, action);
}
return state;
};

export const testExports = { operations };
74 changes: 74 additions & 0 deletions src/common/staking/staking-duck.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
initialState,
stakingReducers,
stakingActions,
// testExports,
stakingSelectors
} from './index';
import _ from 'lodash';
import sinon from 'sinon';
import { setGlobalContext } from '../context';
import moment from 'moment';

describe('Staking DucK', () => {
let stakingService = {};

let state = {};
// let store = {
// dispatch() {},
// getState() {
// return state;
// }
// };
// const testAction = { test: 'test' };
beforeEach(() => {
sinon.restore();
state = { staking: _.cloneDeep(initialState) };
setGlobalContext({
stakingService: stakingService
});
});
describe('Selectors', () => {
beforeEach(() => {
state = { staking: { ...initialState } };
});

it('selectStakingInfo', () => {
expect(stakingSelectors.selectStakingInfo(state)).toEqual({
...initialState,
hasStaked: false,
canStake: false,
canWithdrawStake: false,
canWithdrawReward: false,
minStakeDate: null
});
});
});
// describe('Operations', () => {});
describe('Reducers', () => {
it('setStakingReducer', () => {
let state = { ...initialState };
const stake = {
stakeBalance: '160000',
rewardBalance: '8000',
timelockStart: moment()
.utc()
.subtract(30, 'days')
.valueOf(),
timelockEnd: moment()
.utc()
.add(30, 'days')
.valueOf(),
canStake: true,
minStakeAmount: '10000'
};
let newState = stakingReducers.setStakeReducer(
state,
stakingActions.setStakingAction(stake)
);
expect(newState).toEqual({
...stake
});
});
});
});
2 changes: 2 additions & 0 deletions src/common/store/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import scheduler from '../scheduler';
import marketplace from '../marketplace';
import tokenSwap from '../token-swap';
import contracts from '../contract';
import staking from '../staking';

export const createReducers = (scope = 'main') => {
let scopedReducers = {};
Expand Down Expand Up @@ -59,6 +60,7 @@ export const createReducers = (scope = 'main') => {
did,
tokenSwap,
contracts,
staking,
...scopedReducers
});
};
Expand Down
4 changes: 4 additions & 0 deletions src/main/blockchain/contracts/contract-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class ContractService {
});
}

findByType(type) {
return Contract.findByType(type);
}

loadContracts() {
return Contract.findAll();
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/blockchain/contracts/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class Contract extends BaseModel {
return this.query().where({ env });
}

static findByType(type) {
return this.query().where({ env, type });
}

static create(data) {
return this.query().insertAndFetch({ ...data, env });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const airtableBaseUrl = CONFIG.airtableBaseUrl;

const CONFIG_URL = `${airtableBaseUrl}${AIRTABLE_NAME}`;

export class StakingService {
export class DepositService {
constructor({ web3Service }) {
this.activeContract = null;
this.deprecatedContracts = [];
Expand Down Expand Up @@ -123,15 +123,15 @@ export class StakingService {
}
async acquireContract() {
let { activeContract, deprecatedContracts } = await this.fetchConfig();
this.activeContract = new StakingContract(
this.activeContract = new DepositContract(
this.web3,
activeContract.address,
activeContract.abi,
!!activeContract.deprecated
);
this.deprecatedContracts = deprecatedContracts.map(
contract =>
new StakingContract(
new DepositContract(
this.web3,
contract.address,
contract.abi,
Expand Down Expand Up @@ -192,7 +192,7 @@ export class EtheriumContract {
}
}

export class StakingContract extends EtheriumContract {
export class DepositContract extends EtheriumContract {
constructor(web3, address, abi, isDeprecated) {
super(web3, address, abi);
this.isDeprecated = isDeprecated;
Expand Down Expand Up @@ -274,4 +274,4 @@ export class SelfKeyTokenContract extends EtheriumContract {
}
}

export default StakingService;
export default DepositService;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import StakingService, {
StakingContract,
import DepositService, {
DepositContract,
SelfKeyTokenContract,
EtheriumContract
} from './staking-service';
} from './deposit-service';
import fetch from 'node-fetch';
import sinon from 'sinon';

Expand Down Expand Up @@ -34,10 +34,10 @@ const remoteConfig = {

const parseContractAbi = contract => ({ ...contract, abi: JSON.parse(contract.abi) });

describe('StackingService', () => {
describe('DepositService', () => {
let service = null;
beforeEach(() => {
service = new StakingService({ web3Service: web3ServiceMock });
service = new DepositService({ web3Service: web3ServiceMock });
fetch.mockResolvedValue({
json() {
return remoteConfig;
Expand Down Expand Up @@ -73,14 +73,14 @@ describe('StackingService', () => {

describe('getStakingInfo', () => {
it('checks deprecatred contracts for stakes', async () => {
sinon.stub(StakingContract.prototype, 'getBalance').resolves(0);
sinon.stub(DepositContract.prototype, 'getBalance').resolves(0);
await service.acquireContract();
await service.getStakingInfo('test', 'test', 'test');
expect(service.activeContract.getBalance.callCount).toBe(3);
});
it('checks gets release date for stakes', async () => {
sinon.stub(StakingContract.prototype, 'getBalance').resolves(100);
sinon.stub(StakingContract.prototype, 'getReleaseDate').resolves(0);
sinon.stub(DepositContract.prototype, 'getBalance').resolves(100);
sinon.stub(DepositContract.prototype, 'getReleaseDate').resolves(0);
await service.acquireContract();
await service.getStakingInfo('test', 'test', 'test');
expect(service.activeContract.getReleaseDate.callCount).toBe(1);
Expand Down Expand Up @@ -198,14 +198,14 @@ describe('Contract', () => {
});
});

describe('StakingContract', () => {
describe('DepositContract', () => {
let contract = null;
const testDepositor = 'test';
const testServiceOwner = 'owner';
const testServiceID = 'testId';

beforeEach(() => {
contract = new StakingContract(
contract = new DepositContract(
web3ServiceMock,
activeContract.address,
activeContract.abi,
Expand Down
52 changes: 52 additions & 0 deletions src/main/blockchain/staking/staking-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import moment from 'moment';
import config from 'common/config';
import { Wallet } from '../../wallet/wallet';
import Token from '../../token/token';
import BN from 'bignumber.js';

export class StakingService {
constructor({ contractService, web3Service, walletTokenService }) {
this.contractService = contractService;
this.web3Service = web3Service;
this.walletTokenService = walletTokenService;
}
async fetchStake(walletId) {
let contracts = await this.contractService.findByType('staking');
contracts = contracts.filter(c => !c.deprecated);
if (!contracts.length) {
return null;
}

const contractInfo = contracts[0];

const contract = new this.web3Service.web3.eth.Contract(
contractInfo.abi,
contractInfo.address
);

const wallet = await Wallet.findById(walletId);
const [token] = await Token.findBySymbol(config.constants.primaryToken);

const keyBalance = await contract.methods
.balances(token.address, wallet.address)
.call({ from: wallet.address });

const timelockEnd = await contract.methods
.timelocks(token.address, wallet.address)
.call({ from: wallet.address });

const timelockStart = moment()
.utc()
.valueOf();

const stakeBalance = new BN(keyBalance).div(new BN(10).pow(token.decimal)).toString();

return {
stakeBalance: stakeBalance,
rewardBalance: '8000',
timelockStart,
timelockEnd,
minStakeAmount: '10000'
};
}
}
Loading