contract-helper
Version:
A contract helper for tron and eth network
278 lines (249 loc) • 8.53 kB
text/typescript
import { expect } from "chai";
import { formatBase58Address, formatToEthAddress } from "../src";
import { ethers, FunctionFragment, getAddress, Interface } from "ethers";
import {
transformContractCallArgs,
findFragmentFromAbi,
buildAggregateCall,
buildUpAggregateResponse,
} from "../src/contract-utils";
import {
MultiCallArgs,
AggregateContractResponse,
ContractCall,
} from "../src/types";
import BigNumber from "bignumber.js";
import { BytesLike } from "ethers";
describe("ContractHelperUtils", () => {
it("formatBase58Address", async () => {
// invalid address
expect(formatBase58Address("123456abc")).to.equal("123456abc");
// base58 address
expect(formatBase58Address("TPoYNMEiYhnqhW2go2paY8Z6uNYfEQMjQK")).to.equal(
"TPoYNMEiYhnqhW2go2paY8Z6uNYfEQMjQK"
);
// hex address
expect(
formatBase58Address("4197BDC4A77898E79F09066D1B2E52314760910BE6")
).to.equal("TPoYNMEiYhnqhW2go2paY8Z6uNYfEQMjQK");
});
it("formatToEthAddress", async () => {
// invalid address
expect(formatToEthAddress("123456abc")).to.equal("123456abc");
// base58 address
expect(formatToEthAddress("TPoYNMEiYhnqhW2go2paY8Z6uNYfEQMjQK")).to.equal(
"0x97BDc4a77898e79F09066d1b2e52314760910be6"
);
// tron hex address
expect(
formatToEthAddress("4197BDC4A77898E79F09066D1B2E52314760910BE6")
).to.equal("0x97BDc4a77898e79F09066d1b2e52314760910be6");
// eth address
expect(
formatToEthAddress("0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97")
).to.equal("0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97");
});
});
describe("ContractHelperUtils internal methods", () => {
const sampleAbi = [
"function foo(uint256 a, address b) view returns (bool success, uint256 result)",
"function bar() view returns (uint256)",
];
const sampleFragment = ethers.FunctionFragment.from(
"function foo(uint256 a, address b) view returns (bool success, uint256 result)"
);
describe("transformContractCallArgs", () => {
it("should parse with method name and ABI", () => {
const input = {
address: "0x1234",
abi: sampleAbi,
method: "foo",
args: [42, "0x0000000000000000000000000000000000000000"],
};
const result = transformContractCallArgs(input, "evm");
expect(result.address).to.equal("0x1234");
expect(result.method.name).to.equal("foo");
expect(result.args).to.deep.equal([
42,
"0x0000000000000000000000000000000000000000",
]);
});
it("should parse with method signature string and ABI optional", () => {
const input = {
address: "0x1234",
method: "foo(uint256,address)",
args: [42, "0x0000000000000000000000000000000000000000"],
};
const result = transformContractCallArgs(input, "evm");
expect(result.method.fragment.format("sighash")).to.equal(
sampleFragment.format("sighash")
);
});
it("should throw if method not found in ABI", () => {
expect(() =>
transformContractCallArgs(
{
address: "0x1234",
abi: sampleAbi,
method: "nonexistent",
},
"evm"
)
).to.throw();
});
});
describe("findFragmentFromAbi", () => {
it("should find FunctionFragment by methodName in call", () => {
const contractCallContext = {
key: "1",
address: "0x1234567890123456789012345678901234567890",
abi: sampleAbi,
call: {
methodName: "foo",
methodParameters: [42, "0x0000000000000000000000000000000000000001"],
},
};
const fragment = findFragmentFromAbi(contractCallContext);
expect(fragment).to.be.instanceOf(FunctionFragment);
expect(fragment?.name).to.equal("foo");
expect(fragment?.inputs.length).to.equal(2);
});
it("should find null if methodName not found", () => {
const contractCallContext = {
key: "2",
address: "0x1234567890123456789012345678901234567890",
abi: sampleAbi,
call: {
methodName: "nonexistent",
methodParameters: [],
},
};
expect(findFragmentFromAbi(contractCallContext)).to.be.null;
});
it("should trim methodName before searching", () => {
const contractCallContext = {
key: "3",
address: "0x1234567890123456789012345678901234567890",
abi: sampleAbi,
call: {
methodName: " bar ",
methodParameters: [],
},
};
const fragment = findFragmentFromAbi(contractCallContext);
expect(fragment?.name).to.equal("bar");
});
it("should find the function fragment if methodName is full signature", () => {
const contractCall = {
key: "3",
address: "0x0000000000000000000000000000000000000000",
abi: sampleAbi,
call: {
methodName: "function bar() view returns (uint256)",
methodParameters: [],
},
};
const fragment = findFragmentFromAbi(contractCall);
expect(fragment).to.not.be.null;
expect(fragment?.name).to.equal("bar");
});
});
describe("buildAggregateCall", () => {
it("should build aggregate calls correctly", () => {
// 构造测试ABI和函数签名
const abi = [
"function foo(uint256 a, string b) view returns (bool)",
"function bar() view returns (uint256)",
];
const iface = new Interface(abi);
// MultiCallArgs示例
const multiCallArgs = [
{
key: "foo",
address: "0x1234567890123456789012345678901234567890",
method: iface.getFunction("foo")!.format("sighash"),
args: [42, "hello"],
},
{
key: "bar",
address: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
method: iface.getFunction("bar")!.format("minimal"),
args: [],
},
];
// encodeFunction 模拟编码逻辑
const encodeFunction = (fragment: FunctionFragment, values: any[]) => {
// 这里只返回 fragment.name + encoded params as string for test
return `${fragment.name}(${values.join(",")})`;
};
const result = buildAggregateCall(multiCallArgs, encodeFunction, "evm");
expect(result).to.be.an("array").with.length(2);
expect(result[0]).to.deep.equal({
contractCallIndex: 0,
target: "0x1234567890123456789012345678901234567890",
encodedData: "foo(42,hello)",
});
expect(result[1]).to.deep.equal({
contractCallIndex: 1,
target: getAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"),
encodedData: "bar()",
});
});
it("should handle empty input", () => {
const result = buildAggregateCall([], () => "", "evm");
expect(result).to.deep.equal([]);
});
});
describe("buildUpAggregateResponse", () => {
it("should decode and format the multicall response correctly", () => {
const abi = [
"function foo(uint256 a, string b) view returns (bool success)",
"function bar() view returns (uint256)",
];
const multiCallArgs = [
{
key: "call1",
address: "0x1234567890123456789012345678901234567890",
method: "foo",
args: [42, "hello"],
abi,
},
{
key: "call2",
address: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
method: "bar",
args: [],
abi,
},
];
// 模拟 response 的 returnData 顺序对应 multiCallArgs
const response = {
blockNumber: new BigNumber(123456),
returnData: ["0x01", "0x2a"], // 模拟编码数据
};
// decode 模拟函数,模拟解码返回值
const decode = (fragment: FunctionFragment) => {
if (fragment.name === "foo") {
return ["true"]; // 模拟foo返回true
}
if (fragment.name === "bar") {
return ["42"]; // 模拟bar返回42
}
return [""];
};
// format 模拟函数,直接返回解码值
const format = (value: any, fragment: FunctionFragment) => value;
const result = buildUpAggregateResponse(
multiCallArgs,
response,
decode,
format,
"evm"
);
expect(result).to.deep.equal({
[multiCallArgs[0].key]: ["true"],
[multiCallArgs[1].key]: "42",
});
});
});
});