Skip to content

Commit

Permalink
Add "myFirstDapp" contract (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
warner authored and katelynsills committed Nov 3, 2019
1 parent f6b4155 commit 18a8fc6
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 1,744 deletions.
16 changes: 13 additions & 3 deletions lib/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ export default async function initMain(progname, rawArgs, priv) {
}
const [DIR] = args;

const { mkdir, stat, lstat, symlink, readdir, readFile, writeFile } = fs.promises;
const {
mkdir,
stat,
lstat,
symlink,
readdir,
readFile,
writeFile,
} = fs.promises;

console.log(`initializing ${DIR}`);
try {
Expand All @@ -28,9 +36,11 @@ export default async function initMain(progname, rawArgs, priv) {
const templateDir = `${__dirname}/../template`;
const writeTemplate = async stem => {
const template = await readFile(`${templateDir}${stem}`, 'utf-8');
const content = template.replace(/['"]@DIR@['"]/g, JSON.stringify(DIR)).replace(/@DIR@/g, DIR);
const content = template
.replace(/['"]@DIR@['"]/g, JSON.stringify(DIR))
.replace(/@DIR@/g, DIR);
return writeFile(`${DIR}${stem}`, content);
}
};

const recursiveTemplate = async (templateDir, suffix = '') => {
const cur = `${templateDir}${suffix}`;
Expand Down
28 changes: 0 additions & 28 deletions template/contract/automaticRefund.js

This file was deleted.

78 changes: 73 additions & 5 deletions template/contract/deploy.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,86 @@
// Agoric Dapp contract deployment script
import fs from 'fs';

import harden from '@agoric/harden';

const DAPP_NAME = "@DIR@";

export default async function deployContract(homeP, { bundleSource, pathResolve }) {
// Create a source bundle for the Zoe automaticRefeund contract.
const { source, moduleFormat } = await bundleSource(`./automaticRefund.js`);
// Create a source bundle for the "myFirstDapp" smart contract.
const { source, moduleFormat } = await bundleSource(`./myFirstDapp.js`);

const contractHandle = homeP~.zoe~.install(source, moduleFormat);
const contractID = await homeP~.registrar~.register(DAPP_NAME, contractHandle);
const installationHandle = await homeP~.zoe~.install(source, moduleFormat);
const contractId = await homeP~.registrar~.register(DAPP_NAME, installationHandle);
const contractsJson = JSON.stringify({
[DAPP_NAME]: contractID,
[DAPP_NAME]: contractId,
});

const CONTRACT_NAME = 'myFirstDapp';

console.log('- myFirstDapp installed', CONTRACT_NAME, '=>', installationHandle);

// 1. Assays
const assays = await Promise.all([
homeP~.moolaMint~.getAssay(),
homeP~.simoleanMint~.getAssay(),
]);

// 2. Contract instance.
try {
await homeP~.zoe~.makeInstance(installationHandle, { assays });
} catch(e) {}
const { instance, instanceHandle, terms } = await homeP~.zoe~.makeInstance(installationHandle, { assays });

// 3. Offer rules
const units = await Promise.all([
terms~.assays~.[0]~.makeUnits(10000),
terms~.assays~.[1]~.makeUnits(10000),
terms~.assays~.[2]~.makeUnits(0),
]);

const offerRules = harden({
payoutRules: [
{
kind: 'offerExactly',
units: units[0],
},
{
kind: 'offerExactly',
units: units[1],
},
{
kind: 'wantAtLeast',
units: units[2],
},
],
exitRule: {
kind: 'onDemand',
},
});

// 4. Payments (from mint, not from purse)

const faucets = await Promise.all([
homeP~.moolaMint~.mint(units[0]),
homeP~.simoleanMint~.mint(units[1]),
]);

const payments = await Promise.all([
faucets[0]~.withdrawAll(),
faucets[1]~.withdrawAll(),
]);

// 5. Liquidities.
const { escrowReceipt } = await homeP~.zoe~.escrow(offerRules, payments);
const liquidityOk = await instance~.addLiquidity(escrowReceipt);
console.log(liquidityOk);

if (liquidityOk) {
// Only store if the contract instance has liquidities.
const instanceId = await homeP~.registrar~.register(CONTRACT_NAME, instanceHandle);
console.log('- Autoswap instance', CONTRACT_NAME, '=>', instanceId);
}

// Save the contractID somewhere where the UI can find it.
const cjfile = pathResolve(`../api/contracts.json`);
console.log('writing', cjfile);
Expand Down
134 changes: 134 additions & 0 deletions template/contract/myFirstDapp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import harden from '@agoric/harden';
import { hasValidPayoutRules, makeUnits } from '@agoric/ertp/core/zoe/contracts/helpers/offerRules';
import { rejectOffer, defaultAcceptanceMsg } from '@agoric/ertp/core/zoe/contracts/helpers/userFlow';
import { vectorWith, vectorWithout } from '@agoric/ertp/core/zoe/contracts/helpers/extents';

import { makeMint } from '@agoric/ertp/core/mint';

/** EDIT THIS CONTRACT WITH YOUR OWN BUSINESS LOGIC */

/**
* This contract has a similar interface to the autoswap contract, but
* doesn't do much. The contract assumes that the first offer it
* receives adds 1 unit of each assay as liquidity. Then, a user can
* trade 1 of the first assay for 1 of the second assay and vice versa
* for as long as they want, as long as they alternate the direction
* of the trade.
*
* Please see autoswap.js for the real version of a uniswap implementation.
*/
export const makeContract = harden((zoe, terms) => {
// The user passes in an array of two assays for the two kinds of
// assets to be swapped.
const startingAssays = terms.assays;

// There is also a third assay, the assay for the liquidity token,
// which is created in this contract. We will return all three as
// the canonical array of assays for this contract

// TODO: USE THE LIQUIDITY MINT TO MINT TOKENS
const liquidityMint = makeMint('liquidity');
const liquidityAssay = liquidityMint.getAssay();
const assays = [...startingAssays, liquidityAssay];

// This offer handle is used to store the assets in the liquidity pool.
let poolOfferHandle;

// Let's make sure the swap offer that we get has the correct
// structure.
const isValidSimpleSwapOffer = myPayoutRules => {
const kindsOfferFirst = ['offerExactly', 'wantExactly', 'wantAtLeast'];
const kindsWantFirst = ['wantExactly', 'offerExactly', 'wantAtLeast'];
return (
(hasValidPayoutRules(kindsOfferFirst, assays, myPayoutRules) ||
hasValidPayoutRules(kindsWantFirst, assays, myPayoutRules)) &&
myPayoutRules[0].units.extent === 1 &&
myPayoutRules[1].units.extent === 1
);
};

// This dumb contract assumes that the first offer this
// receives is to add 1 unit of liquidity for both assays. If we
// don't do this, this contract will break.
const addLiquidity = async escrowReceipt => {
const { offerHandle } = await zoe.burnEscrowReceipt(escrowReceipt);
// TODO: CHECK HERE THAT OFFER IS A VALID LIQUIDITY OFFER

// Create an empty offer to represent the extents of the
// liquidity pool.
if (poolOfferHandle === undefined) {
poolOfferHandle = zoe.escrowEmptyOffer();
}

// This will only happen once so we will just swap the pool
// extents and the offer extents to put what was offered in the
// pool.
const offerHandles = harden([poolOfferHandle, offerHandle]);
const [poolExtents, offerExtents] = await zoe.getExtentsFor(offerHandles);

zoe.reallocate(offerHandles, [offerExtents, poolExtents]);
zoe.complete(harden([offerHandle]));

// TODO: MINT LIQUIDITY TOKENS AND REALLOCATE THEM TO THE USER
// THROUGH ZOE HERE
return 'Added liquidity';
};

// The price is always 1. Always.
// TODO: CHANGE THIS AND CREATE YOUR OWN BONDING CURVE
const getPrice = unitsIn => {
const indexIn = unitsIn[0].extent === 1 ? 0 : 1;
const indexOut = 1 - indexIn;
const extentOpsArray = zoe.getExtentOpsArray();
const labels = zoe.getLabels();
const unitsOut = makeUnits(extentOpsArray[indexOut], labels[indexOut], 1);
return unitsOut;
};

const makeOffer = async escrowReceipt => {
const {
offerHandle,
offerRules: { payoutRules },
} = await zoe.burnEscrowReceipt(escrowReceipt);

// TODO: CHANGE THIS TO ADD YOUR OWN CHECKS HERE
if (!isValidSimpleSwapOffer(payoutRules)) {
return rejectOffer(zoe, offerHandle, 'The swap offer was invalid.');
}
const offerHandles = harden([poolOfferHandle, offerHandle]);
const [poolExtents, offerExtents] = zoe.getExtentsFor(offerHandles);
const extentOpsArray = zoe.getExtentOpsArray();
const [firstExtent, secondExtent] = offerExtents;
const offerExtentsOut = [
secondExtent,
firstExtent,
extentOpsArray[2].empty(),
];
// we want to add the thing offered to the pool and give back the
// other thing
// TODO: ADD YOUR OWN LOGIC HERE
const newPoolExtents = vectorWithout(
extentOpsArray,
vectorWith(extentOpsArray, poolExtents, offerExtents),
offerExtentsOut,
);
zoe.reallocate(offerHandles, harden([newPoolExtents, offerExtentsOut]));
zoe.complete(harden([offerHandle]));
return defaultAcceptanceMsg;
};

// The API exposed to the user
const simpleSwap = harden({
addLiquidity,
getPrice,
makeOffer,
getLiquidityAssay: () => liquidityAssay,
// IMPLEMENT THIS
// removeLiquidity,
getPoolExtents: () => zoe.getExtentsFor(harden([poolOfferHandle]))[0],
});
return harden({
instance: simpleSwap,
assays,
});
});
Loading

0 comments on commit 18a8fc6

Please sign in to comment.