Skip to content

Commit

Permalink
Merge pull request #31 from rsksmart/feat/upgradeable-token-support-v2
Browse files Browse the repository at this point in the history
Feat/upgradeable token support v2
  • Loading branch information
Sergio authored Mar 21, 2023
2 parents 8ae26fc + f422ffa commit 8a7dd53
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 58 deletions.
66 changes: 57 additions & 9 deletions dist/lib/ContractParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ var _utils = require("./utils");function _interopRequireDefault(obj) {return obj



function mapInterfacesToERCs(interfaces) {
return Object.keys(interfaces).
filter(k => interfaces[k] === true).
map(t => _types.contractsInterfaces[t] || t);
}

function hasMethodSelector(txInputData, selector) {
return selector && txInputData && txInputData.includes(selector);
}

class ContractParser {
constructor({ abi, log, initConfig, nod3 } = {}) {
initConfig = initConfig || {};
Expand Down Expand Up @@ -147,32 +157,70 @@ class ContractParser {
}, {});
}

hasMethodSelector(txInputData, selector) {
return selector && txInputData ? txInputData.includes(selector) : null;
}


getMethodsBySelectors(txInputData) {
let methods = this.getMethodsSelectors();
return Object.keys(methods).
filter(method => this.hasMethodSelector(txInputData, methods[method]) === true);
filter(method => hasMethodSelector(txInputData, methods[method]) === true);
}

async getContractInfo(txInputData, contract) {
let { interfaces, methods } = await this.getContractImplementedInterfaces(txInputData, contract);

interfaces = mapInterfacesToERCs(interfaces);
return { methods, interfaces };
}

async getEIP1967Info(contractAddress) {
const { isUpgradeable, impContractAddress } = await this.isERC1967(contractAddress);
if (isUpgradeable) {
// manual check required
const proxyContractBytecode = await this.getContractCodeFromNode(impContractAddress);
const methods = this.getMethodsBySelectors(proxyContractBytecode);
let interfaces = this.getInterfacesByMethods(methods);

interfaces = mapInterfacesToERCs(interfaces);
return { methods, interfaces: [...interfaces, 'ERC1967'] };
}
return { methods: [], interfaces: [] };
}

async getContractImplementedInterfaces(txInputData, contract) {
let methods = this.getMethodsBySelectors(txInputData);
let isErc165 = false;
// skip non-erc165 contracts
if ((0, _rskUtils.includesAll)(methods, ['supportsInterface(bytes4)'])) {
isErc165 = await this.implementsErc165(contract);
}
let interfaces;
if (isErc165) interfaces = await this.getInterfacesERC165(contract);else
interfaces = this.getInterfacesByMethods(methods);
interfaces = Object.keys(interfaces).
filter(k => interfaces[k] === true).
map(t => _types.contractsInterfaces[t] || t);
if (isErc165) {
interfaces = await this.getInterfacesERC165(contract);
} else {
interfaces = this.getInterfacesByMethods(methods);
}

return { methods, interfaces };
}

async isERC1967(contractAddress) {
// check For ERC1967
// https://eips.ethereum.org/EIPS/eip-1967
// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc storage address where the implementation address is stored
const storedValue = await this.nod3.eth.getStorageAt(contractAddress, '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc');
const isUpgradeable = storedValue !== '0x0';
if (isUpgradeable) {
const impContractAddress = `0x${storedValue.slice(-40)}`; // extract contract address
return { isUpgradeable, impContractAddress };
} else {
return { isUpgradeable, impContractAddress: storedValue };
}
}

async getContractCodeFromNode(contractAddress) {
return this.nod3.eth.getContractCodeAt(contractAddress);
}

async getInterfacesERC165(contract) {
let ifaces = {};
let keys = Object.keys(_interfacesIds.default);
Expand Down
6 changes: 4 additions & 2 deletions dist/lib/EventDecoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ function EventDecoder(abi, logger) {

const parsedArgs = [];

for (const i in eventFragment.inputs) parsedArgs.push(
encodeElement(eventFragment.inputs[i].type, args[i]));
for (const i in eventFragment.inputs) {
parsedArgs.push(
encodeElement(eventFragment.inputs[i].type, args[i]));

}

return Object.assign({}, log, {
signature: (0, _rskUtils.remove0x)(topic),
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rsksmart/rsk-contract-parser",
"version": "0.0.11",
"version": "0.0.12",
"description": "",
"main": "dist/index.js",
"scripts": {
Expand Down Expand Up @@ -48,7 +48,7 @@
},
"dependencies": {
"@ethersproject/abi": "^5.7.0",
"@rsksmart/nod3": "^0.4.1",
"@rsksmart/nod3": "^0.5.0",
"@rsksmart/rsk-utils": "^1.1.0",
"bs58": "^4.0.1",
"secp256k1": "^3.7.1"
Expand Down
70 changes: 59 additions & 11 deletions src/lib/ContractParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ import {
soliditySignature
} from './utils'

function mapInterfacesToERCs (interfaces) {
return Object.keys(interfaces)
.filter(k => interfaces[k] === true)
.map(t => contractsInterfaces[t] || t)
}

function hasMethodSelector (txInputData, selector) {
return selector && txInputData && txInputData.includes(selector)
}

export class ContractParser {
constructor ({ abi, log, initConfig, nod3 } = {}) {
initConfig = initConfig || {}
Expand Down Expand Up @@ -89,8 +99,8 @@ export class ContractParser {
value.forEach(v => _addresses.push(v))
} else {
let i = 0
while (2 + (i+1) * 40 <= value.length) {
_addresses.push('0x' + value.slice(2 + i * 40, 2 + (i+1) * 40))
while (2 + (i + 1) * 40 <= value.length) {
_addresses.push('0x' + value.slice(2 + i * 40, 2 + (i + 1) * 40))
i++
}
}
Expand Down Expand Up @@ -147,32 +157,70 @@ export class ContractParser {
}, {})
}

hasMethodSelector (txInputData, selector) {
return (selector && txInputData) ? txInputData.includes(selector) : null
}


getMethodsBySelectors (txInputData) {
let methods = this.getMethodsSelectors()
return Object.keys(methods)
.filter(method => this.hasMethodSelector(txInputData, methods[method]) === true)
.filter(method => hasMethodSelector(txInputData, methods[method]) === true)
}

async getContractInfo (txInputData, contract) {
let { interfaces, methods } = await this.getContractImplementedInterfaces(txInputData, contract)

interfaces = mapInterfacesToERCs(interfaces)
return { methods, interfaces }
}

async getEIP1967Info (contractAddress) {
const { isUpgradeable, impContractAddress } = await this.isERC1967(contractAddress)
if (isUpgradeable) {
// manual check required
const proxyContractBytecode = await this.getContractCodeFromNode(impContractAddress)
const methods = this.getMethodsBySelectors(proxyContractBytecode)
let interfaces = this.getInterfacesByMethods(methods)

interfaces = mapInterfacesToERCs(interfaces)
return { methods, interfaces: [...interfaces, 'ERC1967'] }
}
return {methods: [], interfaces: []}
}

async getContractImplementedInterfaces (txInputData, contract) {
let methods = this.getMethodsBySelectors(txInputData)
let isErc165 = false
// skip non-erc165 contracts
if (includesAll(methods, ['supportsInterface(bytes4)'])) {
isErc165 = await this.implementsErc165(contract)
}
let interfaces
if (isErc165) interfaces = await this.getInterfacesERC165(contract)
else interfaces = this.getInterfacesByMethods(methods)
interfaces = Object.keys(interfaces)
.filter(k => interfaces[k] === true)
.map(t => contractsInterfaces[t] || t)
if (isErc165) {
interfaces = await this.getInterfacesERC165(contract)
} else {
interfaces = this.getInterfacesByMethods(methods)
}

return { methods, interfaces }
}

async isERC1967 (contractAddress) {
// check For ERC1967
// https://eips.ethereum.org/EIPS/eip-1967
// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc storage address where the implementation address is stored
const storedValue = await this.nod3.eth.getStorageAt(contractAddress, '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc')
const isUpgradeable = storedValue !== '0x0'
if (isUpgradeable) {
const impContractAddress = `0x${storedValue.slice(-40)}` // extract contract address
return { isUpgradeable, impContractAddress }
} else {
return { isUpgradeable, impContractAddress: storedValue}
}
}

async getContractCodeFromNode (contractAddress) {
return this.nod3.eth.getContractCodeAt(contractAddress)
}

async getInterfacesERC165 (contract) {
let ifaces = {}
let keys = Object.keys(interfacesIds)
Expand Down
10 changes: 6 additions & 4 deletions src/lib/EventDecoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function EventDecoder (abi, logger) {
return decoded.toHexString()
}
const res = add0x(Buffer.isBuffer(decoded) ? bufferToHex(decoded) : decoded.toString(16))
if(type === 'address' || type === 'address[]') return res.toLowerCase()
if (type === 'address' || type === 'address[]') return res.toLowerCase()
return res
}

Expand All @@ -45,9 +45,11 @@ function EventDecoder (abi, logger) {

const parsedArgs = []

for (const i in eventFragment.inputs) parsedArgs.push(
encodeElement(eventFragment.inputs[i].type, args[i])
)
for (const i in eventFragment.inputs) {
parsedArgs.push(
encodeElement(eventFragment.inputs[i].type, args[i])
)
}

return Object.assign({}, log, {
signature: remove0x(topic),
Expand Down
40 changes: 19 additions & 21 deletions test/ContractParser.spec.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import { assert } from 'chai'
import { ContractParser } from '../src/lib/ContractParser'
import nod3 from '../src/lib/nod3Connect'
import { assert } from "chai";
import { ContractParser } from "../src/lib/ContractParser";
import nod3 from "../src/lib/nod3Connect";

const contracts = [
'0xebea27d994371cd0cb9896ae4c926bc5221f6317']
const contracts = ["0xebea27d994371cd0cb9896ae4c926bc5221f6317"];

const parser = new ContractParser({ nod3 })
const parser = new ContractParser({ nod3 });

describe('# Network', function () {
it('should be connected to RSK testnet', async function () {
let net = await nod3.net.version()
console.log(net)
assert.equal(net.id, '31')
})
})

describe('Contract parser', function () {
describe("# Network", function () {
it("should be connected to RSK testnet", async function () {
let net = await nod3.net.version();
console.log(net);
assert.equal(net.id, "31");
});
});

describe("Contract parser", function () {
for (let address of contracts) {
it('should return the token data', async () => {
let contract = parser.makeContract(address)
const info = await parser.getTokenData(contract)
console.log({ info })
})
it("should return the token data", async () => {
let contract = parser.makeContract(address);
const info = await parser.getTokenData(contract);
console.log({ info });
});
}
})
});

0 comments on commit 8a7dd53

Please sign in to comment.