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

claim spoofing fix #149

Merged
merged 12 commits into from
Jan 6, 2025
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
6 changes: 6 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
"ghcr.io/devcontainers/features/node:1": {}
}
}
21 changes: 10 additions & 11 deletions contract/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CHAINID=agoriclocal
USER1ADDR=$(kagd keys show user1 -a --keyring-backend="test")
USER1ADDR=$(agd keys show tg -a --keyring-backend="test")
ACCT_ADDR=$(USER1ADDR)
BLD=000000ubld

Expand All @@ -13,8 +13,8 @@ list:
awk -v RS= -F: '$$1 ~ /^[^#%]+$$/ { print $$1 }'

balance-q:
kagd keys show user1 -a --keyring-backend="test"
kagd query bank balances $(ACCT_ADDR)
agd keys show tg -a --keyring-backend="test"
agd query bank balances $(ACCT_ADDR)
tgrecojs marked this conversation as resolved.
Show resolved Hide resolved

GAS_ADJUSTMENT=1.2
SIGN_BROADCAST_OPTS=--keyring-backend=test --chain-id=$(CHAINID) \
Expand All @@ -25,40 +25,40 @@ mint100:
make FUNDS=1000$(ATOM) fund-acct
cd /usr/src/agoric-sdk && \
yarn --silent agops vaults open --wantMinted 100 --giveCollateral 100 >/tmp/want-ist.json && \
yarn --silent agops perf satisfaction --executeOffer /tmp/want-ist.json --from user1 --keyring-backend=test
yarn --silent agops perf satisfaction --executeOffer /tmp/want-ist.json --from tg --keyring-backend=test

# Keep mint4k around a while for compatibility
mint4k:
make FUNDS=1000$(ATOM) fund-acct
cd /usr/src/agoric-sdk && \
yarn --silent agops vaults open --wantMinted 4000 --giveCollateral 1000 >/tmp/want4k.json && \
yarn --silent agops perf satisfaction --executeOffer /tmp/want4k.json --from user1 --keyring-backend=test
yarn --silent agops perf satisfaction --executeOffer /tmp/want4k.json --from tg --keyring-backend=test

FUNDS=321$(BLD)
fund-acct:
kagd tx bank send validator $(ACCT_ADDR) $(FUNDS) \
agd tx bank send validator $(ACCT_ADDR) $(FUNDS) \
$(SIGN_BROADCAST_OPTS) \
-o json >,tx.json
jq '{code: .code, height: .height}' ,tx.json

gov-q:
kagd query gov proposals --output json | \
agd query gov proposals --output json | \
jq -c '.proposals[] | [.proposal_id,.voting_end_time,.status]'

gov-voting-q:
kagd query gov proposals --status=voting_period --output json | \
agd query gov proposals --status=voting_period --output json | \
jq -c '.proposals[].proposal_id'

PROPOSAL=1
VOTE_OPTION=yes
vote:
kagd tx gov vote $(PROPOSAL) $(VOTE_OPTION) --from=validator \
agd tx gov vote $(PROPOSAL) $(VOTE_OPTION) --from=validator \
$(SIGN_BROADCAST_OPTS) \
-o json >,tx.json
jq '{code: .code, height: .height}' ,tx.json

instance-q:
kagd query vstorage data published.agoricNames.instance -o json
agd query vstorage data published.agoricNames.instance -o json

start-contract: check-contract-airdrop

Expand All @@ -73,7 +73,6 @@ start: make start-contract-airdrop start-contract
start-contract-airdrop:
yarn node scripts/deploy-contract.js \
--install src/airdrop.contract.js \
--eval src/platform-goals/board-aux.core.js \
--eval src/airdrop.proposal.js

