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

feat: fee payer for sudo fee #70

Closed
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"test": "jest --runInBand -b src/testcases",
"test:interchaintx": "jest --runInBand -b src/testcases/interchaintx",
"test:simple": "jest --runInBand -b src/testcases/simple",
"test:ibc_transfer": "jest --runInBand -b src/testcases/ibc_transfer",
"test:interchain_kv_query": "jest --runInBand -b src/testcases/interchain_kv_query",
"test:interchain_tx_query": "jest --runInBand -b src/testcases/interchain_tx_query",
"test:interchain_tx_query_resubmit": "jest --runInBand -b src/testcases/interchain_tx_query_resubmit",
Expand Down
21 changes: 19 additions & 2 deletions src/helpers/cosmos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,16 +333,33 @@ export class CosmosWrapper {
return attributes;
}

async feeGrant(to: string): Promise<void> {
const msg = new proto.cosmos.feegrant.v1beta1.MsgGrantAllowance({
granter: this.wallet.address.toString(),
grantee: to,
allowance: cosmosclient.codec.instanceToProtoAny(
new proto.cosmos.feegrant.v1beta1.BasicAllowance({}),
),
});
await this.execTx(
{
amount: [{ denom: NEUTRON_DENOM, amount: '2000000' }],
gas_limit: Long.fromString('600000000'),
},
[msg],
);
}

