Last active
December 16, 2024 06:21
-
-
Save kran/15d4c51ee4a4063af9a718e6c3a78931 to your computer and use it in GitHub Desktop.
ethers.nashorn.js
This file contains hidden or 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
/** | |
* <dependency> | |
* <groupId>org.bouncycastle</groupId> | |
* <artifactId>bcprov-jdk18on</artifactId> | |
* <version>1.79</version> | |
* </dependency> | |
*/ | |
(function() { | |
const version = "6.13.1"; | |
const WordSize = 32; | |
const Padding = new Uint8Array(WordSize); | |
const maxValue = 0x1fffffffffffff; | |
const BigInteger = Java.type('java.math.BigInteger'); | |
const MessageDigest = Java.type('java.security.MessageDigest'); | |
const ByteArray = Java.type('byte[]'); | |
const Security = Java.type('java.security.Security'); | |
const BouncyCastleProvider = Java.type('org.bouncycastle.jce.provider.BouncyCastleProvider'); | |
Security.addProvider(new BouncyCastleProvider()); | |
const HttpClient = Java.type("java.net.http.HttpClient"); | |
const HttpRequest = Java.type("java.net.http.HttpRequest"); | |
const HttpResponse = Java.type("java.net.http.HttpResponse"); | |
const URI = Java.type("java.net.URI"); | |
const BodyPublishers = Java.type("java.net.http.HttpRequest.BodyPublishers"); | |
const BodyHandlers = Java.type("java.net.http.HttpResponse.BodyHandlers"); | |
const fetch = function(req) { | |
const client = HttpClient.newBuilder().build(); | |
let requestBuilder = HttpRequest.newBuilder() | |
.uri(URI.create(req.url)) | |
.method(req.method, req.body ? | |
BodyPublishers.ofString(req.body) : | |
BodyPublishers.noBody()); | |
if(req.headers) { | |
for(var key in req.headers) { | |
if(req.headers.hasOwnProperty(key)) { | |
requestBuilder.header(key, req.headers[key]); | |
} | |
} | |
} | |
const response = client.send(requestBuilder.build(), BodyHandlers.ofString()); | |
return { | |
status: response.statusCode(), | |
body: function() { | |
return response.body(); | |
}, | |
headers: response.headers().map() | |
}; | |
} | |
Number.isInteger = function(value) { | |
if (typeof value !== 'number') { | |
return false; | |
} | |
if (!isFinite(value)) { | |
return false; | |
} | |
return Math.floor(value) === value; | |
}; | |
Uint8Array.prototype.slice = function(start, end) { | |
if (end === undefined) { | |
end = this.length; | |
} | |
var newArray = new Uint8Array(end - start); | |
for (var i = start; i < end; i++) { | |
newArray[i - start] = this[i]; | |
} | |
return newArray; | |
}; | |
function inherits(ctor, superCtor) { | |
ctor.super_ = superCtor; | |
ctor.prototype = Object.create(superCtor.prototype, { | |
constructor: { | |
value: ctor, | |
enumerable: false, | |
writable: true, | |
configurable: true | |
} | |
}); | |
} | |
function BigInt(value) { | |
// 如果已经是 BigInteger 类型,直接返回 | |
if (value instanceof BigInteger) { | |
return value; | |
} | |
if (typeof value === 'string') { | |
// 处理十六进制字符串 | |
if (value.startsWith('0x')) { | |
return new BigInteger(value.substring(2), 16); | |
} | |
// 处理普通数字字符串 | |
return new BigInteger(value, 10); | |
} | |
if (typeof value === 'number') { | |
return new BigInteger(value.toString(), 10); | |
} | |
// 处理布尔值 | |
if (typeof value === 'boolean') { | |
return new BigInteger(value ? "1" : "0", 10); | |
} | |
throw new Error("Cannot convert " + value + " to BigInt"); | |
} | |
const BN_0$a = BigInt(0); | |
const BN_1$5 = BigInt(1); | |
const Nibbles$1 = "0123456789abcdef"; | |
function toBigInt(value) { | |
if (value instanceof Uint8Array) { | |
let result = "0x0"; | |
for (let i = 0; i < value.length; i++) { | |
let v = value[i]; | |
result += Nibbles$1[v >> 4]; | |
result += Nibbles$1[v & 0x0f]; | |
} | |
return BigInt(result); | |
} | |
return getBigInt(value); | |
} | |
function toNumber(value) { | |
return getNumber(toBigInt(value)); | |
} | |
function isError(error, code) { | |
return (error && error.code === code); | |
}; | |
function isCallException(error) { | |
return isError(error, "CALL_EXCEPTION"); | |
}; | |
function Proxy(target, fns) { | |
return { | |
__noSuchProperty__: function(prop) { | |
return fns.get(target, prop, null); | |
} | |
}; | |
} | |
Object.assign = function(target) { | |
// 检查target是否为null或undefined | |
if (target == null) { | |
throw new TypeError('Cannot convert undefined or null to object'); | |
} | |
// 将target转换为对象 | |
var to = Object(target); | |
// 遍历所有源对象 | |
for (var i = 1; i < arguments.length; i++) { | |
var nextSource = arguments[i]; | |
// 跳过null和undefined的源对象 | |
if (nextSource != null) { | |
// 遍历源对象的所有可枚举属性 | |
for (var nextKey in nextSource) { | |
// 确保是对象自身的属性而不是原型链上的 | |
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { | |
to[nextKey] = nextSource[nextKey]; | |
} | |
} | |
} | |
} | |
return to; | |
} | |
const resultNames = new WeakMap(); | |
function getNames(result) { | |
return resultNames.get(result); | |
} | |
function setNames(result, names) { | |
resultNames.set(result, names); | |
} | |
function concat(datas) { | |
return "0x" + datas.map((d) => hexlify(d).substring(2)).join(""); | |
} | |
function isHexString(value, length) { | |
if (typeof (value) !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) { | |
return false; | |
} | |
if (typeof (length) == "number" && value.length !== 2 + 2 * length) { | |
return false; | |
} | |
if (length === true && (value.length % 2) !== 0) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Returns true if %%value%% is a valid representation of arbitrary | |
* data (i.e. a valid [[DataHexString]] or a Uint8Array). | |
*/ | |
function isBytesLike(value) { | |
return (isHexString(value, true) || (value instanceof Uint8Array)); | |
} | |
function assertPrivate(givenGuard, guard, className) { | |
if(className == null) { | |
className = ""; | |
} | |
if(givenGuard !== guard) { | |
let method = className, operation = "new"; | |
if(className) { | |
method += "."; | |
operation += " " + className; | |
} | |
assert(false, `private constructor; use ${method}from* methods`, "UNSUPPORTED_OPERATION", { | |
operation: operation | |
}); | |
} | |
} | |
function _getBytes(value, name, copy) { | |
if (value instanceof Uint8Array) { | |
if (copy) { | |
return new Uint8Array(value); | |
} | |
return value; | |
} | |
if (typeof (value) === "string" && value.match(/^0x(?:[0-9a-f][0-9a-f])*$/i)) { | |
const result = new Uint8Array((value.length - 2) / 2); | |
let offset = 2; | |
for (let i = 0; i < result.length; i++) { | |
result[i] = parseInt(value.substring(offset, offset + 2), 16); | |
offset += 2; | |
} | |
return result; | |
} | |
assertArgument(false, "invalid BytesLike value", name || "value", value); | |
} | |
/** | |
* Get a typed Uint8Array for %%value%%. If already a Uint8Array | |
* the original %%value%% is returned; if a copy is required use | |
* [[getBytesCopy]]. | |
* | |
* @see: getBytesCopy | |
*/ | |
function getBytes(value, name) { | |
return _getBytes(value, name, false); | |
} | |
/** | |
* Get a typed Uint8Array for %%value%%, creating a copy if necessary | |
* to prevent any modifications of the returned value from being | |
* reflected elsewhere. | |
* | |
* @see: getBytes | |
*/ | |
function getBytesCopy(value, name) { | |
return _getBytes(value, name, true); | |
} | |
function getBigInt(value, name) { | |
if(value instanceof BigInteger){ | |
return value; | |
} | |
switch (typeof (value)) { | |
case "bigint": return value; | |
case "number": | |
assertArgument(Number.isInteger(value), "underflow", name || "value", value); | |
assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); | |
return BigInt(value); | |
case "string": | |
try { | |
if (value === "") { | |
throw new Error("empty string"); | |
} | |
if (value[0] === "-" && value[1] !== "-") { | |
return -BigInt(value.substring(1)); | |
} | |
return BigInt(value); | |
} | |
catch (e) { | |
assertArgument(false, `invalid BigNumberish string: ${e.message}`, name || "value", value); | |
} | |
} | |
assertArgument(false, "invalid BigNumberish value", name || "value", value); | |
} | |
/** | |
* Returns %%value%% as a bigint, validating it is valid as a bigint | |
* value and that it is positive. | |
*/ | |
function getUint(value, name) { | |
const result = getBigInt(value, name); | |
assert(result >= BN_0$a, "unsigned value cannot be negative", "NUMERIC_FAULT", { | |
fault: "overflow", operation: "getUint", value: value | |
}); | |
return result; | |
} | |
function uint8ArrayToByteArray(uint8Array) { | |
var bytes = new ByteArray(uint8Array.length); | |
for (var i = 0; i < uint8Array.length; i++) { | |
bytes[i] = uint8Array[i]; | |
} | |
return bytes; | |
} | |
function byteArrayToUint8Array(byteArray) { | |
var uint8Array = new Uint8Array(byteArray.length); | |
for (var i = 0; i < byteArray.length; i++) { | |
uint8Array[i] = byteArray[i] & 0xFF; // 使用位运算确保值在0-255范围内 | |
} | |
return uint8Array; | |
} | |
function hexlify(data) { | |
const HexCharacters = "0123456789abcdef"; | |
// 添加 getBytes 确保数据格式正确 | |
const bytes = getBytes(data); | |
let result = "0x"; | |
for (let i = 0; i < bytes.length; i++) { | |
const v = bytes[i] & 0xFF; | |
result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f]; | |
} | |
return result; | |
} | |
function checkType(value, type, name) { | |
const types = type.split("|").map(t => t.trim()); | |
for (let i = 0; i < types.length; i++) { | |
switch (type) { | |
case "any": | |
return; | |
case "bigint": | |
if(value instanceof BigInteger) return; | |
case "boolean": | |
case "number": | |
case "string": | |
if (typeof (value) === type) { | |
return; | |
} | |
} | |
} | |
const error = new Error(`invalid value for type ${type}`); | |
error.code = "INVALID_ARGUMENT"; | |
error.argument = `value.${name}`; | |
error.value = value; | |
throw error; | |
}; | |
function keccak256(_data) { | |
try { | |
var data = getBytes(_data, 'data'); | |
// 使用 BouncyCastle 的 Keccak-256 | |
var md = MessageDigest.getInstance("Keccak-256", "BC"); | |
var hash = md.digest(uint8ArrayToByteArray(data)); | |
return hexlify(byteArrayToUint8Array(hash)); | |
} catch (e) { | |
throw new Error('Failed to compute keccak256: ' + e.message); | |
} | |
} | |
function id(value) { | |
return keccak256(toUtf8Bytes(value)); | |
} | |
//export | |
function _toUtf8String(codePoints) { | |
return codePoints.map((codePoint) => { | |
if (codePoint <= 0xffff) { | |
return String.fromCharCode(codePoint); | |
} | |
codePoint -= 0x10000; | |
return String.fromCharCode((((codePoint >> 10) & 0x3ff) + 0xd800), ((codePoint & 0x3ff) + 0xdc00)); | |
}).join(""); | |
} | |
/** | |
* Returns the string represented by the UTF-8 data %%bytes%%. | |
* | |
* When %%onError%% function is specified, it is called on UTF-8 | |
* errors allowing recovery using the [[Utf8ErrorFunc]] API. | |
* (default: [error](Utf8ErrorFuncs)) | |
*/ | |
function toUtf8String(bytes, onError) { | |
return _toUtf8String(getUtf8CodePoints(bytes, onError)); | |
} | |
/** | |
* Returns the UTF-8 code-points for %%str%%. | |
* | |
* If %%form%% is specified, the string is normalized. | |
*/ | |
function toUtf8CodePoints(str, form) { | |
return getUtf8CodePoints(toUtf8Bytes(str, form)); | |
} | |
function errorFunc(reason, offset, bytes, output, badCodepoint) { | |
assertArgument(false, `invalid codepoint at offset ${offset}; ${reason}`, "bytes", bytes); | |
} | |
function ignoreFunc(reason, offset, bytes, output, badCodepoint) { | |
// If there is an invalid prefix (including stray continuation), skip any additional continuation bytes | |
if (reason === "BAD_PREFIX" || reason === "UNEXPECTED_CONTINUE") { | |
let i = 0; | |
for (let o = offset + 1; o < bytes.length; o++) { | |
if (bytes[o] >> 6 !== 0x02) { | |
break; | |
} | |
i++; | |
} | |
return i; | |
} | |
// This byte runs us past the end of the string, so just jump to the end | |
// (but the first byte was read already read and therefore skipped) | |
if (reason === "OVERRUN") { | |
return bytes.length - offset - 1; | |
} | |
// Nothing to skip | |
return 0; | |
} | |
function replaceFunc(reason, offset, bytes, output, badCodepoint) { | |
// Overlong representations are otherwise "valid" code points; just non-deistingtished | |
if (reason === "OVERLONG") { | |
assertArgument(typeof (badCodepoint) === "number", "invalid bad code point for replacement", "badCodepoint", badCodepoint); | |
output.push(badCodepoint); | |
return 0; | |
} | |
// Put the replacement character into the output | |
output.push(0xfffd); | |
// Otherwise, process as if ignoring errors | |
return ignoreFunc(reason, offset, bytes); | |
} | |
const Utf8ErrorFuncs = Object.freeze({ | |
error: errorFunc, | |
ignore: ignoreFunc, | |
replace: replaceFunc | |
}); | |
// http://stackoverflow.com/questions/13356493/decode-utf-8-with-javascript#13691499 | |
function getUtf8CodePoints(_bytes, onError) { | |
if (onError == null) { | |
onError = Utf8ErrorFuncs.error; | |
} | |
const bytes = getBytes(_bytes, "bytes"); | |
const result = []; | |
let i = 0; | |
// Invalid bytes are ignored | |
while (i < bytes.length) { | |
const c = bytes[i++]; | |
// 0xxx xxxx | |
if (c >> 7 === 0) { | |
result.push(c); | |
continue; | |
} | |
// Multibyte; how many bytes left for this character? | |
let extraLength = null; | |
let overlongMask = null; | |
// 110x xxxx 10xx xxxx | |
if ((c & 0xe0) === 0xc0) { | |
extraLength = 1; | |
overlongMask = 0x7f; | |
// 1110 xxxx 10xx xxxx 10xx xxxx | |
} | |
else if ((c & 0xf0) === 0xe0) { | |
extraLength = 2; | |
overlongMask = 0x7ff; | |
// 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx | |
} | |
else if ((c & 0xf8) === 0xf0) { | |
extraLength = 3; | |
overlongMask = 0xffff; | |
} | |
else { | |
if ((c & 0xc0) === 0x80) { | |
i += onError("UNEXPECTED_CONTINUE", i - 1, bytes, result); | |
} | |
else { | |
i += onError("BAD_PREFIX", i - 1, bytes, result); | |
} | |
continue; | |
} | |
// Do we have enough bytes in our data? | |
if (i - 1 + extraLength >= bytes.length) { | |
i += onError("OVERRUN", i - 1, bytes, result); | |
continue; | |
} | |
// Remove the length prefix from the char | |
let res = c & ((1 << (8 - extraLength - 1)) - 1); | |
for (let j = 0; j < extraLength; j++) { | |
let nextChar = bytes[i]; | |
// Invalid continuation byte | |
if ((nextChar & 0xc0) != 0x80) { | |
i += onError("MISSING_CONTINUE", i, bytes, result); | |
res = null; | |
break; | |
} | |
res = (res << 6) | (nextChar & 0x3f); | |
i++; | |
} | |
// See above loop for invalid continuation byte | |
if (res === null) { | |
continue; | |
} | |
// Maximum code point | |
if (res > 0x10ffff) { | |
i += onError("OUT_OF_RANGE", i - 1 - extraLength, bytes, result, res); | |
continue; | |
} | |
// Reserved for UTF-16 surrogate halves | |
if (res >= 0xd800 && res <= 0xdfff) { | |
i += onError("UTF16_SURROGATE", i - 1 - extraLength, bytes, result, res); | |
continue; | |
} | |
// Check for overlong sequences (more bytes than needed) | |
if (res <= overlongMask) { | |
i += onError("OVERLONG", i - 1 - extraLength, bytes, result, res); | |
continue; | |
} | |
result.push(res); | |
} | |
return result; | |
} | |
function toUtf8Bytes(str, form) { | |
assertArgument(typeof (str) === "string", "invalid string value", "str", str); | |
if (form != null) { | |
assertNormalize(form); | |
str = str.normalize(form); | |
} | |
let result = []; | |
for (let i = 0; i < str.length; i++) { | |
const c = str.charCodeAt(i); | |
if (c < 0x80) { | |
result.push(c); | |
} | |
else if (c < 0x800) { | |
result.push((c >> 6) | 0xc0); | |
result.push((c & 0x3f) | 0x80); | |
} | |
else if ((c & 0xfc00) === 0xd800) { | |
i++; | |
const c2 = str.charCodeAt(i); | |
assertArgument(i < str.length && ((c2 & 0xfc00) === 0xdc00), "invalid surrogate pair", "str", str); | |
// Surrogate Pair | |
const pair = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff); | |
result.push((pair >> 18) | 0xf0); | |
result.push(((pair >> 12) & 0x3f) | 0x80); | |
result.push(((pair >> 6) & 0x3f) | 0x80); | |
result.push((pair & 0x3f) | 0x80); | |
} | |
else { | |
result.push((c >> 12) | 0xe0); | |
result.push(((c >> 6) & 0x3f) | 0x80); | |
result.push((c & 0x3f) | 0x80); | |
} | |
} | |
return new Uint8Array(result); | |
} | |
function getChecksumAddress(address) { | |
address = address.toLowerCase(); | |
const chars = address.substring(2).split(""); | |
const expanded = new Uint8Array(40); | |
for (let i = 0; i < 40; i++) { | |
expanded[i] = chars[i].charCodeAt(0); | |
} | |
const hashed = getBytes(keccak256(expanded)); | |
for (let i = 0; i < 40; i += 2) { | |
if ((hashed[i >> 1] >> 4) >= 8) { | |
chars[i] = chars[i].toUpperCase(); | |
} | |
if ((hashed[i >> 1] & 0x0f) >= 8) { | |
chars[i + 1] = chars[i + 1].toUpperCase(); | |
} | |
} | |
return "0x" + chars.join(""); | |
} | |
function getAddress(address) { | |
assertArgument(typeof (address) === "string", "invalid address", "address", address); | |
if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) { | |
// Missing the 0x prefix | |
if (!address.startsWith("0x")) { | |
address = "0x" + address; | |
} | |
const result = getChecksumAddress(address); | |
// It is a checksummed address with a bad checksum | |
assertArgument(!address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) || result === address, "bad address checksum", "address", address); | |
return result; | |
} | |
// Maybe ICAP? (we only support direct mode) | |
if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) { | |
// It is an ICAP address with a bad checksum | |
assertArgument(address.substring(2, 4) === ibanChecksum(address), "bad icap checksum", "address", address); | |
let result = fromBase36(address.substring(4)).toString(16); | |
while (result.length < 40) { | |
result = "0" + result; | |
} | |
return getChecksumAddress("0x" + result); | |
} | |
assertArgument(false, "invalid address", "address", address); | |
} | |
function resolveAddress(target, resolver) { | |
if (typeof (target) === "string") { | |
if (target.match(/^0x[0-9a-f]{40}$/i)) { | |
return getAddress(target); | |
} | |
assert(resolver != null, "ENS resolution requires a provider", "UNSUPPORTED_OPERATION", { operation: "resolveName" }); | |
return checkAddress(target, resolver.resolveName(target)); | |
} | |
else if (isAddressable(target)) { | |
return checkAddress(target, target.getAddress()); | |
} | |
else if (target && typeof (target.then) === "function") { | |
return checkAddress(target, target); | |
} | |
assertArgument(false, "unsupported addressable value", "target", target); | |
} | |
function resolveArgs(_runner, inputs, args) { | |
// Recursively descend into args and resolve any addresses | |
const runner = getRunner(_runner, "resolveName"); | |
const resolver = canResolve(runner) ? runner : null; | |
return inputs.map((param, index) => { | |
return param.walkAsync(args[index], (type, value) => { | |
value = Typed.dereference(value, type); | |
if (type === "address") { | |
return resolveAddress(value, resolver); | |
} | |
return value; | |
}); | |
}); | |
} | |
function canResolve(value) { | |
return (value && typeof (value.resolveName) === "function"); | |
} | |
function canCall(value) { | |
return (value && typeof (value.call) === "function"); | |
} | |
function canEstimate(value) { | |
return (value && typeof (value.estimateGas) === "function"); | |
} | |
function getRunner(value, feature) { | |
if (value == null) { | |
return null; | |
} | |
if (typeof (value[feature]) === "function") { | |
return value; | |
} | |
if (value.provider && typeof (value.provider[feature]) === "function") { | |
return value.provider; | |
} | |
return null; | |
} | |
function buildWrappedMethod(contract, key) { | |
var getFragment = function () { | |
var args = Array.prototype.slice.call(arguments); | |
var fragment = contract.interface.getFunction(key, args); | |
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", { | |
operation: "fragment", | |
info: { key: key, args: args } | |
}); | |
return fragment; | |
}; | |
var populateTransaction = function () { | |
var args = Array.prototype.slice.call(arguments); | |
var fragment = getFragment.apply(null, args); | |
// If an overrides was passed in, copy it and normalize the values | |
var overrides = {}; | |
if (fragment.inputs.length + 1 === args.length) { | |
overrides = copyOverrides(args.pop()); | |
if (overrides.from) { | |
overrides.from = resolveAddress(overrides.from, getResolver(contract.runner)); | |
} | |
} | |
if (fragment.inputs.length !== args.length) { | |
throw new Error("internal error: fragment inputs doesn't match arguments; should not happen"); | |
} | |
var resolvedArgs = resolveArgs(contract.runner, fragment.inputs, args); | |
return Object.assign({}, overrides, { | |
to: contract.getAddress(), | |
data: contract.interface.encodeFunctionData(fragment, resolvedArgs) | |
}); | |
}; | |
var staticCall = function () { | |
var args = Array.prototype.slice.call(arguments); | |
var result = staticCallResult.apply(null, args); | |
if (result.length === 1) { | |
return result[0]; | |
} | |
return result; | |
}; | |
var send = function () { | |
var args = Array.prototype.slice.call(arguments); | |
var runner = contract.runner; | |
assert(canSend(runner), "contract runner does not support sending transactions", "UNSUPPORTED_OPERATION", { operation: "sendTransaction" }); | |
var tx = runner.sendTransaction(populateTransaction.apply(null, args)); | |
var provider = getProvider(contract.runner); | |
// @TODO: the provider can be null; make a custom dummy provider that will throw a | |
// meaningful error | |
return new ContractTransactionResponse(contract.interface, provider, tx); | |
}; | |
var estimateGas = function () { | |
var args = Array.prototype.slice.call(arguments); | |
var runner = getRunner(contract.runner, "estimateGas"); | |
assert(canEstimate(runner), "contract runner does not support gas estimation", "UNSUPPORTED_OPERATION", { operation: "estimateGas" }); | |
return runner.estimateGas(populateTransaction.apply(null, args)); | |
}; | |
var staticCallResult = function () { | |
var args = Array.prototype.slice.call(arguments); | |
var runner = getRunner(contract.runner, "call"); | |
assert(canCall(runner), "contract runner does not support calling", "UNSUPPORTED_OPERATION", { operation: "call" }); | |
var tx = populateTransaction.apply(null, args); | |
var result = "0x"; | |
try { | |
result = runner.call(tx); | |
} | |
catch (error) { | |
if (isCallException(error) && error.data) { | |
throw contract.interface.makeError(error.data, tx); | |
} | |
throw error; | |
} | |
var fragment = getFragment.apply(null, args); | |
return contract.interface.decodeFunctionResult(fragment, result); | |
}; | |
var method = function () { | |
var args = Array.prototype.slice.call(arguments); | |
var fragment = getFragment.apply(null, args); | |
if (fragment.constant) { | |
return staticCall.apply(null, args); | |
} | |
return send.apply(null, args); | |
}; | |
defineProperties(method, { | |
methodName: contract.interface.getFunctionName(key), | |
_contract: contract, | |
_key: key, | |
getFragment: getFragment, | |
estimateGas: estimateGas, | |
populateTransaction: populateTransaction, | |
send: send, | |
staticCall: staticCall, | |
staticCallResult: staticCallResult | |
}); | |
// Only works on non-ambiguous keys (refined fragment is always non-ambiguous) | |
Object.defineProperty(method, "fragment", { | |
configurable: false, | |
enumerable: true, | |
get: function() { | |
var fragment = contract.interface.getFunction(key); | |
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", { | |
operation: "fragment", | |
info: { key: key } | |
}); | |
return fragment; | |
} | |
}); | |
return method; | |
} | |
function buildWrappedEvent(contract, key) { | |
var getFragment = function() { | |
var args = Array.prototype.slice.call(arguments); | |
var fragment = contract.interface.getEvent(key, args); | |
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", { | |
operation: "fragment", | |
info: { key: key, args: args } | |
}); | |
return fragment; | |
}; | |
var method = function() { | |
var args = Array.prototype.slice.call(arguments); | |
return new PreparedTopicFilter(contract, getFragment.apply(null, args), args); | |
}; | |
defineProperties(method, { | |
name: contract.interface.getEventName(key), | |
_contract: contract, | |
_key: key, | |
getFragment: getFragment | |
}); | |
// Only works on non-ambiguous keys (refined fragment is always non-ambiguous) | |
Object.defineProperty(method, "fragment", { | |
configurable: false, | |
enumerable: true, | |
get: function() { | |
var fragment = contract.interface.getEvent(key); | |
assert(fragment, "no matching fragment", "UNSUPPORTED_OPERATION", { | |
operation: "fragment", | |
info: { key: key } | |
}); | |
return fragment; | |
} | |
}); | |
return method; | |
} | |
function allowSingle(set, allowed) { | |
let included = []; | |
for (const key in allowed.keys()) { | |
if (set.has(key)) { | |
included.push(key); | |
} | |
} | |
if (included.length > 1) { | |
throw new Error(`conflicting types: ${included.join(", ")}`); | |
} | |
} | |
// Functions to process a Solidity Signature TokenString from left-to-right for... | |
// ...the name with an optional type, returning the name | |
function consumeName(type, tokens) { | |
if (tokens.peekKeyword(KwTypes)) { | |
const keyword = tokens.pop().text; | |
if (keyword !== type) { | |
throw new Error(`expected ${type}, got ${keyword}`); | |
} | |
} | |
return tokens.popType("ID"); | |
} | |
// ...all keywords matching allowed, returning the keywords | |
function consumeKeywords(tokens, allowed) { | |
const keywords = new Set(); | |
while (true) { | |
const keyword = tokens.peekType("KEYWORD"); | |
if (keyword == null || (allowed && !allowed.has(keyword))) { | |
break; | |
} | |
tokens.pop(); | |
if (keywords.has(keyword)) { | |
throw new Error(`duplicate keywords: ${JSON.stringify(keyword)}`); | |
} | |
keywords.add(keyword); | |
} | |
return Object.freeze(keywords); | |
} | |
// ...all visibility keywords, returning the coalesced mutability | |
function consumeMutability(tokens) { | |
let modifiers = consumeKeywords(tokens, KwVisib); | |
// Detect conflicting modifiers | |
allowSingle(modifiers, setify("constant payable nonpayable".split(" "))); | |
allowSingle(modifiers, setify("pure view payable nonpayable".split(" "))); | |
// Process mutability states | |
if (modifiers.has("view")) { | |
return "view"; | |
} | |
if (modifiers.has("pure")) { | |
return "pure"; | |
} | |
if (modifiers.has("payable")) { | |
return "payable"; | |
} | |
if (modifiers.has("nonpayable")) { | |
return "nonpayable"; | |
} | |
// Process legacy `constant` last | |
if (modifiers.has("constant")) { | |
return "view"; | |
} | |
return "nonpayable"; | |
} | |
// ...a parameter list, returning the ParamType list | |
function consumeParams(tokens, allowIndexed) { | |
return tokens.popParams().map((t) => ParamType.from(t, allowIndexed)); | |
} | |
// ...a gas limit, returning a BigNumber or null if none | |
function consumeGas(tokens) { | |
if (tokens.peekType("AT")) { | |
tokens.pop(); | |
if (tokens.peekType("NUMBER")) { | |
return getBigInt(tokens.pop().text); | |
} | |
throw new Error("invalid gas"); | |
} | |
return null; | |
} | |
function consumeEoi(tokens) { | |
if (tokens.length) { | |
throw new Error(`unexpected tokens at offset ${tokens.offset}: ${tokens.toString()}`); | |
} | |
} | |
function verifyBasicType(type) { | |
const match = type.match(regexType); | |
assertArgument(match, "invalid type", "type", type); | |
if (type === "uint") { | |
return "uint256"; | |
} | |
if (type === "int") { | |
return "int256"; | |
} | |
if (match[2]) { | |
// bytesXX | |
const length = parseInt(match[2]); | |
assertArgument(length !== 0 && length <= 32, "invalid bytes length", "type", type); | |
} | |
else if (match[3]) { | |
// intXX or uintXX | |
const size = parseInt(match[3]); | |
assertArgument(size !== 0 && size <= 256 && (size % 8) === 0, "invalid numeric width", "type", type); | |
} | |
return type; | |
} | |
function stringify(value) { | |
if (value == null) { | |
return "null"; | |
} | |
if (Array.isArray(value)) { | |
return "[ " + (value.map(stringify)).join(", ") + " ]"; | |
} | |
if (value instanceof Uint8Array) { | |
const HEX = "0123456789abcdef"; | |
let result = "0x"; | |
for (let i = 0; i < value.length; i++) { | |
result += HEX[value[i] >> 4]; | |
result += HEX[value[i] & 0xf]; | |
} | |
return result; | |
} | |
if (typeof (value) === "object" && typeof (value.toJSON) === "function") { | |
return stringify(value.toJSON()); | |
} | |
if (value instanceof BigInteger) { | |
return value.toString(); | |
} | |
switch (typeof (value)) { | |
case "boolean": | |
case "symbol": | |
return value.toString(); | |
case "bigint": | |
return BigInt(value).toString(); | |
case "number": | |
return (value).toString(); | |
case "string": | |
return JSON.stringify(value); | |
case "object": { | |
const keys = Object.keys(value); | |
keys.sort(); | |
return "{ " + keys.map((k) => `${stringify(k)}: ${stringify(value[k])}`).join(", ") + " }"; | |
} | |
} | |
return `[ COULD NOT SERIALIZE ]`; | |
}; | |
function makeError(message, code, info) { | |
var shortMessage = message; | |
{ | |
var details = []; | |
if (info) { | |
if ("message" in info || "code" in info || "name" in info) { | |
throw new Error("value will overwrite populated values: " + stringify(info)); | |
} | |
for (var key in info) { | |
if (key === "shortMessage") { | |
continue; | |
} | |
var value = (info[key]); | |
details.push(key + "=" + stringify(value)); | |
} | |
} | |
details.push("code=" + code); | |
details.push("version=" + version); | |
if (details.length) { | |
message += " (" + details.join(", ") + ")"; | |
} | |
} | |
var error; | |
switch (code) { | |
case "INVALID_ARGUMENT": | |
error = new TypeError(message); | |
break; | |
case "NUMERIC_FAULT": | |
case "BUFFER_OVERRUN": | |
error = new RangeError(message); | |
break; | |
default: | |
error = new Error(message); | |
} | |
defineProperties(error, { code: code }); | |
if (info) { | |
Object.assign(error, info); | |
} | |
if (error.shortMessage == null) { | |
defineProperties(error, { shortMessage: shortMessage }); | |
} | |
return error; | |
}; | |
function defineProperties(target, values, types) { | |
for (let key in values) { | |
let value = values[key]; | |
const type = (types ? types[key] : null); | |
if (type) { | |
checkType(value, type, key); | |
} | |
Object.defineProperty(target, key, { enumerable: true, value: value, writable: false }); | |
} | |
}; | |
function assert(check, message, code, info) { | |
if (!check) { | |
throw makeError(message, code, info); | |
} | |
}; | |
function assertArgument(check, message, name, value) { | |
assert(check, message, "INVALID_ARGUMENT", { argument: name, value: value }); | |
} | |
function assertArgumentCount(count, expectedCount, message) { | |
if (message == null) { | |
message = ""; | |
} | |
if (message) { | |
message = ": " + message; | |
} | |
assert(count >= expectedCount, "missing arguemnt" + message, "MISSING_ARGUMENT", { | |
count: count, | |
expectedCount: expectedCount | |
}); | |
assert(count <= expectedCount, "too many arguments" + message, "UNEXPECTED_ARGUMENT", { | |
count: count, | |
expectedCount: expectedCount | |
}); | |
} | |
function toBeArray(_value) { | |
const value = getUint(_value, "value"); | |
if (value === BN_0$a) { | |
return new Uint8Array([]); | |
} | |
let hex = value.toString(16); | |
if (hex.length % 2) { | |
hex = "0" + hex; | |
} | |
const result = new Uint8Array(hex.length / 2); | |
for (let i = 0; i < result.length; i++) { | |
const offset = i * 2; | |
result[i] = parseInt(hex.substring(offset, offset + 2), 16); | |
} | |
return result; | |
} | |
function getValue$1(value) { | |
let bytes = toBeArray(value); | |
assert(bytes.length <= WordSize, "value out-of-bounds", "BUFFER_OVERRUN", { buffer: bytes, length: WordSize, offset: bytes.length }); | |
if (bytes.length !== WordSize) { | |
bytes = getBytesCopy(concat([Padding.slice(bytes.length % WordSize), bytes])); | |
} | |
return bytes; | |
} | |
function lex(text) { | |
const tokens = []; | |
const throwError = (message) => { | |
const token = (offset < text.length) ? JSON.stringify(text[offset]) : "$EOI"; | |
throw new Error(`invalid token ${token} at ${offset}: ${message}`); | |
}; | |
let brackets = []; | |
let commas = []; | |
let offset = 0; | |
while (offset < text.length) { | |
// Strip off any leading whitespace | |
let cur = text.substring(offset); | |
let match = cur.match(regexWhitespacePrefix); | |
if (match) { | |
offset += match[1].length; | |
cur = text.substring(offset); | |
} | |
const token = { depth: brackets.length, linkBack: -1, linkNext: -1, match: -1, type: "", text: "", offset: offset, value: -1 }; | |
tokens.push(token); | |
let type = (SimpleTokens[cur[0]] || ""); | |
if (type) { | |
token.type = type; | |
token.text = cur[0]; | |
offset++; | |
if (type === "OPEN_PAREN") { | |
brackets.push(tokens.length - 1); | |
commas.push(tokens.length - 1); | |
} | |
else if (type === "CLOSE_PAREN") { | |
if (brackets.length === 0) { | |
throwError("no matching open bracket"); | |
} | |
token.match = brackets.pop(); | |
(tokens[token.match]).match = tokens.length - 1; | |
token.depth--; | |
token.linkBack = commas.pop(); | |
(tokens[token.linkBack]).linkNext = tokens.length - 1; | |
} | |
else if (type === "COMMA") { | |
token.linkBack = commas.pop(); | |
(tokens[token.linkBack]).linkNext = tokens.length - 1; | |
commas.push(tokens.length - 1); | |
} | |
else if (type === "OPEN_BRACKET") { | |
token.type = "BRACKET"; | |
} | |
else if (type === "CLOSE_BRACKET") { | |
// Remove the CLOSE_BRACKET | |
let suffix = tokens.pop().text; | |
if (tokens.length > 0 && tokens[tokens.length - 1].type === "NUMBER") { | |
const value = tokens.pop().text; | |
suffix = value + suffix; | |
(tokens[tokens.length - 1]).value = getNumber(value); | |
} | |
if (tokens.length === 0 || tokens[tokens.length - 1].type !== "BRACKET") { | |
throw new Error("missing opening bracket"); | |
} | |
(tokens[tokens.length - 1]).text += suffix; | |
} | |
continue; | |
} | |
match = cur.match(regexIdPrefix); | |
if (match) { | |
token.text = match[1]; | |
offset += token.text.length; | |
if (Keywords.has(token.text)) { | |
token.type = "KEYWORD"; | |
continue; | |
} | |
if (token.text.match(regexType)) { | |
token.type = "TYPE"; | |
continue; | |
} | |
token.type = "ID"; | |
continue; | |
} | |
match = cur.match(regexNumberPrefix); | |
if (match) { | |
token.text = match[1]; | |
token.type = "NUMBER"; | |
offset += token.text.length; | |
continue; | |
} | |
throw new Error(`unexpected token ${JSON.stringify(cur[0])} at position ${offset}`); | |
} | |
return new TokenString(tokens.map((t) => Object.freeze(t))); | |
} | |
const _guard$2 = {}; | |
const _gaurd = {}; | |
const passProperties = ["then"]; | |
const _typedSymbol = "_ethers_typed"; | |
const regexArrayType = new RegExp(/^(.*)\[([0-9]*)\]$/); | |
const internal = "_ethers_internal"; | |
const ParamTypeInternal = "_ParamTypeInternal"; | |
const ErrorFragmentInternal = "_ErrorInternal"; | |
const EventFragmentInternal = "_EventInternal"; | |
const ConstructorFragmentInternal = "_ConstructorInternal"; | |
const FallbackFragmentInternal = "_FallbackInternal"; | |
const FunctionFragmentInternal = "_FunctionInternal"; | |
const StructFragmentInternal = "_StructInternal"; | |
function setify(items) { | |
const result = new Set(); | |
items.forEach((k) => result.add(k)); | |
return Object.freeze(result); | |
} | |
const _kwVisibDeploy = "external public payable override"; | |
const KwVisibDeploy = setify(_kwVisibDeploy.split(" ")); | |
// Visibility Keywords | |
const _kwVisib = "constant external internal payable private public pure view override"; | |
const KwVisib = setify(_kwVisib.split(" ")); | |
const _kwTypes = "constructor error event fallback function receive struct"; | |
const KwTypes = setify(_kwTypes.split(" ")); | |
const _kwModifiers = "calldata memory storage payable indexed"; | |
const KwModifiers = setify(_kwModifiers.split(" ")); | |
const _kwOther = "tuple returns"; | |
// All Keywords | |
const _keywords = [_kwTypes, _kwModifiers, _kwOther, _kwVisib].join(" "); | |
const Keywords = setify(_keywords.split(" ")); | |
// Single character tokens | |
const SimpleTokens = { | |
"(": "OPEN_PAREN", ")": "CLOSE_PAREN", | |
"[": "OPEN_BRACKET", "]": "CLOSE_BRACKET", | |
",": "COMMA", "@": "AT" | |
}; | |
// Parser regexes to consume the next token | |
const regexWhitespacePrefix = new RegExp("^(\\s*)"); | |
const regexNumberPrefix = new RegExp("^([0-9]+)"); | |
const regexIdPrefix = new RegExp("^([a-zA-Z$_][a-zA-Z0-9$_]*)"); | |
// Parser regexs to check validity | |
const regexId = new RegExp("^([a-zA-Z$_][a-zA-Z0-9$_]*)$"); | |
const regexType = new RegExp("^(address|bool|bytes([0-9]*)|string|u?int([0-9]*))$"); | |
function ParamType(guard, name, type, baseType, indexed, components, arrayLength, arrayChildren) { | |
assertPrivate(guard, _guard$2, "ParamType"); | |
Object.defineProperty(this, internal, { value: ParamTypeInternal }); | |
if (components) { | |
components = Object.freeze(components.slice()); | |
} | |
if (baseType === "array") { | |
if (arrayLength == null || arrayChildren == null) { | |
throw new Error(""); | |
} | |
} | |
else if (arrayLength != null || arrayChildren != null) { | |
throw new Error(""); | |
} | |
if (baseType === "tuple") { | |
if (components == null) { | |
throw new Error(""); | |
} | |
} | |
else if (components != null) { | |
throw new Error(""); | |
} | |
defineProperties(this, { | |
name: name, | |
type: type, | |
baseType: baseType, | |
indexed: indexed, | |
components: components, | |
arrayLength: arrayLength, | |
arrayChildren: arrayChildren | |
}); | |
} | |
ParamType.prototype.format = function(format) { | |
if (format == null) { | |
format = "sighash"; | |
} | |
if (format === "json") { | |
var name = this.name || ""; | |
if (this.isArray()) { | |
var result = JSON.parse(this.arrayChildren.format("json")); | |
result.name = name; | |
result.type += "[" + (this.arrayLength < 0 ? "" : String(this.arrayLength)) + "]"; | |
return JSON.stringify(result); | |
} | |
var result = { | |
type: ((this.baseType === "tuple") ? "tuple" : this.type), | |
name: name | |
}; | |
if (typeof (this.indexed) === "boolean") { | |
result.indexed = this.indexed; | |
} | |
if (this.isTuple()) { | |
result.components = this.components.map(function(c) { | |
return JSON.parse(c.format(format)); | |
}); | |
} | |
return JSON.stringify(result); | |
} | |
var result = ""; | |
// Array | |
if (this.isArray()) { | |
result += this.arrayChildren.format(format); | |
result += "[" + (this.arrayLength < 0 ? "" : String(this.arrayLength)) + "]"; | |
} | |
else { | |
if (this.isTuple()) { | |
result += "(" + this.components.map(function(comp) { | |
return comp.format(format); | |
}).join((format === "full") ? ", " : ",") + ")"; | |
} | |
else { | |
result += this.type; | |
} | |
} | |
if (format !== "sighash") { | |
if (this.indexed === true) { | |
result += " indexed"; | |
} | |
if (format === "full" && this.name) { | |
result += " " + this.name; | |
} | |
} | |
return result; | |
}; | |
ParamType.prototype.isArray = function() { | |
return (this.baseType === "array"); | |
}; | |
ParamType.prototype.isTuple = function() { | |
return (this.baseType === "tuple"); | |
}; | |
ParamType.prototype.isIndexable = function() { | |
return (this.indexed != null); | |
}; | |
ParamType.prototype.walk = function(value, process) { | |
if (this.isArray()) { | |
if (!Array.isArray(value)) { | |
throw new Error("invalid array value"); | |
} | |
if (this.arrayLength !== -1 && value.length !== this.arrayLength) { | |
throw new Error("array is wrong length"); | |
} | |
var _this = this; | |
return value.map(function(v) { | |
return _this.arrayChildren.walk(v, process); | |
}); | |
} | |
if (this.isTuple()) { | |
if (!Array.isArray(value)) { | |
throw new Error("invalid tuple value"); | |
} | |
if (value.length !== this.components.length) { | |
throw new Error("array is wrong length"); | |
} | |
var _this = this; | |
return value.map(function(v, i) { | |
return _this.components[i].walk(v, process); | |
}); | |
} | |
return process(this.type, value); | |
}; | |
ParamType.prototype._walkAsync = function(promises, value, process, setValue) { | |
if (this.isArray()) { | |
if (!Array.isArray(value)) { | |
throw new Error("invalid array value"); | |
} | |
if (this.arrayLength !== -1 && value.length !== this.arrayLength) { | |
throw new Error("array is wrong length"); | |
} | |
var childType = this.arrayChildren; | |
var result = value.slice(); | |
result.forEach(function(value, index) { | |
childType._walkAsync(promises, value, process, function(value) { | |
result[index] = value; | |
}); | |
}); | |
setValue(result); | |
return; | |
} | |
if (this.isTuple()) { | |
var components = this.components; | |
// Convert the object into an array | |
var result; | |
if (Array.isArray(value)) { | |
result = value.slice(); | |
} | |
else { | |
if (value == null || typeof (value) !== "object") { | |
throw new Error("invalid tuple value"); | |
} | |
result = components.map(function(param) { | |
if (!param.name) { | |
throw new Error("cannot use object value with unnamed components"); | |
} | |
if (!(param.name in value)) { | |
throw new Error("missing value for component " + param.name); | |
} | |
return value[param.name]; | |
}); | |
} | |
if (result.length !== this.components.length) { | |
throw new Error("array is wrong length, " + result.length + ", " + this.components.length); | |
} | |
var _this = this; | |
result.forEach(function(value, index) { | |
components[index]._walkAsync(promises, value, process, function(value) { | |
result[index] = value; | |
}); | |
}); | |
setValue(result); | |
return; | |
} | |
var result = process(this.type, value); | |
setValue(result); | |
}; | |
ParamType.prototype.walkAsync = function(value, process) { | |
var result = [value]; | |
this._walkAsync([], value, process, function(value) { | |
result[0] = value; | |
}); | |
return result[0]; | |
}; | |
ParamType.from = function(obj, allowIndexed) { | |
if (ParamType.isParamType(obj)) { | |
return obj; | |
} | |
if (typeof (obj) === "string") { | |
try { | |
return ParamType.from(lex(obj), allowIndexed); | |
} | |
catch (error) { | |
assertArgument(false, "invalid param type", "obj", obj); | |
} | |
} | |
else if (obj instanceof TokenString) { | |
var type = "", baseType = ""; | |
var comps = null; | |
if (consumeKeywords(obj, setify(["tuple"])).has("tuple") || obj.peekType("OPEN_PAREN")) { | |
// Tuple | |
baseType = "tuple"; | |
comps = obj.popParams().map(function(t) { | |
return ParamType.from(t); | |
}); | |
type = "tuple(" + comps.map(function(c) { | |
return c.format(); | |
}).join(",") + ")"; | |
} | |
else { | |
// Normal | |
type = verifyBasicType(obj.popType("TYPE")); | |
baseType = type; | |
} | |
// Check for Array | |
var arrayChildren = null; | |
var arrayLength = null; | |
while (obj.length && obj.peekType("BRACKET")) { | |
var bracket = obj.pop(); //arrays[i]; | |
arrayChildren = new ParamType(_guard$2, "", type, baseType, null, comps, arrayLength, arrayChildren); | |
arrayLength = bracket.value; | |
type += bracket.text; | |
baseType = "array"; | |
comps = null; | |
} | |
var indexed = null; | |
var keywords = consumeKeywords(obj, KwModifiers); | |
if (keywords.has("indexed")) { | |
if (!allowIndexed) { | |
throw new Error(""); | |
} | |
indexed = true; | |
} | |
var name = (obj.peekType("ID") ? obj.pop().text : ""); | |
if (obj.length) { | |
throw new Error("leftover tokens"); | |
} | |
return new ParamType(_guard$2, name, type, baseType, indexed, comps, arrayLength, arrayChildren); | |
} | |
var name = obj.name; | |
assertArgument(!name || (typeof (name) === "string" && name.match(regexId)), "invalid name", "obj.name", name); | |
var indexed = obj.indexed; | |
if (indexed != null) { | |
assertArgument(allowIndexed, "parameter cannot be indexed", "obj.indexed", obj.indexed); | |
indexed = !!indexed; | |
} | |
var type = obj.type; | |
var arrayMatch = type.match(regexArrayType); | |
if (arrayMatch) { | |
var arrayLength = parseInt(arrayMatch[2] || "-1"); | |
var arrayChildren = ParamType.from({ | |
type: arrayMatch[1], | |
components: obj.components | |
}); | |
return new ParamType(_guard$2, name || "", type, "array", indexed, null, arrayLength, arrayChildren); | |
} | |
if (type === "tuple" || type.startsWith("tuple(") || type.startsWith("(")) { | |
var comps = (obj.components != null) ? obj.components.map(function(c) { | |
return ParamType.from(c); | |
}) : null; | |
var tuple = new ParamType(_guard$2, name || "", type, "tuple", indexed, comps, null, null); | |
// @TODO: use lexer to validate and normalize type | |
return tuple; | |
} | |
type = verifyBasicType(obj.type); | |
return new ParamType(_guard$2, name || "", type, type, indexed, null, null, null); | |
}; | |
ParamType.isParamType = function(value) { | |
return (value && value[internal] === ParamTypeInternal); | |
}; | |
function TokenString(tokens) { | |
var _offset = 0; | |
var _tokens = tokens.slice(); | |
Object.defineProperty(this, 'offset', { | |
get: function() { return _offset; } | |
}); | |
Object.defineProperty(this, 'length', { | |
get: function() { return _tokens.length - _offset; } | |
}); | |
this.clone = function() { | |
return new TokenString(_tokens); | |
}; | |
this.reset = function() { | |
_offset = 0; | |
}; | |
function subTokenString(from, to) { | |
if (from === undefined) from = 0; | |
if (to === undefined) to = 0; | |
return new TokenString(_tokens.slice(from, to).map(function(t) { | |
return Object.freeze(Object.assign({}, t, { | |
match: (t.match - from), | |
linkBack: (t.linkBack - from), | |
linkNext: (t.linkNext - from), | |
})); | |
})); | |
} | |
this.popKeyword = function(allowed) { | |
var top = this.peek(); | |
if (top.type !== "KEYWORD" || !allowed.has(top.text)) { | |
throw new Error("expected keyword " + top.text); | |
} | |
return this.pop().text; | |
}; | |
this.popType = function(type) { | |
if (this.peek().type !== type) { | |
var top = this.peek(); | |
throw new Error("expected " + type + "; got " + top.type + " " + JSON.stringify(top.text)); | |
} | |
return this.pop().text; | |
}; | |
this.popParen = function() { | |
var top = this.peek(); | |
if (top.type !== "OPEN_PAREN") { | |
throw new Error("bad start"); | |
} | |
var result = subTokenString(_offset + 1, top.match + 1); | |
_offset = top.match + 1; | |
return result; | |
}; | |
this.popParams = function() { | |
var top = this.peek(); | |
if (top.type !== "OPEN_PAREN") { | |
throw new Error("bad start"); | |
} | |
var result = []; | |
while (_offset < top.match - 1) { | |
var link = this.peek().linkNext; | |
result.push(subTokenString(_offset + 1, link)); | |
_offset = link; | |
} | |
_offset = top.match + 1; | |
return result; | |
}; | |
this.peek = function() { | |
if (_offset >= _tokens.length) { | |
throw new Error("out-of-bounds"); | |
} | |
return _tokens[_offset]; | |
}; | |
this.peekKeyword = function(allowed) { | |
var top = this.peekType("KEYWORD"); | |
return (top != null && allowed.has(top)) ? top : null; | |
}; | |
this.peekType = function(type) { | |
if (this.length === 0) { | |
return null; | |
} | |
var top = this.peek(); | |
return (top.type === type) ? top.text : null; | |
}; | |
this.pop = function() { | |
var result = this.peek(); | |
_offset++; | |
return result; | |
}; | |
this.toString = function() { | |
var tokens = []; | |
for (var i = _offset; i < _tokens.length; i++) { | |
var token = _tokens[i]; | |
tokens.push(token.type + ":" + token.text); | |
} | |
return "<TokenString " + tokens.join(" ") + ">"; | |
}; | |
} | |
function Fragment(guard, type, inputs) { | |
assertPrivate(guard, _guard$2, "Fragment"); | |
inputs = Object.freeze(inputs.slice()); | |
defineProperties(this, { | |
type: type, | |
inputs: inputs | |
}); | |
} | |
Fragment.from = function(obj) { | |
if (typeof (obj) === "string") { | |
// Try parsing JSON... | |
try { | |
Fragment.from(JSON.parse(obj)); | |
} | |
catch (e) { } | |
// ...otherwise, use the human-readable lexer | |
return Fragment.from(lex(obj)); | |
} | |
if (obj instanceof TokenString) { | |
// Human-readable ABI (already lexed) | |
var type = obj.peekKeyword(KwTypes); | |
switch (type) { | |
case "constructor": return ConstructorFragment.from(obj); | |
case "error": return ErrorFragment.from(obj); | |
case "event": return EventFragment.from(obj); | |
case "fallback": | |
case "receive": | |
return FallbackFragment.from(obj); | |
case "function": return FunctionFragment.from(obj); | |
case "struct": return StructFragment.from(obj); | |
} | |
} | |
else if (typeof (obj) === "object") { | |
// JSON ABI | |
switch (obj.type) { | |
case "constructor": return ConstructorFragment.from(obj); | |
case "error": return ErrorFragment.from(obj); | |
case "event": return EventFragment.from(obj); | |
case "fallback": | |
case "receive": | |
return FallbackFragment.from(obj); | |
case "function": return FunctionFragment.from(obj); | |
case "struct": return StructFragment.from(obj); | |
} | |
assert(false, "unsupported type: " + obj.type, "UNSUPPORTED_OPERATION", { | |
operation: "Fragment.from" | |
}); | |
} | |
assertArgument(false, "unsupported frgament object", "obj", obj); | |
}; | |
Fragment.isConstructor = function(value) { | |
return ConstructorFragment.isFragment(value); | |
}; | |
Fragment.isError = function(value) { | |
return ErrorFragment.isFragment(value); | |
}; | |
Fragment.isEvent = function(value) { | |
return EventFragment.isFragment(value); | |
}; | |
Fragment.isFunction = function(value) { | |
return FunctionFragment.isFragment(value); | |
}; | |
Fragment.isStruct = function(value) { | |
return StructFragment.isFragment(value); | |
}; | |
/** | |
* An abstract class to represent An individual fragment | |
* which has a name from a parse ABI. | |
*/ | |
function NamedFragment(guard, type, name, inputs) { | |
Fragment.call(this, guard, type, inputs); | |
assertArgument(typeof (name) === "string" && name.match(regexId), "invalid identifier", "name", name); | |
inputs = Object.freeze(inputs.slice()); | |
defineProperties(this, { name: name }); | |
} | |
NamedFragment.prototype = Object.create(Fragment.prototype); | |
NamedFragment.prototype.constructor = NamedFragment; | |
function joinParams(format, params) { | |
return "(" + params.map((p) => p.format(format)).join((format === "full") ? ", " : ",") + ")"; | |
} | |
/** | |
* A Fragment which represents a //Custom Error//. | |
*/ | |
function ErrorFragment(guard, name, inputs) { | |
NamedFragment.call(this, guard, "error", name, inputs); | |
Object.defineProperty(this, internal, { value: ErrorFragmentInternal }); | |
} | |
ErrorFragment.prototype = Object.create(NamedFragment.prototype); | |
ErrorFragment.prototype.constructor = ErrorFragment; | |
Object.defineProperty(ErrorFragment.prototype, "selector", { | |
get: function() { | |
return id(this.format("sighash")).substring(0, 10); | |
}, | |
enumerable: true | |
}); | |
ErrorFragment.prototype.format = function(format) { | |
if (format == null) { | |
format = "sighash"; | |
} | |
if (format === "json") { | |
return JSON.stringify({ | |
type: "error", | |
name: this.name, | |
inputs: this.inputs.map(function(input) { | |
return JSON.parse(input.format(format)); | |
}) | |
}); | |
} | |
var result = []; | |
if (format !== "sighash") { | |
result.push("error"); | |
} | |
result.push(this.name + joinParams(format, this.inputs)); | |
return result.join(" "); | |
}; | |
ErrorFragment.from = function(obj) { | |
if (ErrorFragment.isFragment(obj)) { | |
return obj; | |
} | |
if (typeof (obj) === "string") { | |
return ErrorFragment.from(lex(obj)); | |
} | |
else if (obj instanceof TokenString) { | |
var name = consumeName("error", obj); | |
var inputs = consumeParams(obj); | |
consumeEoi(obj); | |
return new ErrorFragment(_guard$2, name, inputs); | |
} | |
return new ErrorFragment(_guard$2, obj.name, obj.inputs ? obj.inputs.map(ParamType.from) : []); | |
}; | |
ErrorFragment.isFragment = function(value) { | |
return (value && value[internal] === ErrorFragmentInternal); | |
}; | |
/** | |
* A Fragment which represents an Event. | |
*/ | |
function EventFragment() { | |
NamedFragment.apply(this, [arguments[0], "event", arguments[1], arguments[2]]); | |
} | |
inherits(EventFragment, NamedFragment); | |
Object.defineProperty(EventFragment.prototype, "anonymous", { | |
enumerable: true | |
}); | |
Object.defineProperty(EventFragment.prototype, "topicHash", { | |
get: function() { | |
return id(this.format("sighash")); | |
}, | |
enumerable: true | |
}); | |
EventFragment.prototype.format = function(format) { | |
if (format == null) { | |
format = "sighash"; | |
} | |
if (format === "json") { | |
return JSON.stringify({ | |
type: "event", | |
anonymous: this.anonymous, | |
name: this.name, | |
inputs: this.inputs.map(function(i) { | |
return JSON.parse(i.format(format)); | |
}) | |
}); | |
} | |
var result = []; | |
if (format !== "sighash") { | |
result.push("event"); | |
} | |
result.push(this.name + joinParams(format, this.inputs)); | |
if (format !== "sighash" && this.anonymous) { | |
result.push("anonymous"); | |
} | |
return result.join(" "); | |
}; | |
EventFragment.getTopicHash = function(name, params) { | |
params = (params || []).map(function(p) { | |
return ParamType.from(p); | |
}); | |
var fragment = new EventFragment(_guard$2, name, params, false); | |
return fragment.topicHash; | |
}; | |
EventFragment.from = function(obj) { | |
if (EventFragment.isFragment(obj)) { | |
return obj; | |
} | |
if (typeof (obj) === "string") { | |
try { | |
return EventFragment.from(lex(obj)); | |
} | |
catch (error) { | |
assertArgument(false, "invalid event fragment", "obj", obj); | |
} | |
} | |
else if (obj instanceof TokenString) { | |
var name = consumeName("event", obj); | |
var inputs = consumeParams(obj, true); | |
var anonymous = !!consumeKeywords(obj, setify(["anonymous"])).has("anonymous"); | |
consumeEoi(obj); | |
return new EventFragment(_guard$2, name, inputs, anonymous); | |
} | |
return new EventFragment(_guard$2, obj.name, obj.inputs ? obj.inputs.map(function(p) { | |
return ParamType.from(p, true); | |
}) : [], !!obj.anonymous); | |
}; | |
EventFragment.isFragment = function(value) { | |
return (value && value[internal] === EventFragmentInternal); | |
}; | |
/** | |
* A Fragment which represents a constructor. | |
*/ | |
function ConstructorFragment() { | |
Fragment.apply(this, arguments); | |
Object.defineProperty(this, internal, { value: ConstructorFragmentInternal }); | |
defineProperties(this, { | |
payable: arguments[3], | |
gas: arguments[4] | |
}); | |
} | |
ConstructorFragment.prototype = Object.create(Fragment.prototype); | |
ConstructorFragment.prototype.constructor = ConstructorFragment; | |
ConstructorFragment.prototype.format = function(format) { | |
assert(format != null && format !== "sighash", "cannot format a constructor for sighash", "UNSUPPORTED_OPERATION", { operation: "format(sighash)" }); | |
if (format === "json") { | |
return JSON.stringify({ | |
type: "constructor", | |
stateMutability: (this.payable ? "payable" : "undefined"), | |
payable: this.payable, | |
gas: ((this.gas != null) ? this.gas : undefined), | |
inputs: this.inputs.map(function(i) { | |
return JSON.parse(i.format(format)); | |
}) | |
}); | |
} | |
var result = ["constructor" + joinParams(format, this.inputs)]; | |
if (this.payable) { | |
result.push("payable"); | |
} | |
if (this.gas != null) { | |
result.push("@" + this.gas.toString()); | |
} | |
return result.join(" "); | |
}; | |
ConstructorFragment.from = function(obj) { | |
if (ConstructorFragment.isFragment(obj)) { | |
return obj; | |
} | |
if (typeof (obj) === "string") { | |
try { | |
return ConstructorFragment.from(lex(obj)); | |
} | |
catch (error) { | |
assertArgument(false, "invalid constuctor fragment", "obj", obj); | |
} | |
} | |
else if (obj instanceof TokenString) { | |
consumeKeywords(obj, setify(["constructor"])); | |
var inputs = consumeParams(obj); | |
var payable = !!consumeKeywords(obj, KwVisibDeploy).has("payable"); | |
var gas = consumeGas(obj); | |
consumeEoi(obj); | |
return new ConstructorFragment(_guard$2, "constructor", inputs, payable, gas); | |
} | |
return new ConstructorFragment(_guard$2, "constructor", | |
obj.inputs ? obj.inputs.map(ParamType.from) : [], | |
!!obj.payable, | |
(obj.gas != null) ? obj.gas : null | |
); | |
}; | |
ConstructorFragment.isFragment = function(value) { | |
return (value && value[internal] === ConstructorFragmentInternal); | |
}; | |
/** | |
* A Fragment which represents a method. | |
*/ | |
function FallbackFragment(guard, inputs, payable) { | |
Fragment.call(this, guard, "fallback", inputs); | |
Object.defineProperty(this, internal, { value: FallbackFragmentInternal }); | |
defineProperties(this, { payable: payable }); | |
} | |
FallbackFragment.prototype = Object.create(Fragment.prototype); | |
FallbackFragment.prototype.constructor = FallbackFragment; | |
FallbackFragment.prototype.format = function(format) { | |
var type = ((this.inputs.length === 0) ? "receive" : "fallback"); | |
if (format === "json") { | |
var stateMutability = (this.payable ? "payable" : "nonpayable"); | |
return JSON.stringify({ type: type, stateMutability: stateMutability }); | |
} | |
return type + "()" + (this.payable ? " payable" : ""); | |
}; | |
FallbackFragment.from = function(obj) { | |
if (FallbackFragment.isFragment(obj)) { | |
return obj; | |
} | |
if (typeof (obj) === "string") { | |
try { | |
return FallbackFragment.from(lex(obj)); | |
} | |
catch (error) { | |
assertArgument(false, "invalid fallback fragment", "obj", obj); | |
} | |
} | |
else if (obj instanceof TokenString) { | |
var errorObj = obj.toString(); | |
var topIsValid = obj.peekKeyword(setify(["fallback", "receive"])); | |
assertArgument(topIsValid, "type must be fallback or receive", "obj", errorObj); | |
var type = obj.popKeyword(setify(["fallback", "receive"])); | |
// receive() | |
if (type === "receive") { | |
var inputs = consumeParams(obj); | |
assertArgument(inputs.length === 0, "receive cannot have arguments", "obj.inputs", inputs); | |
consumeKeywords(obj, setify(["payable"])); | |
consumeEoi(obj); | |
return new FallbackFragment(_guard$2, [], true); | |
} | |
// fallback() [payable] | |
// fallback(bytes) [payable] returns (bytes) | |
var inputs = consumeParams(obj); | |
if (inputs.length) { | |
assertArgument(inputs.length === 1 && inputs[0].type === "bytes", "invalid fallback inputs", "obj.inputs", inputs.map(function(i) { return i.format("minimal"); }).join(", ")); | |
} | |
else { | |
inputs = [ParamType.from("bytes")]; | |
} | |
var mutability = consumeMutability(obj); | |
assertArgument(mutability === "nonpayable" || mutability === "payable", "fallback cannot be constants", "obj.stateMutability", mutability); | |
if (consumeKeywords(obj, setify(["returns"])).has("returns")) { | |
var outputs = consumeParams(obj); | |
assertArgument(outputs.length === 1 && outputs[0].type === "bytes", "invalid fallback outputs", "obj.outputs", outputs.map(function(i) { return i.format("minimal"); }).join(", ")); | |
} | |
consumeEoi(obj); | |
return new FallbackFragment(_guard$2, inputs, mutability === "payable"); | |
} | |
if (obj.type === "receive") { | |
return new FallbackFragment(_guard$2, [], true); | |
} | |
if (obj.type === "fallback") { | |
var inputs = [ParamType.from("bytes")]; | |
var payable = (obj.stateMutability === "payable"); | |
return new FallbackFragment(_guard$2, inputs, payable); | |
} | |
assertArgument(false, "invalid fallback description", "obj", obj); | |
}; | |
FallbackFragment.isFragment = function(value) { | |
return (value && value[internal] === FallbackFragmentInternal); | |
}; | |
/** | |
* A Fragment which represents a method. | |
*/ | |
function FunctionFragment() { | |
var guard = arguments[0]; | |
var name = arguments[1]; | |
var stateMutability = arguments[2]; | |
var inputs = arguments[3]; | |
var outputs = arguments[4]; | |
var gas = arguments[5]; | |
NamedFragment.apply(this, [guard, 'function', name, inputs]); | |
Object.defineProperty(this, internal, { value: FunctionFragmentInternal }); | |
outputs = Object.freeze(outputs.slice()); | |
var constant = (stateMutability === "view" || stateMutability === "pure"); | |
var payable = (stateMutability === "payable"); | |
defineProperties(this, { | |
constant: constant, | |
gas: gas, | |
outputs: outputs, | |
payable: payable, | |
stateMutability: stateMutability | |
}); | |
} | |
FunctionFragment.prototype = Object.create(NamedFragment.prototype); | |
FunctionFragment.prototype.constructor = FunctionFragment; | |
Object.defineProperty(FunctionFragment.prototype, "selector", { | |
get: function() { | |
return id(this.format("sighash")).substring(0, 10); | |
} | |
}); | |
FunctionFragment.prototype.format = function(format) { | |
if (format == null) { | |
format = "sighash"; | |
} | |
if (format === "json") { | |
return JSON.stringify({ | |
type: "function", | |
name: this.name, | |
constant: this.constant, | |
stateMutability: ((this.stateMutability !== "nonpayable") ? this.stateMutability : undefined), | |
payable: this.payable, | |
gas: ((this.gas != null) ? this.gas : undefined), | |
inputs: this.inputs.map(function(i) { return JSON.parse(i.format(format)); }), | |
outputs: this.outputs.map(function(o) { return JSON.parse(o.format(format)); }) | |
}); | |
} | |
var result = []; | |
if (format !== "sighash") { | |
result.push("function"); | |
} | |
result.push(this.name + joinParams(format, this.inputs)); | |
if (format !== "sighash") { | |
if (this.stateMutability !== "nonpayable") { | |
result.push(this.stateMutability); | |
} | |
if (this.outputs && this.outputs.length) { | |
result.push("returns"); | |
result.push(joinParams(format, this.outputs)); | |
} | |
if (this.gas != null) { | |
result.push("@" + this.gas.toString()); | |
} | |
} | |
return result.join(" "); | |
}; | |
FunctionFragment.getSelector = function(name, params) { | |
params = (params || []).map(function(p) { return ParamType.from(p); }); | |
var fragment = new FunctionFragment(_guard$2, name, "view", params, [], null); | |
return fragment.selector; | |
}; | |
FunctionFragment.from = function(obj) { | |
if (FunctionFragment.isFragment(obj)) { | |
return obj; | |
} | |
if (typeof (obj) === "string") { | |
try { | |
return FunctionFragment.from(lex(obj)); | |
} | |
catch (error) { | |
assertArgument(false, "invalid function fragment", "obj", obj); | |
} | |
} | |
else if (obj instanceof TokenString) { | |
var name = consumeName("function", obj); | |
var inputs = consumeParams(obj); | |
var mutability = consumeMutability(obj); | |
var outputs = []; | |
if (consumeKeywords(obj, setify(["returns"])).has("returns")) { | |
outputs = consumeParams(obj); | |
} | |
var gas = consumeGas(obj); | |
consumeEoi(obj); | |
return new FunctionFragment(_guard$2, name, mutability, inputs, outputs, gas); | |
} | |
var stateMutability = obj.stateMutability; | |
if (stateMutability == null) { | |
stateMutability = "payable"; | |
if (typeof (obj.constant) === "boolean") { | |
stateMutability = "view"; | |
if (!obj.constant) { | |
stateMutability = "payable"; | |
if (typeof (obj.payable) === "boolean" && !obj.payable) { | |
stateMutability = "nonpayable"; | |
} | |
} | |
} | |
else if (typeof (obj.payable) === "boolean" && !obj.payable) { | |
stateMutability = "nonpayable"; | |
} | |
} | |
return new FunctionFragment(_guard$2, obj.name, stateMutability, | |
obj.inputs ? obj.inputs.map(ParamType.from) : [], | |
obj.outputs ? obj.outputs.map(ParamType.from) : [], | |
(obj.gas != null) ? obj.gas : null); | |
}; | |
FunctionFragment.isFragment = function(value) { | |
return (value && value[internal] === FunctionFragmentInternal); | |
}; | |
/** | |
* A Fragment which represents a structure. | |
*/ | |
function StructFragment(guard, name, inputs) { | |
NamedFragment.call(this, guard, "struct", name, inputs); | |
Object.defineProperty(this, internal, { value: StructFragmentInternal }); | |
} | |
StructFragment.prototype = Object.create(NamedFragment.prototype); | |
StructFragment.prototype.constructor = StructFragment; | |
/** | |
* Returns a string representation of this struct as %%format%%. | |
*/ | |
StructFragment.prototype.format = function() { | |
throw new Error("@TODO"); | |
}; | |
/** | |
* Returns a new **StructFragment** for %%obj%%. | |
*/ | |
StructFragment.from = function(obj) { | |
if (typeof (obj) === "string") { | |
try { | |
return StructFragment.from(lex(obj)); | |
} | |
catch (error) { | |
assertArgument(false, "invalid struct fragment", "obj", obj); | |
} | |
} | |
else if (obj instanceof TokenString) { | |
var name = consumeName("struct", obj); | |
var inputs = consumeParams(obj); | |
consumeEoi(obj); | |
return new StructFragment(_guard$2, name, inputs); | |
} | |
return new StructFragment(_guard$2, obj.name, obj.inputs ? obj.inputs.map(ParamType.from) : []); | |
}; | |
// @TODO: fix this return type | |
/** | |
* Returns ``true`` and provides a type guard if %%value%% is a | |
* **StructFragment**. | |
*/ | |
StructFragment.isFragment = function(value) { | |
return (value && value[internal] === StructFragmentInternal); | |
}; | |
/** | |
* When sending values to or receiving values from a [[Contract]], the | |
* data is generally encoded using the [ABI standard](link-solc-abi). | |
* | |
* The AbiCoder provides a utility to encode values to ABI data and | |
* decode values from ABI data. | |
* | |
* Most of the time, developers should favour the [[Contract]] class, | |
* which further abstracts a lot of the finer details of ABI data. | |
* | |
* @_section api/abi/abi-coder:ABI Encoding | |
*/ | |
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI | |
// https://docs.soliditylang.org/en/v0.8.17/control-structures.html | |
const PanicReasons$1 = new Map(); | |
PanicReasons$1.set(0x00, "GENERIC_PANIC"); | |
PanicReasons$1.set(0x01, "ASSERT_FALSE"); | |
PanicReasons$1.set(0x11, "OVERFLOW"); | |
PanicReasons$1.set(0x12, "DIVIDE_BY_ZERO"); | |
PanicReasons$1.set(0x21, "ENUM_RANGE_ERROR"); | |
PanicReasons$1.set(0x22, "BAD_STORAGE_DATA"); | |
PanicReasons$1.set(0x31, "STACK_UNDERFLOW"); | |
PanicReasons$1.set(0x32, "ARRAY_RANGE_ERROR"); | |
PanicReasons$1.set(0x41, "OUT_OF_MEMORY"); | |
PanicReasons$1.set(0x51, "UNINITIALIZED_FUNCTION_CALL"); | |
const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/); | |
const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/); | |
let defaultCoder = null; | |
let defaultMaxInflation = 1024; | |
function getBuiltinCallException(action, tx, data, abiCoder) { | |
var message = "missing revert data"; | |
var reason = null; | |
var invocation = null; | |
var revert = null; | |
if (data) { | |
message = "execution reverted"; | |
var bytes = getBytes(data); | |
data = hexlify(data); | |
if (bytes.length === 0) { | |
message += " (no data present; likely require(false) occurred"; | |
reason = "require(false)"; | |
} | |
else if (bytes.length % 32 !== 4) { | |
message += " (could not decode reason; invalid data length)"; | |
} | |
else if (hexlify(bytes.slice(0, 4)) === "0x08c379a0") { | |
// Error(string) | |
try { | |
reason = abiCoder.decode(["string"], bytes.slice(4))[0]; | |
revert = { | |
signature: "Error(string)", | |
name: "Error", | |
args: [reason] | |
}; | |
message += ": " + JSON.stringify(reason); | |
} | |
catch (error) { | |
message += " (could not decode reason; invalid string data)"; | |
} | |
} | |
else if (hexlify(bytes.slice(0, 4)) === "0x4e487b71") { | |
// Panic(uint256) | |
try { | |
var code = Number(abiCoder.decode(["uint256"], bytes.slice(4))[0]); | |
revert = { | |
signature: "Panic(uint256)", | |
name: "Panic", | |
args: [code] | |
}; | |
reason = "Panic due to " + (PanicReasons$1.get(code) || "UNKNOWN") + "(" + code + ")"; | |
message += ": " + reason; | |
} | |
catch (error) { | |
message += " (could not decode panic code)"; | |
} | |
} | |
else { | |
message += " (unknown custom error)"; | |
} | |
} | |
var transaction = { | |
to: (tx.to ? getAddress(tx.to) : null), | |
data: (tx.data || "0x") | |
}; | |
if (tx.from) { | |
transaction.from = getAddress(tx.from); | |
} | |
return makeError(message, "CALL_EXCEPTION", { | |
action: action, | |
data: data, | |
reason: reason, | |
transaction: transaction, | |
invocation: invocation, | |
revert: revert | |
}); | |
} | |
function Coder(name, type, localName, dynamic) { | |
// The coder name: | |
// - address, uint256, tuple, array, etc. | |
this.name = void 0; | |
// The fully expanded type, including composite types: | |
// - address, uint256, tuple(address,bytes), uint256[3][4][], etc. | |
this.type = void 0; | |
// The localName bound in the signature, in this example it is "baz": | |
// - tuple(address foo, uint bar) baz | |
this.localName = void 0; | |
// Whether this type is dynamic: | |
// - Dynamic: bytes, string, address[], tuple(boolean[]), etc. | |
// - Not Dynamic: address, uint256, boolean[3], tuple(address, uint8) | |
this.dynamic = void 0; | |
defineProperties(this, { | |
name: name, | |
type: type, | |
localName: localName, | |
dynamic: dynamic | |
}, { | |
name: "string", | |
type: "string", | |
localName: "string", | |
dynamic: "boolean" | |
}); | |
} | |
Coder.prototype._throwError = function(message, value) { | |
assertArgument(false, message, this.localName, value); | |
}; | |
function getNumber(value, name) { | |
if(value instanceof BigInteger) { | |
return value; | |
} | |
switch (typeof (value)) { | |
case "bigint": | |
assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); | |
return Number(value); | |
case "number": | |
assertArgument(Number.isInteger(value), "underflow", name || "value", value); | |
assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); | |
return value; | |
case "string": | |
try { | |
if (value === "") { | |
throw new Error("empty string"); | |
} | |
return getNumber(BigInt(value), name); | |
} | |
catch (e) { | |
assertArgument(false, `invalid numeric string: ${e.message}`, name || "value", value); | |
} | |
} | |
assertArgument(false, "invalid numeric value", name || "value", value); | |
} | |
function fromTwos(_value, _width) { | |
const value = getUint(_value, "value"); | |
const width = BigInt(getNumber(_width, "width")); | |
// 使用BigInteger的shiftRight替代>> | |
assert(value.shiftRight(width).equals(BN_0$a), "overflow", "NUMERIC_FAULT", { | |
operation: "fromTwos", fault: "overflow", value: _value | |
}); | |
// 使用BigInteger的shiftRight和subtract替代>>和- | |
if (value.shiftRight(width.subtract(BN_1$5)).equals(BN_0$a) === false) { | |
// 使用BigInteger的shiftLeft、subtract、not、and和add替代<<、-、~、&和+ | |
const mask = BN_1$5.shiftLeft(width).subtract(BN_1$5); | |
return value.not().and(mask).add(BN_1$5).negate(); | |
} | |
return value; | |
} | |
function toTwos(_value, _width) { | |
let value = getBigInt(_value, "value"); | |
const width = BigInt(getNumber(_width, "width")); | |
// 使用BigInteger的shiftLeft和subtract替代<<和- | |
const limit = BN_1$5.shiftLeft(width.subtract(BN_1$5)); | |
if (value.compareTo(BN_0$a) < 0) { | |
value = value.negate(); | |
assert(value.compareTo(limit) <= 0, "too low", "NUMERIC_FAULT", { | |
operation: "toTwos", fault: "overflow", value: _value | |
}); | |
// 使用BigInteger的shiftLeft、subtract替代<<和- | |
const mask = BN_1$5.shiftLeft(width).subtract(BN_1$5); | |
// 使用BigInteger的not、and和add替代~、&和+ | |
return value.not().and(mask).add(BN_1$5); | |
} else { | |
assert(value.compareTo(limit) < 0, "too high", "NUMERIC_FAULT", { | |
operation: "toTwos", fault: "overflow", value: _value | |
}); | |
} | |
return value; | |
} | |
function mask(_value, _bits) { | |
const value = getUint(_value, "value"); | |
const bits = BigInt(getNumber(_bits, "bits")); | |
const mask = BN_1$5.shiftLeft(bits).subtract(BN_1$5); | |
return value.and(mask); | |
} | |
function Writer() { | |
var _data = []; | |
var _dataLength = 0; | |
function _writeData(data) { | |
_data.push(data); | |
_dataLength += data.length; | |
return data.length; | |
} | |
Object.defineProperty(this, "data", { | |
get: function() { | |
return concat(_data); | |
} | |
}); | |
Object.defineProperty(this, "length", { | |
get: function() { | |
return _dataLength; | |
} | |
}); | |
this.appendWriter = function(writer) { | |
return _writeData(getBytesCopy(writer.data)); | |
}; | |
this.writeBytes = function(value) { | |
var bytes = getBytesCopy(value); | |
var paddingOffset = bytes.length % WordSize; | |
if (paddingOffset) { | |
bytes = getBytesCopy(concat([bytes, Padding.slice(paddingOffset)])); | |
} | |
return _writeData(bytes); | |
}; | |
this.writeValue = function(value) { | |
return _writeData(getValue$1(value)); | |
}; | |
this.writeUpdatableValue = function() { | |
var offset = _data.length; | |
_data.push(Padding); | |
_dataLength += WordSize; | |
return function(value) { | |
_data[offset] = getValue$1(value); | |
}; | |
}; | |
} | |
/** | |
* @_ignore | |
*/ | |
function Reader(data, allowLoose, maxInflation) { | |
var _this = this; | |
var _data; | |
var _offset; | |
var _bytesRead; | |
var _parent; | |
var _maxInflation; | |
defineProperties(this, { allowLoose: !!allowLoose }); | |
_data = getBytesCopy(data); | |
_bytesRead = 0; | |
_parent = null; | |
_maxInflation = (maxInflation != null) ? maxInflation : 1024; | |
_offset = 0; | |
function _incrementBytesRead(count) { | |
if (_parent) { | |
return _parent._incrementBytesRead(count); | |
} | |
_bytesRead += count; | |
// Check for excessive inflation (see: #4537) | |
assert(_maxInflation < 1 || _bytesRead <= _maxInflation * _this.dataLength, `compressed ABI data exceeds inflation ratio of ${_maxInflation} ( see: https:/\/github.com/ethers-io/ethers.js/issues/4537 )`, "BUFFER_OVERRUN", { | |
buffer: getBytesCopy(_data), offset: _offset, | |
length: count, info: { | |
bytesRead: _bytesRead, | |
dataLength: _this.dataLength | |
} | |
}); | |
} | |
function _peekBytes(offset, length, loose) { | |
var alignedLength = Math.ceil(length / WordSize) * WordSize; | |
if (_offset + alignedLength > _data.length) { | |
if (_this.allowLoose && loose && _offset + length <= _data.length) { | |
alignedLength = length; | |
} | |
else { | |
assert(false, "data out-of-bounds", "BUFFER_OVERRUN", { | |
buffer: getBytesCopy(_data), | |
length: _data.length, | |
offset: _offset + alignedLength | |
}); | |
} | |
} | |
return _data.slice(_offset, _offset + alignedLength); | |
} | |
Object.defineProperty(this, "data", { | |
get: function() { return hexlify(_data); } | |
}); | |
Object.defineProperty(this, "dataLength", { | |
get: function() { return _data.length; } | |
}); | |
Object.defineProperty(this, "consumed", { | |
get: function() { return _offset; } | |
}); | |
Object.defineProperty(this, "bytes", { | |
get: function() { return new Uint8Array(_data); } | |
}); | |
// Create a sub-reader with the same underlying data, but offset | |
this.subReader = function(offset) { | |
var reader = new Reader(_data.slice(_offset + offset), _this.allowLoose, _maxInflation); | |
reader._parent = _this; | |
return reader; | |
}; | |
// Read bytes | |
this.readBytes = function(length, loose) { | |
var bytes = _peekBytes(0, length, !!loose); | |
_incrementBytesRead(length); | |
_offset += bytes.length; | |
// @TODO: Make sure the length..end bytes are all 0? | |
return bytes.slice(0, length); | |
}; | |
// Read a numeric values | |
this.readValue = function() { | |
return toBigInt(_this.readBytes(WordSize)); | |
}; | |
this.readIndex = function() { | |
return toNumber(_this.readBytes(WordSize)); | |
}; | |
} | |
function AnonymousCoder(coder) { | |
Coder.call(this, coder.name, coder.type, "_", coder.dynamic); | |
this.coder = coder; | |
} | |
AnonymousCoder.prototype = Object.create(Coder.prototype); | |
AnonymousCoder.prototype.constructor = AnonymousCoder; | |
AnonymousCoder.prototype.defaultValue = function() { | |
return this.coder.defaultValue(); | |
}; | |
AnonymousCoder.prototype.encode = function(writer, value) { | |
return this.coder.encode(writer, value); | |
}; | |
AnonymousCoder.prototype.decode = function(reader) { | |
return this.coder.decode(reader); | |
}; | |
/** | |
* @_ignore | |
*/ | |
function pack(writer, coders, values) { | |
let arrayValues = []; | |
if (Array.isArray(values)) { | |
arrayValues = values; | |
} | |
else if (values && typeof (values) === "object") { | |
let unique = {}; | |
arrayValues = coders.map((coder) => { | |
const name = coder.localName; | |
assert(name, | |
"cannot encode object for signature with missing names", "INVALID_ARGUMENT", | |
{ argument: "values", info: { coder: coder }, value: values } | |
); | |
assert(!unique[name], | |
"cannot encode object for signature with duplicate names", "INVALID_ARGUMENT", | |
{ argument: "values", info: { coder: coder }, value: values }); | |
unique[name] = true; | |
return values[name]; | |
}); | |
} | |
else { | |
assertArgument(false, "invalid tuple value", "tuple", values); | |
} | |
assertArgument(coders.length === arrayValues.length, "types/value length mismatch", "tuple", values); | |
let staticWriter = new Writer(); | |
let dynamicWriter = new Writer(); | |
let updateFuncs = []; | |
coders.forEach((coder, index) => { | |
let value = arrayValues[index]; | |
if (coder.dynamic) { | |
// Get current dynamic offset (for the future pointer) | |
let dynamicOffset = dynamicWriter.length; | |
// Encode the dynamic value into the dynamicWriter | |
coder.encode(dynamicWriter, value); | |
// Prepare to populate the correct offset once we are done | |
let updateFunc = staticWriter.writeUpdatableValue(); | |
updateFuncs.push((baseOffset) => { | |
updateFunc(baseOffset + dynamicOffset); | |
}); | |
} | |
else { | |
coder.encode(staticWriter, value); | |
} | |
}); | |
// Backfill all the dynamic offsets, now that we know the static length | |
updateFuncs.forEach((func) => { func(staticWriter.length); }); | |
let length = writer.appendWriter(staticWriter); | |
length += writer.appendWriter(dynamicWriter); | |
return length; | |
} | |
/** | |
* @_ignore | |
*/ | |
function unpack(reader, coders) { | |
let values = []; | |
let keys = []; | |
// A reader anchored to this base | |
let baseReader = reader.subReader(0); | |
coders.forEach((coder) => { | |
let value = null; | |
if (coder.dynamic) { | |
let offset = reader.readIndex(); | |
let offsetReader = baseReader.subReader(offset); | |
try { | |
value = coder.decode(offsetReader); | |
} | |
catch (error) { | |
// Cannot recover from this | |
if (isError(error, "BUFFER_OVERRUN")) { | |
throw error; | |
} | |
value = error; | |
value.baseType = coder.name; | |
value.name = coder.localName; | |
value.type = coder.type; | |
} | |
} | |
else { | |
try { | |
value = coder.decode(reader); | |
} | |
catch (error) { | |
// Cannot recover from this | |
if (isError(error, "BUFFER_OVERRUN")) { | |
throw error; | |
} | |
value = error; | |
value.baseType = coder.name; | |
value.name = coder.localName; | |
value.type = coder.type; | |
} | |
} | |
if (value == undefined) { | |
throw new Error("investigate"); | |
} | |
values.push(value); | |
keys.push(coder.localName || null); | |
}); | |
return values; | |
} | |
/** | |
* @_ignore | |
*/ | |
function ArrayCoder(coder, length, localName) { | |
var type = (coder.type + "[" + (length >= 0 ? length : "") + "]"); | |
var dynamic = (length === -1 || coder.dynamic); | |
Coder.call(this, "array", type, localName, dynamic); | |
defineProperties(this, { coder: coder, length: length }); | |
} | |
inherits(ArrayCoder, Coder); | |
ArrayCoder.prototype.defaultValue = function() { | |
// Verifies the child coder is valid (even if the array is dynamic or 0-length) | |
var defaultChild = this.coder.defaultValue(); | |
var result = []; | |
for (var i = 0; i < this.length; i++) { | |
result.push(defaultChild); | |
} | |
return result; | |
}; | |
ArrayCoder.prototype.encode = function(writer, _value) { | |
var value = Typed.dereference(_value, "array"); | |
if (!Array.isArray(value)) { | |
this._throwError("expected array value", value); | |
} | |
var count = this.length; | |
if (count === -1) { | |
count = value.length; | |
writer.writeValue(value.length); | |
} | |
assertArgumentCount(value.length, count, "coder array" + (this.localName ? (" " + this.localName) : "")); | |
var coders = []; | |
for (var i = 0; i < value.length; i++) { | |
coders.push(this.coder); | |
} | |
return pack(writer, coders, value); | |
}; | |
ArrayCoder.prototype.decode = function(reader) { | |
var count = this.length; | |
if (count === -1) { | |
count = reader.readIndex(); | |
// Check that there is *roughly* enough data to ensure | |
// stray random data is not being read as a length. Each | |
// slot requires at least 32 bytes for their value (or 32 | |
// bytes as a link to the data). This could use a much | |
// tighter bound, but we are erroring on the side of safety. | |
assert(count * WordSize <= reader.dataLength, "insufficient data length", "BUFFER_OVERRUN", { buffer: reader.bytes, offset: count * WordSize, length: reader.dataLength }); | |
} | |
var coders = []; | |
for (var i = 0; i < count; i++) { | |
coders.push(new AnonymousCoder(this.coder)); | |
} | |
return unpack(reader, coders); | |
}; | |
/** | |
* @_ignore | |
*/ | |
function BooleanCoder(localName) { | |
Coder.call(this, "bool", "bool", localName, false); | |
} | |
BooleanCoder.prototype = Object.create(Coder.prototype); | |
BooleanCoder.prototype.constructor = BooleanCoder; | |
BooleanCoder.prototype.defaultValue = function() { | |
return false; | |
}; | |
BooleanCoder.prototype.encode = function(writer, _value) { | |
var value = Typed.dereference(_value, "bool"); | |
return writer.writeValue(value ? 1 : 0); | |
}; | |
BooleanCoder.prototype.decode = function(reader) { | |
return !!reader.readValue(); | |
}; | |
/** | |
* @_ignore | |
*/ | |
function DynamicBytesCoder(type, localName) { | |
Coder.call(this, type, type, localName, true); | |
} | |
DynamicBytesCoder.prototype = Object.create(Coder.prototype); | |
DynamicBytesCoder.prototype.constructor = DynamicBytesCoder; | |
DynamicBytesCoder.prototype.defaultValue = function() { | |
return "0x"; | |
}; | |
DynamicBytesCoder.prototype.encode = function(writer, value) { | |
value = getBytesCopy(value); | |
var length = writer.writeValue(value.length); | |
length += writer.writeBytes(value); | |
return length; | |
}; | |
DynamicBytesCoder.prototype.decode = function(reader) { | |
return reader.readBytes(reader.readIndex(), true); | |
}; | |
/** | |
* @_ignore | |
*/ | |
function BytesCoder(localName) { | |
DynamicBytesCoder.call(this, "bytes", localName); | |
} | |
BytesCoder.prototype = Object.create(DynamicBytesCoder.prototype); | |
BytesCoder.prototype.constructor = BytesCoder; | |
BytesCoder.prototype.decode = function(reader) { | |
return hexlify(DynamicBytesCoder.prototype.decode.call(this, reader)); | |
}; | |
/** | |
* @_ignore | |
*/ | |
function FixedBytesCoder(size, localName) { | |
var name = "bytes" + String(size); | |
Coder.call(this, name, name, localName, false); | |
defineProperties(this, { size: size }, { size: "number" }); | |
} | |
FixedBytesCoder.prototype = Object.create(Coder.prototype); | |
FixedBytesCoder.prototype.constructor = FixedBytesCoder; | |
FixedBytesCoder.prototype.defaultValue = function() { | |
return ("0x0000000000000000000000000000000000000000000000000000000000000000").substring(0, 2 + this.size * 2); | |
}; | |
FixedBytesCoder.prototype.encode = function(writer, _value) { | |
var data = getBytesCopy(Typed.dereference(_value, this.type)); | |
if (data.length !== this.size) { | |
this._throwError("incorrect data length", _value); | |
} | |
return writer.writeBytes(data); | |
}; | |
FixedBytesCoder.prototype.decode = function(reader) { | |
return hexlify(reader.readBytes(this.size)); | |
}; | |
const Empty = new Uint8Array([]); | |
/** | |
* @_ignore | |
*/ | |
function NullCoder(localName) { | |
Coder.call(this, "null", "", localName, false); | |
} | |
NullCoder.prototype = Object.create(Coder.prototype); | |
NullCoder.prototype.constructor = NullCoder; | |
NullCoder.prototype.defaultValue = function() { | |
return null; | |
}; | |
NullCoder.prototype.encode = function(writer, value) { | |
if (value != null) { | |
this._throwError("not null", value); | |
} | |
return writer.writeBytes(Empty); | |
}; | |
NullCoder.prototype.decode = function(reader) { | |
reader.readBytes(0); | |
return null; | |
}; | |
const BN_0$5 = BigInt(0); | |
const BN_1$2 = BigInt(1); | |
const BN_MAX_UINT256$1 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); | |
/** | |
* @_ignore | |
*/ | |
function NumberCoder(size, signed, localName) { | |
var name = ((signed ? "int" : "uint") + (size * 8)); | |
Coder.call(this, name, name, localName, false); | |
defineProperties(this, { size: size, signed: signed }, { size: "number", signed: "boolean" }); | |
} | |
NumberCoder.prototype = Object.create(Coder.prototype); | |
NumberCoder.prototype.constructor = NumberCoder; | |
NumberCoder.prototype.defaultValue = function() { | |
return 0; | |
}; | |
NumberCoder.prototype.encode = function(writer, _value) { | |
var value = getBigInt(Typed.dereference(_value, this.type)); | |
// Check bounds are safe for encoding | |
var maxUintValue = mask(BN_MAX_UINT256$1, WordSize * 8); | |
if (this.signed) { | |
var bounds = mask(maxUintValue, (this.size * 8) - 1); | |
if (value > bounds || value < -(bounds + BN_1$2)) { | |
this._throwError("value out-of-bounds", _value); | |
} | |
value = toTwos(value, 8 * WordSize); | |
} | |
else if (value < BN_0$5 || value > mask(maxUintValue, this.size * 8)) { | |
this._throwError("value out-of-bounds", _value); | |
} | |
return writer.writeValue(value); | |
}; | |
NumberCoder.prototype.decode = function(reader) { | |
try { | |
var value = mask(reader.readValue(), this.size * 8); | |
if (this.signed) { | |
value = fromTwos(value, this.size * 8); | |
} | |
return value; | |
} | |
catch(e) { | |
throw e; | |
} | |
}; | |
/** | |
* @_ignore | |
*/ | |
function StringCoder(localName) { | |
DynamicBytesCoder.call(this, "string", localName); | |
} | |
StringCoder.prototype = Object.create(DynamicBytesCoder.prototype); | |
StringCoder.prototype.constructor = StringCoder; | |
StringCoder.prototype.defaultValue = function() { | |
return ""; | |
}; | |
StringCoder.prototype.encode = function(writer, _value) { | |
return DynamicBytesCoder.prototype.encode.call(this, writer, toUtf8Bytes(Typed.dereference(_value, "string"))); | |
}; | |
StringCoder.prototype.decode = function(reader) { | |
return toUtf8String(DynamicBytesCoder.prototype.decode.call(this, reader)); | |
}; | |
/** | |
* @_ignore | |
*/ | |
function TupleCoder(coders, localName) { | |
var dynamic = false; | |
var types = []; | |
coders.forEach(function(coder) { | |
if (coder.dynamic) { | |
dynamic = true; | |
} | |
types.push(coder.type); | |
}); | |
var type = ("tuple(" + types.join(",") + ")"); | |
Coder.call(this, "tuple", type, localName, dynamic); | |
defineProperties(this, { coders: Object.freeze(coders.slice()) }); | |
} | |
TupleCoder.prototype = Object.create(Coder.prototype); | |
TupleCoder.prototype.constructor = TupleCoder; | |
TupleCoder.prototype.defaultValue = function() { | |
var values = []; | |
var self = this; | |
this.coders.forEach(function(coder) { | |
values.push(coder.defaultValue()); | |
}); | |
// We only output named properties for uniquely named coders | |
var uniqueNames = this.coders.reduce(function(accum, coder) { | |
var name = coder.localName; | |
if (name) { | |
if (!accum[name]) { | |
accum[name] = 0; | |
} | |
accum[name]++; | |
} | |
return accum; | |
}, {}); | |
// Add named values | |
this.coders.forEach(function(coder, index) { | |
var name = coder.localName; | |
if (!name || uniqueNames[name] !== 1) { | |
return; | |
} | |
if (name === "length") { | |
name = "_length"; | |
} | |
if (values[name] != null) { | |
return; | |
} | |
values[name] = values[index]; | |
}); | |
return Object.freeze(values); | |
}; | |
TupleCoder.prototype.encode = function(writer, _value) { | |
var value = Typed.dereference(_value, "tuple"); | |
return pack(writer, this.coders, value); | |
}; | |
TupleCoder.prototype.decode = function(reader) { | |
return unpack(reader, this.coders); | |
}; | |
function AddressCoder(localName) { | |
Coder.call(this, "address", "address", localName, false); | |
} | |
AddressCoder.prototype = Object.create(Coder.prototype); | |
AddressCoder.prototype.constructor = AddressCoder; | |
AddressCoder.prototype.defaultValue = function() { | |
return "0x0000000000000000000000000000000000000000"; | |
}; | |
AddressCoder.prototype.encode = function(writer, _value) { | |
var value = Typed.dereference(_value, "string"); | |
try { | |
value = getAddress(value); | |
} | |
catch (error) { | |
return this._throwError(error.message, _value); | |
} | |
return writer.writeValue(value); | |
}; | |
AddressCoder.prototype.decode = function(reader) { | |
return getAddress(toBeHex(reader.readValue(), 20)); | |
}; | |
/** | |
* The **AbiCoder** is a low-level class responsible for encoding JavaScript | |
* values into binary data and decoding binary data into JavaScript values. | |
*/ | |
function AbiCoder() { | |
var self = this; | |
function getCoder(param) { | |
if (param.isArray()) { | |
return new ArrayCoder(getCoder(param.arrayChildren), param.arrayLength, param.name); | |
} | |
if (param.isTuple()) { | |
return new TupleCoder(param.components.map(function(c) { | |
return getCoder(c); | |
}), param.name); | |
} | |
switch (param.baseType) { | |
case "address": | |
return new AddressCoder(param.name); | |
case "bool": | |
return new BooleanCoder(param.name); | |
case "string": | |
return new StringCoder(param.name); | |
case "bytes": | |
return new BytesCoder(param.name); | |
case "": | |
return new NullCoder(param.name); | |
} | |
// u?int[0-9]* | |
var match = param.type.match(paramTypeNumber); | |
if (match) { | |
var size = parseInt(match[2] || "256"); | |
assertArgument(size !== 0 && size <= 256 && (size % 8) === 0, "invalid " + match[1] + " bit length", "param", param); | |
return new NumberCoder(size / 8, (match[1] === "int"), param.name); | |
} | |
// bytes[0-9]+ | |
match = param.type.match(paramTypeBytes); | |
if (match) { | |
var size = parseInt(match[1]); | |
assertArgument(size !== 0 && size <= 32, "invalid bytes length", "param", param); | |
return new FixedBytesCoder(size, param.name); | |
} | |
assertArgument(false, "invalid type", "type", param.type); | |
} | |
this.getDefaultValue = function(types) { | |
var coders = types.map(function(type) { | |
return getCoder(ParamType.from(type)); | |
}); | |
var coder = new TupleCoder(coders, "_"); | |
return coder.defaultValue(); | |
}; | |
this.encode = function(types, values) { | |
assertArgumentCount(values.length, types.length, "types/values length mismatch"); | |
var coders = types.map(function(type) { | |
return getCoder(ParamType.from(type)); | |
}); | |
var coder = (new TupleCoder(coders, "_")); | |
var writer = new Writer(); | |
coder.encode(writer, values); | |
return writer.data; | |
}; | |
this.decode = function(types, data, loose) { | |
var coders = types.map(function(type) { | |
return getCoder(ParamType.from(type)); | |
}); | |
var coder = new TupleCoder(coders, "_"); | |
return coder.decode(new Reader(data, loose, defaultMaxInflation)); | |
}; | |
} | |
AbiCoder._setDefaultMaxInflation = function(value) { | |
assertArgument(typeof (value) === "number" && Number.isInteger(value), "invalid defaultMaxInflation factor", "value", value); | |
defaultMaxInflation = value; | |
}; | |
AbiCoder.defaultAbiCoder = function() { | |
if (defaultCoder == null) { | |
defaultCoder = new AbiCoder(); | |
} | |
return defaultCoder; | |
}; | |
AbiCoder.getBuiltinCallException = function(action, tx, data) { | |
return getBuiltinCallException(action, tx, data, AbiCoder.defaultAbiCoder()); | |
}; | |
/** | |
* The Interface class is a low-level class that accepts an | |
* ABI and provides all the necessary functionality to encode | |
* and decode paramaters to and results from methods, events | |
* and errors. | |
* | |
* It also provides several convenience methods to automatically | |
* search and find matching transactions and events to parse them. | |
* | |
* @_subsection api/abi:Interfaces [interfaces] | |
*/ | |
/** | |
* When using the [[Interface-parseLog]] to automatically match a Log to its event | |
* for parsing, a **LogDescription** is returned. | |
*/ | |
function LogDescription(fragment, topic, args) { | |
/** | |
* The matching fragment for the ``topic0``. | |
*/ | |
this.fragment = undefined; | |
/** | |
* The name of the Event. | |
*/ | |
this.name = undefined; | |
/** | |
* The full Event signature. | |
*/ | |
this.signature = undefined; | |
/** | |
* The topic hash for the Event. | |
*/ | |
this.topic = undefined; | |
/** | |
* The arguments passed into the Event with ``emit``. | |
*/ | |
this.args = undefined; | |
var name = fragment.name; | |
var signature = fragment.format(); | |
defineProperties(this, { | |
fragment: fragment, | |
name: name, | |
signature: signature, | |
topic: topic, | |
args: args | |
}); | |
} | |
/** | |
* When using the [[Interface-parseTransaction]] to automatically match | |
* a transaction data to its function for parsing, | |
* a **TransactionDescription** is returned. | |
*/ | |
function TransactionDescription(fragment, selector, args, value) { | |
/** | |
* The matching fragment from the transaction ``data``. | |
*/ | |
this.fragment = undefined; | |
/** | |
* The name of the Function from the transaction ``data``. | |
*/ | |
this.name = undefined; | |
/** | |
* The arguments passed to the Function from the transaction ``data``. | |
*/ | |
this.args = undefined; | |
/** | |
* The full Function signature from the transaction ``data``. | |
*/ | |
this.signature = undefined; | |
/** | |
* The selector for the Function from the transaction ``data``. | |
*/ | |
this.selector = undefined; | |
/** | |
* The ``value`` (in wei) from the transaction. | |
*/ | |
this.value = undefined; | |
var name = fragment.name; | |
var signature = fragment.format(); | |
defineProperties(this, { | |
fragment: fragment, | |
name: name, | |
args: args, | |
signature: signature, | |
selector: selector, | |
value: value | |
}); | |
} | |
/** | |
* When using the [[Interface-parseError]] to automatically match an | |
* error for a call result for parsing, an **ErrorDescription** is returned. | |
*/ | |
function ErrorDescription(fragment, selector, args) { | |
/** | |
* The matching fragment. | |
*/ | |
this.fragment = undefined; | |
/** | |
* The name of the Error. | |
*/ | |
this.name = undefined; | |
/** | |
* The arguments passed to the Error with ``revert``. | |
*/ | |
this.args = undefined; | |
/** | |
* The full Error signature. | |
*/ | |
this.signature = undefined; | |
/** | |
* The selector for the Error. | |
*/ | |
this.selector = undefined; | |
var name = fragment.name; | |
var signature = fragment.format(); | |
defineProperties(this, { | |
fragment: fragment, | |
name: name, | |
args: args, | |
signature: signature, | |
selector: selector | |
}); | |
} | |
/** | |
* An **Indexed** is used as a value when a value that does not | |
* fit within a topic (i.e. not a fixed-length, 32-byte type). It | |
* is the ``keccak256`` of the value, and used for types such as | |
* arrays, tuples, bytes and strings. | |
*/ | |
function Indexed(hash) { | |
/** | |
* The ``keccak256`` of the value logged. | |
*/ | |
this.hash = undefined; | |
/** | |
* @_ignore: | |
*/ | |
this._isIndexed = undefined; | |
defineProperties(this, { | |
hash: hash, | |
_isIndexed: true | |
}); | |
} | |
/** | |
* Returns ``true`` if %%value%% is an **Indexed**. | |
* | |
* This provides a Type Guard for property access. | |
*/ | |
Indexed.isIndexed = function(value) { | |
return !!(value && value._isIndexed); | |
}; | |
// https://docs.soliditylang.org/en/v0.8.13/control-structures.html?highlight=panic#panic-via-assert-and-error-via-require | |
const PanicReasons = { | |
"0": "generic panic", | |
"1": "assert(false)", | |
"17": "arithmetic overflow", | |
"18": "division or modulo by zero", | |
"33": "enum overflow", | |
"34": "invalid encoded storage byte array accessed", | |
"49": "out-of-bounds array access; popping on an empty array", | |
"50": "out-of-bounds access of an array or bytesN", | |
"65": "out of memory", | |
"81": "uninitialized function", | |
}; | |
const BuiltinErrors = { | |
"0x08c379a0": { | |
signature: "Error(string)", | |
name: "Error", | |
inputs: ["string"], | |
reason: (message) => { | |
return `reverted with reason string ${JSON.stringify(message)}`; | |
} | |
}, | |
"0x4e487b71": { | |
signature: "Panic(uint256)", | |
name: "Panic", | |
inputs: ["uint256"], | |
reason: (code) => { | |
let reason = "unknown panic code"; | |
if (code >= 0 && code <= 0xff && PanicReasons[code.toString()]) { | |
reason = PanicReasons[code.toString()]; | |
} | |
return `reverted with panic code 0x${code.toString(16)} (${reason})`; | |
} | |
} | |
}; | |
function n(value, width) { | |
let signed = false; | |
if (width < 0) { | |
signed = true; | |
width *= -1; | |
} | |
// @TODO: Check range is valid for value | |
return new Typed(_gaurd, `${signed ? "" : "u"}int${width}`, value, { signed: signed, width: width }); | |
} | |
function b(value, size) { | |
// @TODO: Check range is valid for value | |
return new Typed(_gaurd, `bytes${(size) ? size : ""}`, value, { size: size }); | |
} | |
function Typed(gaurd, type, value, options) { | |
if (!(this instanceof Typed)) { | |
return new Typed(gaurd, type, value, options); | |
} | |
if (options == null) { | |
options = null; | |
} | |
assertPrivate(_gaurd, gaurd, "Typed"); | |
defineProperties(this, { _typedSymbol: _typedSymbol, type: type, value: value }); | |
this._options = options; | |
// Check the value is valid | |
this.format(); | |
} | |
Typed.prototype.format = function() { | |
if (this.type === "array") { | |
throw new Error(""); | |
} | |
else if (this.type === "dynamicArray") { | |
throw new Error(""); | |
} | |
else if (this.type === "tuple") { | |
return "tuple(" + this.value.map(function(v) { return v.format(); }).join(",") + ")"; | |
} | |
return this.type; | |
}; | |
Typed.prototype.defaultValue = function() { | |
return 0; | |
}; | |
Typed.prototype.minValue = function() { | |
return 0; | |
}; | |
Typed.prototype.maxValue = function() { | |
return 0; | |
}; | |
Typed.prototype.isBigInt = function() { | |
return !!(this.type.match(/^u?int[0-9]+$/)); | |
}; | |
Typed.prototype.isData = function() { | |
return this.type.startsWith("bytes"); | |
}; | |
Typed.prototype.isString = function() { | |
return (this.type === "string"); | |
}; | |
Typed.prototype.getTupleName = function() { | |
if (this.type !== "tuple") { | |
throw TypeError("not a tuple"); | |
} | |
return this._options; | |
}; | |
Typed.prototype.getArrayLength = function() { | |
if (this.type !== "array") { | |
throw TypeError("not an array"); | |
} | |
if (this._options === true) { | |
return -1; | |
} | |
if (this._options === false) { | |
return (this.value).length; | |
} | |
return null; | |
}; | |
Typed.from = function(type, value) { | |
return new Typed(_gaurd, type, value); | |
}; | |
Typed.uint8 = function(v) { return n(v, 8); }; | |
Typed.uint16 = function(v) { return n(v, 16); }; | |
Typed.uint24 = function(v) { return n(v, 24); }; | |
Typed.uint32 = function(v) { return n(v, 32); }; | |
Typed.uint40 = function(v) { return n(v, 40); }; | |
Typed.uint48 = function(v) { return n(v, 48); }; | |
Typed.uint56 = function(v) { return n(v, 56); }; | |
Typed.uint64 = function(v) { return n(v, 64); }; | |
Typed.uint72 = function(v) { return n(v, 72); }; | |
Typed.uint80 = function(v) { return n(v, 80); }; | |
Typed.uint88 = function(v) { return n(v, 88); }; | |
Typed.uint96 = function(v) { return n(v, 96); }; | |
Typed.uint104 = function(v) { return n(v, 104); }; | |
Typed.uint112 = function(v) { return n(v, 112); }; | |
Typed.uint120 = function(v) { return n(v, 120); }; | |
Typed.uint128 = function(v) { return n(v, 128); }; | |
Typed.uint136 = function(v) { return n(v, 136); }; | |
Typed.uint144 = function(v) { return n(v, 144); }; | |
Typed.uint152 = function(v) { return n(v, 152); }; | |
Typed.uint160 = function(v) { return n(v, 160); }; | |
Typed.uint168 = function(v) { return n(v, 168); }; | |
Typed.uint176 = function(v) { return n(v, 176); }; | |
Typed.uint184 = function(v) { return n(v, 184); }; | |
Typed.uint192 = function(v) { return n(v, 192); }; | |
Typed.uint200 = function(v) { return n(v, 200); }; | |
Typed.uint208 = function(v) { return n(v, 208); }; | |
Typed.uint216 = function(v) { return n(v, 216); }; | |
Typed.uint224 = function(v) { return n(v, 224); }; | |
Typed.uint232 = function(v) { return n(v, 232); }; | |
Typed.uint240 = function(v) { return n(v, 240); }; | |
Typed.uint248 = function(v) { return n(v, 248); }; | |
Typed.uint256 = function(v) { return n(v, 256); }; | |
Typed.uint = function(v) { return n(v, 256); }; | |
Typed.int8 = function(v) { return n(v, -8); }; | |
Typed.int16 = function(v) { return n(v, -16); }; | |
Typed.int24 = function(v) { return n(v, -24); }; | |
Typed.int32 = function(v) { return n(v, -32); }; | |
Typed.int40 = function(v) { return n(v, -40); }; | |
Typed.int48 = function(v) { return n(v, -48); }; | |
Typed.int56 = function(v) { return n(v, -56); }; | |
Typed.int64 = function(v) { return n(v, -64); }; | |
Typed.int72 = function(v) { return n(v, -72); }; | |
Typed.int80 = function(v) { return n(v, -80); }; | |
Typed.int88 = function(v) { return n(v, -88); }; | |
Typed.int96 = function(v) { return n(v, -96); }; | |
Typed.int104 = function(v) { return n(v, -104); }; | |
Typed.int112 = function(v) { return n(v, -112); }; | |
Typed.int120 = function(v) { return n(v, -120); }; | |
Typed.int128 = function(v) { return n(v, -128); }; | |
Typed.int136 = function(v) { return n(v, -136); }; | |
Typed.int144 = function(v) { return n(v, -144); }; | |
Typed.int152 = function(v) { return n(v, -152); }; | |
Typed.int160 = function(v) { return n(v, -160); }; | |
Typed.int168 = function(v) { return n(v, -168); }; | |
Typed.int176 = function(v) { return n(v, -176); }; | |
Typed.int184 = function(v) { return n(v, -184); }; | |
Typed.int192 = function(v) { return n(v, -192); }; | |
Typed.int200 = function(v) { return n(v, -200); }; | |
Typed.int208 = function(v) { return n(v, -208); }; | |
Typed.int216 = function(v) { return n(v, -216); }; | |
Typed.int224 = function(v) { return n(v, -224); }; | |
Typed.int232 = function(v) { return n(v, -232); }; | |
Typed.int240 = function(v) { return n(v, -240); }; | |
Typed.int248 = function(v) { return n(v, -248); }; | |
Typed.int256 = function(v) { return n(v, -256); }; | |
Typed.int = function(v) { return n(v, -256); }; | |
Typed.bytes1 = function(v) { return b(v, 1); }; | |
Typed.bytes2 = function(v) { return b(v, 2); }; | |
Typed.bytes3 = function(v) { return b(v, 3); }; | |
Typed.bytes4 = function(v) { return b(v, 4); }; | |
Typed.bytes5 = function(v) { return b(v, 5); }; | |
Typed.bytes6 = function(v) { return b(v, 6); }; | |
Typed.bytes7 = function(v) { return b(v, 7); }; | |
Typed.bytes8 = function(v) { return b(v, 8); }; | |
Typed.bytes9 = function(v) { return b(v, 9); }; | |
Typed.bytes10 = function(v) { return b(v, 10); }; | |
Typed.bytes11 = function(v) { return b(v, 11); }; | |
Typed.bytes12 = function(v) { return b(v, 12); }; | |
Typed.bytes13 = function(v) { return b(v, 13); }; | |
Typed.bytes14 = function(v) { return b(v, 14); }; | |
Typed.bytes15 = function(v) { return b(v, 15); }; | |
Typed.bytes16 = function(v) { return b(v, 16); }; | |
Typed.bytes17 = function(v) { return b(v, 17); }; | |
Typed.bytes18 = function(v) { return b(v, 18); }; | |
Typed.bytes19 = function(v) { return b(v, 19); }; | |
Typed.bytes20 = function(v) { return b(v, 20); }; | |
Typed.bytes21 = function(v) { return b(v, 21); }; | |
Typed.bytes22 = function(v) { return b(v, 22); }; | |
Typed.bytes23 = function(v) { return b(v, 23); }; | |
Typed.bytes24 = function(v) { return b(v, 24); }; | |
Typed.bytes25 = function(v) { return b(v, 25); }; | |
Typed.bytes26 = function(v) { return b(v, 26); }; | |
Typed.bytes27 = function(v) { return b(v, 27); }; | |
Typed.bytes28 = function(v) { return b(v, 28); }; | |
Typed.bytes29 = function(v) { return b(v, 29); }; | |
Typed.bytes30 = function(v) { return b(v, 30); }; | |
Typed.bytes31 = function(v) { return b(v, 31); }; | |
Typed.bytes32 = function(v) { return b(v, 32); }; | |
Typed.address = function(v) { return new Typed(_gaurd, "address", v); }; | |
Typed.bool = function(v) { return new Typed(_gaurd, "bool", !!v); }; | |
Typed.bytes = function(v) { return new Typed(_gaurd, "bytes", v); }; | |
Typed.string = function(v) { return new Typed(_gaurd, "string", v); }; | |
Typed.array = function(v, dynamic) { | |
throw new Error("not implemented yet"); | |
}; | |
Typed.tuple = function(v, name) { | |
throw new Error("not implemented yet"); | |
}; | |
Typed.overrides = function(v) { | |
return new Typed(_gaurd, "overrides", Object.assign({}, v)); | |
}; | |
Typed.isTyped = function(value) { | |
return (value | |
&& typeof (value) === "object" | |
&& "_typedSymbol" in value | |
&& value._typedSymbol === _typedSymbol); | |
}; | |
Typed.dereference = function(value, type) { | |
if (Typed.isTyped(value)) { | |
if (value.type !== type) { | |
throw new Error("invalid type: expecetd " + type + ", got " + value.type); | |
} | |
return value.value; | |
} | |
return value; | |
}; | |
function Interface(fragments) { | |
var self = this; | |
var abi = []; | |
if (typeof (fragments) === "string") { | |
abi = JSON.parse(fragments); | |
} | |
else { | |
abi = fragments; | |
} | |
this._functions = new Map(); | |
this._errors = new Map(); | |
this._events = new Map(); | |
// this._structs = new Map(); | |
var frags = []; | |
for (var i = 0; i < abi.length; i++) { | |
try { | |
frags.push(Fragment.from(abi[i])); | |
} | |
catch (error) { | |
print("[Warning] Invalid Fragment " + JSON.stringify(abi[i]) + ":", error.message); | |
} | |
} | |
defineProperties(this, { | |
fragments: Object.freeze(frags) | |
}); | |
var fallback = null; | |
var receive = false; | |
this._abiCoder = this.getAbiCoder(); | |
// Add all fragments by their signature | |
this.fragments.forEach(function(fragment, index) { | |
var bucket; | |
switch (fragment.type) { | |
case "constructor": | |
if (self.deploy) { | |
print("duplicate definition - constructor"); | |
return; | |
} | |
//checkNames(fragment, "input", fragment.inputs); | |
defineProperties(self, { deploy: fragment }); | |
return; | |
case "fallback": | |
if (fragment.inputs.length === 0) { | |
receive = true; | |
} | |
else { | |
assertArgument(!fallback || fragment.payable !== fallback.payable, "conflicting fallback fragments", "fragments[" + index + "]", fragment); | |
fallback = fragment; | |
receive = fallback.payable; | |
} | |
return; | |
case "function": | |
//checkNames(fragment, "input", fragment.inputs); | |
//checkNames(fragment, "output", (<FunctionFragment>fragment).outputs); | |
bucket = self._functions; | |
break; | |
case "event": | |
//checkNames(fragment, "input", fragment.inputs); | |
bucket = self._events; | |
break; | |
case "error": | |
bucket = self._errors; | |
break; | |
default: | |
return; | |
} | |
// Two identical entries; ignore it | |
var signature = fragment.format(); | |
if (bucket.has(signature)) { | |
return; | |
} | |
bucket.set(signature, fragment); | |
}); | |
// If we do not have a constructor add a default | |
if (!this.deploy) { | |
defineProperties(this, { | |
deploy: ConstructorFragment.from("constructor()") | |
}); | |
} | |
defineProperties(this, { fallback: fallback, receive: receive }); | |
} | |
Interface.prototype.format = function(minimal) { | |
var format = (minimal ? "minimal" : "full"); | |
var abi = this.fragments.map(function(f) { return f.format(format); }); | |
return abi; | |
}; | |
Interface.prototype.formatJson = function() { | |
var abi = this.fragments.map(function(f) { return f.format("json"); }); | |
// We need to re-bundle the JSON fragments a bit | |
return JSON.stringify(abi.map(function(j) { return JSON.parse(j); })); | |
}; | |
Interface.prototype.getAbiCoder = function() { | |
return AbiCoder.defaultAbiCoder(); | |
}; | |
Interface.prototype._getFunction = function(key, values, forceUnique) { | |
var self = this; | |
// Selector | |
if (isHexString(key)) { | |
var selector = key.toLowerCase(); | |
var fns = this._functions.values(); | |
for (let i = 0; i < fns.length; i++) { | |
const fragment = fns[i]; | |
if (selector === fragment.selector) { | |
return fragment; | |
} | |
} | |
return null; | |
} | |
// It is a bare name, look up the function (will return null if ambiguous) | |
if (key.indexOf("(") === -1) { | |
var matching = []; | |
this._functions.forEach(function(fragment, name) { | |
if (name.split("(" /* fix:) */)[0] === key) { | |
matching.push(fragment); | |
} | |
}); | |
if (values) { | |
var lastValue = (values.length > 0) ? values[values.length - 1] : null; | |
var valueLength = values.length; | |
var allowOptions = true; | |
if (Typed.isTyped(lastValue) && lastValue.type === "overrides") { | |
allowOptions = false; | |
valueLength--; | |
} | |
// Remove all matches that don't have a compatible length. The args | |
// may contain an overrides, so the match may have n or n - 1 parameters | |
for (var i = matching.length - 1; i >= 0; i--) { | |
var inputs = matching[i].inputs.length; | |
if (inputs !== valueLength && (!allowOptions || inputs !== valueLength - 1)) { | |
matching.splice(i, 1); | |
} | |
} | |
// Remove all matches that don't match the Typed signature | |
for (var i = matching.length - 1; i >= 0; i--) { | |
var inputs = matching[i].inputs; | |
for (var j = 0; j < values.length; j++) { | |
// Not a typed value | |
if (!Typed.isTyped(values[j])) { | |
continue; | |
} | |
// We are past the inputs | |
if (j >= inputs.length) { | |
if (values[j].type === "overrides") { | |
continue; | |
} | |
matching.splice(i, 1); | |
break; | |
} | |
// Make sure the value type matches the input type | |
if (values[j].type !== inputs[j].baseType) { | |
matching.splice(i, 1); | |
break; | |
} | |
} | |
} | |
} | |
// We found a single matching signature with an overrides, but the | |
// last value is something that cannot possibly be an options | |
if (matching.length === 1 && values && values.length !== matching[0].inputs.length) { | |
var lastArg = values[values.length - 1]; | |
if (lastArg == null || Array.isArray(lastArg) || typeof (lastArg) !== "object") { | |
matching.splice(0, 1); | |
} | |
} | |
if (matching.length === 0) { | |
return null; | |
} | |
if (matching.length > 1 && forceUnique) { | |
var matchStr = matching.map(function(m) { return JSON.stringify(m.format()); }).join(", "); | |
assertArgument(false, "ambiguous function description (i.e. matches " + matchStr + ")", "key", key); | |
} | |
return matching[0]; | |
} | |
// Normalize the signature and lookup the function | |
var result = this._functions.get(FunctionFragment.from(key).format()); | |
if (result) { | |
return result; | |
} | |
return null; | |
}; | |
Interface.prototype.getFunctionName = (key) => { | |
const fragment = this._getFunction(key, null, false); | |
assertArgument(fragment, "no matching function", "key", key); | |
return fragment.name; | |
} | |
/** | |
* Returns true if %%key%% (a function selector, function name or | |
* function signature) is present in the ABI. | |
* | |
* In the case of a function name, the name may be ambiguous, so | |
* accessing the [[FunctionFragment]] may require refinement. | |
*/ | |
Interface.prototype.hasFunction = (key) => { | |
return !!this._getFunction(key, null, false); | |
} | |
/** | |
* Get the [[FunctionFragment]] for %%key%%, which may be a function | |
* selector, function name or function signature that belongs to the ABI. | |
* | |
* If %%values%% is provided, it will use the Typed API to handle | |
* ambiguous cases where multiple functions match by name. | |
* | |
* If the %%key%% and %%values%% do not refine to a single function in | |
* the ABI, this will throw. | |
*/ | |
Interface.prototype.getFunction = (key, values) => { | |
return this._getFunction(key, values || null, true); | |
} | |
/** | |
* Iterate over all functions, calling %%callback%%, sorted by their name. | |
*/ | |
Interface.prototype.forEachFunction = (callback) => { | |
const names = Array.from(this._functions.keys()); | |
names.sort((a, b) => a.localeCompare(b)); | |
for (let i = 0; i < names.length; i++) { | |
const name = names[i]; | |
callback((this._functions.get(name)), i); | |
} | |
} | |
// Find an event definition by any means necessary (unless it is ambiguous) | |
Interface.prototype._getEvent = (key, values, forceUnique) => { | |
// EventTopic | |
if (isHexString(key)) { | |
const eventTopic = key.toLowerCase(); | |
for (const fragment of this._events.values()) { | |
if (eventTopic === fragment.topicHash) { | |
return fragment; | |
} | |
} | |
return null; | |
} | |
// It is a bare name, look up the function (will return null if ambiguous) | |
if (key.indexOf("(") === -1) { | |
const matching = []; | |
this._events.forEach(function(fragment, name) { | |
if (name.split("(" /* fix:) */)[0] === key) { | |
matching.push(fragment); | |
} | |
}); | |
if (values) { | |
// Remove all matches that don't have a compatible length. | |
for (let i = matching.length - 1; i >= 0; i--) { | |
if (matching[i].inputs.length < values.length) { | |
matching.splice(i, 1); | |
} | |
} | |
// Remove all matches that don't match the Typed signature | |
for (let i = matching.length - 1; i >= 0; i--) { | |
const inputs = matching[i].inputs; | |
for (let j = 0; j < values.length; j++) { | |
// Not a typed value | |
if (!Typed.isTyped(values[j])) { | |
continue; | |
} | |
// Make sure the value type matches the input type | |
if (values[j].type !== inputs[j].baseType) { | |
matching.splice(i, 1); | |
break; | |
} | |
} | |
} | |
} | |
if (matching.length === 0) { | |
return null; | |
} | |
if (matching.length > 1 && forceUnique) { | |
const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", "); | |
assertArgument(false, `ambiguous event description (i.e. matches ${matchStr})`, "key", key); | |
} | |
return matching[0]; | |
} | |
// Normalize the signature and lookup the function | |
const result = this._events.get(EventFragment.from(key).format()); | |
if (result) { | |
return result; | |
} | |
return null; | |
} | |
/** | |
* Get the event name for %%key%%, which may be a topic hash, | |
* event name or event signature that belongs to the ABI. | |
*/ | |
Interface.prototype.getEventName = (key) => { | |
const fragment = this._getEvent(key, null, false); | |
assertArgument(fragment, "no matching event", "key", key); | |
return fragment.name; | |
} | |
/** | |
* Returns true if %%key%% (an event topic hash, event name or | |
* event signature) is present in the ABI. | |
* | |
* In the case of an event name, the name may be ambiguous, so | |
* accessing the [[EventFragment]] may require refinement. | |
*/ | |
Interface.prototype.hasEvent = (key) => { | |
return !!this._getEvent(key, null, false); | |
} | |
/** | |
* Get the [[EventFragment]] for %%key%%, which may be a topic hash, | |
* event name or event signature that belongs to the ABI. | |
* | |
* If %%values%% is provided, it will use the Typed API to handle | |
* ambiguous cases where multiple events match by name. | |
* | |
* If the %%key%% and %%values%% do not refine to a single event in | |
* the ABI, this will throw. | |
*/ | |
Interface.prototype.getEvent = (key, values) => { | |
return this._getEvent(key, values || null, true); | |
} | |
/** | |
* Iterate over all events, calling %%callback%%, sorted by their name. | |
*/ | |
Interface.prototype.forEachEvent = (callback) => { | |
const names = Array.from(this._events.keys()); | |
names.sort((a, b) => a.localeCompare(b)); | |
for (let i = 0; i < names.length; i++) { | |
const name = names[i]; | |
callback((this._events.get(name)), i); | |
} | |
} | |
/** | |
* Get the [[ErrorFragment]] for %%key%%, which may be an error | |
* selector, error name or error signature that belongs to the ABI. | |
* | |
* If %%values%% is provided, it will use the Typed API to handle | |
* ambiguous cases where multiple errors match by name. | |
* | |
* If the %%key%% and %%values%% do not refine to a single error in | |
* the ABI, this will throw. | |
*/ | |
Interface.prototype.getError = (key, values) => { | |
if (isHexString(key)) { | |
const selector = key.toLowerCase(); | |
if (BuiltinErrors[selector]) { | |
return ErrorFragment.from(BuiltinErrors[selector].signature); | |
} | |
for (const fragment of this._errors.values()) { | |
if (selector === fragment.selector) { | |
return fragment; | |
} | |
} | |
return null; | |
} | |
// It is a bare name, look up the function (will return null if ambiguous) | |
if (key.indexOf("(") === -1) { | |
const matching = []; | |
this._errors.forEach(function(fragment, name) { | |
if (name.split("(" /* fix:) */)[0] === key) { | |
matching.push(fragment); | |
} | |
}); | |
if (matching.length === 0) { | |
if (key === "Error") { | |
return ErrorFragment.from("error Error(string)"); | |
} | |
if (key === "Panic") { | |
return ErrorFragment.from("error Panic(uint256)"); | |
} | |
return null; | |
} | |
else if (matching.length > 1) { | |
const matchStr = matching.map((m) => JSON.stringify(m.format())).join(", "); | |
assertArgument(false, `ambiguous error description (i.e. ${matchStr})`, "name", key); | |
} | |
return matching[0]; | |
} | |
// Normalize the signature and lookup the function | |
key = ErrorFragment.from(key).format(); | |
if (key === "Error(string)") { | |
return ErrorFragment.from("error Error(string)"); | |
} | |
if (key === "Panic(uint256)") { | |
return ErrorFragment.from("error Panic(uint256)"); | |
} | |
const result = this._errors.get(key); | |
if (result) { | |
return result; | |
} | |
return null; | |
} | |
/** | |
* Iterate over all errors, calling %%callback%%, sorted by their name. | |
*/ | |
Interface.prototype.forEachError = (callback) => { | |
const names = Array.from(this._errors.keys()); | |
names.sort((a, b) => a.localeCompare(b)); | |
for (let i = 0; i < names.length; i++) { | |
const name = names[i]; | |
callback((this._errors.get(name)), i); | |
} | |
} | |
Interface.prototype._decodeParams = (params, data) => { | |
return this._abiCoder.decode(params, data); | |
} | |
Interface.prototype._encodeParams = (params, values) => { | |
return this._abiCoder.encode(params, values); | |
} | |
/** | |
* Encodes a ``tx.data`` object for deploying the Contract with | |
* the %%values%% as the constructor arguments. | |
*/ | |
Interface.prototype.encodeDeploy = (values) => { | |
return this._encodeParams(this.deploy.inputs, values || []); | |
} | |
/** | |
* Decodes the result %%data%% (e.g. from an ``eth_call``) for the | |
* specified error (see [[getError]] for valid values for | |
* %%key%%). | |
* | |
* Most developers should prefer the [[parseCallResult]] method instead, | |
* which will automatically detect a ``CALL_EXCEPTION`` and throw the | |
* corresponding error. | |
*/ | |
Interface.prototype.decodeErrorResult = (fragment, data) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getError(fragment); | |
assertArgument(f, "unknown error", "fragment", fragment); | |
fragment = f; | |
} | |
assertArgument(dataSlice(data, 0, 4) === fragment.selector, `data signature does not match error ${fragment.name}.`, "data", data); | |
return this._decodeParams(fragment.inputs, dataSlice(data, 4)); | |
} | |
/** | |
* Encodes the transaction revert data for a call result that | |
* reverted from the the Contract with the sepcified %%error%% | |
* (see [[getError]] for valid values for %%fragment%%) with the %%values%%. | |
* | |
* This is generally not used by most developers, unless trying to mock | |
* a result from a Contract. | |
*/ | |
Interface.prototype.encodeErrorResult = (fragment, values) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getError(fragment); | |
assertArgument(f, "unknown error", "fragment", fragment); | |
fragment = f; | |
} | |
return concat([ | |
fragment.selector, | |
this._encodeParams(fragment.inputs, values || []) | |
]); | |
} | |
/** | |
* Decodes the %%data%% from a transaction ``tx.data`` for | |
* the function specified (see [[getFunction]] for valid values | |
* for %%fragment%%). | |
* | |
* Most developers should prefer the [[parseTransaction]] method | |
* instead, which will automatically detect the fragment. | |
*/ | |
Interface.prototype.decodeFunctionData = (fragment, data) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getFunction(fragment); | |
assertArgument(f, "unknown function", "fragment", fragment); | |
fragment = f; | |
} | |
assertArgument(dataSlice(data, 0, 4) === fragment.selector, `data signature does not match function ${fragment.name}.`, "data", data); | |
return this._decodeParams(fragment.inputs, dataSlice(data, 4)); | |
} | |
/** | |
* Encodes the ``tx.data`` for a transaction that calls the function | |
* specified (see [[getFunction]] for valid values for %%fragment%%) with | |
* the %%values%%. | |
*/ | |
Interface.prototype.encodeFunctionData = (fragment, values) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getFunction(fragment); | |
assertArgument(f, "unknown function", "fragment", fragment); | |
fragment = f; | |
} | |
return concat([ | |
fragment.selector, | |
this._encodeParams(fragment.inputs, values || []) | |
]); | |
} | |
/** | |
* Decodes the result %%data%% (e.g. from an ``eth_call``) for the | |
* specified function (see [[getFunction]] for valid values for | |
* %%key%%). | |
* | |
* Most developers should prefer the [[parseCallResult]] method instead, | |
* which will automatically detect a ``CALL_EXCEPTION`` and throw the | |
* corresponding error. | |
*/ | |
Interface.prototype.decodeFunctionResult = (fragment, data) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getFunction(fragment); | |
assertArgument(f, "unknown function", "fragment", fragment); | |
fragment = f; | |
} | |
let message = "invalid length for result data"; | |
const bytes = getBytesCopy(data, "decode result"); | |
if ((bytes.length % 32) === 0) { | |
try { | |
return this._abiCoder.decode(fragment.outputs, bytes); | |
} | |
catch (error) { | |
print(error, error.stack) | |
message = "could not decode result data"; | |
} | |
} | |
// Call returned data with no error, but the data is junk | |
assert(false, message, "BAD_DATA", { | |
value: hexlify(bytes), | |
info: { method: fragment.name, signature: fragment.format() } | |
}); | |
} | |
Interface.prototype.makeError = (_data, tx) => { | |
const data = getBytes(_data, "data"); | |
const error = AbiCoder.getBuiltinCallException("call", tx, data); | |
// Not a built-in error; try finding a custom error | |
const customPrefix = "execution reverted (unknown custom error)"; | |
if (error.message.startsWith(customPrefix)) { | |
const selector = hexlify(data.slice(0, 4)); | |
const ef = this.getError(selector); | |
if (ef) { | |
try { | |
const args = this._abiCoder.decode(ef.inputs, data.slice(4)); | |
error.revert = { | |
name: ef.name, signature: ef.format(), args: args | |
}; | |
error.reason = error.revert.signature; | |
error.message = `execution reverted: ${error.reason}`; | |
} | |
catch (e) { | |
error.message = `execution reverted (coult not decode custom error)`; | |
} | |
} | |
} | |
// Add the invocation, if available | |
const parsed = this.parseTransaction(tx); | |
if (parsed) { | |
error.invocation = { | |
method: parsed.name, | |
signature: parsed.signature, | |
args: parsed.args | |
}; | |
} | |
return error; | |
} | |
/** | |
* Encodes the result data (e.g. from an ``eth_call``) for the | |
* specified function (see [[getFunction]] for valid values | |
* for %%fragment%%) with %%values%%. | |
* | |
* This is generally not used by most developers, unless trying to mock | |
* a result from a Contract. | |
*/ | |
Interface.prototype.encodeFunctionResult = (fragment, values) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getFunction(fragment); | |
assertArgument(f, "unknown function", "fragment", fragment); | |
fragment = f; | |
} | |
return hexlify(this._abiCoder.encode(fragment.outputs, values || [])); | |
} | |
// Create the filter for the event with search criteria (e.g. for eth_filterLog) | |
Interface.prototype.encodeFilterTopics = (fragment, values) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getEvent(fragment); | |
assertArgument(f, "unknown event", "eventFragment", fragment); | |
fragment = f; | |
} | |
assert(values.length <= fragment.inputs.length, `too many arguments for ${fragment.format()}`, "UNEXPECTED_ARGUMENT", { count: values.length, expectedCount: fragment.inputs.length }); | |
const topics = []; | |
if (!fragment.anonymous) { | |
topics.push(fragment.topicHash); | |
} | |
// @TODO: Use the coders for this; to properly support tuples, etc. | |
const encodeTopic = (param, value) => { | |
if (param.type === "string") { | |
return id(value); | |
} | |
else if (param.type === "bytes") { | |
return keccak256(hexlify(value)); | |
} | |
if (param.type === "bool" && typeof (value) === "boolean") { | |
value = (value ? "0x01" : "0x00"); | |
} | |
else if (param.type.match(/^u?int/)) { | |
value = toBeHex(value); // @TODO: Should this toTwos?? | |
} | |
else if (param.type.match(/^bytes/)) { | |
value = zeroPadBytes(value, 32); | |
} | |
else if (param.type === "address") { | |
// Check addresses are valid | |
this._abiCoder.encode(["address"], [value]); | |
} | |
return zeroPadValue(hexlify(value), 32); | |
}; | |
values.forEach((value, index) => { | |
const param = fragment.inputs[index]; | |
if (!param.indexed) { | |
assertArgument(value == null, "cannot filter non-indexed parameters; must be null", ("contract." + param.name), value); | |
return; | |
} | |
if (value == null) { | |
topics.push(null); | |
} | |
else if (param.baseType === "array" || param.baseType === "tuple") { | |
assertArgument(false, "filtering with tuples or arrays not supported", ("contract." + param.name), value); | |
} | |
else if (Array.isArray(value)) { | |
topics.push(value.map((value) => encodeTopic(param, value))); | |
} | |
else { | |
topics.push(encodeTopic(param, value)); | |
} | |
}); | |
// Trim off trailing nulls | |
while (topics.length && topics[topics.length - 1] === null) { | |
topics.pop(); | |
} | |
return topics; | |
} | |
Interface.prototype.encodeEventLog = (fragment, values) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getEvent(fragment); | |
assertArgument(f, "unknown event", "eventFragment", fragment); | |
fragment = f; | |
} | |
const topics = []; | |
const dataTypes = []; | |
const dataValues = []; | |
if (!fragment.anonymous) { | |
topics.push(fragment.topicHash); | |
} | |
assertArgument(values.length === fragment.inputs.length, "event arguments/values mismatch", "values", values); | |
fragment.inputs.forEach((param, index) => { | |
const value = values[index]; | |
if (param.indexed) { | |
if (param.type === "string") { | |
topics.push(id(value)); | |
} | |
else if (param.type === "bytes") { | |
topics.push(keccak256(value)); | |
} | |
else if (param.baseType === "tuple" || param.baseType === "array") { | |
// @TODO | |
throw new Error("not implemented"); | |
} | |
else { | |
topics.push(this._abiCoder.encode([param.type], [value])); | |
} | |
} | |
else { | |
dataTypes.push(param); | |
dataValues.push(value); | |
} | |
}); | |
return { | |
data: this._abiCoder.encode(dataTypes, dataValues), | |
topics: topics | |
}; | |
} | |
// Decode a filter for the event and the search criteria | |
Interface.prototype.decodeEventLog = (fragment, data, topics) => { | |
if (typeof (fragment) === "string") { | |
const f = this.getEvent(fragment); | |
assertArgument(f, "unknown event", "eventFragment", fragment); | |
fragment = f; | |
} | |
if (topics != null && !fragment.anonymous) { | |
const eventTopic = fragment.topicHash; | |
assertArgument(isHexString(topics[0], 32) && topics[0].toLowerCase() === eventTopic, "fragment/topic mismatch", "topics[0]", topics[0]); | |
topics = topics.slice(1); | |
} | |
const indexed = []; | |
const nonIndexed = []; | |
const dynamic = []; | |
fragment.inputs.forEach((param, index) => { | |
if (param.indexed) { | |
if (param.type === "string" || param.type === "bytes" || param.baseType === "tuple" || param.baseType === "array") { | |
indexed.push(ParamType.from({ type: "bytes32", name: param.name })); | |
dynamic.push(true); | |
} | |
else { | |
indexed.push(param); | |
dynamic.push(false); | |
} | |
} | |
else { | |
nonIndexed.push(param); | |
dynamic.push(false); | |
} | |
}); | |
const resultIndexed = (topics != null) ? this._abiCoder.decode(indexed, concat(topics)) : null; | |
const resultNonIndexed = this._abiCoder.decode(nonIndexed, data, true); | |
//const result: (Array<any> & { [ key: string ]: any }) = [ ]; | |
const values = []; | |
const keys = []; | |
let nonIndexedIndex = 0, indexedIndex = 0; | |
fragment.inputs.forEach((param, index) => { | |
let value = null; | |
if (param.indexed) { | |
if (resultIndexed == null) { | |
value = new Indexed(null); | |
} | |
else if (dynamic[index]) { | |
value = new Indexed(resultIndexed[indexedIndex++]); | |
} | |
else { | |
try { | |
value = resultIndexed[indexedIndex++]; | |
} | |
catch (error) { | |
value = error; | |
} | |
} | |
} | |
else { | |
try { | |
value = resultNonIndexed[nonIndexedIndex++]; | |
} | |
catch (error) { | |
value = error; | |
} | |
} | |
values.push(value); | |
keys.push(param.name || null); | |
}); | |
return values; | |
} | |
/** | |
* Parses a transaction, finding the matching function and extracts | |
* the parameter values along with other useful function details. | |
* | |
* If the matching function cannot be found, return null. | |
*/ | |
Interface.prototype.parseTransaction = (tx) => { | |
const data = getBytes(tx.data, "tx.data"); | |
const value = getBigInt((tx.value != null) ? tx.value : 0, "tx.value"); | |
const fragment = this.getFunction(hexlify(data.slice(0, 4))); | |
if (!fragment) { | |
return null; | |
} | |
const args = this._abiCoder.decode(fragment.inputs, data.slice(4)); | |
return new TransactionDescription(fragment, fragment.selector, args, value); | |
} | |
Interface.prototype.parseCallResult = function(data) { | |
throw new Error("@TODO"); | |
} | |
/** | |
* Parses a receipt log, finding the matching event and extracts | |
* the parameter values along with other useful event details. | |
* | |
* If the matching event cannot be found, returns null. | |
*/ | |
Interface.prototype.parseLog = function(log) { | |
const fragment = this.getEvent(log.topics[0]); | |
if (!fragment || fragment.anonymous) { | |
return null; | |
} | |
// @TODO: If anonymous, and the only method, and the input count matches, should we parse? | |
// Probably not, because just because it is the only event in the ABI does | |
// not mean we have the full ABI; maybe just a fragment? | |
return new LogDescription(fragment, fragment.topicHash, this.decodeEventLog(fragment, log.data, log.topics)); | |
} | |
/** | |
* Parses a revert data, finding the matching error and extracts | |
* the parameter values along with other useful error details. | |
* | |
* If the matching error cannot be found, returns null. | |
*/ | |
Interface.prototype.parseError = function(data) { | |
const hexData = hexlify(data); | |
const fragment = this.getError(dataSlice(hexData, 0, 4)); | |
if (!fragment) { | |
return null; | |
} | |
const args = this._abiCoder.decode(fragment.inputs, dataSlice(hexData, 4)); | |
return new ErrorDescription(fragment, fragment.selector, args); | |
} | |
/** | |
* Creates a new [[Interface]] from the ABI %%value%%. | |
* | |
* The %%value%% may be provided as an existing [[Interface]] object, | |
* a JSON-encoded ABI or any Human-Readable ABI format. | |
*/ | |
Interface.from = function(value) { | |
// Already an Interface, which is immutable | |
if (value instanceof Interface) { | |
return value; | |
} | |
// JSON | |
if (typeof (value) === "string") { | |
return new Interface(JSON.parse(value)); | |
} | |
// An Interface; possibly from another v6 instance | |
if (typeof (value.formatJson) === "function") { | |
return new Interface(value.formatJson()); | |
} | |
// A legacy Interface; from an older version | |
if (typeof (value.format) === "function") { | |
return new Interface(value.format("json")); | |
} | |
// Array of fragments | |
return new Interface(value); | |
} | |
// Additional methods... | |
function BaseContract(target, abi, runner, _deployTx) { | |
var self = this; | |
assertArgument(typeof (target) === "string" || isAddressable(target), "invalid value for Contract target", "target", target); | |
if (runner == null) { | |
runner = null; | |
} | |
var iface = Interface.from(abi); | |
defineProperties(this, { | |
target: target, | |
runner: runner, | |
interface: iface | |
}); | |
this.internal = { value: {} }; | |
var addr = null; | |
var deployTx = null; | |
if (_deployTx) { | |
var provider = getProvider(runner); | |
// @TODO: the provider can be null; make a custom dummy provider that will throw a | |
// meaningful error | |
deployTx = new ContractTransactionResponse(this.interface, provider, _deployTx); | |
} | |
var subs = new Map(); | |
// Resolve the target as the address | |
if (typeof (target) === "string") { | |
if (isHexString(target)) { | |
addr = target; | |
} | |
else { | |
var resolver = getRunner(runner, "resolveName"); | |
if (!canResolve(resolver)) { | |
throw makeError("contract runner does not support name resolution", "UNSUPPORTED_OPERATION", { | |
operation: "resolveName" | |
}); | |
} | |
addr = resolver.resolveName(target); | |
if (addr == null) { | |
throw makeError("an ENS name used for a contract target must be correctly configured", "UNCONFIGURED_NAME", { value: target }); | |
} | |
} | |
} | |
else { | |
addr = target.getAddress(); | |
if (addr == null) { | |
throw new Error("TODO"); | |
} | |
} | |
// Set our private values | |
this.internal = { addr: addr, deployTx: deployTx, subs: subs }; | |
// Add the event filters | |
var filters = new Proxy({}, { | |
get: function(target, prop, receiver) { | |
// 处理特殊属性(如Promise的then等) | |
if (typeof (prop) === "symbol" || passProperties.indexOf(prop) >= 0) { | |
return target[prop]; // 使用直接属性访问替代Reflect.get | |
} | |
try { | |
return self.getEvent(prop); | |
} catch (error) { | |
if (!isError(error, "INVALID_ARGUMENT") || error.argument !== "key") { | |
throw error; | |
} | |
} | |
return undefined; | |
}, | |
has: function(target, prop) { | |
if (passProperties.indexOf(prop) >= 0) { | |
return prop in target; // 使用 in 操作符替代Reflect.has | |
} | |
return (prop in target) || self.interface.hasEvent(String(prop)); | |
} | |
}); | |
defineProperties(this, { filters: filters }); | |
defineProperties(this, { | |
fallback: ((iface.receive || iface.fallback) ? (buildWrappedFallback(this)) : null) | |
}); | |
// Return a Proxy that will respond to functions | |
return new Proxy(this, { | |
get: function(target, prop, receiver) { | |
if (typeof (prop) === "symbol" || prop in target || passProperties.indexOf(prop) >= 0) { | |
return target[prop]; | |
} | |
// Undefined properties should return undefined | |
try { | |
return target.getFunction(prop); | |
} | |
catch (error) { | |
if (!isError(error, "INVALID_ARGUMENT") || error.argument !== "key") { | |
throw error; | |
} | |
} | |
return undefined; | |
}, | |
has: function(target, prop) { | |
if (typeof (prop) === "symbol" || prop in target || passProperties.indexOf(prop) >= 0) { | |
return prop in target; | |
} | |
return target.interface.hasFunction(prop); | |
} | |
}); | |
} | |
BaseContract.prototype.connect = function(runner) { | |
return new BaseContract(this.target, this.interface, runner); | |
}; | |
BaseContract.prototype.attach = function(target) { | |
return new BaseContract(target, this.interface, this.runner); | |
}; | |
BaseContract.prototype.getAddress = function() { | |
return this.internal.addr; | |
}; | |
BaseContract.prototype.getDeployedCode = function() { | |
var provider = getProvider(this.runner); | |
assert(provider, "runner does not support .provider", "UNSUPPORTED_OPERATION", { operation: "getDeployedCode" }); | |
var code = provider.getCode(this.getAddress()); | |
if (code === "0x") { | |
return null; | |
} | |
return code; | |
}; | |
BaseContract.prototype.waitForDeployment = function(interval, timeout) { | |
// We have the deployement transaction; just use that (throws if deployement fails) | |
var deployTx = this.deploymentTransaction(); | |
if (deployTx) { | |
deployTx.wait(); | |
return this; | |
} | |
// Check for code | |
var code = this.getDeployedCode(); | |
if (code != null) { | |
return this; | |
} | |
// Make sure we can subscribe to a provider event | |
var provider = getProvider(this.runner); | |
assert(provider != null, "contract runner does not support .provider", "UNSUPPORTED_OPERATION", { operation: "waitForDeployment" }); | |
var nowTs = Date.now(); | |
while(Date.now() - nowTs > timeout) { | |
wait(interval); | |
var code = this.getDeployedCode(); | |
if (code != null) { | |
return this; | |
} | |
} | |
throw makeError("wait for contract deployment timeout", "TIMEOUT", { operation: "waitForDeployment"}); | |
}; | |
BaseContract.prototype.deploymentTransaction = function() { | |
return this.internal.deployTx; | |
}; | |
BaseContract.prototype.getFunction = function(key) { | |
if (typeof (key) !== "string") { | |
key = key.format(); | |
} | |
var func = buildWrappedMethod(this, key); | |
return func; | |
}; | |
BaseContract.prototype.getEvent = function(key) { | |
if (typeof (key) !== "string") { | |
key = key.format(); | |
} | |
return buildWrappedEvent(this, key); | |
}; | |
BaseContract.prototype.queryTransaction = function(hash) { | |
throw new Error("@TODO"); | |
}; | |
BaseContract.prototype.queryFilter = function(event, fromBlock, toBlock) { | |
var self = this; | |
if (fromBlock == null) { | |
fromBlock = 0; | |
} | |
if (toBlock == null) { | |
toBlock = "latest"; | |
} | |
var internal = this.internal; | |
var address = internal.addr; | |
var subInfo = getSubInfo(this, event); | |
var fragment = subInfo.fragment; | |
var topics = subInfo.topics; | |
var filter = { address: address, topics: topics, fromBlock: fromBlock, toBlock: toBlock }; | |
var provider = getProvider(this.runner); | |
assert(provider, "contract runner does not have a provider", "UNSUPPORTED_OPERATION", { operation: "queryFilter" }); | |
return (provider.getLogs(filter)).map(function(log) { | |
var foundFragment = fragment; | |
if (foundFragment == null) { | |
try { | |
foundFragment = self.interface.getEvent(log.topics[0]); | |
} | |
catch (error) { } | |
} | |
if (foundFragment) { | |
try { | |
return new EventLog(log, self.interface, foundFragment); | |
} | |
catch (error) { | |
return new UndecodedEventLog(log, error); | |
} | |
} | |
return new Log(log, provider); | |
}); | |
}; | |
BaseContract.buildClass = function(abi) { | |
function CustomContract(address, runner) { | |
if (runner === undefined) runner = null; | |
BaseContract.call(this, address, abi, runner); | |
} | |
CustomContract.prototype = Object.create(BaseContract.prototype); | |
CustomContract.prototype.constructor = CustomContract; | |
return CustomContract; | |
}; | |
BaseContract.from = function(target, abi, runner) { | |
if (runner == null) { | |
runner = null; | |
} | |
return new this(target, abi, runner); | |
}; | |
function JsonRpcProvider(url, network) { | |
var self = this; | |
// 私有属性 | |
var _url = url || "http://localhost:8545"; | |
var _nextId = 1; // JSON-RPC请求ID | |
// 基础JSON-RPC调用方法 | |
function send(method, params) { | |
var request = { | |
url: _url, | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify({ | |
jsonrpc: "2.0", | |
id: (_nextId++), | |
method: method, | |
params: params || [] | |
}) | |
}; | |
var resp; | |
try { | |
resp = fetch(request); | |
} | |
catch (_error) { | |
throw _error; | |
} | |
const respBody = JSON.parse(resp.body()); | |
// The response is an error | |
if ("error" in respBody) { | |
throw this.getRpcError(request, resp); | |
} | |
// All good; send the result | |
return respBody.result; | |
} | |
// 基础方法实现 | |
this.call = function(transaction) { | |
return send("eth_call", [{ | |
to: transaction.to, | |
data: transaction.data, | |
from: transaction.from, | |
value: transaction.value ? "0x" + transaction.value.toString(16) : undefined | |
}, "latest"]); | |
}; | |
this.getBalance = function(address) { | |
return send("eth_getBalance", [address, "latest"]).then(function(result) { | |
return BigInt(result); | |
}); | |
}; | |
this.getTransactionCount = function(address) { | |
return send("eth_getTransactionCount", [address, "latest"]).then(function(result) { | |
return parseInt(result, 16); | |
}); | |
}; | |
this.getCode = function(address) { | |
return send("eth_getCode", [address, "latest"]); | |
}; | |
this.getBlock = function(blockNumber) { | |
var tag = (typeof blockNumber === "number") ? | |
"0x" + blockNumber.toString(16) : | |
blockNumber; | |
return send("eth_getBlockByNumber", [tag, false]); | |
}; | |
this.getTransaction = function(hash) { | |
return send("eth_getTransactionByHash", [hash]); | |
}; | |
this.getTransactionReceipt = function(hash) { | |
return send("eth_getTransactionReceipt", [hash]); | |
}; | |
this.sendTransaction = function(signedTx) { | |
return send("eth_sendRawTransaction", [signedTx]); | |
}; | |
// 事件相关 | |
this.getLogs = function(filter) { | |
return send("eth_getLogs", [filter]); | |
}; | |
// 工具方法 | |
this.estimateGas = function(tx) { | |
return send("eth_estimateGas", [tx]).then(function(result) { | |
return BigInt(result); | |
}); | |
}; | |
// 网络相关 | |
this.getNetwork = function() { | |
return send("eth_chainId").then(function(chainId) { | |
return { | |
chainId: parseInt(chainId, 16), | |
name: getNetworkName(parseInt(chainId, 16)) | |
}; | |
}); | |
}; | |
// 辅助函数 | |
function getNetworkName(chainId) { | |
var networks = { | |
1: "mainnet", | |
3: "ropsten", | |
4: "rinkeby", | |
5: "goerli", | |
42: "kovan" | |
}; | |
return networks[chainId] || "unknown"; | |
} | |
// 初始化 | |
if (network) { | |
this._network = network; | |
} | |
} | |
return { | |
Contract: BaseContract, | |
JsonRpcProvider: JsonRpcProvider | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment