Skip to content

Commit 06e5433

Browse files
Transpile fe6249ec
1 parent 24c9728 commit 06e5433

File tree

8 files changed

+266
-1
lines changed

8 files changed

+266
-1
lines changed

.changeset/healthy-books-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`CAIP2` and `CAIP10`: Add libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers.

.changeset/proud-planes-arrive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`Bytes`: Add a library of common operation that operate on `bytes` objects.

contracts/mocks/StatelessUpgradeable.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol";
99
import {AuthorityUtils} from "@openzeppelin/contracts/access/manager/AuthorityUtils.sol";
1010
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
1111
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
12+
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
13+
import {CAIP2} from "@openzeppelin/contracts/utils/CAIP2.sol";
14+
import {CAIP10} from "@openzeppelin/contracts/utils/CAIP10.sol";
1215
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
1316
import {CircularBuffer} from "@openzeppelin/contracts/utils/structs/CircularBuffer.sol";
1417
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

contracts/utils/README.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
3131
* {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type.
3232
* {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`].
3333
* {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648].
34+
* {Bytes}: Common operations on bytes objects.
3435
* {Strings}: Common operations for strings formatting.
3536
* {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters.
3637
* {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays.
@@ -41,6 +42,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
4142
* {Packing}: A library for packing and unpacking multiple values into bytes32
4243
* {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes].
4344
* {Comparators}: A library that contains comparator functions to use with with the {Heap} library.
45+
* {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers.
4446

4547
[NOTE]
4648
====

test/helpers/chains.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// NOTE: this file defines some examples of CAIP-2 and CAIP-10 identifiers.
2+
// The following listing does not pretend to be exhaustive or even accurate. It SHOULD NOT be used in production.
3+
4+
const { ethers } = require('hardhat');
5+
const { mapValues } = require('./iterate');
6+
7+
// EVM (https://axelarscan.io/resources/chains?type=evm)
8+
const ethereum = {
9+
Ethereum: '1',
10+
optimism: '10',
11+
binance: '56',
12+
Polygon: '137',
13+
Fantom: '250',
14+
fraxtal: '252',
15+
filecoin: '314',
16+
Moonbeam: '1284',
17+
centrifuge: '2031',
18+
kava: '2222',
19+
mantle: '5000',
20+
base: '8453',
21+
immutable: '13371',
22+
arbitrum: '42161',
23+
celo: '42220',
24+
Avalanche: '43114',
25+
linea: '59144',
26+
blast: '81457',
27+
scroll: '534352',
28+
aurora: '1313161554',
29+
};
30+
31+
// Cosmos (https://axelarscan.io/resources/chains?type=cosmos)
32+
const cosmos = {
33+
Axelarnet: 'axelar-dojo-1',
34+
osmosis: 'osmosis-1',
35+
cosmoshub: 'cosmoshub-4',
36+
juno: 'juno-1',
37+
'e-money': 'emoney-3',
38+
injective: 'injective-1',
39+
crescent: 'crescent-1',
40+
kujira: 'kaiyo-1',
41+
'secret-snip': 'secret-4',
42+
secret: 'secret-4',
43+
sei: 'pacific-1',
44+
stargaze: 'stargaze-1',
45+
assetmantle: 'mantle-1',
46+
fetch: 'fetchhub-4',
47+
ki: 'kichain-2',
48+
evmos: 'evmos_9001-2',
49+
aura: 'xstaxy-1',
50+
comdex: 'comdex-1',
51+
persistence: 'core-1',
52+
regen: 'regen-1',
53+
umee: 'umee-1',
54+
agoric: 'agoric-3',
55+
xpla: 'dimension_37-1',
56+
acre: 'acre_9052-1',
57+
stride: 'stride-1',
58+
carbon: 'carbon-1',
59+
sommelier: 'sommelier-3',
60+
neutron: 'neutron-1',
61+
rebus: 'reb_1111-1',
62+
archway: 'archway-1',
63+
provenance: 'pio-mainnet-1',
64+
ixo: 'ixo-5',
65+
migaloo: 'migaloo-1',
66+
teritori: 'teritori-1',
67+
haqq: 'haqq_11235-1',
68+
celestia: 'celestia',
69+
ojo: 'agamotto',
70+
chihuahua: 'chihuahua-1',
71+
saga: 'ssc-1',
72+
dymension: 'dymension_1100-1',
73+
fxcore: 'fxcore',
74+
c4e: 'perun-1',
75+
bitsong: 'bitsong-2b',
76+
nolus: 'pirin-1',
77+
lava: 'lava-mainnet-1',
78+
'terra-2': 'phoenix-1',
79+
terra: 'columbus-5',
80+
};
81+
82+
const makeCAIP = ({ namespace, reference, account }) => ({
83+
namespace,
84+
reference,
85+
account,
86+
caip2: `${namespace}:${reference}`,
87+
caip10: `${namespace}:${reference}:${account}`,
88+
toCaip10: other => `${namespace}:${reference}:${ethers.getAddress(other.target ?? other.address ?? other)}`,
89+
});
90+
91+
module.exports = {
92+
CHAINS: mapValues(
93+
Object.assign(
94+
mapValues(ethereum, reference => ({
95+
namespace: 'eip155',
96+
reference,
97+
account: ethers.Wallet.createRandom().address,
98+
})),
99+
mapValues(cosmos, reference => ({
100+
namespace: 'cosmos',
101+
reference,
102+
account: ethers.encodeBase58(ethers.randomBytes(32)),
103+
})),
104+
),
105+
makeCAIP,
106+
),
107+
getLocalCAIP: account =>
108+
ethers.provider.getNetwork().then(({ chainId }) => makeCAIP({ namespace: 'eip155', reference: chainId, account })),
109+
};

test/utils/Bytes.test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const { ethers } = require('hardhat');
2+
const { expect } = require('chai');
3+
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4+
5+
async function fixture() {
6+
const mock = await ethers.deployContract('$Bytes');
7+
return { mock };
8+
}
9+
10+
const lorem = ethers.toUtf8Bytes(
11+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
12+
);
13+
const present = lorem.at(1);
14+
const absent = 255;
15+
16+
describe('Bytes', function () {
17+
before(async function () {
18+
Object.assign(this, await loadFixture(fixture));
19+
});
20+
21+
describe('indexOf', function () {
22+
it('first', async function () {
23+
expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.indexOf(present));
24+
});
25+
26+
it('from index', async function () {
27+
for (const start in Array(lorem.length + 10).fill()) {
28+
const index = lorem.indexOf(present, start);
29+
const result = index === -1 ? ethers.MaxUint256 : index;
30+
expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal(result);
31+
}
32+
});
33+
34+
it('absent', async function () {
35+
expect(await this.mock.$indexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256);
36+
});
37+
});
38+
39+
describe('lastIndexOf', function () {
40+
it('first', async function () {
41+
expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.lastIndexOf(present));
42+
});
43+
44+
it('from index', async function () {
45+
for (const start in Array(lorem.length + 10).fill()) {
46+
const index = lorem.lastIndexOf(present, start);
47+
const result = index === -1 ? ethers.MaxUint256 : index;
48+
expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal(
49+
result,
50+
);
51+
}
52+
});
53+
54+
it('absent', async function () {
55+
expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256);
56+
});
57+
});
58+
59+
describe('slice', function () {
60+
describe('slice(bytes, uint256)', function () {
61+
for (const [descr, start] of Object.entries({
62+
'start = 0': 0,
63+
'start within bound': 10,
64+
'start out of bound': 1000,
65+
})) {
66+
it(descr, async function () {
67+
const result = ethers.hexlify(lorem.slice(start));
68+
expect(await this.mock.$slice(lorem, start)).to.equal(result);
69+
});
70+
}
71+
});
72+
73+
describe('slice(bytes, uint256, uint256)', function () {
74+
for (const [descr, [start, end]] of Object.entries({
75+
'start = 0': [0, 42],
76+
'start and end within bound': [17, 42],
77+
'end out of bound': [42, 1000],
78+
'start = end': [17, 17],
79+
'start > end': [42, 17],
80+
})) {
81+
it(descr, async function () {
82+
const result = ethers.hexlify(lorem.slice(start, end));
83+
expect(await this.mock.$slice(lorem, start, ethers.Typed.uint256(end))).to.equal(result);
84+
});
85+
}
86+
});
87+
});
88+
});

test/utils/CAIP.test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const { ethers } = require('hardhat');
2+
const { expect } = require('chai');
3+
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4+
5+
const { CHAINS, getLocalCAIP } = require('../helpers/chains');
6+
7+
async function fixture() {
8+
const caip2 = await ethers.deployContract('$CAIP2');
9+
const caip10 = await ethers.deployContract('$CAIP10');
10+
return { caip2, caip10 };
11+
}
12+
13+
describe('CAIP utilities', function () {
14+
beforeEach(async function () {
15+
Object.assign(this, await loadFixture(fixture));
16+
});
17+
18+
describe('CAIP-2', function () {
19+
it('local()', async function () {
20+
const { caip2 } = await getLocalCAIP();
21+
expect(await this.caip2.$local()).to.equal(caip2);
22+
});
23+
24+
for (const { namespace, reference, caip2 } of Object.values(CHAINS))
25+
it(`format(${namespace}, ${reference})`, async function () {
26+
expect(await this.caip2.$format(namespace, reference)).to.equal(caip2);
27+
});
28+
29+
for (const { namespace, reference, caip2 } of Object.values(CHAINS))
30+
it(`parse(${caip2})`, async function () {
31+
expect(await this.caip2.$parse(caip2)).to.deep.equal([namespace, reference]);
32+
});
33+
});
34+
35+
describe('CAIP-10', function () {
36+
const { address: account } = ethers.Wallet.createRandom();
37+
38+
it(`local(${account})`, async function () {
39+
const { caip10 } = await getLocalCAIP(account);
40+
expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(caip10);
41+
});
42+
43+
for (const { account, caip2, caip10 } of Object.values(CHAINS))
44+
it(`format(${caip2}, ${account})`, async function () {
45+
expect(await this.caip10.$format(ethers.Typed.string(caip2), ethers.Typed.string(account))).to.equal(caip10);
46+
});
47+
48+
for (const { account, caip2, caip10 } of Object.values(CHAINS))
49+
it(`parse(${caip10})`, async function () {
50+
expect(await this.caip10.$parse(caip10)).to.deep.equal([caip2, account]);
51+
});
52+
});
53+
});

0 commit comments

Comments
 (0)