-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
User defined mapping fix and test cases for structureToSignatureType (#…
…50) * Move getStatesFromSourceCode to utils. * Test cases for getStatesFromSourceCode with declarations from dataTypeParser test. * Added more state declarations in test. * Fix for states of map with structs. * Add test case for constant and immutable state variables. * Exclude constant variables from states. Co-authored-by: nikugogoi <95nikass@gmail.com>
- Loading branch information
1 parent
9da2fb4
commit 25ed9c7
Showing
5 changed files
with
265 additions
and
71 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import {getStatesFromSourceCode} from "./contract"; | ||
|
||
describe('getStatesfromSourceCode', () => { | ||
test('elementary types', async () => { | ||
const sourceCode = ` | ||
contract Test { | ||
string public name = "Uniswap"; | ||
uint8 public decimals = 18; | ||
int256 public integers = -21; | ||
bool internal boolean = true; | ||
address private plainAddress = '0x1ca7c995f8eF0A2989BbcE08D5B7Efe50A584aa1' | ||
address payable payableAddress = '0xbb38B6F181541a6cEdfDac0b94175B2431Aa1A02' | ||
bytes32 byteText = "ByteText"; | ||
} | ||
`; | ||
|
||
const states = await getStatesFromSourceCode(sourceCode); | ||
expect(states).toStrictEqual([ | ||
{ slot: 0, type: 'string name;', variable: 'name' }, | ||
{ slot: 1, type: 'uint8 decimals;', variable: 'decimals' }, | ||
{ slot: 2, type: 'int256 integers;', variable: 'integers' }, | ||
{ slot: 3, type: 'bool boolean;', variable: 'boolean' }, | ||
{ slot: 4, type: 'address plainAddress;', variable: 'plainAddress' }, | ||
{ | ||
slot: 5, | ||
type: 'address payableAddress;', | ||
variable: 'payableAddress' | ||
}, | ||
{ slot: 6, type: 'bytes32 byteText;', variable: 'byteText' } | ||
]) | ||
}) | ||
|
||
test('array types', async () => { | ||
const sourceCode = ` | ||
contract Test { | ||
string[] public names = ["Uniswap"]; | ||
string[][] public nestedNames; | ||
uint[] public decimalList; | ||
} | ||
`; | ||
|
||
const states = await getStatesFromSourceCode(sourceCode); | ||
expect(states).toStrictEqual([ | ||
{ slot: 0, type: 'string[] names;', variable: 'names' }, | ||
{ slot: 1, type: 'string[] nestedNames;', variable: 'nestedNames' }, | ||
{ slot: 2, type: 'uint[] decimalList;', variable: 'decimalList' } | ||
]) | ||
}) | ||
|
||
test('mapping types', async () => { | ||
const sourceCode = ` | ||
contract Test { | ||
mapping (address => uint96) internal balances; | ||
mapping (address => mapping (address => uint96)) internal allowances; | ||
} | ||
`; | ||
|
||
const states = await getStatesFromSourceCode(sourceCode); | ||
expect(states).toStrictEqual([ | ||
{ | ||
slot: 0, | ||
type: 'mapping (address => uint96) balances;', | ||
variable: 'balances' | ||
}, | ||
{ | ||
slot: 1, | ||
type: 'mapping (address => mapping (address => uint96)) allowances;', | ||
variable: 'allowances' | ||
} | ||
]) | ||
}) | ||
|
||
test('user defined types', async () => { | ||
const sourceCode = ` | ||
contract Test { | ||
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } | ||
ActionChoices choice; | ||
struct Checkpoint { | ||
uint32 fromBlock; | ||
uint96 votes; | ||
} | ||
Checkpoint checkpoint; | ||
Checkpoint[] public checkpointList; | ||
mapping(address => Checkpoint) public checkpointMap; | ||
mapping(address => mapping(uint => Checkpoint)) public checkpointNestedMap; | ||
struct NestedCheckpoint { | ||
uint32 fromBlock; | ||
SomeVotes votes; | ||
} | ||
struct SomeVotes { | ||
uint96 votes; | ||
} | ||
mapping(address => mapping(uint => NestedCheckpoint)) public nestedCheckpointNestedMap; | ||
struct KeyFlag { | ||
uint key; | ||
bool deleted; | ||
} | ||
struct ComplexCheckpoint { | ||
uint32 fromBlock; | ||
mapping(uint => SomeVotes) votes; | ||
KeyFlag[] keys; | ||
} | ||
mapping(address => ComplexCheckpoint) public complexCheckpointMap; | ||
} | ||
`; | ||
|
||
const states = await getStatesFromSourceCode(sourceCode); | ||
|
||
expect(states).toStrictEqual([ | ||
{ | ||
slot: 0, | ||
type: 'ActionChoices choice; struct Checkpoint {uint32 fromBlock;uint96 votes;} struct NestedCheckpoint {uint32 fromBlock;SomeVotes votes;} struct SomeVotes {uint96 votes;} struct KeyFlag {uint key;bool deleted;} struct ComplexCheckpoint {uint32 fromBlock;mapping (uint => SomeVotes) votes;KeyFlag[] keys;}', | ||
variable: 'choice' | ||
}, | ||
{ | ||
slot: 1, | ||
type: 'Checkpoint checkpoint; struct Checkpoint {uint32 fromBlock;uint96 votes;} struct NestedCheckpoint {uint32 fromBlock;SomeVotes votes;} struct SomeVotes {uint96 votes;} struct KeyFlag {uint key;bool deleted;} struct ComplexCheckpoint {uint32 fromBlock;mapping (uint => SomeVotes) votes;KeyFlag[] keys;}', | ||
variable: 'checkpoint' | ||
}, | ||
{ | ||
slot: 2, | ||
type: 'Checkpoint[] checkpointList; struct Checkpoint {uint32 fromBlock;uint96 votes;} struct NestedCheckpoint {uint32 fromBlock;SomeVotes votes;} struct SomeVotes {uint96 votes;} struct KeyFlag {uint key;bool deleted;} struct ComplexCheckpoint {uint32 fromBlock;mapping (uint => SomeVotes) votes;KeyFlag[] keys;}', | ||
variable: 'checkpointList' | ||
}, | ||
{ | ||
slot: 3, | ||
type: 'mapping (address => Checkpoint) checkpointMap; struct Checkpoint {uint32 fromBlock;uint96 votes;} struct NestedCheckpoint {uint32 fromBlock;SomeVotes votes;} struct SomeVotes {uint96 votes;} struct KeyFlag {uint key;bool deleted;} struct ComplexCheckpoint {uint32 fromBlock;mapping (uint => SomeVotes) votes;KeyFlag[] keys;}', | ||
variable: 'checkpointMap' | ||
}, | ||
{ | ||
slot: 4, | ||
type: 'mapping (address => mapping (uint => Checkpoint)) checkpointNestedMap; struct Checkpoint {uint32 fromBlock;uint96 votes;} struct NestedCheckpoint {uint32 fromBlock;SomeVotes votes;} struct SomeVotes {uint96 votes;} struct KeyFlag {uint key;bool deleted;} struct ComplexCheckpoint {uint32 fromBlock;mapping (uint => SomeVotes) votes;KeyFlag[] keys;}', | ||
variable: 'checkpointNestedMap' | ||
}, | ||
{ | ||
slot: 5, | ||
type: 'mapping (address => mapping (uint => NestedCheckpoint)) nestedCheckpointNestedMap; struct Checkpoint {uint32 fromBlock;uint96 votes;} struct NestedCheckpoint {uint32 fromBlock;SomeVotes votes;} struct SomeVotes {uint96 votes;} struct KeyFlag {uint key;bool deleted;} struct ComplexCheckpoint {uint32 fromBlock;mapping (uint => SomeVotes) votes;KeyFlag[] keys;}', | ||
variable: 'nestedCheckpointNestedMap' | ||
}, | ||
{ | ||
slot: 6, | ||
type: 'mapping (address => ComplexCheckpoint) complexCheckpointMap; struct Checkpoint {uint32 fromBlock;uint96 votes;} struct NestedCheckpoint {uint32 fromBlock;SomeVotes votes;} struct SomeVotes {uint96 votes;} struct KeyFlag {uint key;bool deleted;} struct ComplexCheckpoint {uint32 fromBlock;mapping (uint => SomeVotes) votes;KeyFlag[] keys;}', | ||
variable: 'complexCheckpointMap' | ||
} | ||
]) | ||
}) | ||
|
||
test('constant state variables', async () => { | ||
const sourceCode = ` | ||
contract Test { | ||
string public name = "Uniswap"; | ||
uint public constant decimals = 18; | ||
bool internal boolean = true; | ||
} | ||
`; | ||
|
||
const states = await getStatesFromSourceCode(sourceCode); | ||
|
||
expect(states).toStrictEqual([ | ||
{ slot: 0, type: 'string name;', variable: 'name' }, | ||
{ slot: 1, type: 'bool boolean;', variable: 'boolean' } | ||
]) | ||
}) | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { StateVariableDeclaration, StructDefinition, TypeName } from 'solidity-parser-diligence'; | ||
|
||
import State from '../models/contract/state'; | ||
|
||
const parser = require('@solidity-parser/parser'); // eslint-disable-line | ||
|
||
const structureToSignatureType = (name: string, typeName: TypeName, structs: StructDefinition[], level = 0, isArray = false): {signature: string; type: string; hasStruct: boolean} => { | ||
let structsDef = ''; | ||
if (level === 0 && structs && structs.length) { | ||
for (const struct of structs) { | ||
structsDef += ` struct ${struct.name} {` + struct.members.map(m => structureToSignatureType(m.name, m.typeName, structs, level + 1).signature).join('') + '}'; | ||
} | ||
} | ||
|
||
switch (typeName.type) { | ||
case 'ElementaryTypeName': | ||
return { | ||
signature: `${typeName.name}${isArray ? '[]' : ''} ${name};`, | ||
type: typeName.name, | ||
hasStruct: false, | ||
} | ||
case 'ArrayTypeName': { | ||
const res = structureToSignatureType(name, typeName.baseTypeName, structs, level + 1, true); | ||
return { | ||
signature: res.signature + (level === 0 && res.hasStruct ? structsDef : ''), | ||
type: typeName.baseTypeName?.type, | ||
hasStruct: res.hasStruct, | ||
} | ||
} | ||
case 'Mapping': { | ||
const res = structureToSignatureType(name, typeName.valueType, structs, level + 1); | ||
return { | ||
signature: `mapping (${typeName.keyType.name} => ${res.type}) ${name};` + (level === 0 && res.hasStruct ? structsDef : ''), | ||
type: `mapping (${typeName.keyType.name} => ${res.type})`, // for mapping => mapping case | ||
hasStruct: res.hasStruct, | ||
} | ||
} | ||
case 'UserDefinedTypeName': | ||
return { | ||
signature: `${typeName.namePath}${isArray ? '[]' : ''} ${name};` + (level === 0 ? structsDef : ''), | ||
type: typeName.namePath, | ||
hasStruct: true, | ||
}; | ||
} | ||
} | ||
|
||
export const getStatesFromSourceCode = async(sourceCode: string): Promise<State[]> => { | ||
const ast = parser.parse(sourceCode, { | ||
tolerant: true, | ||
}); | ||
|
||
let list = []; | ||
const contractDefinitions = ast?.children?.filter((item) => item.type === 'ContractDefinition'); | ||
for (const contractDefinition of contractDefinitions) { | ||
const states = contractDefinition?.subNodes.filter(n => n.type == 'StateVariableDeclaration') as StateVariableDeclaration[]; | ||
const structs = contractDefinition?.subNodes.filter(n => n.type == 'StructDefinition') as StructDefinition[]; | ||
// TODO: Handle EnumDefinition type subnodes. | ||
|
||
// isImmutable property of state variable is not present in type StateVariableDeclaration. | ||
// TODO: Filter immutable variables after support added in https://github.com/ConsenSys/solidity-parser-antlr | ||
list = list.concat(states?.filter((item) => !item.variables[0]?.isDeclaredConst) | ||
.map((item, slot) => { | ||
const type: string = structureToSignatureType(item.variables[0]?.name, item.variables[0]?.typeName, structs).signature; | ||
return { | ||
slot, | ||
type, | ||
variable: item.variables[0]?.name, | ||
} | ||
}) | ||
); | ||
} | ||
|
||
return list as State[]; | ||
} |