async executeContract(
contract: string,
msg: string,
msg: string | Record<string, unknown>,
funds: proto.cosmos.base.v1beta1.ICoin[] = [],
sender: string = this.wallet.address.toString(),
): Promise<InlineResponse20075TxResponse> {
const msgExecute = new cosmwasmproto.cosmwasm.wasm.v1.MsgExecuteContract({
sender,
contract,
msg: Buffer.from(msg),
msg: Buffer.from(typeof msg == 'string' ? msg : JSON.stringify(msg)),
funds,
});

Expand Down
79 changes: 62 additions & 17 deletions src/testcases/simple.test.ts → src/testcases/ibc_transfer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import {
import { getRemoteHeight, getWithAttempts } from '../helpers/wait';
import { TestStateLocalCosmosTestNet } from './common_localcosmosnet';

describe('Neutron / Simple', () => {
describe('Neutron / IBC-transfer', () => {
let testState: TestStateLocalCosmosTestNet;
let cm: CosmosWrapper;
let cm2: CosmosWrapper;
let cm3: CosmosWrapper;
let contractAddress: string;

beforeAll(async () => {
Expand All @@ -27,12 +28,22 @@ describe('Neutron / Simple', () => {
testState.wallets.neutron.demo1,
NEUTRON_DENOM,
);
cm3 = new CosmosWrapper(
Copy link
Contributor

Choose a reason for hiding this comment

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

cm3 before cm2 seems strange. Maybe it's time to rename these variables?
cm -> ntrnDemo1
cm3 -> ntrnDemo2
cm2 -> cosmosDemo2
Or please suggest something.
Because it's getting hard to read in test cases.

testState.sdk1,
testState.blockWaiter1,
testState.wallets.neutron.demo2,
NEUTRON_DENOM,
);
cm2 = new CosmosWrapper(
testState.sdk2,
testState.blockWaiter2,
testState.wallets.cosmos.demo2,
COSMOS_DENOM,
);
const codeId = await cm.storeWasm(NeutronContract.IBC_TRANSFER);
// BEWARE: this contract sends 2 txs
const res = await cm.instantiate(codeId, '{}', 'ibc_transfer');
contractAddress = res[0]._contract_address;
});

describe('Wallets', () => {
Expand All @@ -46,18 +57,6 @@ describe('Neutron / Simple', () => {
});
});

describe('Contracts', () => {
let codeId: string;
test('store contract', async () => {
codeId = await cm.storeWasm(NeutronContract.IBC_TRANSFER);
expect(parseInt(codeId)).toBeGreaterThan(0);
});
test('instantiate', async () => {
const res = await cm.instantiate(codeId, '{}', 'ibc_transfer');
contractAddress = res[0]._contract_address;
});
});

describe('IBC', () => {
describe('Correct way', () => {
let relayerBalance = 0;
Expand Down Expand Up @@ -152,7 +151,6 @@ describe('Neutron / Simple', () => {
);
expect(res.code).toEqual(0);
});

test('execute contract', async () => {
const res = await cm.executeContract(
contractAddress,
Expand All @@ -167,7 +165,6 @@ describe('Neutron / Simple', () => {
);
expect(res.code).toEqual(0);
});

test('check wallet balance', async () => {
await cm.blockWaiter.waitBlocks(10);
const balances = await cm2.queryBalances(
Expand Down Expand Up @@ -202,6 +199,55 @@ describe('Neutron / Simple', () => {
expect(balance).toBe(50000 - 3000 - 2333 * 2);
});
});
describe('Fee payer', () => {
beforeAll(async () => {
await cm.msgSend(contractAddress.toString(), '50000');
await cm.executeContract(contractAddress, {
set_fees: {
denom: cm.denom,
ack_fee: '2333',
recv_fee: '0',
timeout_fee: '2666',
payer: cm3.wallet.address.toString(),
},
});
});
test('should return error bc there is no fee grant yet', async () => {
await expect(
cm.executeContract(contractAddress, {
send: {
channel: 'channel-0',
to: testState.wallets.cosmos.demo2.address.toString(),
denom: NEUTRON_DENOM,
amount: '1000',
},
}),
).rejects.toThrow(/fee-grant not found/);
});
test('should spend fee payer tokens', async () => {
await cm3.feeGrant(contractAddress);
const balanceBefore = await cm.queryDenomBalance(
cm3.wallet.address.toString(),
NEUTRON_DENOM,
);
await cm.executeContract(contractAddress, {
send: {
channel: 'channel-0',
to: testState.wallets.cosmos.demo2.address.toString(),
denom: NEUTRON_DENOM,
amount: '1000',
},
});
await cm.blockWaiter.waitBlocks(10); // must be enought to process the tx
const balanceAfter = await cm.queryDenomBalance(
cm3.wallet.address.toString(),
NEUTRON_DENOM,
);
// there are 2 txs in the contract so we must multiply the fee by 2
// as ack is done and ackFee must be spent, and timeoutFee is refunded
expect(balanceBefore - balanceAfter).toEqual(2333 * 2);
});
});
describe('Missing fee', () => {
beforeAll(async () => {
await cm.executeContract(
Expand Down Expand Up @@ -319,8 +365,7 @@ describe('Neutron / Simple', () => {
).rejects.toThrow(/insufficient funds/);
});
});

describe('Not enough amount of tokens on contract to pay fee', () => {
describe('Enough amount of tokens on contract to pay fee', () => {
beforeAll(async () => {
await cm.executeContract(
contractAddress,
Expand Down
135 changes: 129 additions & 6 deletions src/testcases/interchaintx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('Neutron / Interchain TXs', () => {
let testState: TestStateLocalCosmosTestNet;
let cm1: CosmosWrapper;
let cm2: CosmosWrapper;
let cm3: CosmosWrapper;
let contractAddress: string;
let icaAddress1: string;
let icaAddress2: string;
Expand All @@ -41,6 +42,12 @@ describe('Neutron / Interchain TXs', () => {
testState.wallets.cosmos.demo2,
COSMOS_DENOM,
);
cm3 = new CosmosWrapper(
testState.sdk1,
testState.blockWaiter1,
testState.wallets.neutron.icq,
NEUTRON_DENOM,
);
});

describe('Interchain Tx with multiple ICAs', () => {
Expand Down Expand Up @@ -206,11 +213,93 @@ describe('Neutron / Interchain TXs', () => {
expect(res2.data.delegation_responses).toEqual([]);
});
test('check contract balance', async () => {
const res = await cm1.queryBalances(contractAddress);
const balance = res.balances.find((b) => b.denom === cm1.denom)?.amount;
expect(balance).toEqual('98000');
const balance = await cm1.queryDenomBalance(contractAddress, cm1.denom);
expect(balance).toEqual(98000);
});
});

describe('Send Interchain TX and fee payer', () => {
let contractBalance: number;
let payerBalance: number;
beforeAll(async () => {
await cm1.executeContract(contractAddress, {
set_fees: {
denom: cm1.denom,
ack_fee: '2000',
recv_fee: '0',
timeout_fee: '2000',
payer: cm3.wallet.address.toString(),
},
});
await cm3.feeGrant(contractAddress);
contractBalance = await cm1.queryDenomBalance(
contractAddress,
cm1.denom,
);
payerBalance = await cm3.queryDenomBalance(
cm3.wallet.address.toString(),
cm1.denom,
);
});
test('delegate from first ICA', async () => {
const res = await cm1.executeContract(contractAddress, {
delegate: {
interchain_account_id: icaId1,
validator: testState.wallets.cosmos.val1.address.toString(),
amount: '2000',
denom: cm2.denom,
},
});
expect(res.code).toEqual(0);
const sequenceId = getSequenceId(res.raw_log);
await waitForAck(cm1, contractAddress, icaId1, sequenceId);
const qres = await getAck(cm1, contractAddress, icaId1, sequenceId);
expect(qres).toMatchObject<AcknowledgementResult>({
success: ['/cosmos.staking.v1beta1.MsgDelegate'],
});
});
test('check validator state', async () => {
const res1 = await getWithAttempts(
cm2.blockWaiter,
() =>
rest.staking.delegatorDelegations(
cm2.sdk as CosmosSDK,
icaAddress1 as unknown as AccAddress,
),
async (delegations) =>
delegations.data.delegation_responses?.length == 1,
);
expect(res1.data.delegation_responses).toEqual([
{
balance: { amount: '4000', denom: cm2.denom },
delegation: {
delegator_address: icaAddress1,
shares: '4000.000000000000000000',
validator_address:
'cosmosvaloper18hl5c9xn5dze2g50uaw0l2mr02ew57zk0auktn',
},
},
]);
const res2 = await rest.staking.delegatorDelegations(
cm2.sdk as CosmosSDK,
icaAddress2 as unknown as AccAddress,
);
expect(res2.data.delegation_responses).toEqual([]);
});
test('check contract and payer balance', async () => {
expect(await cm1.queryDenomBalance(contractAddress, cm1.denom)).toEqual(
contractBalance,
);
expect(
payerBalance -
(await cm3.queryDenomBalance(
cm3.wallet.address.toString(),
cm1.denom,
)),
).toEqual(2000);
});
});

describe('Error cases', () => {
test('delegate for unknown validator from second ICA', async () => {
const res = await cm1.executeContract(
Expand Down Expand Up @@ -370,6 +459,40 @@ describe('Neutron / Interchain TXs', () => {
).rejects.toThrow(/invalid coins/);
});
});
describe('fee payer without grant', () => {
beforeAll(async () => {
await cm1.executeContract(
contractAddress,
JSON.stringify({
set_fees: {
denom: cm1.denom,
ack_fee: '2000',
recv_fee: '0',
timeout_fee: '2000',
payer: testState.wallets.neutron.demo2.address.toString(),
},
}),
);
});
test('delegate without fee grant', async () => {
await expect(
cm1.executeContract(
contractAddress,
JSON.stringify({
delegate: {
interchain_account_id: icaId1,
validator: (
testState.wallets.cosmos.val1
.address as cosmosclient.ValAddress
).toString(),
amount: '2000',
denom: cm2.denom,
},
}),
),
).rejects.toThrow(/fee-grant not found/);
});
});
describe('insufficient funds for fee', () => {
beforeAll(async () => {
await cm1.executeContract(
Expand Down Expand Up @@ -417,6 +540,7 @@ describe('Neutron / Interchain TXs', () => {
});
});
});

describe('Recreation', () => {
test('recreate ICA1', async () => {
const res = await cm1.executeContract(
Expand Down Expand Up @@ -474,18 +598,17 @@ describe('Neutron / Interchain TXs', () => {
);
expect(res.data.delegation_responses).toEqual([
{
balance: { amount: '1020', denom: cm2.denom },
balance: { amount: '3020', denom: cm2.denom },
delegation: {
delegator_address: icaAddress1,
shares: '1020.000000000000000000',
shares: '3020.000000000000000000',
validator_address:
'cosmosvaloper18hl5c9xn5dze2g50uaw0l2mr02ew57zk0auktn',
},
},
]);
});
});

test('delegate with sudo failure', async () => {
await cleanAckResults(cm1, contractAddress);

Expand Down