Skip to content

Commit

Permalink
Undercollat vault creation (#33)
Browse files Browse the repository at this point in the history
* fix testing

* some more testing

* moar testing

* use var names, automatically set collat_factor

* cleaning up

* address comments

* updated vault simulator version

* more instructions on forge verify-contract

* fix bug: all mults done together

---------

Co-authored-by: --systemdf <[email protected]>
  • Loading branch information
anihamde and --systemdf authored Mar 20, 2024
1 parent cbf3c4a commit 99bcbd8
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 36 deletions.
6 changes: 4 additions & 2 deletions per_multicall/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ For verifying contracts, you can use the `forge verify-contract` command.
For example, to verify the ERC1967Proxy contract on the Optimism network, you can run the following commands:

```
forge verify-contract --via-ir <contract-address> ERC1967Proxy --verifier blockscout --verifier-url https://optimism-sepolia.blockscout.com/api/
forge verify-contract --via-ir <contract-address> ERC1967Proxy --verifier-url https://api-sepolia-optimistic.etherscan.io/api
forge verify-contract --via-ir <contract-address> ERC1967Proxy --verifier blockscout --verifier-url https://optimism-sepolia.blockscout.com/api/ --chain-id 11155420
forge verify-contract --via-ir <contract-address> ERC1967Proxy --verifier-url https://api-sepolia-optimistic.etherscan.io/api --etherscan-api-key <optimistic-etherscan-api-key> --chain-id 11155420
You may have to specify the constructor arguments used to initialize the contract, using the `--constructor-args` flag. For more info see the [forge instructions on verifying contracts](https://book.getfoundry.sh/forge/deploying?highlight=verify#verifying-a-pre-existing-contract).
```
2 changes: 1 addition & 1 deletion per_multicall/remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ ds-test/=lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
openzeppelin-contracts-upgradable/=lib/openzeppelin-contracts-upgradable/
openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/
@pythnetwork/pyth-sdk-solidity=node_modules/@pythnetwork/pyth-sdk-solidity/
@pythnetwork/express-relay-sdk-solidity/=node_modules/@pythnetwork/express-relay-sdk-solidity
42 changes: 33 additions & 9 deletions per_multicall/script/Vault.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ contract VaultScript is Script {
}

function deployWeth() public returns (address) {
(, uint256 skDeployer) = getDeployer();
vm.startBroadcast(skDeployer);
WETH9 weth = new WETH9();
vm.stopBroadcast();
console.log("deployed weth contract at", address(weth));
return address(weth);
}
Expand Down Expand Up @@ -78,31 +81,44 @@ contract VaultScript is Script {
}

function deployExpressRelay() public returns (address) {
(, uint256 skDeployer) = getDeployer();
(address operatorAddress, uint256 operatorSk) = makeAddrAndKey(
"perOperator"
);
console.log("pk per operator", operatorAddress);
console.log("sk per operator", operatorSk);
vm.startBroadcast(skDeployer);
payable(operatorAddress).transfer(0.01 ether);
ExpressRelay multicall = new ExpressRelay(operatorAddress, 0);
vm.stopBroadcast();
console.log("deployed ExpressRelay contract at", address(multicall));
return address(multicall);
}

function deployVault(
address multicall,
address oracle
address oracle,
bool allowUndercollateralized
) public returns (address) {
// make token vault deployer wallet
(, uint256 tokenVaultDeployerSk) = makeAddrAndKey("tokenVaultDeployer");
console.log("sk token vault deployer", tokenVaultDeployerSk);
TokenVault vault = new TokenVault(multicall, oracle);
vm.startBroadcast(tokenVaultDeployerSk);
TokenVault vault = new TokenVault(
multicall,
oracle,
allowUndercollateralized
);
vm.stopBroadcast();
console.log("deployed vault contract at", address(vault));
return address(vault);
}

function deployMockPyth() public returns (address) {
(, uint256 skDeployer) = getDeployer();
vm.startBroadcast(skDeployer);
MockPyth mockPyth = new MockPyth(1_000_000_000_000, 0);
vm.stopBroadcast();
console.log("deployed mock pyth contract at", address(mockPyth));
return address(mockPyth);
}
Expand All @@ -111,8 +127,7 @@ contract VaultScript is Script {
public
returns (address, address, address, address, address)
{
(address deployer, uint256 skDeployer) = getDeployer();
vm.startBroadcast(skDeployer);
(address deployer, ) = getDeployer();
address weth = deployWeth();
address expressRelay = deployExpressRelay();
address opportunityAdapter = deployOpportunityAdapter(
Expand All @@ -122,8 +137,7 @@ contract VaultScript is Script {
weth
);
address mockPyth = deployMockPyth();
address vault = deployVault(expressRelay, mockPyth);
vm.stopBroadcast();
address vault = deployVault(expressRelay, mockPyth, false);
return (expressRelay, opportunityAdapter, mockPyth, vault, weth);
}

Expand All @@ -133,9 +147,12 @@ contract VaultScript is Script {
The erc-20 tokens have their actual name as symbol and pyth price feed id as their name. A huge amount of these tokens are minted to the token vault
@param pyth The address of the already deployed pyth contract to use
*/
function setupTestnet(address pyth, address weth) public {
function setupTestnet(
address pyth,
address weth,
bool allowUndercollateralized
) public {
(address deployer, uint256 skDeployer) = getDeployer();
vm.startBroadcast(skDeployer);
if (pyth == address(0)) pyth = deployMockPyth();
if (weth == address(0)) weth = deployWeth();
address expressRelay = deployExpressRelay();
Expand All @@ -145,7 +162,11 @@ contract VaultScript is Script {
expressRelay,
weth
);
address vault = deployVault(expressRelay, pyth);
address vault = deployVault(
expressRelay,
pyth,
allowUndercollateralized
);
address[] memory tokens = new address[](5);
uint256 lots_of_money = 10 ** 36;
// Vault simulator assumes the token name is pyth pricefeed id in mainnet
Expand Down Expand Up @@ -179,10 +200,13 @@ contract VaultScript is Script {
"PYTH"
)
);

vm.startBroadcast(skDeployer);
for (uint i = 0; i < 5; i++) {
MyToken(tokens[i]).mint(vault, lots_of_money);
}
vm.stopBroadcast();

string memory obj = "";
vm.serializeAddress(obj, "tokens", tokens);
vm.serializeAddress(obj, "per", expressRelay);
Expand Down
2 changes: 1 addition & 1 deletion per_multicall/src/Structs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ struct Vault {
uint256 amountCollateral;
uint256 amountDebt;
uint256 minHealthRatio; // 10**18 is 100%
uint256 minPermissionLessHealthRatio;
uint256 minPermissionlessHealthRatio;
bytes32 tokenIdCollateral;
bytes32 tokenIdDebt;
}
Expand Down
37 changes: 27 additions & 10 deletions per_multicall/src/TokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,24 @@ contract TokenVault is IExpressRelayFeeReceiver {
address public immutable expressRelay;
mapping(uint256 => Vault) _vaults;
address _oracle;
bool _allowUndercollateralized;

/**
* @notice TokenVault constructor - Initializes a new token vault contract with given parameters
*
* @param expressRelayAddress: address of the express relay
* @param oracleAddress: address of the oracle contract
* @param allowUndercollateralized: boolean to allow undercollateralized vaults to be created and updated. Can be set to true for testing.
*/
constructor(address expressRelayAddress, address oracleAddress) {
constructor(
address expressRelayAddress,
address oracleAddress,
bool allowUndercollateralized
) {
_nVaults = 0;
expressRelay = expressRelayAddress;
_oracle = oracleAddress;
_allowUndercollateralized = allowUndercollateralized;
}

/**
Expand Down Expand Up @@ -81,6 +88,10 @@ contract TokenVault is IExpressRelayFeeReceiver {
return convertToUint(oracle.getPrice(id), 18);
}

function getAllowUndercollateralized() public view returns (bool) {
return _allowUndercollateralized;
}

function getOracle() public view returns (address) {
return _oracle;
}
Expand Down Expand Up @@ -128,7 +139,7 @@ contract TokenVault is IExpressRelayFeeReceiver {
* @param amountCollateral: amount of collateral tokens in the vault
* @param amountDebt: amount of debt tokens in the vault
* @param minHealthRatio: minimum health ratio of the vault, 10**18 is 100%
* @param minPermissionLessHealthRatio: minimum health ratio of the vault before permissionless liquidations are allowed. This should be less than minHealthRatio
* @param minPermissionlessHealthRatio: minimum health ratio of the vault before permissionless liquidations are allowed. This should be less than minHealthRatio
* @param tokenIdCollateral: price feed Id of the collateral token
* @param tokenIdDebt: price feed Id of the debt token
* @param updateData: data to update price feeds with
Expand All @@ -139,7 +150,7 @@ contract TokenVault is IExpressRelayFeeReceiver {
uint256 amountCollateral,
uint256 amountDebt,
uint256 minHealthRatio,
uint256 minPermissionLessHealthRatio,
uint256 minPermissionlessHealthRatio,
bytes32 tokenIdCollateral,
bytes32 tokenIdDebt,
bytes[] calldata updateData
Expand All @@ -151,14 +162,17 @@ contract TokenVault is IExpressRelayFeeReceiver {
amountCollateral,
amountDebt,
minHealthRatio,
minPermissionLessHealthRatio,
minPermissionlessHealthRatio,
tokenIdCollateral,
tokenIdDebt
);
if (minPermissionLessHealthRatio > minHealthRatio) {
if (minPermissionlessHealthRatio > minHealthRatio) {
revert InvalidHealthRatios();
}
if (_getVaultHealth(vault) < vault.minHealthRatio) {
if (
!_allowUndercollateralized &&
_getVaultHealth(vault) < vault.minHealthRatio
) {
revert UncollateralizedVaultCreation();
}

Expand Down Expand Up @@ -209,7 +223,10 @@ contract TokenVault is IExpressRelayFeeReceiver {
vault.amountCollateral = futureCollateral;
vault.amountDebt = futureDebt;

if (_getVaultHealth(vault) < vault.minHealthRatio) {
if (
!_allowUndercollateralized &&
_getVaultHealth(vault) < vault.minHealthRatio
) {
revert InvalidVaultUpdate();
}

Expand Down Expand Up @@ -270,8 +287,8 @@ contract TokenVault is IExpressRelayFeeReceiver {
* @notice liquidate function - liquidates a vault
* This function calculates the health of the vault and based on the vault parameters one of the following actions is taken:
* 1. If health >= minHealthRatio, don't liquidate
* 2. If minHealthRatio > health >= minPermissionLessHealthRatio, only liquidate if the vault is permissioned via express relay
* 3. If minPermissionLessHealthRatio > health, liquidate no matter what
* 2. If minHealthRatio > health >= minPermissionlessHealthRatio, only liquidate if the vault is permissioned via express relay
* 3. If minPermissionlessHealthRatio > health, liquidate no matter what
*
* @param vaultId: Id of the vault to be liquidated
*/
Expand All @@ -284,7 +301,7 @@ contract TokenVault is IExpressRelayFeeReceiver {
}

if (
vaultHealth >= vault.minPermissionLessHealthRatio &&
vaultHealth >= vault.minPermissionlessHealthRatio &&
!IExpressRelay(expressRelay).isPermissioned(
address(this),
abi.encode(vaultId)
Expand Down
7 changes: 6 additions & 1 deletion per_multicall/test/ExpressRelayIntegration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,13 @@ contract ExpressRelayIntegrationTest is
vm.prank(perOperatorAddress, perOperatorAddress);
mockPyth = new MockPyth(1_000_000, 0);

bool allowUndercollateralized = false;
vm.prank(tokenVaultDeployer, tokenVaultDeployer); // we prank here to standardize the value of the token contract address across different runs
tokenVault = new TokenVault(address(expressRelay), address(mockPyth));
tokenVault = new TokenVault(
address(expressRelay),
address(mockPyth),
allowUndercollateralized
);
console.log("contract of token vault is", address(tokenVault));
feeSplitTokenVault = defaultFeeSplitProtocol;

Expand Down
2 changes: 1 addition & 1 deletion per_sdk/protocols/token_vault_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async def get_accounts(self) -> list[ProtocolAccount]:
"amount_debt": vault_dict["amountDebt"],
"min_health_ratio": vault_dict["minHealthRatio"],
"min_permissionless_health_ratio": vault_dict[
"minPermissionLessHealthRatio"
"minPermissionlessHealthRatio"
],
}
accounts.append(account)
Expand Down
2 changes: 1 addition & 1 deletion vault-simulator/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault-simulator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vault-simulator"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
license-file = "license.txt"

Expand Down
34 changes: 25 additions & 9 deletions vault-simulator/src/simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,32 @@ pub async fn run_simulator(simulator_options: SimulatorOptions) -> Result<()> {
let collateral_value_usd: U256 = precision * U256::from(random::<u64>() % 900 + 100);
tracing::info!("Collateral value usd: {}", collateral_value_usd);
tracing::info!("Collateral price: {}", collateral_update.price);
tracing::info!("Debt price: {}", collateral_update.price);
tracing::info!("Debt price: {}", debt_update.price);

let amount_collateral: U256 =
collateral_value_usd * precision * 1100001 / 1000000 / collateral_update.price; // Slightly more than 110% to make sure the vault is created
let amount_debt = collateral_value_usd * precision / debt_update.price;
let contract =
SignableTokenVaultContract::new(simulator_options.vault_contract, client.clone());

let min_health_ratio = U256::exp10(18) * 110 / 100;
let min_permission_less_health_ratio = U256::exp10(18) * 105 / 100;
let min_health_numerator: U256 = U256::from(1100000);
let min_health_permissionless_numerator: U256 = U256::from(1050000);
let min_health_denominator: U256 = U256::from(1000000);
let min_health_ratio = U256::exp10(18) * min_health_numerator / min_health_denominator;
let min_permissionless_health_ratio =
U256::exp10(18) * min_health_permissionless_numerator / min_health_denominator;

let collat_ratio: U256;
let allow_undercollateralized: bool = contract.get_allow_undercollateralized().call().await?;
if allow_undercollateralized {
// Less than min_health_ratio, greater than permissionless ratio, to create the vault undercollateralized
collat_ratio = (min_health_numerator + min_health_permissionless_numerator) / 2;
} else {
// Slightly more than min_health_ratio to create the vault overcollateralized
collat_ratio = min_health_numerator * 10_001 / 10_000;
}

let amount_collateral: U256 = collateral_value_usd * precision * collat_ratio
/ min_health_denominator
/ collateral_update.price;
let amount_debt = collateral_value_usd * precision / debt_update.price;

let token_id_collateral: [u8; 32] = <[u8; 32]>::from_hex(collateral_info.price_id).unwrap();
let token_id_debt: [u8; 32] = <[u8; 32]>::from_hex(debt_info.price_id).unwrap();
Expand All @@ -218,16 +236,14 @@ pub async fn run_simulator(simulator_options: SimulatorOptions) -> Result<()> {
tracing::info!("Amount collateral: {}", amount_collateral);
tracing::info!("Amount debt: {}", amount_debt);

let contract =
SignableTokenVaultContract::new(simulator_options.vault_contract, client.clone());
let tx = contract
.create_vault(
collateral_info.address,
debt_info.address,
amount_collateral,
amount_debt,
min_health_ratio,
min_permission_less_health_ratio,
min_permissionless_health_ratio,
token_id_collateral,
token_id_debt,
update_data.clone(),
Expand Down

0 comments on commit 99bcbd8

Please sign in to comment.