Test and implement "can post an ERC20 transfer and search for the transaction"

This commit is contained in:
Simon Warta 2020-03-02 11:39:21 +01:00
parent 5aca5ebd69
commit ca2394657d
2 changed files with 157 additions and 44 deletions

View File

@ -589,6 +589,124 @@ describe("CosmWasmConnection", () => {
connection.disconnect();
});
it("can post an ERC20 transfer and search for the transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path);
const senderAddress = connection.codec.identityToAddress(sender);
const recipient = makeRandomAddress();
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: recipient,
memo: "My first payment",
amount: {
quantity: "75",
fractionalDigits: 0,
tokenTicker: "ISA" as TokenTicker,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const { transactionId } = response;
const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info));
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
// search by id
const byIdResults = await connection.searchTx({ id: transactionId });
expect(byIdResults.length).toEqual(1);
const byIdResult = byIdResults[0];
expect(byIdResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(byIdResult), "Expected transaction to succeed");
assert(byIdResult.log, "Log must be available");
const [firstByIdlog] = JSON.parse(byIdResult.log);
expect(firstByIdlog.events.length).toEqual(2);
expect(firstByIdlog.events[0].type).toEqual("message");
expect(firstByIdlog.events[1].type).toEqual("wasm");
// wasm event attributes added by contract
expect(firstByIdlog.events[1].attributes).toContain({ key: "action", value: "transfer" });
expect(firstByIdlog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
expect(firstByIdlog.events[1].attributes).toContain({ key: "recipient", value: recipient });
// wasm event attributes added wasmd
expect(firstByIdlog.events[1].attributes).toContain({
key: "contract_address",
value: defaultConfig.erc20Tokens![1].contractAddress,
});
const byIdTransaction = byIdResult.transaction;
assert(isSendTransaction(byIdTransaction), "Expected send transaction");
expect(byIdTransaction).toEqual(unsigned);
// search by sender address
const bySenderResults = await connection.searchTx({ sentFromOrTo: senderAddress });
expect(bySenderResults).toBeTruthy();
expect(bySenderResults.length).toBeGreaterThanOrEqual(1);
const bySenderResult = bySenderResults[bySenderResults.length - 1];
expect(bySenderResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(bySenderResult), "Expected transaction to succeed");
assert(bySenderResult.log, "Log must be available");
const [firstBySenderLog] = JSON.parse(bySenderResult.log);
expect(firstBySenderLog.events.length).toEqual(2);
expect(firstBySenderLog.events[0].type).toEqual("message");
expect(firstBySenderLog.events[1].type).toEqual("wasm");
// wasm event attributes added by contract
expect(firstBySenderLog.events[1].attributes).toContain({ key: "action", value: "transfer" });
expect(firstBySenderLog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
expect(firstBySenderLog.events[1].attributes).toContain({ key: "recipient", value: recipient });
// wasm event attributes added wasmd
expect(firstBySenderLog.events[1].attributes).toContain({
key: "contract_address",
value: defaultConfig.erc20Tokens![1].contractAddress,
});
const bySenderTransaction = bySenderResult.transaction;
assert(isSendTransaction(bySenderTransaction), "Expected send transaction");
expect(bySenderTransaction).toEqual(unsigned);
// search by recipient address
const byRecipientResults = await connection.searchTx({ sentFromOrTo: recipient });
expect(byRecipientResults.length).toBeGreaterThanOrEqual(1);
const byRecipientResult = byRecipientResults[byRecipientResults.length - 1];
expect(byRecipientResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(byRecipientResult), "Expected transaction to succeed");
assert(byRecipientResult.log, "Log must be available");
const [firstByRecipientLog] = JSON.parse(bySenderResult.log);
expect(firstByRecipientLog.events.length).toEqual(2);
expect(firstByRecipientLog.events[0].type).toEqual("message");
expect(firstByRecipientLog.events[1].type).toEqual("wasm");
// wasm event attributes added by contract
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "action", value: "transfer" });
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "recipient", value: recipient });
// wasm event attributes added wasmd
expect(firstByRecipientLog.events[1].attributes).toContain({
key: "contract_address",
value: defaultConfig.erc20Tokens![1].contractAddress,
});
const byRecipeintTransaction = byRecipientResult.transaction;
assert(isSendTransaction(byRecipeintTransaction), "Expected send transaction");
expect(byRecipeintTransaction).toEqual(unsigned);
// search by height
const heightResults = await connection.searchTx({ height: byIdResult.height });
expect(heightResults.length).toEqual(1);
const heightResult = heightResults[0];
expect(heightResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(heightResult), "Expected transaction to succeed");
assert(heightResult.log, "Log must be available");
const [firstHeightLog] = JSON.parse(heightResult.log);
expect(firstHeightLog.events.length).toEqual(2);
const heightTransaction = heightResult.transaction;
assert(isSendTransaction(heightTransaction), "Expected send transaction");
expect(heightTransaction).toEqual(unsigned);
connection.disconnect();
});
it("can search by minHeight and maxHeight", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
@ -980,47 +1098,4 @@ describe("CosmWasmConnection", () => {
})().catch(done.fail);
});
});
describe("integration tests", () => {
it("can send ERC20 tokens", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucet.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, faucet.path);
const senderAddress = connection.codec.identityToAddress(sender);
const recipient = makeRandomAddress();
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: recipient,
memo: "My first payment",
amount: {
quantity: "75",
fractionalDigits: 0,
tokenTicker: "ISA" as TokenTicker,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info));
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
const recipientAccount = await connection.getAccount({ address: recipient });
assert(recipientAccount, "Recipient account must have ISA tokens");
expect(recipientAccount.balance).toEqual([
{
tokenTicker: "ISA" as TokenTicker,
quantity: "75",
fractionalDigits: 0,
},
]);
connection.disconnect();
});
});
});

View File

@ -60,6 +60,16 @@ function isDefined<X>(value: X | undefined): value is X {
return value !== undefined;
}
function deduplicate<T>(input: ReadonlyArray<T>, comparator: (a: T, b: T) => number): ReadonlyArray<T> {
const out = new Array<T>();
for (const element of input) {
if (!out.find(o => comparator(o, element) === 0)) {
out.push(element);
}
}
return out;
}
/** Account and undefined are valid events. The third option means no event fired yet */
type LastWatchAccountEvent = Account | undefined | "no_event_fired_yet";
@ -324,7 +334,35 @@ export class CosmWasmConnection implements BlockchainConnection {
} else if (height) {
txs = await this.cosmWasmClient.searchTx({ height: height }, filter);
} else if (sentFromOrTo) {
txs = await this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter);
const pendingRequests = new Array<Promise<readonly TxsResponse[]>>();
pendingRequests.push(this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter));
for (const contract of this.erc20Tokens.map(token => token.contractAddress)) {
const searchBySender = [
{
key: "wasm.contract_address",
value: contract,
},
{
key: "wasm.sender",
value: sentFromOrTo,
},
];
const searchByRecipient = [
{
key: "wasm.contract_address",
value: contract,
},
{
key: "wasm.recipient",
value: sentFromOrTo,
},
];
pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchBySender }, filter));
pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchByRecipient }, filter));
}
const responses = await Promise.all(pendingRequests);
const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []);
txs = deduplicate(allResults, (a, b) => a.txhash.localeCompare(b.txhash));
} else {
throw new Error("Unsupported query");
}