Skip to content

Commit

Permalink
feat(localchain): add withdraw facet to localChainAccount
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed May 17, 2024
1 parent 8733efe commit 4286fe7
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 12 deletions.
26 changes: 19 additions & 7 deletions packages/vats/src/localchain.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// @ts-check
import { E } from '@endo/far';
import { M } from '@endo/patterns';
import { AmountShape } from '@agoric/ertp';
import { AmountShape, PaymentShape } from '@agoric/ertp';

const { Fail } = assert;

/**
* @import {Amount, Payment} from '@agoric/ertp/exported.js';
* @import {TypedJson, ResponseTo} from '@agoric/cosmic-proto';
* @import {BankManager, Bank} from './vat-bank.js';
* @import {ScopedBridgeManager} from './types.js';
*/

/** @import {TypedJson, ResponseTo} from '@agoric/cosmic-proto'; */

/**
* @typedef {{
* system: ScopedBridgeManager;
Expand All @@ -28,9 +28,8 @@ const { Fail } = assert;

export const LocalChainAccountI = M.interface('LocalChainAccount', {
getAddress: M.callWhen().returns(M.string()),
deposit: M.callWhen(M.remotable('Payment'))
.optional(M.pattern())
.returns(AmountShape),
deposit: M.callWhen(PaymentShape).returns(AmountShape),
withdraw: M.callWhen(AmountShape).returns(PaymentShape),
executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())),
});

Expand All @@ -54,7 +53,8 @@ const prepareLocalChainAccount = zone =>
* This is safe, since even if the payment lies about its brand, ERTP will
* reject spoofed payment objects when depositing into a purse.
*
* @param {Payment} payment
* @param {Payment<'nat'>} payment
* @returns {Promise<Amount>}
*/
async deposit(payment) {
const { bank } = this.state;
Expand All @@ -63,6 +63,18 @@ const prepareLocalChainAccount = zone =>
const purse = E(bank).getPurse(allegedBrand);
return E(purse).deposit(payment);
},
/**
* Withdraw a payment from the account's bank purse that matches the
* alleged brand.
*
* @param {Amount<'nat'>} amount
* @returns {Promise<Payment>} payment
*/
async withdraw(amount) {
const { bank } = this.state;
const purse = E(bank).getPurse(amount.brand);
return E(purse).withdraw(amount);
},
/**
* Execute a batch of transactions and return the responses. Use
* `typedJson()` on the arguments to get typed return values.
Expand Down
81 changes: 76 additions & 5 deletions packages/vats/test/localchain.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'
import { E } from '@endo/far';
import { M, matches } from '@endo/patterns';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { AmountMath, AssetKind, makeIssuerKit } from '@agoric/ertp';
import { buildRootObject } from '../src/vat-bank.js';
import { prepareLocalChainTools } from '../src/localchain.js';

Expand Down Expand Up @@ -36,6 +37,18 @@ const makeTestContext = async t => {
ret = 'agoricfoo';
break;
}
case 'VBANK_GET_BALANCE': {
const { address, denom, type: _type, ...rest } = obj;
t.is(address, 'agoricfoo');
t.deepEqual(rest, {});
if (denom === 'ubld') {
ret = '100000000';
} else {
t.fail(`unrecognized denomination ${denom}`);
}
break;
}

default: {
t.is(obj, null);
}
Expand All @@ -58,6 +71,21 @@ const makeTestContext = async t => {
bankManager,
};
const localchain = makeLocalChain(powers);
const bldIssuerKit = makeIssuerKit(
'BLD',
AssetKind.NAT,
harden({ decimalPlaces: 6 }),
);

const oneHundredBldAmt = AmountMath.make(bldIssuerKit.brand, 100_000_000n);
const oneHundredBldPmt = bldIssuerKit.mint.mintPayment(oneHundredBldAmt);

const beanIssuerKit = makeIssuerKit(
'BEAN',
AssetKind.NAT,
harden({ decimalPlaces: 6 }),
);
const oneHundredBeanAmt = AmountMath.make(beanIssuerKit.brand, 100_000_000n);

return {
baggage,
Expand All @@ -67,6 +95,11 @@ const makeTestContext = async t => {
bankVat,
bankManager,
localchain,
bldIssuerKit,
bldIssuer: bldIssuerKit.issuer,
oneHundredBldPmt,
oneHundredBldAmt,
oneHundredBeanAmt,
};
};

Expand All @@ -75,11 +108,49 @@ test.beforeEach(t => {
});

test('localchain - deposit and withdraw', async t => {
const { localchain } = await t.context;
const user = async () => {
const { localchain } = await t.context;
const lca = await E(localchain).makeAccount();
t.true(matches(lca, M.remotable('LocalChainAccount')));

const lca = await E(localchain).makeAccount();
t.true(matches(lca, M.remotable('LocalChainAccount')));
const address = await E(lca).getAddress();
t.is(address, 'agoricfoo');
return { lca };
};
const { lca } = await user();

const address = await E(lca).getAddress();
t.is(address, 'agoricfoo');
const gov = async () => {
const { bankManager, bldIssuerKit } = await t.context;
await t.notThrowsAsync(
E(bankManager).addAsset('ubld', 'BLD', 'Staking Token', bldIssuerKit),
);
};
await gov();

/** @param {import('../src/localchain.js').LocalChainAccount} usersLca */
const userDeposit = async usersLca => {
const { oneHundredBldPmt, oneHundredBldAmt } = await t.context;
const res = await E(usersLca).deposit(oneHundredBldPmt);
t.deepEqual(
res,
oneHundredBldAmt,
'deposit returns description of payment',
);
};
await userDeposit(lca);

/** @param {import('../src/localchain.js').LocalChainAccount} usersLca */
const userWithdraw = async usersLca => {
const { oneHundredBldAmt, bldIssuer, oneHundredBeanAmt } = await t.context;

const payment = await E(usersLca).withdraw(oneHundredBldAmt);
// @ts-expect-error Argument of type 'Payment' is not assignable to parameter of type 'ERef<Payment<"nat", any>>'.
const paymentAmount = await E(bldIssuer).getAmountOf(payment);
t.deepEqual(paymentAmount, oneHundredBldAmt, 'withdraw returns a payment');

await t.throwsAsync(() => E(usersLca).withdraw(oneHundredBeanAmt), {
message: /not found in collection "brandToAssetRecord"/,
});
};
await userWithdraw(lca);
});

0 comments on commit 4286fe7

Please sign in to comment.