start-contract-swap:
Expand Down
2 changes: 1 addition & 1 deletion contract/scripts/deploy-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const options = {
help: { type: 'boolean' },
install: { type: 'string' },
eval: { type: 'string', multiple: true },
service: { type: 'string', default: 'kagd' },
service: { type: 'string', default: 'agd' },
workdir: { type: 'string', default: '/workspace/contract' },
};
/**
Expand Down
140 changes: 106 additions & 34 deletions contract/src/airdrop.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,70 @@ import { E } from '@endo/far';
import { AmountMath, AmountShape, AssetKind, MintShape } from '@agoric/ertp';
import { TimeMath } from '@agoric/time';
import { TimerShape } from '@agoric/zoe/src/typeGuards.js';
import { bech32 } from 'bech32';
import { sha256 } from '@noble/hashes/sha256';
import { ripemd160 } from '@noble/hashes/ripemd160';
import {
atomicRearrange,
makeRatio,
withdrawFromSeat,
} from '@agoric/zoe/src/contractSupport/index.js';
import { decodeBase64 } from '@endo/base64';
import { divideBy } from '@agoric/zoe/src/contractSupport/ratio.js';
import { makeTracer } from '@agoric/internal';
import { makeTracer, mustMatch } from '@agoric/internal';
import { makeWaker, oneDay } from './helpers/time.js';
import {
handleFirstIncarnation,
makeCancelTokenMaker,
} from './helpers/validation.js';
import { makeStateMachine } from './helpers/stateMachine.js';
import { createClaimSuccessMsg } from './helpers/messages.js';
import { objectToMap } from './helpers/objectTools.js';
import { getMerkleRootFromMerkleProof } from './merkle-tree/index.js';
import '@agoric/zoe/exported.js';

const ProofDataShape = harden({
hash: M.string(),
direction: M.string(),
});

const OfferArgsShape = harden({
tier: M.number(),
key: M.string(),
proof: M.arrayOf(ProofDataShape),
});

const compose =
(...fns) =>
args =>
fns.reduceRight((x, f) => f(x), args);
const toAgoricBech = (data, limit) =>
bech32.encode('agoric', bech32.toWords(data), limit);

/**
* Creates a digest function for a given hash function.
*
* @param {object} hashFn - The hash function object (e.g., sha256, ripemd160). It must implement `create()` and the resulting object must implement `update()` and `digest()`.
* @returns {function(Uint8Array): Uint8Array} - A function that takes data and returns the digest.
*/
const createDigest =
hashFn =>
/**
* @param {Uint8Array} data - The data to hash.
* @returns {Uint8Array} - The hash digest.
*/
data =>
hashFn.create().update(data).digest();

const createSha256Digest = createDigest(sha256);
const createRipe160Digest = createDigest(ripemd160);

const computeAddress = compose(
toAgoricBech,
createRipe160Digest,
createSha256Digest,
decodeBase64,
);

const TT = makeTracer('ContractStartFn');

