Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Decode string output #200

Merged
merged 6 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"microbundle": "^0.15.0",
"npm-run-all": "^4.1.5",
"perf_hooks": "^0.0.1",
"prettier": "^2.8.1",
"prettier": "^2.8.4",
"prettier-plugin-organize-imports": "^2.3.4",
"size-limit": "^7.0.8",
"ts-jest": "^27.1.4",
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,7 @@ Decoding support:
- `uint256`
- `bytes32`
- `uint8`
- `string` (only if there is one string output, not multiple yet)

_Assume all types outside the above types will break for now_

Expand Down Expand Up @@ -1560,7 +1561,7 @@ In partnership with GitPOAP, Essential ETH wants to recognize **all** contributo

<br/>

![Alt](https://www.gitpoap.io/_next/image?url=https%3A%2F%2Fassets.poap.xyz%2Fgitpoap3a-2022-essential-eth-contributor-2022-logo-1667245904706.png&w=2048&q=75 'Essential ETH Contributor - GitPOAP')
![Alt](https://www.gitpoap.io/_next/image?url=https%3A%2F%2Fassets.poap.xyz%2Fgitpoap3a-2023-essential-eth-contributor-2022-logo-1670961141021.png&w=750&q=75)

<br/>

Expand Down
2 changes: 1 addition & 1 deletion scripts/pre-commit.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
npx npm-check-updates -u "/.*big.*$/"
npm i
npm i ethers@latest web3@latest prettier@latest
npm i prettier@latest

66 changes: 15 additions & 51 deletions src/classes/test/Contract/fei.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Contract as EthersContract } from '@ethersproject/contracts';
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import type { TinyBig } from '../../..';
import { JsonRpcProvider } from '../../../index';
import { Contract as EssentialEthContract } from '../../Contract';
Expand All @@ -10,79 +8,45 @@ import { feiABI } from './fei-abi';
const JSONABI = feiABI;

const rpcURL = rpcUrls.mainnet;
const ethersProvider = new StaticJsonRpcProvider(rpcURL);
const essentialEthProvider = new JsonRpcProvider(rpcURL);

// https://etherscan.io/address/0xBFfB152b9392e38CdDc275D818a3Db7FE364596b
const contractAddress = '0xBFfB152b9392e38CdDc275D818a3Db7FE364596b';

type ContractLike = EthersContract | EssentialEthContract;
const smartContractGetFeiAmountsToRedeem = async (
contract: ContractLike,
contract: EssentialEthContract,
address: string,
) => {
const merkleRoot = (await contract.getAmountsToRedeem(address)) as TinyBig[];
return merkleRoot;
};

const ethersContract = new EthersContract(
contractAddress,
JSONABI as any,
ethersProvider,
);
const essentialEthContract = new EssentialEthContract(
contractAddress,
JSONABI,
essentialEthProvider,
);
describe('fEI contract', () => {
it('should fetch unclaimed amounts "[uint256, uint256, uint256]" data-type', async () => {
const [ethersResponse, essentialEthResponse] = await Promise.all([
smartContractGetFeiAmountsToRedeem(
ethersContract,
'0xf5dBA31743ea341057280bb3AdD5c2Fb505BDC4C',
),
smartContractGetFeiAmountsToRedeem(
essentialEthContract,
'0xf5dBA31743ea341057280bb3AdD5c2Fb505BDC4C',
),
]);
expect(ethersResponse[0].toString()).toBe(
essentialEthResponse[0].toString(),
);
expect(ethersResponse[1].toString()).toBe(
essentialEthResponse[1].toString(),
);
expect(ethersResponse[2].toString()).toBe(
essentialEthResponse[2].toString(),
const essentialEthResponse = await smartContractGetFeiAmountsToRedeem(
essentialEthContract,
'0xf5dBA31743ea341057280bb3AdD5c2Fb505BDC4C',
);

expect(essentialEthResponse[0].toString()).toBe('0');
expect(essentialEthResponse[1].toString()).toBe('0');
expect(essentialEthResponse[2].toString()).toBe('0');
expect(essentialEthResponse[2].toNumber()).toBe(0);
});
it('should fetch "uint8" data-type', async () => {
const [ethersResponse, essentialEthResponse] = await Promise.all([
ethersContract.decimals(),
essentialEthContract.decimals(),
]);
expect(ethersResponse).toStrictEqual(essentialEthResponse);
const essentialEthResponse = await essentialEthContract.decimals();
expect(essentialEthResponse).toBe(18);
});

it('should throw errors for methods using unsupported data-types', async () => {
await expect(essentialEthContract.name()).rejects.toThrow(
'essential-eth does not yet support "string" outputs. Make a PR today!',
);
});
// it.only('should fetch "string" name data-type', async () => {
// const [ethersResponse, essentialEthResponse] = await Promise.all([
// ethersContract.symbol(),
// essentialEthContract.symbol(),
// ]);
// expect(ethersResponse).toStrictEqual(essentialEthResponse);
it('should fetch "string" name data-type', async () => {
const essentialEthResponse = await essentialEthContract.symbol();
expect(essentialEthResponse).toBe('FGEN');

// const [ethers2Response, essential2EthResponse] = await Promise.all([
// ethersContract.name(),
// essentialEthContract.name(),
// ]);
// expect(ethers2Response).toStrictEqual(essential2EthResponse);
// });
const essential2EthResponse = await essentialEthContract.name();
expect(essential2EthResponse).toBe('Fei Genesis Group');
});
});
46 changes: 44 additions & 2 deletions src/classes/utils/encode-decode-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ import { hexToDecimal } from './hex-to-decimal';
export const hexFalse = '0'.repeat(64);
const hexTrue = '0'.repeat(63) + '1';

/**
* @param hex
* @example
*/
function hexToUtf8(hex: any) {
let str = '';
let i = 0;
const l = hex.length;
if (hex.substring(0, 2) === '0x') {
i = 2;
}
for (; i < l; i += 2) {
const code = parseInt(hex.substr(i, 2), 16);
if (code === 0) continue; // Skip null bytes
str += String.fromCharCode(code);
}
try {
return decodeURIComponent(escape(str)); // Convert UTF-8 to Unicode
} catch (e) {
return str; // Return original string if conversion fails
}
}

/**
* Expands an integer type to use a default of 256 bits. Used for consistency; not required in Solidity
*
Expand Down Expand Up @@ -125,15 +148,34 @@ export function decodeRPCResponse(
nodeResponse: string,
) {
const rawOutputs = jsonABIArgument.outputs;
const slicedResponse = nodeResponse.slice(2);

if (
jsonABIArgument?.outputs?.length === 1 &&
jsonABIArgument.outputs[0].type === 'string'
) {
const [hexOffset, responseData] = [
slicedResponse.slice(0, 64),
slicedResponse.slice(64),
];
const decimalOffset = Number(hexToDecimal(`0x${hexOffset}`));
const hexLength = responseData.slice(0, decimalOffset * 2);
const decimalLength = Number(hexToDecimal(`0x${hexLength}`));
const hexToDecode = responseData.slice(
decimalOffset * 2,
decimalOffset * 2 + decimalLength * 2,
);
return hexToUtf8(hexToDecode);
}
// chunk response every 64 characters
const encodedOutputs = nodeResponse.slice(2).match(/.{1,64}/g);
const encodedOutputs = slicedResponse.match(/.{1,64}/g);
const outputs = (encodedOutputs || []).map((output: string, i: number) => {
const outputType = (rawOutputs || [])[i].type;
switch (outputType) {
case 'bool':
return output === hexTrue;
case 'address':
/* address types have 26 leading zeroes to remove */
/* address types have 24 leading zeroes to remove */
return toChecksumAddress(`0x${output.slice(24)}`);
case 'uint256':
case 'uint120':
Expand Down