Merge branch 'v5.4'

This commit is contained in:
Richard Moore 2022-02-18 18:23:02 -05:00
commit eb91d708ac
6 changed files with 219 additions and 67 deletions

View File

@ -83,7 +83,7 @@ export class BigNumber implements Hexable {
div(other: BigNumberish): BigNumber {
const o = BigNumber.from(other);
if (o.isZero()) {
throwFault("division by zero", "div");
throwFault("division-by-zero", "div");
}
return toBigNumber(toBN(this).div(toBN(other)));
}
@ -95,7 +95,7 @@ export class BigNumber implements Hexable {
mod(other: BigNumberish): BigNumber {
const value = toBN(other);
if (value.isNeg()) {
throwFault("cannot modulo negative values", "mod");
throwFault("division-by-zero", "mod");
}
return toBigNumber(toBN(this).umod(value));
}
@ -103,7 +103,7 @@ export class BigNumber implements Hexable {
pow(other: BigNumberish): BigNumber {
const value = toBN(other);
if (value.isNeg()) {
throwFault("cannot raise to negative values", "pow");
throwFault("negative-power", "pow");
}
return toBigNumber(toBN(this).pow(value));
}
@ -111,7 +111,7 @@ export class BigNumber implements Hexable {
and(other: BigNumberish): BigNumber {
const value = toBN(other);
if (this.isNegative() || value.isNeg()) {
throwFault("cannot 'and' negative values", "and");
throwFault("unbound-bitwise-result", "and");
}
return toBigNumber(toBN(this).and(value));
}
@ -119,7 +119,7 @@ export class BigNumber implements Hexable {
or(other: BigNumberish): BigNumber {
const value = toBN(other);
if (this.isNegative() || value.isNeg()) {
throwFault("cannot 'or' negative values", "or");
throwFault("unbound-bitwise-result", "or");
}
return toBigNumber(toBN(this).or(value));
}
@ -127,28 +127,28 @@ export class BigNumber implements Hexable {
xor(other: BigNumberish): BigNumber {
const value = toBN(other);
if (this.isNegative() || value.isNeg()) {
throwFault("cannot 'xor' negative values", "xor");
throwFault("unbound-bitwise-result", "xor");
}
return toBigNumber(toBN(this).xor(value));
}
mask(value: number): BigNumber {
if (this.isNegative() || value < 0) {
throwFault("cannot mask negative values", "mask");
throwFault("negative-width", "mask");
}
return toBigNumber(toBN(this).maskn(value));
}
shl(value: number): BigNumber {
if (this.isNegative() || value < 0) {
throwFault("cannot shift negative values", "shl");
throwFault("negative-width", "shl");
}
return toBigNumber(toBN(this).shln(value));
}
shr(value: number): BigNumber {
if (this.isNegative() || value < 0) {
throwFault("cannot shift negative values", "shr");
throwFault("negative-width", "shr");
}
return toBigNumber(toBN(this).shrn(value));
}

View File

@ -5,7 +5,7 @@ import { getAddress, getCreate2Address, getContractAddress, getIcapAddress, isAd
import * as base64 from "@ethersproject/base64";
import { Base58 as base58 } from "@ethersproject/basex";
import { arrayify, concat, hexConcat, hexDataSlice, hexDataLength, hexlify, hexStripZeros, hexValue, hexZeroPad, isBytes, isBytesLike, isHexString, joinSignature, zeroPad, splitSignature, stripZeros } from "@ethersproject/bytes";
import { _TypedDataEncoder, hashMessage, id, isValidName, namehash } from "@ethersproject/hash";
import { _TypedDataEncoder, dnsEncode, hashMessage, id, isValidName, namehash } from "@ethersproject/hash";
import { defaultPath, entropyToMnemonic, getAccountPath, HDNode, isValidMnemonic, mnemonicToEntropy, mnemonicToSeed } from "@ethersproject/hdnode";
import { getJsonWalletAddress } from "@ethersproject/json-wallets";
import { keccak256 } from "@ethersproject/keccak256";
@ -114,6 +114,7 @@ export {
formatBytes32String,
parseBytes32String,
dnsEncode,
hashMessage,
namehash,
isValidName,

View File

@ -1,7 +1,7 @@
"use strict";
import { id } from "./id";
import { isValidName, namehash } from "./namehash";
import { dnsEncode, isValidName, namehash } from "./namehash";
import { hashMessage, messagePrefix } from "./message";
import { TypedDataEncoder as _TypedDataEncoder } from "./typed-data";
@ -9,6 +9,7 @@ import { TypedDataEncoder as _TypedDataEncoder } from "./typed-data";
export {
id,
dnsEncode,
namehash,
isValidName,

View File

@ -46,3 +46,12 @@ export function namehash(name: string): string {
return hexlify(result);
}
export function dnsEncode(name: string): string {
return hexlify(concat(name.split(".").map((comp) => {
// We jam in an _ prefix to fill in with the length later
// Note: Nameprep throws if the component is over 63 bytes
const bytes = toUtf8Bytes("_" + nameprep(comp));
bytes[0] = bytes.length - 1;
return bytes;
}))) + "00";
}

View File

@ -217,6 +217,45 @@ export class Logger {
messageDetails.push(`version=${ this.version }`);
const reason = message;
let url = "";
switch (code) {
case ErrorCode.NUMERIC_FAULT: {
url = "NUMERIC_FAULT";
const fault = message;
switch (fault) {
case "overflow": case "underflow":
url += "-" + fault;
break;
case "division-by-zero": case "negative-modulo":
url += "-undefined";
break;
case "negative-power": case "negative-width":
url += "-unsupported";
break;
case "unbound-bitwise-result":
url += "-unbound-result";
break;
}
break;
}
case ErrorCode.CALL_EXCEPTION:
case ErrorCode.INSUFFICIENT_FUNDS:
case ErrorCode.MISSING_NEW:
case ErrorCode.NONCE_EXPIRED:
case ErrorCode.REPLACEMENT_UNDERPRICED:
case ErrorCode.TRANSACTION_REPLACED:
case ErrorCode.UNPREDICTABLE_GAS_LIMIT:
url = code;
break;
}
if (url) {
message += " [ See: https:/\/ethers.org/errors/" + url + " ]";
}
if (messageDetails.length) {
message += " (" + messageDetails.join(", ") + ")";
}

View File

@ -8,7 +8,7 @@ import { Base58 } from "@ethersproject/basex";
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
import { arrayify, concat, hexConcat, hexDataLength, hexDataSlice, hexlify, hexValue, hexZeroPad, isHexString } from "@ethersproject/bytes";
import { HashZero } from "@ethersproject/constants";
import { namehash } from "@ethersproject/hash";
import { dnsEncode, namehash } from "@ethersproject/hash";
import { getNetwork, Network, Networkish } from "@ethersproject/networks";
import { Deferrable, defineReadOnly, getStatic, resolveProperties } from "@ethersproject/properties";
import { Transaction } from "@ethersproject/transactions";
@ -278,6 +278,24 @@ function getIpfsLink(link: string): string {
return `https:/\/gateway.ipfs.io/ipfs/${ link }`;
}
function numPad(value: number): Uint8Array {
const result = arrayify(value);
if (result.length > 32) { throw new Error("internal; should not happen"); }
const padded = new Uint8Array(32);
padded.set(result, 32 - result.length);
return padded;
}
function bytesPad(value: Uint8Array): Uint8Array {
if ((value.length % 32) === 0) { return value; }
const result = new Uint8Array(Math.ceil(value.length / 32) * 32);
result.set(value);
return result;
}
export class Resolver implements EnsResolver {
readonly provider: BaseProvider;
@ -286,6 +304,9 @@ export class Resolver implements EnsResolver {
readonly _resolvedAddress: null | string;
// For EIP-2544 names, the ancestor that provided the resolver
_supportsEip2544: null | Promise<boolean>;
// The resolvedAddress is only for creating a ReverseLookup resolver
constructor(provider: BaseProvider, address: string, name: string, resolvedAddress?: string) {
defineReadOnly(this, "provider", provider);
@ -294,21 +315,85 @@ export class Resolver implements EnsResolver {
defineReadOnly(this, "_resolvedAddress", resolvedAddress);
}
async _fetchBytes(selector: string, parameters?: string): Promise<null | string> {
supportsWildcard(): Promise<boolean> {
if (!this._supportsEip2544) {
// supportsInterface(bytes4 = selector("resolve(bytes,bytes)"))
this._supportsEip2544 = this.provider.call({
to: this.address,
data: "0x01ffc9a79061b92300000000000000000000000000000000000000000000000000000000"
}).then((result) => {
return BigNumber.from(result).eq(1);
}).catch((error) => {
if (error.code === Logger.errors.CALL_EXCEPTION) { return false; }
// Rethrow the error: link is down, etc. Let future attempts retry.
this._supportsEip2544 = null;
throw error;
});
}
return this._supportsEip2544;
}
async _fetch(selector: string, parameters?: string): Promise<null | string> {
// e.g. keccak256("addr(bytes32,uint256)")
const tx = {
to: this.address,
data: hexConcat([ selector, namehash(this.name), (parameters || "0x") ])
};
// Wildcard support; use EIP-2544 to resolve the request
let parseBytes = false;
if (await this.supportsWildcard()) {
parseBytes = true;
const p0 = arrayify(dnsEncode(this.name));
const p1 = arrayify(tx.data);
// selector("resolve(bytes,bytes)")
const bytes: Array<string | Uint8Array> = [ "0x9061b923" ];
let byteCount = 0;
// Place-holder pointer to p0
const placeHolder0 = bytes.length;
bytes.push("0x");
byteCount += 32;
// Place-holder pointer to p1
const placeHolder1 = bytes.length;
bytes.push("0x");
byteCount += 32;
// The length and padded value of p0
bytes[placeHolder0] = numPad(byteCount);
bytes.push(numPad(p0.length));
bytes.push(bytesPad(p0));
byteCount += 32 + Math.ceil(p0.length / 32) * 32;
// The length and padded value of p0
bytes[placeHolder1] = numPad(byteCount);
bytes.push(numPad(p1.length));
bytes.push(bytesPad(p1));
byteCount += 32 + Math.ceil(p1.length / 32) * 32;
tx.data = hexConcat(bytes);
}
try {
return _parseBytes(await this.provider.call(tx));
let result = await this.provider.call(tx);
if (parseBytes) { result = _parseBytes(result); }
return result;
} catch (error) {
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
return null;
}
}
async _fetchBytes(selector: string, parameters?: string): Promise<null | string> {
const result = await this._fetch(selector, parameters);
if (result != null) { return _parseBytes(result); }
return null;
}
_getAddress(coinType: number, hexBytes: string): string {
const coinInfo = coinInfos[String(coinType)];
@ -378,16 +463,12 @@ export class Resolver implements EnsResolver {
if (coinType === 60) {
try {
// keccak256("addr(bytes32)")
const transaction = {
to: this.address,
data: ("0x3b3b57de" + namehash(this.name).substring(2))
};
const hexBytes = await this.provider.call(transaction);
const result = await this._fetch("0x3b3b57de");
// No address
if (hexBytes === "0x" || hexBytes === HashZero) { return null; }
if (result === "0x" || result === HashZero) { return null; }
return this.provider.formatter.callAddress(hexBytes);
return this.provider.formatter.callAddress(result);
} catch (error) {
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
throw error;
@ -1672,18 +1753,36 @@ export class BaseProvider extends Provider implements EnsProvider {
async getResolver(name: string): Promise<null | Resolver> {
try {
const address = await this._getResolver(name);
if (address == null) { return null; }
return new Resolver(this, address, name);
} catch (error) {
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
throw error;
let currentName = name;
while (true) {
if (currentName === "" || currentName === ".") { return null; }
// Optimization since the eth node cannot change and does
// not have a wildcar resolver
if (name !== "eth" && currentName === "eth") { return null; }
// Check the current node for a resolver
const addr = await this._getResolver(currentName, "getResolver");
// Found a resolver!
if (addr != null) {
const resolver = new Resolver(this, addr, name);
// Legacy resolver found, using EIP-2544 so it isn't safe to use
if (currentName !== name && !(await resolver.supportsWildcard())) { return null; }
return resolver;
}
// Get the parent node
currentName = currentName.split(".").slice(1).join(".");
}
}
async _getResolver(name: string): Promise<string> {
// Get the resolver from the blockchain
async _getResolver(name: string, operation?: string): Promise<string> {
if (operation == null) { operation = "ENS"; }
const network = await this.getNetwork();
// No ENS...
@ -1691,22 +1790,22 @@ export class BaseProvider extends Provider implements EnsProvider {
logger.throwError(
"network does not support ENS",
Logger.errors.UNSUPPORTED_OPERATION,
{ operation: "ENS", network: network.name }
{ operation, network: network.name }
);
}
// keccak256("resolver(bytes32)")
const transaction = {
to: network.ensAddress,
data: ("0x0178b8bf" + namehash(name).substring(2))
};
try {
return this.formatter.callAddress(await this.call(transaction));
// keccak256("resolver(bytes32)")
const addrData = await this.call({
to: network.ensAddress,
data: ("0x0178b8bf" + namehash(name).substring(2))
});
return this.formatter.callAddress(addrData);
} catch (error) {
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
throw error;
// ENS registry cannot throw errors on resolver(bytes32)
}
return null;
}
async resolveName(name: string | Promise<string>): Promise<null | string> {
@ -1735,34 +1834,17 @@ export class BaseProvider extends Provider implements EnsProvider {
address = await address;
address = this.formatter.address(address);
const reverseName = address.substring(2).toLowerCase() + ".addr.reverse";
const node = address.substring(2).toLowerCase() + ".addr.reverse";
const resolverAddress = await this._getResolver(reverseName);
if (!resolverAddress) { return null; }
const resolverAddr = await this._getResolver(node, "lookupAddress");
if (resolverAddr == null) { return null; }
// keccak("name(bytes32)")
let bytes = arrayify(await this.call({
to: resolverAddress,
data: ("0x691f3431" + namehash(reverseName).substring(2))
const name = _parseString(await this.call({
to: resolverAddr,
data: ("0x691f3431" + namehash(node).substring(2))
}));
// Strip off the dynamic string pointer (0x20)
if (bytes.length < 32 || !BigNumber.from(bytes.slice(0, 32)).eq(32)) { return null; }
bytes = bytes.slice(32);
// Not a length-prefixed string
if (bytes.length < 32) { return null; }
// Get the length of the string (from the length-prefix)
const length = BigNumber.from(bytes.slice(0, 32)).toNumber();
bytes = bytes.slice(32);
// Length longer than available data
if (length > bytes.length) { return null; }
const name = toUtf8String(bytes.slice(0, length));
// Make sure the reverse record matches the foward record
const addr = await this.resolveName(name);
if (addr != address) { return null; }
@ -1775,15 +1857,35 @@ export class BaseProvider extends Provider implements EnsProvider {
// Address; reverse lookup
const address = this.formatter.address(nameOrAddress);
const reverseName = address.substring(2).toLowerCase() + ".addr.reverse";
const node = address.substring(2).toLowerCase() + ".addr.reverse";
const resolverAddress = await this._getResolver(reverseName);
const resolverAddress = await this._getResolver(node, "getAvatar");
if (!resolverAddress) { return null; }
resolver = new Resolver(this, resolverAddress, "_", address);
// Try resolving the avatar against the addr.reverse resolver
resolver = new Resolver(this, resolverAddress, node);
try {
const avatar = await resolver.getAvatar();
if (avatar) { return avatar.url; }
} catch (error) {
if (error.code !== Logger.errors.CALL_EXCEPTION) { throw error; }
}
// Try getting the name and performing forward lookup; allowing wildcards
try {
// keccak("name(bytes32)")
const name = _parseString(await this.call({
to: resolverAddress,
data: ("0x691f3431" + namehash(node).substring(2))
}));
resolver = await this.getResolver(name);
} catch (error) {
if (error.code !== Logger.errors.CALL_EXCEPTION) { throw error; }
return null;
}
} else {
// ENS name; forward lookup
// ENS name; forward lookup with wildcard
resolver = await this.getResolver(nameOrAddress);
if (!resolver) { return null; }
}