Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet UI working with new Smart Wallet contract #6134

Merged
merged 17 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
cli: [link-cli, local-npm]
# FIXME: Use this to enable NPM deploys...
# cli: [link-cli, local-npm]
cli: [link-cli]
steps:
- uses: actions/checkout@v2

Expand Down
3 changes: 3 additions & 0 deletions packages/casting/src/change-follower.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@ export const makePollingChangeFollower = async leader => {
return Far('polling change follower', {
getLatestIterable: async () => iterable,
getEachIterable: async () => iterable,
getReverseIterable: async () => {
throw Error('not implemented for polling change follower');
},
});
};
45 changes: 43 additions & 2 deletions packages/casting/src/follower-cosmjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,21 @@ export const makeCosmjsFollower = (
}
}

/**
* @param {StreamCell<T>} streamCell
* @param {number} currentBlockHeight
* @yields {FollowerElement<T>}
*/
function* reverseValuesFromCell(streamCell, currentBlockHeight) {
for (let i = streamCell.values.length - 1; i >= 0; i -= 1) {
yield followerElementFromStreamCellValue(
streamCell.values[i],
streamCell.blockHeight,
currentBlockHeight,
);
}
}

/**
* @param {StreamCell<T>} streamCell
* @param {number} currentBlockHeight
Expand Down Expand Up @@ -444,7 +459,6 @@ export const makeCosmjsFollower = (
// If the block has no corresponding data, wait for the first block to
// contain data.
for (;;) {
cursorBlockHeight = await getBlockHeight();
cursorData = await getDataAtHeight(cursorBlockHeight);
if (cursorData.length !== 0) {
const cursorStreamCell = streamCellForData(
Expand All @@ -457,6 +471,7 @@ export const makeCosmjsFollower = (
// TODO Long-poll for next block
// https://github.com/Agoric/agoric-sdk/issues/6154
await E(leader).jitter(where);
cursorBlockHeight = await getBlockHeight();
}

// For each subsequent iteration, yield every value that has been
Expand Down Expand Up @@ -551,7 +566,27 @@ export const makeCosmjsFollower = (
}
}

// Enable the periodic fetch.
/**
* @param {number} cursorBlockHeight
* @yields {FollowerElement<T>}
*/
async function* getReverseIterableAtHeight(cursorBlockHeight) {
// Track the data for the last emitted cell (the cell at the
// cursorBlockHeight) so we know not to emit duplicates
// of that cell.
let cursorData;
while (cursorBlockHeight > 0) {
cursorData = await getDataAtHeight(cursorBlockHeight);
if (cursorData.length === 0) {
// No data at the cursor height, so signal beginning of stream.
return;
}
const cursorStreamCell = streamCellForData(cursorBlockHeight, cursorData);
yield* reverseValuesFromCell(cursorStreamCell, cursorBlockHeight);
cursorBlockHeight = cursorStreamCell.blockHeight - 1;
}
}

/** @type {Follower<FollowerElement<T>>} */
return Far('chain follower', {
async getLatestIterable() {
Expand All @@ -563,5 +598,11 @@ export const makeCosmjsFollower = (
}
return getEachIterableAtHeight(height);
},
async getReverseIterable({ height = undefined } = {}) {
if (height === undefined) {
height = await getBlockHeight();
}
return getReverseIterableAtHeight(height);
},
});
};
6 changes: 6 additions & 0 deletions packages/casting/src/follower.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ const makeSubscriptionFollower = spec => {
}
return mapAsyncIterable(ai, transform);
},

getReverseIterable: async () => {
throw Error(
'reverse iteration not implemented for subscription follower',
);
},
});
return follower;
};
Expand Down
18 changes: 18 additions & 0 deletions packages/casting/src/iterable.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,21 @@ export const iterateEach = (follower, options) =>
});
},
});

