Skip to content

Commit

Permalink
Merge pull request #3986 from Agoric/mfig-pegasus-simplify
Browse files Browse the repository at this point in the history
Simplify Pegasus for POLA
  • Loading branch information
mergify[bot] authored Feb 4, 2022
2 parents 3b134fb + bb90e44 commit 529bf23
Show file tree
Hide file tree
Showing 11 changed files with 830 additions and 571 deletions.
21 changes: 11 additions & 10 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,16 +457,17 @@ export default function buildKernel(
assert.typeof(consumed, 'number');
used = BigInt(consumed);
policyInput = ['crank', { computrons: used }];
}
if (useMeter && metering !== null && meterID) {
// If we have a Meter, and the manager reported a non-null value, use it.
assert(used !== undefined);
const underflow = deductMeter(meterID, used, true);
if (underflow) {
console.log(`meter ${meterID} underflow, terminating vat ${vatID}`);
const err = makeError('meter underflow, vat terminated');
setTerminationTrigger(vatID, true, true, err);
return harden(['crank-failed', {}]);
if (useMeter && meterID) {
// If we have a Meter and we want to use it, do so.
const underflow = deductMeter(meterID, used, true);
if (underflow) {
console.log(
`meter ${meterID} underflow, terminating vat ${vatID}`,
);
const err = makeError('meter underflow, vat terminated');
setTerminationTrigger(vatID, true, true, err);
return harden(['crank-failed', {}]);
}
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions packages/notifier/src/notifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const makeNotifierKit = (...args) => {
...baseNotifier,
});

const updater = harden({
const updater = Far('updater', {
updateState(state) {
if (final()) {
throw new Error('Cannot update state after termination.');
Expand Down Expand Up @@ -155,7 +155,6 @@ export const makeNotifierKit = (...args) => {
* @returns {Notifier<T>}
*/
export const makeNotifierFromAsyncIterable = asyncIterableP => {
/** @type {ERef<AsyncIterator<T>>} */
const iteratorP = E(asyncIterableP)[Symbol.asyncIterator]();

/** @type {Promise<UpdateRecord<T>>|undefined} */
Expand Down
40 changes: 22 additions & 18 deletions packages/pegasus/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ In agoric-sdk, start the chain:
```
yarn build
cd packages/cosmic-swingset
make scenario2-setup-nobuild scenario2-run-chain
# In another terminal:
make scenario2-setup-nobuild
# Look for the bootstrap key mnemonic in the command output marked with **Important**
make scenario2-run-chain
# In another terminal, restore that mnemonic (using the below Hermes config):
hermes keys restore agoric -p "m/44'/564'/0'/0/0" -m 'under cake there slam...'
# Run the client
make scenario2-run-client
```

Expand All @@ -23,13 +27,13 @@ host = '127.0.0.1'
port = 3001

[[chains]]
id = 'agoricstage-15'
rpc_addr = 'http://143.198.36.204:26657'
grpc_addr = 'http://143.198.36.204:9090'
websocket_addr = 'ws://143.198.36.204:26657/websocket'
id = 'agoric'
rpc_addr = 'http://127.0.0.1:26657'
grpc_addr = 'http://127.0.0.1:9090'
websocket_addr = 'ws://127.0.0.1:26657/websocket'
rpc_timeout = '10s'
account_prefix = 'agoric'
key_name = 'stagekey'
key_name = 'localkey'
store_prefix = 'ibc'
max_gas = 3000000
gas_price = { price = 0.001, denom = 'urun' }
Expand Down Expand Up @@ -134,27 +138,27 @@ When Golang relayer says "no packets to relay", or when Hermes started, go to Ag
command[0]
E(home.pegasusConnections).entries()
history[0]
[["/ibc-port/transfer/unordered/ics20-1/ibc-channel/channel-0",[Alleged: Connection]{}]]
[["/ibc-port/transfer/unordered/ics20-1/ibc-channel/channel-0",{"actions":[Object Alleged: pegasusConnectionActions]{},"localAddr":"/ibc-port/transfer/unordered/ics20-1/ibc-channel/channel-0","remoteAddr":"/ibc-hop/connection-0/ibc-port/transfer/unordered/ics20-1/ibc-channel/channel-270","remoteDenomSubscription":[Object Alleged: Subscription]{}}]]
command[1]
E(home.agoricNames).lookup('instance', 'Pegasus')
E(history[0][0][1].actions).pegRemote('Muon', 'umuon', 'nat', { decimalPlaces: 6 })
history[1]
[Alleged: InstanceHandle]{}
[Object Alleged: Muon peg]{}
command[2]
E(home.zoe).getPublicFacet(history[1])
E(home.agoricNames).lookup('instance', 'Pegasus')
history[2]
[Alleged: presence o-61]{}
[Object Alleged: InstanceHandle]{}
command[3]
E(history[2]).pegRemote('Muon', history[0][0][1], 'umuon', 'nat', { decimalPlaces: 6 })
E(home.zoe).getPublicFacet(history[2])
history[3]
[Alleged: presence o-158]{}
[Object Alleged: pegasus]{}
command[4]
E(history[3]).getLocalBrand()
E(history[1]).getLocalBrand()
history[4]
[Alleged: Local1 Brand ...]
[Object Alleged: Local1 brand]{}
command[5]
E(history[2]).getLocalIssuer(history[4])
E(history[3]).getLocalIssuer(history[4])
history[5]
[issuer]
[Object Alleged: Local1 issuer]{}
command[6]
E(home.board).getId(history[5])
```
Expand Down
127 changes: 127 additions & 0 deletions packages/pegasus/src/courier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// @ts-check
import { details as X } from '@agoric/assert';

import { AmountMath } from '@agoric/ertp';
import { E } from '@agoric/eventual-send';
import { Far } from '@endo/marshal';
import { makeOncePromiseKit } from './once-promise-kit.js';

/**
* Create or return an existing courier promise kit.
*
* @template K
* @param {K} key
* @param {LegacyMap<K, PromiseRecord<Courier>>} keyToCourierPK
*/
export const getCourierPK = (key, keyToCourierPK) => {
if (keyToCourierPK.has(key)) {
return keyToCourierPK.get(key);
}

// This is the first packet for this denomination.
// Create a new Courier promise kit for it.
const courierPK = makeOncePromiseKit(X`${key} already pegged`);

keyToCourierPK.init(key, courierPK);
return courierPK;
};

/**
* Create the [send, receive] pair.
*
* @typedef {Object} CourierArgs
* @property {ContractFacet} zcf
* @property {ERef<BoardDepositFacet>} board
* @property {ERef<NameHub>} namesByAddress
* @property {Denom} remoteDenom
* @property {Brand} localBrand
* @property {(zcfSeat: ZCFSeat, amounts: AmountKeywordRecord) => void} retain
* @property {(zcfSeat: ZCFSeat, amounts: AmountKeywordRecord) => void} redeem
* @property {ERef<TransferProtocol>} transferProtocol
* @param {ERef<Connection>} connection
* @returns {(args: CourierArgs) => Courier}
*/
export const makeCourierMaker =
connection =>
({
zcf,
board,
namesByAddress,
remoteDenom,
localBrand,
retain,
redeem,
transferProtocol,
}) => {
/** @type {Sender} */
const send = async (zcfSeat, depositAddress) => {
const tryToSend = async () => {
const amount = zcfSeat.getAmountAllocated('Transfer', localBrand);
const transferPacket = await E(transferProtocol).makeTransferPacket({
value: amount.value,
remoteDenom,
depositAddress,
});

// Retain the payment. We must not proceed on failure.
retain(zcfSeat, { Transfer: amount });

// The payment is already escrowed, and proposed to retain, so try sending.
return E(connection)
.send(transferPacket)
.then(ack => E(transferProtocol).assertTransferPacketAck(ack))
.then(
_ => zcfSeat.exit(),
reason => {
// Return the payment to the seat, if possible.
redeem(zcfSeat, { Transfer: amount });
throw reason;
},
);
};

// Reflect any error back to the seat.
return tryToSend().catch(reason => {
zcfSeat.fail(reason);
});
};

/** @type {Receiver} */
const receive = async ({ value, depositAddress }) => {
const localAmount = AmountMath.make(localBrand, value);

// Look up the deposit facet for this board address, if there is one.
/** @type {DepositFacet} */
const depositFacet = await E(board)
.getValue(depositAddress)
.catch(_ => E(namesByAddress).lookup(depositAddress, 'depositFacet'));

const { userSeat, zcfSeat } = zcf.makeEmptySeatKit();

// Redeem the backing payment.
try {
redeem(zcfSeat, { Transfer: localAmount });
zcfSeat.exit();
} catch (e) {
zcfSeat.fail(e);
throw e;
}

// Once we've gotten to this point, their payment is committed and
// won't be refunded on a failed receive.
const payout = await E(userSeat).getPayout('Transfer');

// Send the payout promise to the deposit facet.
//
// We don't want to wait for the depositFacet to return, so that
// it can't hang up (i.e. DoS) an ordered channel, which relies on
// us returning promptly.
E(depositFacet)
.receive(payout)
.catch(_ => {});

return E(transferProtocol).makeTransferPacketAck(true);
};

return Far('courier', { send, receive });
};
120 changes: 120 additions & 0 deletions packages/pegasus/src/ics20.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// @ts-check
import { Nat } from '@agoric/nat';
import { Far } from '@endo/marshal';
import { assert, details as X } from '@agoric/assert';

/**
* @typedef {Object} ICS20TransferPacket Packet shape defined at:
* https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures
* @property {string} amount The extent of the amount
* @property {Denom} denom The denomination of the amount
* @property {string} [sender] The sender address
* @property {DepositAddress} receiver The receiver deposit address
*/

/**
* @param {string} s
*/
const safeJSONParseObject = s => {
/** @type {unknown} */
let obj;
try {
obj = JSON.parse(s);
} catch (e) {
assert.note(e, X`${s} is not valid JSON`);
throw e;
}
assert.typeof(obj, 'object', X`${s} is not a JSON object`);
assert(obj !== null, X`${s} is null`);
return obj;
};

/**
* Convert an inbound packet to a local amount.
*
* @param {Bytes} packet
* @returns {Promise<PacketParts>}
*/
export const parseICS20TransferPacket = async packet => {
const ics20Packet = safeJSONParseObject(packet);
const { amount, denom, receiver } = ics20Packet;

assert.typeof(denom, 'string', X`Denom ${denom} must be a string`);
assert.typeof(receiver, 'string', X`Receiver ${receiver} must be a string`);

// amount is a string in JSON.
assert.typeof(amount, 'string', X`Amount ${amount} must be a string`);
const bigValue = BigInt(amount);

// If we overflow, or don't have a non-negative integer, throw an exception!
const value = Nat(bigValue);

return harden({
depositAddress: receiver,
remoteDenom: denom,
value,
});
};

/**
* Convert the amount to a packet to send. PacketParts.value is limited to
* fungible (bigint) amounts.
*
* @param {PacketParts} param0
* @returns {Promise<Bytes>}
*/
export const makeICS20TransferPacket = async ({
value,
remoteDenom,
depositAddress,
}) => {
// We're using Nat as a dynamic check for overflow.
// @ts-ignore - this causes errors on some versions of TS, but not others.
const stringValue = String(Nat(value));

// Generate the ics20-1 packet.
/** @type {ICS20TransferPacket} */
const ics20 = {
amount: stringValue,
denom: remoteDenom,
receiver: depositAddress,
};

return JSON.stringify(ics20);
};

/**
* Check the results of the transfer.
*
* @param {Bytes} ack
* @returns {Promise<void>}
*/
export const assertICS20TransferPacketAck = async ack => {
const { success, error } = safeJSONParseObject(ack);
assert(success, X`ICS20 transfer error ${error}`);
};

/**
* Create results of the transfer. Acknowledgement shape defined at:
* https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures
*
* @param {boolean} success
* @param {any} error
* @returns {Promise<Bytes>}
*/
export const makeICS20TransferPacketAck = async (success, error) => {
if (success) {
const ack = { success: true };
return JSON.stringify(ack);
}
const nack = { success: false, error: `${error}` };
return JSON.stringify(nack);
};

/** @type {TransferProtocol} */
export const ICS20TransferProtocol = Far('ics20-1 transfer protocol', {
makeTransferPacket: makeICS20TransferPacket,
assertTransferPacketAck: assertICS20TransferPacketAck,
parseTransferPacket: parseICS20TransferPacket,
makeTransferPacketAck: makeICS20TransferPacketAck,
});
Loading

0 comments on commit 529bf23

Please sign in to comment.