Skip to content

Commit

Permalink
Merge branch 'main' into subgraph
Browse files Browse the repository at this point in the history
  • Loading branch information
bpierre committed Sep 4, 2024
2 parents b6770bc + 94aefcb commit 0079e1e
Show file tree
Hide file tree
Showing 17 changed files with 620 additions and 214 deletions.
136 changes: 110 additions & 26 deletions README.md

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions contracts/src/StabilityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {
IERC20 public immutable collToken;
ITroveManager public immutable troveManager;
IBoldToken public immutable boldToken;
// Needed to check if there are pending liquidations
ISortedTroves public immutable sortedTroves;

uint256 internal collBalance; // deposited ether tracker

Expand Down Expand Up @@ -205,17 +203,14 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents {

event TroveManagerAddressChanged(address _newTroveManagerAddress);
event BoldTokenAddressChanged(address _newBoldTokenAddress);
event SortedTrovesAddressChanged(address _newSortedTrovesAddress);

constructor(IAddressesRegistry _addressesRegistry) LiquityBase(_addressesRegistry) {
collToken = _addressesRegistry.collToken();
troveManager = _addressesRegistry.troveManager();
boldToken = _addressesRegistry.boldToken();
sortedTroves = _addressesRegistry.sortedTroves();

emit TroveManagerAddressChanged(address(troveManager));
emit BoldTokenAddressChanged(address(boldToken));
emit SortedTrovesAddressChanged(address(sortedTroves));
}

// --- Getters for public variables. Required by IPool interface ---
Expand Down
19 changes: 13 additions & 6 deletions contracts/src/test/Invariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,24 @@ contract InvariantsTest is Logging, BaseInvariantTest, BaseMultiCollateralTest {
function setUp() public override {
super.setUp();

uint256 n;
try vm.envUint("NUM_BRANCHES") returns (uint256 value) {
n = value;
} catch {
n = 4;
}

// TODO: randomize params? How to do it with Foundry invariant testing?
TestDeployer.TroveManagerParams[] memory paramsList = new TestDeployer.TroveManagerParams[](4);
paramsList[0] = TestDeployer.TroveManagerParams(1.5 ether, 1.1 ether, 1.01 ether, 0.05 ether, 0.1 ether);
paramsList[1] = TestDeployer.TroveManagerParams(1.6 ether, 1.2 ether, 1.01 ether, 0.05 ether, 0.1 ether);
paramsList[2] = TestDeployer.TroveManagerParams(1.6 ether, 1.2 ether, 1.01 ether, 0.05 ether, 0.1 ether);
paramsList[3] = TestDeployer.TroveManagerParams(1.6 ether, 1.25 ether, 1.01 ether, 0.05 ether, 0.1 ether);
TestDeployer.TroveManagerParams[] memory p = new TestDeployer.TroveManagerParams[](n);
if (n > 0) p[0] = TestDeployer.TroveManagerParams(1.5 ether, 1.1 ether, 1.01 ether, 0.05 ether, 0.1 ether);
if (n > 1) p[1] = TestDeployer.TroveManagerParams(1.6 ether, 1.2 ether, 1.01 ether, 0.05 ether, 0.1 ether);
if (n > 2) p[2] = TestDeployer.TroveManagerParams(1.6 ether, 1.2 ether, 1.01 ether, 0.05 ether, 0.1 ether);
if (n > 3) p[3] = TestDeployer.TroveManagerParams(1.6 ether, 1.25 ether, 1.01 ether, 0.05 ether, 0.1 ether);

TestDeployer deployer = new TestDeployer();
Contracts memory contracts;
(contracts.branches, contracts.collateralRegistry, contracts.boldToken, contracts.hintHelpers,, contracts.weth,)
= deployer.deployAndConnectContractsMultiColl(paramsList);
= deployer.deployAndConnectContractsMultiColl(p);
setupContracts(contracts);

handler = new InvariantsTestHandler({contracts: contracts, assumeNoExpectedFailures: true});
Expand Down
58 changes: 52 additions & 6 deletions contracts/src/test/TestContracts/InvariantsTestHandler.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1662,12 +1662,7 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest {

// Justify failures
if (reason.equals("StabilityPool: User must have a non-zero deposit")) {
assertEqDecimal(
v.c.stabilityPool.deposits(msg.sender),
0,
18,
"Shouldn't have failed as user had a non-zero deposit"
);
assertEqDecimal(v.initialBoldDeposit, 0, 18, "Shouldn't have failed as user had a non-zero deposit");
} else {
revert(reason);
}
Expand All @@ -1685,6 +1680,57 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest {
}
}

function claimAllCollGains(uint256 i) external {
i = _bound(i, 0, branches.length - 1);

TestDeployer.LiquityContractsDev memory c = branches[i];
uint256 pendingInterest = c.activePool.calcPendingAggInterest();
uint256 initialBoldDeposit = c.stabilityPool.deposits(msg.sender);
uint256 ethStash = c.stabilityPool.stashedColl(msg.sender);

logCall("claimAllCollGains", i.toString());

string memory errorString;
vm.prank(msg.sender);

try c.stabilityPool.claimAllCollGains() {
// Preconditions
assertEqDecimal(initialBoldDeposit, 0, 18, "Should have failed as user had a non-zero deposit");
assertGtDecimal(ethStash, 0, 18, "Should have failed as ETH stash was zero");

// Effects (deposit)
assertEqDecimal(c.stabilityPool.getCompoundedBoldDeposit(msg.sender), 0, 18, "Wrong deposit");
assertEqDecimal(c.stabilityPool.getDepositorYieldGain(msg.sender), 0, 18, "Wrong yield gain");
assertEqDecimal(c.stabilityPool.getDepositorCollGain(msg.sender), 0, 18, "Wrong coll gain");
assertEqDecimal(c.stabilityPool.stashedColl(msg.sender), 0, 18, "Wrong stashed coll");

// Effects (system)
_mintYield(i, pendingInterest, 0);
spColl[i] -= ethStash;
} catch Error(string memory reason) {
errorString = reason;

// Justify failures
if (reason.equals("StabilityPool: User must have no deposit")) {
assertGtDecimal(initialBoldDeposit, 0, 18, "Shouldn't have failed as user had no deposit");
} else if (reason.equals("StabilityPool: Amount must be non-zero")) {
assertEqDecimal(ethStash, 0, 18, "Shouldn't have failed as ETH stash was non-zero");
} else {
revert(reason);
}
}

if (bytes(errorString).length > 0) {
if (_assumeNoExpectedFailures) vm.assume(false);

info("Expected error: ", errorString);
_log();
} else {
// Cleanup (success)
_sweepColl(i, msg.sender, ethStash);
}
}

//////////////////////
// Batch management //
//////////////////////
Expand Down
116 changes: 110 additions & 6 deletions contracts/utils/deploy-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ e.g. --chain-id can be set via CHAIN_ID instead. Parameters take precedence over

const ANVIL_FIRST_ACCOUNT = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";

const PROTOCOL_CONTRACTS_VALID_NAMES = [
"WETHTester",
"BoldToken",
"CollateralRegistry",
"HintHelpers",
"MultiTroveGetter",
];

const argv = minimist(process.argv.slice(2), {
alias: {
h: "help",
Expand Down Expand Up @@ -185,27 +193,43 @@ Deploying Liquity contracts with the following settings:
`broadcast/DeployLiquity2.s.sol/${options.chainId}/run-latest.json`,
);

const collateralContracts = await getAllCollateralsContracts(deployedContracts, options);

// XXX hotfix: we were leaking Github secrets in "deployer"
// TODO: check if "deployer" is a private key, and calculate its address and use it instead?
const { deployer, ...safeOptions } = options;

const protocolContracts = Object.fromEntries(
filterProtocolContracts(deployedContracts),
);

// write env file
await fs.writeJson("deployment-context-latest.json", {
options: safeOptions,
deployedContracts: Object.fromEntries(deployedContracts),
deployedContracts,
collateralContracts,
protocolContracts,
});

// format deployed contracts
const longestContractName = Math.max(
...deployedContracts.map(([name]) => name.length),
);
const deployedContractsFormatted = deployedContracts
.map(([name, address]) => `${name.padEnd(longestContractName)} ${address}`)
.join("\n");

echo("Contract deployment complete.");
const formatContracts = (contracts: Array<string[]>) =>
contracts.map(([name, address]) => ` ${name.padEnd(longestContractName)} ${address}`).join("\n");

echo("Protocol contracts:");
echo("");
echo(formatContracts(filterProtocolContracts(deployedContracts)));
echo("");
echo(deployedContractsFormatted);
echo(
collateralContracts.map((collateral, index) => (
`Collateral ${index + 1} contracts:\n\n${formatContracts(Object.entries(collateral))}`
)).join("\n\n"),
);
echo("");
echo("Deployment complete.");
echo("");
}

Expand Down Expand Up @@ -255,6 +279,10 @@ async function getDeployedContracts(jsonPath: string) {
throw new Error("Invalid deployment log: " + JSON.stringify(latestRun));
}

function filterProtocolContracts(contracts: Awaited<ReturnType<typeof getDeployedContracts>>) {
return contracts.filter(([name]) => PROTOCOL_CONTRACTS_VALID_NAMES.includes(name));
}

function safeParseInt(value: string) {
const parsed = parseInt(value, 10);
return isNaN(parsed) ? undefined : parsed;
Expand Down Expand Up @@ -296,3 +324,79 @@ async function parseArgs() {

return { options, networkPreset };
}

async function castCall(
rpcUrl: string,
contract: string,
method: string,
...args: string[]
) {
try {
const result = await $`cast call ${contract} ${method} ${args.join(" ")} --rpc-url '${rpcUrl}'`;
return result.stdout.trim();
} catch (error) {
console.error(`Error calling ${contract} ${method} ${args.join(" ")}: ${error}`);
throw error;
}
}

async function getCollateralContracts(
collateralIndex: number,
collateralRegistry: string,
rpcUrl: string,
) {
const [token, troveManager] = await Promise.all([
castCall(rpcUrl, collateralRegistry, "getToken(uint256)(address)", String(collateralIndex)),
castCall(rpcUrl, collateralRegistry, "getTroveManager(uint256)(address)", String(collateralIndex)),
]);

const [
activePool,
borrowerOperations,
sortedTroves,
stabilityPool,
] = await Promise.all([
castCall(rpcUrl, troveManager, "activePool()(address)"),
castCall(rpcUrl, troveManager, "borrowerOperations()(address)"),
castCall(rpcUrl, troveManager, "sortedTroves()(address)"),
castCall(rpcUrl, troveManager, "stabilityPool()(address)"),
]);

return {
activePool,
borrowerOperations,
sortedTroves,
stabilityPool,
token,
troveManager,
};
}

async function getAllCollateralsContracts(
deployedContracts: Array<string[]>,
options: Awaited<ReturnType<typeof parseArgs>>["options"],
) {
const deployedContractsRecord = Object.fromEntries(deployedContracts);

const ccall = async (contract: string, method: string, ...args: string[]) => {
const result = await $`cast call ${contract} ${method} ${args.join(" ")} --rpc-url '${options.rpcUrl}'`;
return result.stdout.trim();
};

const totalCollaterals = Number(
await ccall(
deployedContractsRecord.CollateralRegistry,
"totalCollaterals()",
),
);

return Promise.all(
Array.from({ length: totalCollaterals }, (_, index) => (
getCollateralContracts(
index,
deployedContractsRecord.CollateralRegistry,
options.rpcUrl,
)
)),
);
}
Loading

0 comments on commit 0079e1e

Please sign in to comment.