/**
* @template T
* @param {ERef<import('./types.js').Follower<T>>} follower
* @param {import('./types.js').IterateEachOptions} [options]
*/
export const iterateReverse = (follower, options) =>
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
// For now, just pass through the iterable.
Far('iterateReverse iterable', {
/** @returns {AsyncIterator<T>} */
[Symbol.asyncIterator]: () => {
const eachIterable = E(follower).getReverseIterable(options);
const iterator = E(eachIterable)[Symbol.asyncIterator]();
return Far('iterateEach iterator', {
next: () => E(iterator).next(),
});
},
});
1 change: 1 addition & 0 deletions packages/casting/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export {};
* @typedef {object} Follower
* @property {() => Promise<AsyncIterable<T>>} getLatestIterable
* @property {(options?: IterateEachOptions) => Promise<AsyncIterable<T>>} getEachIterable
* @property {(options?: IterateEachOptions) => Promise<AsyncIterable<T>>} getReverseIterable
*/

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/cosmic-swingset/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ scenario2-setup-nobuild:
$(AGCH) --home=t1/bootstrap keys add bootstrap --keyring-backend=test
$(AGCH) --home=t1/bootstrap keys show -a bootstrap --keyring-backend=test > t1/bootstrap-address
$(AGCH) --home=t1/n0 add-genesis-account `cat t1/bootstrap-address` $(BOOT_COINS)
# Create
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

# Create the (singleton) chain node.
$(AGCH) --home=t1/n0 --keyring-dir=t1/bootstrap gentx --keyring-backend=test bootstrap 73000000ubld --chain-id=$(CHAIN_ID)
$(AGCH) --home=t1/n0 collect-gentxs
Expand Down Expand Up @@ -211,7 +212,11 @@ t1-provision-one-with-powers: wait-for-cosmos
$(AGCH) --home=t1/bootstrap tx swingset provision-one --keyring-backend=test --from=bootstrap \
--gas=auto --gas-adjustment=$(GAS_ADJUSTMENT) --broadcast-mode=block --yes --chain-id=$(CHAIN_ID) \
t1/$(BASE_PORT) $$addr $(AGORIC_POWERS) -ojson | tee /dev/stderr | grep -q '"code":0'; }


# Send some USDC to the vbank/provision module account where it can be traded for IST.
fund-provision-pool: wait-for-cosmos
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 ty

idea: make fund-acct depend on this having run. E.g. make this have a side-effect of noting its completion and have fund-acct depend on that, so that it runs if necesssary..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied to 8fa8326

$(MAKE) ACCT_ADDR=agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346 SOLO_COINS=1234000000ibc/usdc1234 fund-acct

