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

Implement determinstic contract addresses #1253

Merged
merged 13 commits into from
Mar 6, 2023
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ and this project adheres to
- @cosmjs/cosmwasm-stargate: Add constructors `CosmWasmClient.create` and
`SigningCosmWasmClient.createWithSigner` to construct with a given Tendermint
client ([#1376]).
- @cosmjs/cosmwasm-stargate: Add `instantiate2Address` to pre-calculate
addresses for Instantiate2 ([#1253]).
- @cosmjs/stargate: Add `txIndex` to `DeliverTxResponse` and `IndexedTx`
([#1361]).

[#1253]: https://github.com/cosmos/cosmjs/pull/1253
[#1308]: https://github.com/cosmos/cosmjs/pull/1308
[#1361]: https://github.com/cosmos/cosmjs/issues/1361
[#1376]: https://github.com/cosmos/cosmjs/pull/1376
Expand Down
62 changes: 62 additions & 0 deletions packages/cli/examples/instantiate2_addresses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { fromBech32, fromHex, toBech32, toHex, toUtf8 } from "@cosmjs/encoding";
import { _instantiate2AddressIntermediate } from "@cosmjs/cosmwasm-stargate";

function makeTestingAddress(length: number): string {
let data = new Uint8Array(length);
data.fill(0x99, 0);
data.fill(0xaa, 5);
data.fill(0xbb, 10);
data.fill(0xcc, 15);
data.fill(0xdd, 20);
data.fill(0xee, 25);
data.fill(0xff, 30);
return toBech32("purple", data);
}

let out: Array<any> = [];

const checksums = [
fromHex("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5"),
fromHex("1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b"),
];
const salts = [
toUtf8("a"),
fromHex(
"AABBCCDDEEFFFFEEDDBBCCDDAA66551155aaaaBBCC787878789900AABBCCDDEEFFFFEEDDBBCCDDAA66551155aaaaBBCC787878789900aabbbbcc221100acadae",
),
];
const msgs = [null, JSON.stringify({}), JSON.stringify({ some: 123, structure: { nested: ["ok", true] } })];

for (let checksum of checksums) {
for (let creator of [makeTestingAddress(20), makeTestingAddress(32)]) {
for (let salt of salts) {
for (let msg of msgs) {
const { key, addressData, address } = _instantiate2AddressIntermediate(
checksum,
creator,
salt,
msg,
"purple",
);
out.push({
in: {
checksum: toHex(checksum),
creator,
creatorData: toHex(fromBech32(creator).data),
salt: toHex(salt),
msg,
},
intermediate: {
key: toHex(key),
addressData: toHex(addressData),
},
out: {
address: address,
},
});
}
}
}
}

console.log(JSON.stringify(out, undefined, 2));
1 change: 1 addition & 0 deletions packages/cli/run_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ if [ -n "${SIMAPP44_ENABLED:-}" ]; then
fi
yarn node ./bin/cosmjs-cli --init examples/faucet_addresses.ts --code "process.exit(0)"
yarn node ./bin/cosmjs-cli --init examples/generate_address.ts --code "process.exit(0)"
yarn node ./bin/cosmjs-cli --init examples/instantiate2_addresses.ts --code "process.exit(0)"
yarn node ./bin/cosmjs-cli --init examples/local_faucet.ts --code "process.exit(0)"
yarn node ./bin/cosmjs-cli --init examples/mask.ts --code "process.exit(0)"
yarn node ./bin/cosmjs-cli --init examples/multisig_address.ts --code "process.exit(0)"
Expand Down
1 change: 1 addition & 0 deletions packages/cosmwasm-stargate/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { Code, CodeDetails, Contract, ContractCodeHistoryEntry, CosmWasmClient } from "./cosmwasmclient";
export { fromBinary, toBinary } from "./encoding";
export { _instantiate2AddressIntermediate, instantiate2Address } from "./instantiate2";
export {
cosmWasmTypes,
createWasmAminoConverters,
Expand Down
53 changes: 53 additions & 0 deletions packages/cosmwasm-stargate/src/instantiate2.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { fromHex } from "@cosmjs/encoding";

import { instantiate2Address } from "./instantiate2";

describe("instantiate2", () => {
describe("instantiate2Address", () => {
it("works", () => {
// Some entries from https://gist.github.com/webmaster128/e4d401d414bd0e7e6f70482f11877fbe
{
const checksum = fromHex("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5");
const creator = "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py";
const salt = fromHex("61");
expect(instantiate2Address(checksum, creator, salt, "purple")).toEqual(
"purple1t6r960j945lfv8mhl4mage2rg97w63xeynwrupum2s2l7em4lprs9ce5hk",
);
}
{
const checksum = fromHex("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5");
const creator = "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py";
const salt = fromHex(
"aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae",
);
expect(instantiate2Address(checksum, creator, salt, "purple")).toEqual(
"purple1jwzvvfyvpwchrccxl476pxf7c83qawsqv3f2820q0zyrav6eg4jqdcq7gc",
);
}
{
const checksum = fromHex("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5");
const creator = "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m";
const salt = fromHex("61");
expect(instantiate2Address(checksum, creator, salt, "purple")).toEqual(
"purple1juj7jn6j3k9h35euyhealntquc2zmzlxp2ek76jmtypkl4g4vrdsfwmwxk",
);
}
{
const checksum = fromHex("13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5");
const creator = "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m";
const salt = fromHex("61");
expect(instantiate2Address(checksum, creator, salt, "purple")).toEqual(
"purple1juj7jn6j3k9h35euyhealntquc2zmzlxp2ek76jmtypkl4g4vrdsfwmwxk",
);
}
{
const checksum = fromHex("1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b");
const creator = "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py";
const salt = fromHex("61");
expect(instantiate2Address(checksum, creator, salt, "purple")).toEqual(
"purple1h9wyvusc6sy2p7fsgmez0dhvullpsyel7vq38k6d9fa7ehlv59qsvnyh36",
);
}
});
});
});
69 changes: 69 additions & 0 deletions packages/cosmwasm-stargate/src/instantiate2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Sha256, sha256 } from "@cosmjs/crypto";
import { fromBech32, toAscii, toBech32, toUtf8 } from "@cosmjs/encoding";
import { Uint64 } from "@cosmjs/math";
import { assert } from "@cosmjs/utils";

/**
* The "Basic Address" Hash from
* https://github.com/cosmos/cosmos-sdk/blob/v0.45.8/docs/architecture/adr-028-public-key-addresses.md
*/
function hash(type: string, key: Uint8Array): Uint8Array {
return new Sha256(sha256(toAscii(type))).update(key).digest();
}

/**
* Takes an integer [0, 2**64-1] and returns a one-byte encoding of it.
*/
function toUint64(int: number): Uint8Array {
return Uint64.fromNumber(int).toBytesBigEndian();
}

/**
* Private function to export test vector data for https://github.com/cosmos/cosmjs/pull/1253.
* Do not use in production code.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export function _instantiate2AddressIntermediate(
checksum: Uint8Array,
creator: string,
salt: Uint8Array,
msg: string | null,
prefix: string,
): { key: Uint8Array; addressData: Uint8Array; address: string } {
assert(checksum.length === 32);
const creatorData = fromBech32(creator).data;

const msgData = typeof msg === "string" ? toUtf8(msg) : new Uint8Array();

// Validate inputs
if (salt.length < 1 || salt.length > 64) throw new Error("Salt must be between 1 and 64 bytes");

const key = new Uint8Array([
...toAscii("wasm"),
0x00,
...toUint64(checksum.length),
...checksum,
...toUint64(creatorData.length),
...creatorData,
...toUint64(salt.length),
...salt,
...toUint64(msgData.length),
...msgData,
]);
const addressData = hash("module", key);
const address = toBech32(prefix, addressData);
return { key, addressData, address };
}

/**
* Predictable address generation for the MsgInstantiateContract2
* introduced with wasmd 0.29.
*/
export function instantiate2Address(
checksum: Uint8Array,
creator: string,
salt: Uint8Array,
prefix: string,
): string {
return _instantiate2AddressIntermediate(checksum, creator, salt, null, prefix).address;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why msg is always set to null when instantiate2 while Wasmd v0.31.x include msgs when creating address?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I added #1398 for future readers

Copy link
Contributor

Choose a reason for hiding this comment

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

I got it.
However, according to the link you provided, it seems that fix_msg should be null, not that instantiate msg should be null and current code is making instantiate msg to null.

Copy link
Member Author

@webmaster128 webmaster128 Mar 27, 2023

Choose a reason for hiding this comment

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

It refers to different aspects of the same thing. If msg is null or empty during address generation, fixed_msg must be false in the actual instantiation.

If msg is null/empty (during address generation) and fixed_msg = false, then you can instantiate the contract with any msg you want without affecting the generated address.

Copy link
Contributor

@loin3 loin3 Mar 27, 2023

Choose a reason for hiding this comment

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

To be honest, I am not familiar with wasmd, so I'm not sure how to use instantiate2.
If you have a chance, could you make a test code that compares the address obtained with instantiate2Address and the address of the contract instantiated with instantiate2 in the script/wasmd chain?

And thank you for your kind answers :)

Copy link
Member Author

@webmaster128 webmaster128 Mar 27, 2023

Choose a reason for hiding this comment

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

Yeah, it's still early for instantiate2 and not much resources are out there. Feel free to subscribe to #1399 to see

  • Pre-calculation
  • Actual instantiation

in CosmJS. The tests for this ticket will make a few things clearer I guess.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, nice! I strongly advise for removing the msg input field there. Nobody should need to use it.

}