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

Feature/treasury module validation #72

Merged
merged 7 commits into from
Sep 16, 2022
Merged
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
48 changes: 40 additions & 8 deletions contracts/modules/TreasuryModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,16 @@ contract TreasuryModule is
onlyInstanceOperator
{
require(_component.isProduct(feeSpec.componentId), "ERROR:TRS-022:NOT_PRODUCT");


// record original creation timestamp
uint256 originalCreatedAt = _fees[feeSpec.componentId].createdAt;
_fees[feeSpec.componentId] = feeSpec;

// set original creation timestamp if fee spec already existed
if (originalCreatedAt > 0) {
_fees[feeSpec.componentId].createdAt = originalCreatedAt;
}

emit LogTreasuryPremiumFeesSet (
feeSpec.componentId,
feeSpec.fixedFee,
Expand All @@ -192,7 +200,15 @@ contract TreasuryModule is
{
require(_component.isRiskpool(feeSpec.componentId), "ERROR:TRS-023:NOT_RISKPOOL");

// record original creation timestamp
uint256 originalCreatedAt = _fees[feeSpec.componentId].createdAt;
_fees[feeSpec.componentId] = feeSpec;

// set original creation timestamp if fee spec already existed
if (originalCreatedAt > 0) {
_fees[feeSpec.componentId].createdAt = originalCreatedAt;
}

emit LogTreasuryCapitalFeesSet (
feeSpec.componentId,
feeSpec.fixedFee,
Expand Down Expand Up @@ -309,7 +325,11 @@ contract TreasuryModule is

require(
token.balanceOf(riskpoolWalletAddress) >= payout.amount,
"ERROR:TRS-042:RISKPOOL_BALANCE_TOO_SMALL"
"ERROR:TRS-042:RISKPOOL_WALLET_BALANCE_TOO_SMALL"
);
require(
token.allowance(riskpoolWalletAddress, address(this)) >= payout.amount,
"ERROR:TRS-043:PAYOUT_ALLOWANCE_TOO_SMALL"
);

// actual payout to policy holder
Expand All @@ -318,7 +338,7 @@ contract TreasuryModule is
netPayoutAmount = payout.amount;

emit LogTreasuryPayoutTransferred(riskpoolWalletAddress, metadata.owner, payout.amount);
require(success, "ERROR:TRS-043:PAYOUT_TRANSFER_FAILED");
require(success, "ERROR:TRS-044:PAYOUT_TRANSFER_FAILED");

emit LogTreasuryPayoutProcessed(riskpoolId, metadata.owner, payout.amount);
}
Expand All @@ -344,18 +364,21 @@ contract TreasuryModule is
// obtain relevant token for product/riskpool pair
IERC20 token = _componentToken[bundle.riskpoolId];

// calculate and transfer fees
// calculate fees and net capital
feeAmount = _calculateFee(feeSpec, capitalAmount);
netCapitalAmount = capitalAmount - feeAmount;

// check balance and allowance before starting any transfers
require(token.balanceOf(bundleOwner) >= capitalAmount, "ERROR:TRS-052:BALANCE_TOO_SMALL");
require(token.allowance(bundleOwner, address(this)) >= capitalAmount, "ERROR:TRS-053:CAPITAL_TRANSFER_ALLOWANCE_TOO_SMALL");

bool success = TransferHelper.unifiedTransferFrom(token, bundleOwner, _instanceWalletAddress, feeAmount);

emit LogTreasuryFeesTransferred(bundleOwner, _instanceWalletAddress, feeAmount);
require(success, "ERROR:TRS-053:FEE_TRANSFER_FAILED");
require(success, "ERROR:TRS-054:FEE_TRANSFER_FAILED");

// transfer net capital
address riskpoolWallet = getRiskpoolWallet(bundle.riskpoolId);
require(riskpoolWallet != address(0), "ERROR:TRS-054:RISKPOOL_WITHOUT_WALLET");

netCapitalAmount = capitalAmount - feeAmount;
success = TransferHelper.unifiedTransferFrom(token, bundleOwner, riskpoolWallet, netCapitalAmount);

emit LogTreasuryCapitalTransferred(bundleOwner, riskpoolWallet, netCapitalAmount);
Expand Down Expand Up @@ -388,6 +411,15 @@ contract TreasuryModule is
address bundleOwner = _bundle.getOwner(bundleId);
IERC20 token = _componentToken[bundle.riskpoolId];

require(
token.balanceOf(riskpoolWallet) >= amount,
"ERROR:TRS-061:RISKPOOL_WALLET_BALANCE_TOO_SMALL"
);
require(
token.allowance(riskpoolWallet, address(this)) >= amount,
"ERROR:TRS-062:WITHDRAWAL_ALLOWANCE_TOO_SMALL"
);

// TODO consider to introduce withdrawal fees
// ideally symmetrical reusing capital fee spec for riskpool
feeAmount = 0;
Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

from brownie.network import accounts
from brownie.network.account import Account
from brownie.network.state import Chain

from scripts.const import (
GIF_RELEASE,
Expand Down Expand Up @@ -294,6 +295,11 @@ def testCoinSetup(testCoin, owner, customer) -> TestCoin:
testCoin.transfer(customer, 10**6, {'from': owner})
return testCoin

@pytest.fixture(scope="module")
def chain() -> Chain:
return Chain()


def contractFromAddress(contractClass, contractAddress):
return Contract.from_abi(contractClass._name, contractAddress, contractClass.abi)

Expand Down
248 changes: 78 additions & 170 deletions tests/test_treasury_module.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import brownie
import pytest
import time

from brownie.network.account import Account
from brownie.network.state import Chain

from scripts.const import ZERO_ADDRESS
from scripts.instance import GifInstance
Expand Down Expand Up @@ -103,176 +105,6 @@ def test_bundle_creation_with_riskpool_wallet_not_set(
amount,
{'from': bundleOwner})

def test_bundle_creation_allowance_too_small(
instance: GifInstance,
owner: Account,
testCoin,
productOwner: Account,
oracleProvider: Account,
riskpoolKeeper: Account,
capitalOwner: Account,
):
applicationFilter = bytes(0)

(gifProduct, gifRiskpool, gifOracle) = getProductAndRiskpool(
instance,
owner,
testCoin,
productOwner,
oracleProvider,
riskpoolKeeper,
capitalOwner,
True
)

# prepare allowance too small for riskpool funding
safetyFactor = 2
amount = 10000
testCoin.transfer(riskpoolKeeper, safetyFactor * amount, {'from': owner})
testCoin.approve(instance.getTreasury(), 0.9 * amount, {'from': riskpoolKeeper})

# ensures bundle creation fails due to insufficient allowance
with brownie.reverts("ERROR:TRS-055:CAPITAL_TRANSFER_FAILED"):
gifRiskpool.getContract().createBundle(
applicationFilter,
amount,
{'from': riskpoolKeeper})


def test_bundle_withdrawal_allowance_too_small(
instance: GifInstance,
owner: Account,
testCoin,
productOwner: Account,
oracleProvider: Account,
riskpoolKeeper: Account,
capitalOwner: Account,
):
applicationFilter = bytes(0)

# prepare product and riskpool
(gifProduct, gifRiskpool, gifOracle) = getProductAndRiskpool(
instance,
owner,
testCoin,
productOwner,
oracleProvider,
riskpoolKeeper,
capitalOwner,
True
)

# fund bundle
safetyFactor = 2
amount = 10000
testCoin.transfer(riskpoolKeeper, safetyFactor * amount, {'from': owner})
testCoin.approve(instance.getTreasury(), amount, {'from': riskpoolKeeper})
riskpool = gifRiskpool.getContract()

riskpool.createBundle(
applicationFilter,
amount,
{'from': riskpoolKeeper})

bundle = riskpool.getBundle(0)
print(bundle)

(bundleId) = bundle[0]

# check bundle values with expectation
assert bundleId == 1

# close bundle
riskpool.closeBundle(bundleId, {'from': riskpoolKeeper})

# prepare allowance that is too small for bundle withdrawal
testCoin.approve(instance.getTreasury(), 0.9 * amount, {'from': riskpoolKeeper})

# ensures that burning bundle fails during token withdrawal due to insufficient allowance
with brownie.reverts("ERROR:TRS-063:WITHDRAWAL_TRANSFER_FAILED"):
riskpool.burnBundle(
bundleId,
{'from': riskpoolKeeper})


def test_payout_allowance_too_small(
instance: GifInstance,
owner: Account,
testCoin,
productOwner: Account,
oracleProvider: Account,
riskpoolKeeper: Account,
capitalOwner: Account,
customer: Account,
):
applicationFilter = bytes(0)

(gifProduct, gifRiskpool, gifOracle) = getProductAndRiskpool(
instance,
owner,
testCoin,
productOwner,
oracleProvider,
riskpoolKeeper,
capitalOwner,
True
)

# prepare product and riskpool
safetyFactor = 2
amount = 10000
testCoin.transfer(riskpoolKeeper, safetyFactor * amount, {'from': owner})
testCoin.approve(instance.getTreasury(), amount, {'from': riskpoolKeeper})
riskpool = gifRiskpool.getContract()

# fund bundle
riskpool.createBundle(
applicationFilter,
amount,
{'from': riskpoolKeeper})

bundle = riskpool.getBundle(0)
print(bundle)

(bundleId) = bundle[0]

# check bundle values with expectation
assert bundleId == 1

# prepare prolicy application
premium = 100
sumInsured = 1000
product = gifProduct.getContract()
policyController = instance.getPolicy()

policyId = apply_for_policy(instance, owner, product, customer, testCoin, premium, sumInsured)

claimAmount = 800
instanceService = instance.getInstanceService()

# submit a claim for the policy
tx = product.submitClaimWithDeferredResponse(policyId, claimAmount, {'from': customer})
(claimId, requestId) = tx.return_value
claim = instanceService.getClaim(policyId, claimId).dict()
print(claim)

assert claim["state"] == 0 # enum ClaimState {Applied, Confirmed, Declined, Closed}

# confirm the claim
product.confirmClaim(policyId, claimId, claimAmount, {'from': productOwner})

claim = instanceService.getClaim(policyId, claimId).dict()
assert claim["state"] == 1 # enum ClaimState {Applied, Confirmed, Declined, Closed}

# check that it's possible to create payout for claim in confirmed state
payoutAmount = claimAmount

# prepare allowance for payout that is too small
testCoin.approve(instance.getTreasury(), payoutAmount * 0.9, {'from': capitalOwner})

# ensure that the payout fails due to too small allowance
with brownie.reverts("ERROR:TRS-043:PAYOUT_TRANSFER_FAILED"):
product.createPayout(policyId, claimId, payoutAmount, {'from': productOwner})

def test_two_products_different_coin_same_riskpool(
instance: GifInstance,
Expand Down Expand Up @@ -348,6 +180,82 @@ def test_two_products_same_riskpool(
assert gifProduct2.getContract().getId() != gifProduct.getContract().getId()


def test_overwriting_capital_fees(
instance: GifInstance,
instanceOperator: Account,
gifTestProduct,
chain: Chain
):
instanceService = instance.getInstanceService()
instanceOperatorService = instance.getInstanceOperatorService()
treasury = instance.getTreasury()

product = gifTestProduct.getContract()
riskpoolId = gifTestProduct.getRiskpool().getId()

hundredPercent = instanceService.getFeeFractionFullUnit()

existingFeeSpec = treasury.getFeeSpecification(riskpoolId)

# advance chain to ensure new timestamp
chain.sleep(31337)
chain.mine()

# ensure that the new fee spec has a new timstamp
newRiskpoolFeeSpec = instanceOperatorService.createFeeSpecification(
riskpoolId, 999, hundredPercent / 200, str.encode("a"))
assert existingFeeSpec[4] != newRiskpoolFeeSpec[4]

# ensure that the fee spec is updated and all values except for createdAt are updated
tx = instanceOperatorService.setCapitalFees(newRiskpoolFeeSpec, {"from": instanceOperator})
updatedFeeSpec = treasury.getFeeSpecification(riskpoolId)

assert existingFeeSpec[0] == updatedFeeSpec[0]
assert existingFeeSpec[1] != updatedFeeSpec[1]
assert existingFeeSpec[2] != updatedFeeSpec[2]
assert existingFeeSpec[3] != updatedFeeSpec[3]
assert existingFeeSpec[4] == updatedFeeSpec[4]
assert existingFeeSpec[5] != updatedFeeSpec[5]


def test_overwriting_premium_fees(
instance: GifInstance,
instanceOperator: Account,
gifTestProduct,
chain: Chain
):
instanceService = instance.getInstanceService()
instanceOperatorService = instance.getInstanceOperatorService()
treasury = instance.getTreasury()

product = gifTestProduct.getContract()
productId = product.getId()

hundredPercent = instanceService.getFeeFractionFullUnit()

existingFeeSpec = treasury.getFeeSpecification(productId)

# advance chain to ensure new timestamp
chain.sleep(31337)
chain.mine()

# ensure that the new fee spec has a new timstamp
newProductFeeSpec = instanceOperatorService.createFeeSpecification(
productId, 999, hundredPercent / 200, str.encode("a"))
assert existingFeeSpec[4] != newProductFeeSpec[4]

# ensure that the fee spec is updated and all values except for createdAt are updated
tx = instanceOperatorService.setPremiumFees(newProductFeeSpec, {"from": instanceOperator})
updatedFeeSpec = treasury.getFeeSpecification(productId)

assert existingFeeSpec[0] == updatedFeeSpec[0]
assert existingFeeSpec[1] != updatedFeeSpec[1]
assert existingFeeSpec[2] != updatedFeeSpec[2]
assert existingFeeSpec[3] != updatedFeeSpec[3]
assert existingFeeSpec[4] == updatedFeeSpec[4]
assert existingFeeSpec[5] != updatedFeeSpec[5]


def getProductAndRiskpool(
instance: GifInstance,
owner: Account,
Expand Down
Loading