export const messagesObject = {
Expand Down Expand Up @@ -70,14 +116,13 @@ harden(RESTARTING);
/** @import {ContractMeta} from './@types/zoe-contract-facet.d'; */
/** @import {Remotable} from '@endo/marshal' */

export const privateArgsShape = harden({
marshaller: M.remotable('marshaller'),
storageNode: M.remotable('chainStorageNode'),
export const privateArgsShape = {
namesByAddress: M.remotable('marshaller'),
timer: TimerShape,
});
};
harden(privateArgsShape);

export const customTermsShape = harden({
export const customTermsShape = {
targetEpochLength: M.bigint(),
initialPayoutValues: M.arrayOf(M.bigint()),
tokenName: M.string(),
Expand All @@ -86,7 +131,7 @@ export const customTermsShape = harden({
startTime: M.bigint(),
feeAmount: AmountShape,
merkleRoot: M.string(),
});
};
harden(customTermsShape);

export const divideAmountByTwo = brand => amount =>
Expand Down Expand Up @@ -162,7 +207,21 @@ export const start = async (zcf, privateArgs, baggage) => {
/** @type {Zone} */
const zone = makeDurableZone(baggage, 'rootZone');

const { timer } = privateArgs;
const { timer, namesByAddress } = privateArgs;

/**
* @param {string} addr
* @returns {ERef<DepositFacet>}
*/
const getDepositFacet = addr => {
assert.typeof(addr, 'string');
console.log('geting deposit facet for::', addr);
const df = E(namesByAddress).lookup(addr, 'depositFacet');
console.log('------------------------');
console.log('df::', df);
return df;
};

/** @type {ContractTerms} */
const {
startTime = 120n,
Expand Down Expand Up @@ -344,49 +403,62 @@ export const start = async (zcf, privateArgs, baggage) => {
* tier: number;
* }} offerArgs
*/
const claimHandler = (claimSeat, offerArgs) => {
const {
give: { Fee: claimTokensFee },
} = claimSeat.getProposal();

const { proof, key: pubkey, address, tier } = offerArgs;
const claimHandler = async (claimSeat, offerArgs) => {
mustMatch(
offerArgs,
OfferArgsShape,
'offerArgs does not contain the correct data.',
);

// This line was added because of issues when testing
// Is there a way to gracefully test assertion failures????
if (accountStore.has(pubkey)) {
if (accountStore.has(offerArgs.key)) {
claimSeat.exit();
throw new Error(
`Allocation for address ${address} has already been claimed.`,
);
throw new Error(`Token allocation has already been claimed.`);
}
const { proof, key: pubkey, tier } = offerArgs;

const derivedAddress = computeAddress(pubkey);

assert.equal(
getMerkleRootFromMerkleProof(proof),
merkleRoot,
'Computed proof does not equal the correct root hash. ',
);

const paymentAmount = this.state.payoutArray[tier];
const depositFacet = await getDepositFacet(derivedAddress);
const payment = await withdrawFromSeat(zcf, tokenHolderSeat, {
Tokens: this.state.payoutArray[tier],
});
await Promise.all(
...[
Object.values(payment).map(pmtP =>
E.when(pmtP, pmt => E(depositFacet).receive(pmt)),
),
Promise.resolve(
accountStore.add(pubkey, {
address: derivedAddress,
pubkey,
tier,
amountAllocated: payment.value,
epoch: this.state.currentEpoch,
}),
),
],
);

rearrange(
harden([
[tokenHolderSeat, claimSeat, { Tokens: paymentAmount }],
[claimSeat, tokenHolderSeat, { Fee: claimTokensFee }],
[
claimSeat,
tokenHolderSeat,
{ Fee: claimSeat.getProposal().give.Fee },
],
]),
);

claimSeat.exit();

accountStore.add(pubkey, {
address,
pubkey,
tier,
amountAllocated: paymentAmount,
epoch: this.state.currentEpoch,
});

return createClaimSuccessMsg(paymentAmount);
return 'makeClaimTokenInvitation success';
};

return zcf.makeInvitation(
claimHandler,
messagesObject.makeClaimInvitationDescription(),
Expand Down
10 changes: 7 additions & 3 deletions contract/src/airdrop.local.proposal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Fail } from '@endo/errors';
import { makeMarshal } from '@endo/marshal';
import { makeTracer } from '@agoric/internal';
import { installContract } from './platform-goals/start-contract.js';
import { fixHub } from './fixHub.js';
import './types.js';

const contractName = 'tribblesAirdrop';
Expand Down Expand Up @@ -110,8 +111,8 @@ export const startAirdrop = async (powers, config) => {
trace('powers.installation', powers.installation.consume[contractName]);
const {
consume: {
namesByAddressAdmin,
namesByAddress,
namesByAddressAdmin: namesByAddressAdminP,
// namesByAddress,
// bankManager,
board,
chainTimerService,
Expand All @@ -132,10 +133,11 @@ export const startAirdrop = async (powers, config) => {
},
} = powers;

const [issuerIST, feeBrand, timer] = await Promise.all([
const [issuerIST, feeBrand, timer, namesByAddressAdmin] = await Promise.all([
istIssuer,
istBrand,
chainTimerService,
namesByAddressAdminP,
]);

const { customTerms } = config.options;
Expand All @@ -152,6 +154,7 @@ export const startAirdrop = async (powers, config) => {
customTerms?.merkleRoot,
'can not start contract without merkleRoot???',
);
const namesByAddress = await fixHub(namesByAddressAdmin);

const installation = await installContract(powers, {
name: contractName,
Expand All @@ -168,6 +171,7 @@ export const startAirdrop = async (powers, config) => {
issuerNames: ['Tribbles'],
privateArgs: harden({
timer,
namesByAddress,
}),
};
trace('BEFORE astartContract(permittedPowers, startOpts);', { startOpts });
Expand Down
Loading
Loading