-
Notifications
You must be signed in to change notification settings - Fork 11.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bytes library and CAIP2/CAIP10 helpers (#5252)
Co-authored-by: cairo <[email protected]> Co-authored-by: Ernesto García <[email protected]> Co-authored-by: Arr00 <[email protected]>
- Loading branch information
1 parent
bd58895
commit fe6249e
Showing
10 changed files
with
476 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'openzeppelin-solidity': minor | ||
--- | ||
|
||
`CAIP2` and `CAIP10`: Add libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'openzeppelin-solidity': minor | ||
--- | ||
|
||
`Bytes`: Add a library of common operation that operate on `bytes` objects. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {Math} from "./math/Math.sol"; | ||
|
||
/** | ||
* @dev Bytes operations. | ||
*/ | ||
library Bytes { | ||
/** | ||
* @dev Forward search for `s` in `buffer` | ||
* * If `s` is present in the buffer, returns the index of the first instance | ||
* * If `s` is not present in the buffer, returns type(uint256).max | ||
* | ||
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] | ||
*/ | ||
function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { | ||
return indexOf(buffer, s, 0); | ||
} | ||
|
||
/** | ||
* @dev Forward search for `s` in `buffer` starting at position `pos` | ||
* * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance | ||
* * If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max | ||
* | ||
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] | ||
*/ | ||
function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { | ||
unchecked { | ||
uint256 length = buffer.length; | ||
for (uint256 i = pos; i < length; ++i) { | ||
if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) { | ||
return i; | ||
} | ||
} | ||
return type(uint256).max; | ||
} | ||
} | ||
|
||
/** | ||
* @dev Backward search for `s` in `buffer` | ||
* * If `s` is present in the buffer, returns the index of the last instance | ||
* * If `s` is not present in the buffer, returns type(uint256).max | ||
* | ||
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] | ||
*/ | ||
function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { | ||
return lastIndexOf(buffer, s, type(uint256).max); | ||
} | ||
|
||
/** | ||
* @dev Backward search for `s` in `buffer` starting at position `pos` | ||
* * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance | ||
* * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max | ||
* | ||
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] | ||
*/ | ||
function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { | ||
unchecked { | ||
uint256 length = buffer.length; | ||
// NOTE here we cannot do `i = Math.min(pos + 1, length)` because `pos + 1` could overflow | ||
for (uint256 i = Math.min(pos, length - 1) + 1; i > 0; --i) { | ||
if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) { | ||
return i - 1; | ||
} | ||
} | ||
return type(uint256).max; | ||
} | ||
} | ||
|
||
/** | ||
* @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in | ||
* memory. | ||
* | ||
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] | ||
*/ | ||
function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { | ||
return slice(buffer, start, buffer.length); | ||
} | ||
|
||
/** | ||
* @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in | ||
* memory. | ||
* | ||
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] | ||
*/ | ||
function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { | ||
// sanitize | ||
uint256 length = buffer.length; | ||
end = Math.min(end, length); | ||
start = Math.min(start, end); | ||
|
||
// allocate and copy | ||
bytes memory result = new bytes(end - start); | ||
assembly ("memory-safe") { | ||
mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* @dev Reads a bytes32 from a bytes array without bounds checking. | ||
* | ||
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the | ||
* assembly block as such would prevent some optimizations. | ||
*/ | ||
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { | ||
// This is not memory safe in the general case, but all calls to this private function are within bounds. | ||
assembly ("memory-safe") { | ||
value := mload(add(buffer, add(0x20, offset))) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {SafeCast} from "./math/SafeCast.sol"; | ||
import {Bytes} from "./Bytes.sol"; | ||
import {CAIP2} from "./CAIP2.sol"; | ||
import {Strings} from "./Strings.sol"; | ||
|
||
/** | ||
* @dev Helper library to format and parse CAIP-10 identifiers | ||
* | ||
* https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md[CAIP-10] defines account identifiers as: | ||
* account_id: chain_id + ":" + account_address | ||
* chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2}) | ||
* account_address: [-.%a-zA-Z0-9]{1,128} | ||
*/ | ||
library CAIP10 { | ||
using SafeCast for uint256; | ||
using Strings for address; | ||
using Bytes for bytes; | ||
|
||
/// @dev Return the CAIP-10 identifier for an account on the current (local) chain. | ||
function local(address account) internal view returns (string memory) { | ||
return format(CAIP2.local(), account.toChecksumHexString()); | ||
} | ||
|
||
/** | ||
* @dev Return the CAIP-10 identifier for a given caip2 chain and account. | ||
* | ||
* NOTE: This function does not verify that the inputs are properly formatted. | ||
*/ | ||
function format(string memory caip2, string memory account) internal pure returns (string memory) { | ||
return string.concat(caip2, ":", account); | ||
} | ||
|
||
/** | ||
* @dev Parse a CAIP-10 identifier into its components. | ||
* | ||
* NOTE: This function does not verify that the CAIP-10 input is properly formatted. The `caip2` return can be | ||
* parsed using the {CAIP2} library. | ||
*/ | ||
function parse(string memory caip10) internal pure returns (string memory caip2, string memory account) { | ||
bytes memory buffer = bytes(caip10); | ||
|
||
uint256 pos = buffer.lastIndexOf(":"); | ||
return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
import {SafeCast} from "./math/SafeCast.sol"; | ||
import {Bytes} from "./Bytes.sol"; | ||
import {Strings} from "./Strings.sol"; | ||
|
||
/** | ||
* @dev Helper library to format and parse CAIP-2 identifiers | ||
* | ||
* https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-2] defines chain identifiers as: | ||
* chain_id: namespace + ":" + reference | ||
* namespace: [-a-z0-9]{3,8} | ||
* reference: [-_a-zA-Z0-9]{1,32} | ||
*/ | ||
library CAIP2 { | ||
using SafeCast for uint256; | ||
using Strings for uint256; | ||
using Bytes for bytes; | ||
|
||
/// @dev Return the CAIP-2 identifier for the current (local) chain. | ||
function local() internal view returns (string memory) { | ||
return format("eip155", block.chainid.toString()); | ||
} | ||
|
||
/** | ||
* @dev Return the CAIP-2 identifier for a given namespace and reference. | ||
* | ||
* NOTE: This function does not verify that the inputs are properly formatted. | ||
*/ | ||
function format(string memory namespace, string memory ref) internal pure returns (string memory) { | ||
return string.concat(namespace, ":", ref); | ||
} | ||
|
||
/** | ||
* @dev Parse a CAIP-2 identifier into its components. | ||
* | ||
* NOTE: This function does not verify that the CAIP-2 input is properly formatted. | ||
*/ | ||
function parse(string memory caip2) internal pure returns (string memory namespace, string memory ref) { | ||
bytes memory buffer = bytes(caip2); | ||
|
||
uint256 pos = buffer.indexOf(":"); | ||
return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// NOTE: this file defines some examples of CAIP-2 and CAIP-10 identifiers. | ||
// The following listing does not pretend to be exhaustive or even accurate. It SHOULD NOT be used in production. | ||
|
||
const { ethers } = require('hardhat'); | ||
const { mapValues } = require('./iterate'); | ||
|
||
// EVM (https://axelarscan.io/resources/chains?type=evm) | ||
const ethereum = { | ||
Ethereum: '1', | ||
optimism: '10', | ||
binance: '56', | ||
Polygon: '137', | ||
Fantom: '250', | ||
fraxtal: '252', | ||
filecoin: '314', | ||
Moonbeam: '1284', | ||
centrifuge: '2031', | ||
kava: '2222', | ||
mantle: '5000', | ||
base: '8453', | ||
immutable: '13371', | ||
arbitrum: '42161', | ||
celo: '42220', | ||
Avalanche: '43114', | ||
linea: '59144', | ||
blast: '81457', | ||
scroll: '534352', | ||
aurora: '1313161554', | ||
}; | ||
|
||
// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) | ||
const cosmos = { | ||
Axelarnet: 'axelar-dojo-1', | ||
osmosis: 'osmosis-1', | ||
cosmoshub: 'cosmoshub-4', | ||
juno: 'juno-1', | ||
'e-money': 'emoney-3', | ||
injective: 'injective-1', | ||
crescent: 'crescent-1', | ||
kujira: 'kaiyo-1', | ||
'secret-snip': 'secret-4', | ||
secret: 'secret-4', | ||
sei: 'pacific-1', | ||
stargaze: 'stargaze-1', | ||
assetmantle: 'mantle-1', | ||
fetch: 'fetchhub-4', | ||
ki: 'kichain-2', | ||
evmos: 'evmos_9001-2', | ||
aura: 'xstaxy-1', | ||
comdex: 'comdex-1', | ||
persistence: 'core-1', | ||
regen: 'regen-1', | ||
umee: 'umee-1', | ||
agoric: 'agoric-3', | ||
xpla: 'dimension_37-1', | ||
acre: 'acre_9052-1', | ||
stride: 'stride-1', | ||
carbon: 'carbon-1', | ||
sommelier: 'sommelier-3', | ||
neutron: 'neutron-1', | ||
rebus: 'reb_1111-1', | ||
archway: 'archway-1', | ||
provenance: 'pio-mainnet-1', | ||
ixo: 'ixo-5', | ||
migaloo: 'migaloo-1', | ||
teritori: 'teritori-1', | ||
haqq: 'haqq_11235-1', | ||
celestia: 'celestia', | ||
ojo: 'agamotto', | ||
chihuahua: 'chihuahua-1', | ||
saga: 'ssc-1', | ||
dymension: 'dymension_1100-1', | ||
fxcore: 'fxcore', | ||
c4e: 'perun-1', | ||
bitsong: 'bitsong-2b', | ||
nolus: 'pirin-1', | ||
lava: 'lava-mainnet-1', | ||
'terra-2': 'phoenix-1', | ||
terra: 'columbus-5', | ||
}; | ||
|
||
const makeCAIP = ({ namespace, reference, account }) => ({ | ||
namespace, | ||
reference, | ||
account, | ||
caip2: `${namespace}:${reference}`, | ||
caip10: `${namespace}:${reference}:${account}`, | ||
toCaip10: other => `${namespace}:${reference}:${ethers.getAddress(other.target ?? other.address ?? other)}`, | ||
}); | ||
|
||
module.exports = { | ||
CHAINS: mapValues( | ||
Object.assign( | ||
mapValues(ethereum, reference => ({ | ||
namespace: 'eip155', | ||
reference, | ||
account: ethers.Wallet.createRandom().address, | ||
})), | ||
mapValues(cosmos, reference => ({ | ||
namespace: 'cosmos', | ||
reference, | ||
account: ethers.encodeBase58(ethers.randomBytes(32)), | ||
})), | ||
), | ||
makeCAIP, | ||
), | ||
getLocalCAIP: account => | ||
ethers.provider.getNetwork().then(({ chainId }) => makeCAIP({ namespace: 'eip155', reference: chainId, account })), | ||
}; |
Oops, something went wrong.