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

Integration tests for example project structure #145

Merged
merged 11 commits into from
Aug 9, 2022
1 change: 1 addition & 0 deletions example_project_structure/contracts/loan/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub mod loan {
String::from("LoanContract NFT").into_bytes(),
String::from("L-NFT").into_bytes(),
);
instance._init_with_owner(instance.env().caller());
})
}

Expand Down
4 changes: 4 additions & 0 deletions example_project_structure/contracts/stable_coin/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod token {
use lending_project::traits::stable_coin::*;
use openbrush::{
contracts::psp22::extensions::metadata::*,
contracts::psp22::extensions::mintable::*,
traits::Storage,
};

Expand All @@ -28,6 +29,9 @@ pub mod token {
/// Implement PSP22Metadata Trait for our coin
impl PSP22Metadata for StableCoinContract {}

/// implement PSP22Mintable Trait for our coin
impl PSP22Mintable for StableCoinContract {}

// It forces the compiler to check that you implemented all super traits
impl StableCoin for StableCoinContract {}

Expand Down
18 changes: 4 additions & 14 deletions example_project_structure/impls/lending/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ use openbrush::{
},
traits::{
AccountId,
AccountIdExt,
Balance,
Hash,
ZERO_ADDRESS,
},
};

Expand Down Expand Up @@ -82,15 +80,11 @@ pub fn get_reserve_asset<T>(instance: &T, asset_address: &AccountId) -> Result<A
where
T: Storage<Data>,
{
let reserve_asset = instance
instance
.data()
.asset_shares
.get(&asset_address)
.unwrap_or(ZERO_ADDRESS.into());
if reserve_asset.is_zero() {
return Err(LendingError::AssetNotSupported)
}
Ok(reserve_asset)
.ok_or(LendingError::AssetNotSupported)
}

/// internal function which will return the address of asset
Expand All @@ -99,13 +93,9 @@ pub fn get_asset_from_shares<T>(instance: &T, shares_address: &AccountId) -> Res
where
T: Storage<Data>,
{
let token = instance
instance
.data()
.shares_asset
.get(shares_address)
.unwrap_or(ZERO_ADDRESS.into());
if token.is_zero() {
return Err(LendingError::AssetNotSupported)
}
Ok(token)
.ok_or(LendingError::AssetNotSupported)
}
10 changes: 9 additions & 1 deletion example_project_structure/impls/lending/lending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ impl<T: Storage<data::Data> + Storage<pausable::Data>> Lending for T {
Ok(PSP22Ref::total_supply(&mapped_asset))
}

default fn get_asset_shares(&self, asset_address: AccountId) -> Result<AccountId, LendingError> {
self
.data::<data::Data>()
.asset_shares
.get(&asset_address)
.ok_or(LendingError::AssetNotSupported)
}

default fn is_accepted_lending(&self, asset_address: AccountId) -> bool {
!self
.data::<data::Data>()
Expand Down Expand Up @@ -246,7 +254,7 @@ impl<T: Storage<data::Data> + Storage<pausable::Data>> Lending for T {
SharesRef::mint(
&reserve_asset,
contract,
to_repay - repay_amount - loan_info.borrow_amount,
to_repay - repay_amount,
)?;
LoanRef::update_loan(
&loan_account,
Expand Down
7 changes: 6 additions & 1 deletion example_project_structure/traits/lending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,16 @@ pub trait Lending {
#[ink(message)]
fn total_shares(&self, asset_address: AccountId) -> Result<Balance, LendingError>;

/// This function will return the address of shares
/// which is bound to `asset_address` asset token
#[ink(message)]
fn get_asset_shares(&self, asset_address: AccountId) -> Result<AccountId, LendingError>;

/// This function will return true if the asset is accepted by the contract
#[ink(message)]
fn is_accepted_lending(&self, asset_address: AccountId) -> bool;

/// This function will return true if the asset is accepted by the contract
/// This function will return true if the asset is accepted for using as collateral
#[ink(message)]
fn is_accepted_collateral(&self, asset_address: AccountId) -> bool;

Expand Down
5 changes: 3 additions & 2 deletions example_project_structure/traits/stable_coin.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use openbrush::contracts::traits::psp22::{
extensions::metadata::*,
extensions::mintable::*,
*,
};

#[openbrush::wrapper]
pub type StableCoinRef = dyn PSP22 + PSP22Metadata;
pub type StableCoinRef = dyn PSP22 + PSP22Metadata + PSP22Mintable;

#[openbrush::trait_definition]
pub trait StableCoin: PSP22 + PSP22Metadata {}
pub trait StableCoin: PSP22 + PSP22Metadata + PSP22Mintable {}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"prettier": "^2.7.1",
"redspot": "^0.13.5",
"ts-node": "^10.8.0"
},
Expand Down
2 changes: 1 addition & 1 deletion redspot.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ export default {
mocha: {
timeout: 60000
}
} as RedspotUserConfig
} as RedspotUserConfig
3 changes: 2 additions & 1 deletion tests/e2e/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export const consts = {

export const Roles = {
DefaultAdminRole: 0,
Minter: 0xfd9ab216
Minter: 0xfd9ab216,
Manager: 0x73a5ca6d,
}
218 changes: 202 additions & 16 deletions tests/e2e/example_project_structure/lending_contract.tests.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,223 @@
import { expect, setupContract, fromSigner } from '../helpers'
import BN from 'bn.js'
import { Roles } from '../constants'

interface Result {
ok
}

describe('LENDING_CONTRACT', () => {
async function setup() {
const stable_coin = await setupContract('stable_coin_contract', 'new', 'Green Coin', 'GC')
const loan = await (await setupContract('loan_contract', 'new' )).abi
const stablecoin = await setupContract('stable_coin_contract', 'new', 'Stable Coin', 'SC')
const loan = await (await setupContract('loan_contract', 'new')).abi
const shares = await (await setupContract('shares_contract', 'new', '', '')).abi
const lending = await setupContract('lending_contract', 'new', shares.source.hash, loan.source.hash)
const greencoin = await setupContract('stable_coin_contract', 'new', 'Green Coin', 'GC')

return { lending: lending.contract, stablecoin: stablecoin.contract, alice: stablecoin.defaultSigner.address, greencoin: greencoin.contract }
}

return { lending, stable_coin, alice: stable_coin.defaultSigner }
function result(s: string | undefined) {
const result: Result = s != null ? JSON.parse(s) : null
return result
}

async function borrow(lendingContract, borrowedToken, collateralToken, user, approveAmount, collateralAmount, price) {
// collateralToken approves amount of tokens for lending contact
await expect(collateralToken.tx.approve(lendingContract.address, approveAmount)).to.eventually.be.fulfilled
xgreenx marked this conversation as resolved.
Show resolved Hide resolved
// Grant user the manager role
await expect(lendingContract.tx.grantRole(Roles.Manager, user)).to.eventually.be.fulfilled
// Allow collateral
await expect(fromSigner(lendingContract, user).tx.allowCollateral(collateralToken.address)).to.eventually.be.fulfilled

// user approves amount of tokens for lending contact
await expect(fromSigner(borrowedToken, user).tx.approve(lendingContract.address, approveAmount)).to.eventually.be.fulfilled
// Allow new asset
await expect(lendingContract.tx.allowAsset(borrowedToken.address)).to.eventually.be.fulfilled
// Alice lends tokens into lending
await expect(fromSigner(lendingContract, user).tx.lendAssets(borrowedToken.address, approveAmount)).to.eventually.be.fulfilled

// user approves amount of tokens for lending contact
await expect(fromSigner(borrowedToken, user).tx.approve(lendingContract.address, approveAmount)).to.eventually.be.fulfilled
// Set the price of collateralToken for borrowedToken
await expect(fromSigner(lendingContract, user).tx.setAssetPrice(collateralToken.address, borrowedToken.address, price)).to.eventually.be.fulfilled

const alice_balance = (await collateralToken.query.balanceOf(user)).output
// user borrow borrowedToken
await expect(fromSigner(lendingContract, user).tx.borrowAssets(borrowedToken.address, collateralToken.address, collateralAmount)).to.eventually.be
.fulfilled
// @ts-ignore
await expect(collateralToken.query.balanceOf(user)).to.have.output(alice_balance.sub(new BN(collateralAmount)))
}

it('LENDING CONTRACT - accept lending', async () => {
const { lending, stablecoin } = await setup()

// Arrange - Stablecoin is not accepted for lending
await expect(lending.query.isAcceptedLending(stablecoin.address)).to.have.output(false)
// Act - Allow stablecoin for lending
await expect(lending.tx.allowAsset(stablecoin.address)).to.eventually.be.fulfilled
// Assert - Stablecoin is accepted
await expect(lending.query.isAcceptedLending(stablecoin.address)).to.have.output(true)
})

it('LENDING CONTRACT - disallow lending', async () => {
const { lending, stablecoin, alice } = await setup()

// Arrange - Stablecoin is accepted for lending
await expect(lending.tx.allowAsset(stablecoin.address)).to.eventually.be.fulfilled
await expect(lending.query.isAcceptedLending(stablecoin.address)).to.have.output(true)

// Act - Grant Alice the manager role
await expect(lending.tx.grantRole(Roles.Manager, alice)).to.eventually.be.fulfilled
// Act - Disallow stablecoin for lending
await expect(fromSigner(lending, alice).tx.disallowLending(stablecoin.address)).to.eventually.be.fulfilled

// Assert - Stablecoin is not accepted for lending
await expect(lending.query.isAcceptedLending(stablecoin.address)).to.have.output(false)
})

it('LENDING CONTRACT - allow collateral', async () => {
const { lending, stablecoin, alice } = await setup()

// Arrange - Stablecoin is not accepted for collateral
await expect(lending.query.isAcceptedCollateral(stablecoin.address)).to.have.output(false)

// Act - Grant Alice the manager role
await expect(lending.tx.grantRole(Roles.Manager, alice)).to.eventually.be.fulfilled
// Act - Allow collateral for stablecoin
await expect(fromSigner(lending, alice).tx.allowCollateral(stablecoin.address)).to.eventually.be.fulfilled

// Assert - Stablecoin is accepted for collateral
await expect(lending.query.isAcceptedCollateral(stablecoin.address)).to.have.output(true)
})

it('LENDING CONTRACT - disallow collateral', async () => {
const { lending, stablecoin, alice } = await setup()

// Act - Grant Alice the manager role
await expect(lending.tx.grantRole(Roles.Manager, alice)).to.eventually.be.fulfilled
// Act - Allow collateral for stablecoin
await expect(fromSigner(lending, alice).tx.allowCollateral(stablecoin.address)).to.eventually.be.fulfilled
await expect(lending.query.isAcceptedCollateral(stablecoin.address)).to.have.output(true)
// Act - Disallow collateral for stablecoin
await expect(fromSigner(lending, alice).tx.disallowCollateral(stablecoin.address)).to.eventually.be.fulfilled

// Assert - Stablecoin is not accepted for collateral
await expect(lending.query.isAcceptedCollateral(stablecoin.address)).to.have.output(false)
})

it('LENDING CONTRACT - lend asset', async () => {
const { lending, stable_coin, alice } = await setup()
const { lending, stablecoin, alice } = await setup()

const amount = 100

// Alice balance should be >= than lending `amount`
const alice_balance = (await stable_coin.query.balanceOf(alice.address)).output
// Arrange - Alice balance should be >= than lending amount
const alice_balance = (await stablecoin.query.balanceOf(alice)).output
expect(alice_balance).to.gte(amount)

// Alice approves `amount` for lending contact
await expect(stable_coin.tx.approve(lending.contract.address, amount)).to.eventually.be.fulfilled
// Act - Stablecoin contract approves amount for lending contact
await expect(stablecoin.tx.approve(lending.address, amount)).to.eventually.be.fulfilled

// Allow new asset(stable coin) in the lending
await expect(lending.tx.allowAsset(stable_coin.contract.address)).to.eventually.be.fulfilled
// Act - Allow stablecoin for lending
await expect(lending.tx.allowAsset(stablecoin.address)).to.eventually.be.fulfilled

// Alice lends `amount` tokens into lending
await expect(stable_coin.query.balanceOf(lending.contract.address)).to.have.output(0)
await expect(stable_coin.query.balanceOf(alice.address)).to.have.output(alice_balance)
await expect(fromSigner(lending.contract, alice.address).tx.lendAssets(stable_coin.contract.address, amount)).to.eventually.be.fulfilled
await expect(stable_coin.query.balanceOf(lending.contract.address)).to.have.output(amount)
// Act - Alice lends the amount of stablecoin tokens
await expect(fromSigner(lending, alice).tx.lendAssets(stablecoin.address, amount)).to.eventually.be.fulfilled

// Assert - Lending contract has the amount of stablecoin tokens
await expect(stablecoin.query.balanceOf(lending.address)).to.have.output(amount)
// Assert - Alice balance is changed
// @ts-ignore
await expect(stable_coin.query.balanceOf(alice.address)).to.have.output((alice_balance.sub(new BN(amount))))
await expect(stablecoin.query.balanceOf(alice)).to.have.output(alice_balance.sub(new BN(amount)))
})

it('LENDING CONTRACT - borrow and repay full amount', async () => {
const { lending, stablecoin, greencoin, alice } = await setup()

const amount = 1000
const collateralAmount = 100
const price = 1

await greencoin.tx.mint(alice, new BN('1000000'))

// Act - Alice borrows greencoin
const alice_balance = (await stablecoin.query.balanceOf(alice)).output
await borrow(lending, greencoin, stablecoin, alice, amount, collateralAmount, price)

// Act - Alice repays loan
await expect(fromSigner(lending, alice).tx.repay({ u128: 1 }, collateralAmount)).to.eventually.be.fulfilled

// Assert - Alice received borrowed tokens
// @ts-ignore
await expect(stablecoin.query.balanceOf(alice)).to.have.output(alice_balance)
})

it('LENDING CONTRACT - borrow and repay part of amount', async () => {
const { lending, stablecoin, greencoin, alice } = await setup()

const amount = 1000
const collateralAmount = 100
const price = 2

await greencoin.tx.mint(alice, new BN('1000000'))

// Act - Alice borrows greencoin
const alice_balance = (await stablecoin.query.balanceOf(alice)).output
await borrow(lending, greencoin, stablecoin, alice, amount, collateralAmount, price)

// Act - Calculate half of the amount Alice should repay (borrowed amount = 70% of collateral amount)
const loanAmount = (collateralAmount * 70) / 100
const halfOfLoan = (loanAmount * price) / 2
// Act - Alice repays half of loan
await expect(fromSigner(lending, alice).tx.repay({ u128: 1 }, halfOfLoan)).to.eventually.be.fulfilled

// Assert - Alice received half of collateral tokens
// @ts-ignore
await expect(stablecoin.query.balanceOf(alice)).to.have.output(alice_balance.sub(new BN(collateralAmount / 2 + 1)))
})

it('LENDING CONTRACT - withdraw asset', async () => {
const { lending, stablecoin, alice } = await setup()

const amount = 100

// Act - Stablecoin contract approves amount for lending contact
await expect(stablecoin.tx.approve(lending.address, amount)).to.eventually.be.fulfilled
// Act - Allow stablecoin for lending
await expect(lending.tx.allowAsset(stablecoin.address)).to.eventually.be.fulfilled
// Act - Alice lends the amount of stablecoin tokens
await expect(fromSigner(lending, alice).tx.lendAssets(stablecoin.address, amount)).to.eventually.be.fulfilled
// Act - Alice withdraws stablecoin token
const alice_balance = (await stablecoin.query.balanceOf(alice)).output
const sharesAddress = result((await lending.query.getAssetShares(stablecoin.address)).output?.toString()).ok
const withdrawAmount = 1
await expect(fromSigner(lending, alice).tx.withdrawAsset(sharesAddress, withdrawAmount)).to.eventually.be.fulfilled

// Assert - Balance of lending contract decreased at withdraw amount
await expect(stablecoin.query.balanceOf(lending.address)).to.have.output(amount - withdrawAmount)
// Assert - Alice balance increased at withdraw amount
// @ts-ignore
await expect(stablecoin.query.balanceOf(alice)).to.have.output(alice_balance.add(new BN(withdrawAmount)))
})

it('LENDING CONTRACT - liquidate loan', async () => {
const { lending, stablecoin, greencoin, alice } = await setup()

const amount = 1000
const collateralAmount = 100
const price = 10

await greencoin.tx.mint(alice, new BN('1000000'))

// Act - Alice borrows greencoin
await borrow(lending, greencoin, stablecoin, alice, amount, collateralAmount, price)
await expect(fromSigner(lending, alice).tx.liquidateLoan({ u8: 1 })).to.eventually.be.rejected

// Act - Decrease greencoin price, now greencoin price < liquidation price
await expect(fromSigner(lending, alice).tx.setAssetPrice(stablecoin.address, greencoin.address, 1)).to.eventually.be.fulfilled

// Assert - Alice can liquidate loan
await expect(fromSigner(lending, alice).tx.liquidateLoan({ u128: 1 })).to.eventually.be.fulfilled
})
})