Skip to content

Commit

Permalink
fix(loadgen): replace BDL with configurable token (#50)
Browse files Browse the repository at this point in the history
Use USDC for compatibility with newer SDKs
Fix for BLD removed as collateral in Bootstrap change Agoric/agoric-sdk#4387
  • Loading branch information
mhofman authored Feb 5, 2022
1 parent ac50e94 commit b1d8d24
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 77 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,5 @@ To link the loadgen to the remote orchestrator, you need to provide authenticati
The load generators defined so far:

- `faucet`: initialize by creating a `dapp-fungible-faucet` -style mint on the chain, then each cycle requests an invitation and completes it, adding 1000 Tokens to Bob's Purse. Takes 4 round-trips to complete.
- `amm`: initialize by selling some (currently 33%) of the initial RUN to get BLD, then record the balances. Each cycle sells 1% of the recorded BLD to get RUN, then sells 1% of the recorded RUN to get BLD. Because of fees, the total available will drop slowly over time.
- `vault`: initialize by recording our BLD balance and the BLD/RUN price. Each cycle deposits 1% of the recorded BLD balance and borrows half its value in RUN, then pays back the loan and recovers the BLD (less fees).
- `amm`: initialize by selling some (currently 33%) of the initial RUN to get a trade token, then record the balances. Each cycle sells 1% of the recorded trade token to get RUN, then sells 1% of the recorded RUN to get the trade token back. Because of fees, the total available will drop slowly over time.
- `vault`: initialize by recording our collateral token balance and the collateral/RUN price. Each cycle deposits 1% of the recorded collateral balance and borrows half its value in RUN, then pays back the loan and recovers the collateral (less fees).
3 changes: 3 additions & 0 deletions loadgen/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const collateralToken = 'USDC';

export const tradeToken = 'USDC';
68 changes: 41 additions & 27 deletions loadgen/contract/agent-create-vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,24 @@ import { allValues } from './allValues.js';
// This is loaded by the spawner into a new 'spawned' vat on the solo node.
// The default export function is called with some args.

export default async function startAgent([key, home]) {
export default async function startAgent([key, home, collateralToken]) {
const { zoe, scratch, agoricNames, wallet } = home;

console.error(`create-vault: building tools`);
const {
runBrand,
bldBrand,
collateralBrand,
runPurse,
bldPurse,
collateralPurse,
treasuryInstance,
vaultFactoryInstance,
} = await allValues({
runBrand: E(agoricNames).lookup('brand', issuerPetnames.RUN),
// bldBrand: E(agoricNames).lookup('brand', issuerPetnames.BLD),
bldBrand: E(E(wallet).getIssuer(issuerPetnames.BLD)).getBrand(),
collateralBrand: E(
E(wallet).getIssuer(issuerPetnames[collateralToken]),
).getBrand(),
runPurse: E(wallet).getPurse(pursePetnames.RUN),
bldPurse: E(wallet).getPurse(pursePetnames.BLD),
collateralPurse: E(wallet).getPurse(pursePetnames[collateralToken]),
treasuryInstance: E(agoricNames)
.lookup('instance', 'Treasury')
.catch(() => {}),
Expand All @@ -42,50 +43,63 @@ export default async function startAgent([key, home]) {
vaultFactoryInstance || treasuryInstance,
);

const bldBalance = await E(bldPurse).getCurrentAmount();
if (AmountMath.isEmpty(bldBalance)) {
throw Error(`create-vault: getCurrentAmount(BLD) broken (says 0)`);
const collateralBalance = await E(collateralPurse).getCurrentAmount();
if (AmountMath.isEmpty(collateralBalance)) {
throw Error(
`create-vault: getCurrentAmount(${collateralToken}) broken (says 0)`,
);
}
console.error(`create-vault: initial balance: ${disp(bldBalance)} BLD`);
console.error(
`create-vault: initial balance: ${disp(
collateralBalance,
)} ${collateralToken}`,
);
// put 1% into the vault
const bldToLock = AmountMath.make(bldBrand, bldBalance.value / BigInt(100));
const collateralToLock = AmountMath.make(
collateralBrand,
collateralBalance.value / BigInt(100),
);

// we only withdraw half the value of the collateral, giving us 200%
// collateralization
const collaterals = await E(treasuryPublicFacet).getCollaterals();
const cdata = collaterals.find((c) => c.brand === bldBrand);
const cdata = collaterals.find((c) => c.brand === collateralBrand);
const priceRate = cdata.marketPrice;
const half = makeRatio(BigInt(50), runBrand);
const wantedRun = multiplyBy(multiplyBy(bldToLock, priceRate), half);
const wantedRun = multiplyBy(multiplyBy(collateralToLock, priceRate), half);

console.error(`create-vault: tools installed`);

console.error(
`create-vault: bldToLock=${disp(bldToLock)}, wantedRun=${disp(wantedRun)}`,
`create-vault: collateralToLock=${disp(
collateralToLock,
)} ${collateralToken}, wantedRun=${disp(wantedRun)}`,
);

// we fix the 1% 'bldToLock' value at startup, and use it for all cycles
// (we close over 'bldToLock')
// we fix the 1% 'collateralToLock' value at startup, and use it for all cycles
// (we close over 'collateralToLock')
async function openVault() {
console.error('create-vault: openVault');
const openInvitationP = E(treasuryPublicFacet).makeLoanInvitation();
const proposal = harden({
give: {
Collateral: bldToLock,
Collateral: collateralToLock,
},
want: {
RUN: wantedRun,
},
});
const payment = harden({ Collateral: E(bldPurse).withdraw(bldToLock) });
const payment = harden({
Collateral: E(collateralPurse).withdraw(collateralToLock),
});
const seatP = E(zoe).offer(openInvitationP, proposal, payment);
await seatP;
const [bldPayout, runPayout] = await Promise.all([
const [collateralPayout, runPayout] = await Promise.all([
E(seatP).getPayout('Collateral'),
E(seatP).getPayout('RUN'),
]);
await Promise.all([
E(bldPurse).deposit(bldPayout),
E(collateralPurse).deposit(collateralPayout),
E(runPurse).deposit(runPayout),
]);
const offerResult = await E(seatP).getOfferResult();
Expand All @@ -102,33 +116,33 @@ export default async function startAgent([key, home]) {
RUN: runNeeded,
},
want: {
Collateral: AmountMath.makeEmpty(bldBrand),
Collateral: AmountMath.makeEmpty(collateralBrand),
},
};
const payment = harden({ RUN: E(runPurse).withdraw(runNeeded) });
const seatP = E(zoe).offer(closeInvitationP, proposal, payment);
const [runPayout, bldPayout] = await Promise.all([
const [runPayout, collateralPayout] = await Promise.all([
E(seatP).getPayout('RUN'),
E(seatP).getPayout('Collateral'),
]);
await Promise.all([
E(runPurse).deposit(runPayout),
E(bldPurse).deposit(bldPayout),
E(collateralPurse).deposit(collateralPayout),
E(seatP).getOfferResult(),
]);
console.error(`create-vault: vault closed`);
}

const agent = Far('vault agent', {
async doVaultCycle() {
const vault = await openVault(bldToLock);
const vault = await openVault(collateralToLock);
await closeVault(vault);
const [newRunBalance, newBldBalance] = await Promise.all([
const [newRunBalance, newCollateralBalance] = await Promise.all([
E(runPurse).getCurrentAmount(),
E(bldPurse).getCurrentAmount(),
E(collateralPurse).getCurrentAmount(),
]);
console.error('create-vault: cycle done');
return [newRunBalance, newBldBalance];
return [newRunBalance, newCollateralBalance];
},
});

Expand Down
85 changes: 50 additions & 35 deletions loadgen/contract/agent-trade-amm.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ import { allValues } from './allValues.js';
// This is loaded by the spawner into a new 'spawned' vat on the solo node.
// The default export function is called with some args.

export default async function startAgent([key, home]) {
export default async function startAgent([key, home, tradeToken]) {
const { zoe, scratch, agoricNames, wallet, faucet } = home;

console.error(`trade-amm: building tools`);
// const runIssuer = await E(agoricNames).lookup('issuer', issuerPetnames.RUN);
const { runBrand, bldBrand, amm, autoswap, runPurse, bldPurse } =
const { runBrand, targetBrand, amm, autoswap, runPurse, targetPurse } =
await allValues({
runBrand: E(agoricNames).lookup('brand', issuerPetnames.RUN),
// bldBrand: E(agoricNames).lookup('brand', issuerPetnames.BLD),
bldBrand: E(E(wallet).getIssuer(issuerPetnames.BLD)).getBrand(),
targetBrand: E(
E(wallet).getIssuer(issuerPetnames[tradeToken]),
).getBrand(),
amm: E(agoricNames)
.lookup('instance', 'amm')
.catch(() => {}),
autoswap: E(agoricNames)
.lookup('instance', 'autoswap')
.catch(() => {}),
runPurse: E(wallet).getPurse(pursePetnames.RUN),
bldPurse: E(wallet).getPurse(pursePetnames.BLD),
targetPurse: E(wallet).getPurse(pursePetnames[tradeToken]),
});
// const bldBrand = await E(bldPurse).getAllegedBrand();

{
const feePurse = await E(faucet)
Expand Down Expand Up @@ -68,23 +68,26 @@ export default async function startAgent([key, home]) {
bal = await E(runPurse).getCurrentAmount();
return bal;
}
if (which === 'BLD') {
bal = await E(bldPurse).getCurrentAmount();
if (which === tradeToken) {
bal = await E(targetPurse).getCurrentAmount();
return bal;
}
throw Error(`unknown type ${which}`);
}

async function getBalances() {
return allValues({ run: getBalance('RUN'), bld: getBalance('BLD') });
return allValues({
run: getBalance('RUN'),
target: getBalance(tradeToken),
});
}

async function buyRunWithBld(bldOffered) {
async function buyRunWithTarget(targetOffered) {
const proposal = harden({
want: { Out: AmountMath.makeEmpty(runBrand, AssetKind.NAT) },
give: { In: bldOffered },
give: { In: targetOffered },
});
const payment = harden({ In: E(bldPurse).withdraw(bldOffered) });
const payment = harden({ In: E(targetPurse).withdraw(targetOffered) });
const seatP = E(zoe).offer(
E(publicFacet).makeSwapInInvitation(),
proposal,
Expand All @@ -95,15 +98,15 @@ export default async function startAgent([key, home]) {
E(seatP).getPayout('Out'),
]);
await Promise.all([
E(bldPurse).deposit(refundPayout),
E(targetPurse).deposit(refundPayout),
E(runPurse).deposit(payout),
E(seatP).getOfferResult(),
]);
}

async function buyBldWithRun(runOffered) {
async function buyTargetWithRun(runOffered) {
const proposal = harden({
want: { Out: AmountMath.makeEmpty(bldBrand, AssetKind.NAT) },
want: { Out: AmountMath.makeEmpty(targetBrand, AssetKind.NAT) },
give: { In: runOffered },
});
const payment = harden({ In: E(runPurse).withdraw(runOffered) });
Expand All @@ -118,54 +121,66 @@ export default async function startAgent([key, home]) {
]);
await Promise.all([
E(runPurse).deposit(refundPayout),
E(bldPurse).deposit(payout),
E(targetPurse).deposit(payout),
E(seatP).getOfferResult(),
]);
}

// perform the setup transfer
async function doSetupTransfer() {
let { run, bld } = await getBalances();
console.error(`trade-amm setup: initial RUN=${disp(run)} BLD=${disp(bld)}`);
let { run, target } = await getBalances();
console.error(
`trade-amm setup: outstanding RUN=${disp(run)} ${tradeToken}=${disp(
target,
)}`,
);
// eslint-disable-next-line no-constant-condition
if (1) {
// setup: buy BLD with 50% of our remaining RUN (33% of initial amount)
console.error(`trade-amm: buying BLD with 33% of our initial RUN`);
// setup: buy trade token with 50% of our remaining RUN (33% of initial amount)
console.error(
`trade-amm: buying ${tradeToken} with 50% of our outstanding RUN`,
);
const halfAmount = AmountMath.make(runBrand, run.value / BigInt(2));
await buyBldWithRun(halfAmount);
({ run, bld } = await getBalances());
await buyTargetWithRun(halfAmount);
({ run, target } = await getBalances());
}
// we sell 1% of the holdings each time
const runPerCycle = AmountMath.make(runBrand, run.value / BigInt(100));
const bldPerCycle = AmountMath.make(bldBrand, bld.value / BigInt(100));
console.error(`setup: RUN=${disp(run)} BLD=${disp(bld)}`);
const targetPerCycle = AmountMath.make(
targetBrand,
target.value / BigInt(100),
);
console.error(`setup: RUN=${disp(run)} ${tradeToken}=${disp(target)}`);
console.error(
`will trade about ${disp(runPerCycle)} RUN and ${disp(
bldPerCycle,
)} BLD per cycle`,
targetPerCycle,
)} ${tradeToken} per cycle`,
);
console.error(`trade-amm: initial trade complete`);
}
await doSetupTransfer();

const agent = Far('AMM agent', {
async doAMMCycle() {
console.error('trade-amm cycle: BLD->RUN');
const bld = await getBalance('BLD');
const bldOffered = AmountMath.make(bldBrand, bld.value / BigInt(100));
await buyRunWithBld(bldOffered);
console.error(`trade-amm cycle: ${tradeToken}->RUN`);
const target = await getBalance(tradeToken);
const targetOffered = AmountMath.make(
targetBrand,
target.value / BigInt(100),
);
await buyRunWithTarget(targetOffered);

console.error('trade-amm cycle: RUN->BLD');
console.error(`trade-amm cycle: RUN->${tradeToken}`);
const run = await getBalance('RUN');
const runOffered = AmountMath.make(runBrand, run.value / BigInt(100));
await buyBldWithRun(runOffered);
await buyTargetWithRun(runOffered);

const [newRunBalance, newBldBalance] = await Promise.all([
const [newRunBalance, newTargetBalance] = await Promise.all([
E(runPurse).getCurrentAmount(),
E(bldPurse).getCurrentAmount(),
E(targetPurse).getCurrentAmount(),
]);
console.error('trade-amm cycle: done');
return [newRunBalance, newBldBalance];
return [newRunBalance, newTargetBalance];
},
});

Expand Down
2 changes: 2 additions & 0 deletions loadgen/contract/petnames.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
export const pursePetnames = {
RUN: 'Agoric RUN currency',
BLD: 'Agoric staking token',
USDC: 'USD Coin',
};

export const issuerPetnames = {
RUN: 'RUN',
BLD: 'BLD',
USDC: 'USDC',
};
2 changes: 1 addition & 1 deletion loadgen/loop.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ let pushBroker = null;
const tasks = {
// we must start the AMM task before other tasks:
// - AMM sets up Zoe fee purse
// - AMM exchanges some RUN for BLD, and Vault measures the balances
// - AMM exchanges some RUN for target token, and Vault measures the balances
amm: [prepareAMMTrade],
vault: [prepareVaultCycle],
faucet: [prepareFaucet],
Expand Down
19 changes: 10 additions & 9 deletions loadgen/task-create-vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
import path from 'path';
import { E } from '@agoric/eventual-send';
import { disp } from './contract/display.js';
import { collateralToken } from './config.js';

// Prepare to create and close a vault on each cycle. We measure our
// available BLD at startup. On each cycle, we deposit 1% of that value as
// available collateral token at startup. On each cycle, we deposit 1% of that value as
// collateral to borrow as much RUN as they'll give us. Then we pay back the
// loan (using all the borrowed RUN, plus some more as a fee), getting back
// most (but not all?) of our BLD. If we are not interrupted, we finish each
// cycle with slightly less BLD and RUN than we started (because of fees),
// most (but not all?) of our collateral. If we are not interrupted, we finish each
// cycle with slightly less collateral and RUN than we started (because of fees),
// but with no vaults or loans outstanding.

// Make sure to run this after task-trade-amm has started (which converts 50%
// of our BLD into RUN), so we don't TOCTTOU ourselves into believing we have
// twice as much BLD as we really do.
// of our RUN into collateral), so we don't TOCTTOU ourselves into believing we have
// a different amount of collateral.

export async function prepareVaultCycle(homePromise, deployPowers) {
const key = 'open-close-vault';
Expand All @@ -26,14 +27,14 @@ export async function prepareVaultCycle(homePromise, deployPowers) {
const agentBundle = await bundleSource(agentFn);
// create the solo-side agent to drive each cycle, let it handle zoe
const installerP = E(spawner).install(agentBundle);
agent = await E(installerP).spawn([key, home]);
agent = await E(installerP).spawn([key, home, collateralToken]);
}

async function vaultCycle() {
const [newRunBalance, newBldBalance] = await E(agent).doVaultCycle();
const [newRunBalance, newCollateralBalance] = await E(agent).doVaultCycle();
console.log(
`create-vault done: RUN=${disp(newRunBalance)} BLD=${disp(
newBldBalance,
`create-vault done: RUN=${disp(newRunBalance)} ${collateralToken}=${disp(
newCollateralBalance,
)}`,
);
}
Expand Down
Loading

0 comments on commit b1d8d24

Please sign in to comment.