fund-acct: wait-for-cosmos
$(AGCH) \
--home=t1/bootstrap --keyring-backend=test --from=bootstrap \
Expand Down
2 changes: 1 addition & 1 deletion packages/smart-wallet/src/smartWallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ const finish = ({ state, facets }) => {
{
brand: desc.brand,
issuer: desc.issuer,
petname: desc.proposedName,
petname: desc.issuerName,
},
purse,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/smart-wallet/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { observeIteration, subscribeEach } from '@agoric/notifier';
* If this proves to be a problem we can add an option to this or a related
* utility to reset state from RPC.
*
* @param {ERef<StoredSubscriber<import('./smartWallet').UpdateRecord>>} updates
* @param {ERef<Subscriber<import('./smartWallet').UpdateRecord>>} updates
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 this doesn't care about storage

*/
export const coalesceUpdates = updates => {
/** @type {Map<Brand, import('./smartWallet').BrandDescriptor>} */
Expand Down
3 changes: 1 addition & 2 deletions packages/smart-wallet/test/devices.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import bundleCentralSupply from '@agoric/vats/bundles/bundle-centralSupply.js';
import bundleMintHolder from '@agoric/vats/bundles/bundle-mintHolder.js';
import bundleWalletFactory from '@agoric/vats/bundles/bundle-legacy-walletFactory.js';
import bundleWalletFactory from '@agoric/vats/bundles/bundle-walletFactory.js';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a yarn build for this package now that builds ../bundles/bundle-walletFactory.js.

Consider using that instead, so while making changes to this package you don't have to build all vats.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I lean toward: you can assume yarn build is up-to-date for other packages, but changes within a package should get bundled at test-time within a package. So this one is a little awkward: it depends on yarn build in vats whenever a change to the walletFactory contract in this package is changed.

hm. not sure I have a concrete suggestion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by that principle shouldn't this be as suggested?

Suggested change
import bundleWalletFactory from '@agoric/vats/bundles/bundle-walletFactory.js';
import bundleWalletFactory from '../bundles/bundle-walletFactory.js';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's good, provided the test.before() auto-updates that bundle.

import bundleProvisionPool from '@agoric/vats/bundles/bundle-provisionPool.js';

export const devices = {
Expand All @@ -12,7 +12,6 @@ export const devices = {
return bundleCentralSupply;
case 'mintHolder':
return bundleMintHolder;
// TODO(PS0) replace this bundle with the non-legacy smart-wallet
case 'walletFactory':
return bundleWalletFactory;
case 'provisionPool':
Expand Down
2 changes: 1 addition & 1 deletion packages/vats/decentral-core-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"sourceSpec": "@agoric/wallet/contract/src/singleWallet.js"
},
"walletFactory": {
"sourceSpec": "@agoric/wallet/contract/src/walletFactory.js"
"sourceSpec": "@agoric/smart-wallet/src/walletFactory.js"
},
"zoe": {
"sourceSpec": "@agoric/vats/src/vat-zoe.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/vats/decentral-demo-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"sourceSpec": "@agoric/wallet/contract/src/singleWallet.js"
},
"walletFactory": {
"sourceSpec": "@agoric/wallet/contract/src/walletFactory.js"
"sourceSpec": "@agoric/smart-wallet/src/walletFactory.js"
},
"zoe": {
"sourceSpec": "@agoric/vats/src/vat-zoe.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/vats/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test:xs": "exit 0",
"lint-fix": "yarn lint:eslint --fix",
"lint": "run-s --continue-on-error lint:*",
"lint:types": "tsc --maxNodeModuleJsDepth 4 -p jsconfig.json",
"lint:types": "tsc --maxNodeModuleJsDepth 5 -p jsconfig.json",
"lint:eslint": "eslint ."
},
"keywords": [],
Expand Down
4 changes: 2 additions & 2 deletions packages/vats/scripts/build-bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const sourceToBundle = [
`../bundles/bundle-singleWallet.js`,
],
[
`@agoric/wallet/contract/src/walletFactory.js`,
`../bundles/bundle-legacy-walletFactory.js`,
`@agoric/smart-wallet/src/walletFactory.js`,
`../bundles/bundle-walletFactory.js`,
],
];

Expand Down
2 changes: 1 addition & 1 deletion packages/vats/src/core/startWalletFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Stable } from '../tokens.js';

/**
* @param {ERef<ZoeService>} zoe
* @param {Installation<import('@agoric/legacy-smart-wallet/src/walletFactory').start>} inst
* @param {Installation<import('@agoric/smart-wallet/src/walletFactory').start>} inst
* @typedef {Awaited<ReturnType<typeof startFactoryInstance>>} WalletFactoryStartResult
*/
// eslint-disable-next-line no-unused-vars
Expand Down
2 changes: 1 addition & 1 deletion packages/vats/src/core/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
* interchainPool: Promise<Installation<import('@agoric/inter-protocol/src/interchainPool.js').start>>,
* mintHolder: Promise<Installation<import('@agoric/vats/src/mintHolder.js').start>>,
* singleWallet: Promise<Installation<import('@agoric/legacy-smart-wallet/src/singleWallet.js').start>>,
* walletFactory: Promise<Installation<import('@agoric/legacy-smart-wallet/src/walletFactory.js').start>>,
* walletFactory: Promise<Installation<import('@agoric/smart-wallet/src/walletFactory.js').start>>,
* },
* },
* instance:{
Expand Down
2 changes: 1 addition & 1 deletion packages/vats/test/devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import bundlePSMCharter from '@agoric/inter-protocol/bundles/bundle-psmCharter.j
import bundleCentralSupply from '../bundles/bundle-centralSupply.js';
import bundleMintHolder from '../bundles/bundle-mintHolder.js';
import bundleSingleWallet from '../bundles/bundle-singleWallet.js';
import bundleWalletFactory from '../bundles/bundle-legacy-walletFactory.js';
import bundleWalletFactory from '../bundles/bundle-walletFactory.js';
import bundleProvisionPool from '../bundles/bundle-provisionPool.js';

const bundles = {
Expand Down
6 changes: 4 additions & 2 deletions packages/wallet/api/src/marshal-contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ const initSlotVal = (table, slot, val) => {

/**
* Make context for exporting wallet data where brands etc. can be recognized by boardId.
* Export for use outside the smart wallet.
*
* When serializing wallet state for, there's a tension between
*
Expand Down Expand Up @@ -236,7 +235,7 @@ const defaultMakePresence = iface => {
};

/**
* Make context for marshalling wallet or board data. To be imported into the client, which never makes objects.
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
* Make context for unserializing wallet or board data.
*
* @param {(iface: string) => unknown} [makePresence]
*/
Expand Down Expand Up @@ -295,6 +294,9 @@ export const makeImportContext = (makePresence = defaultMakePresence) => {
* @param {string} iface
*/
fromMyWallet: (slot, iface) => {
if (!slot) {
return makePresence('dummy');
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
}
const { kind, id } = parseWalletSlot(walletObjects, slot);
return kind
? provideVal(walletObjects[kind], id, iface)
Expand Down
57 changes: 3 additions & 54 deletions packages/wallet/contract/README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,5 @@
# Smart Wallet contracts
# Legacy Smart Wallet contract

## Single contract
This used to have the multi-tenant wallet but that moved to `packages/smart-wallet`. Now `walletFactory` refers to that one.

The `singleWalet` contract manages a single smart wallet.

# Multi-tenant contract

The `walletFactory` contract provisions and manages smart wallets.

## Common

There can be zero or one wallets per Cosmos address.

lib-wallet has makeWallet but that's really makeWalletKit

1. Generate an address (off-chain)
2. Provision an account using that address, which causes a Bank to get created
??? What happens if you try to provision again using the same address? It's a Cosmos level transaction; maybe that fails.
3. Create a Wallet using the Bank (it includes the implementation of Virtual Purses so when you getAmount it goes down to the Golang layer)
??? What happens if you try to create another wallet using that bank?

1 Address : 0/1 Bank
1 Address : 1 `myAddressNamesAdmin`
1 Bank : 0/1 Wallet

By design there's a 1:1 across all four.

`namesByAddress` and `board` are shared by everybody.

`myAddressNamesAdmin` is from the account you provision.

# Testing
There are no automated tests yet verifying the smart wallet running on chain. Here are procedures you can use instead.

## Notifiers

```
# tab 1 (chain)
cd packages/cosmic-swingset/
make scenario2-setup scenario2-run-chain
# starts bare chain, don’t need AMM

# tab 2 (client server)
cd packages/cosmic-swingset/
make scenario2-run-client
# confirm no errors in logs

# tab 3 (interactive)
agoric open --repl
# confirm in browser that `home.wallet` and `home.smartWallet` exist
agd query vstorage keys 'published.wallet'
# confirm it has a key like `published.wallet.agoric1nqxg4pye30n3trct0hf7dclcwfxz8au84hr3ht`
agoric follow :published.wallet.agoric1nqxg4pye30n3trct0hf7dclcwfxz8au84hr3ht
# confirm it has JSON data
```
This package retains `singleWallet` which is a contract-wrapper around the solo wallet. That too is not long for this life.
Loading