Skip to content

Commit

Permalink
initial commit`
Browse files Browse the repository at this point in the history
  • Loading branch information
kolinko committed Oct 5, 2019
1 parent 7cda9ac commit 35ba825
Show file tree
Hide file tree
Showing 50 changed files with 20,962 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
secret.py
__pycache__
*/__pycache__
cache_code
cache_pabi
cache_pan
cache
cache_pan
.DS_Store
supplement.db
supp2.db
cache_*
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## Installation:

```
git pull ....
pip3 install -r requirements.txt
```

## Running:

You *need* **python3.8** to run Panoramix. Yes, there was no way around it.

```
python3.8 panoramix.py address [func_name] [--verbose|--silent]
```

e.g.

```
python3.8 panoramix.py 0x06012c8cf97bead5deae237070f9587f8e7a266d
```

Output goes to two places:
- console
- cache_pan/ directory - .pan, .json, .asm files

### Optional parameters:

func_name -- name of the function to decompile (note: storage names won't be discovered in this mode)
--verbose -- prints out the assembly and stack as well as regular functions, a good way to try it out is
by running 'python panoramix.py kitties pause --verbose' - it's a simple function

There are more parameters as well. You can find what they do in panoramix.py.

### Address shortcuts
Some contract addresses, which are good for testing, have shortcuts, e.g. you can run
'python panoramix.py kitties' instead of 'python3 panoramix.py 0x06012c8cf97bead5deae237070f9587f8e7a266d'.

See panoramix.py for the list of shortcuts, feel free to add your own.

## Directories & Files

### Code:
- core - modules for doing abstract/symbolic operations
- pano - the proper decompiler
- utils - various helper modules
- tilde - the library for handling pattern matching in python3.8

### Data:
- cache_code - cached bytecodes
- cache_pan - cached decompilation outputs
- cache_pabi - cached auto-generated p-abi files
- supplement.db - sqlite3 database of function definitions
- supp2.db - a lightweight variant o the above

Cache directories are split into subdirectories, so the filesystem doesn't break down with large amounts
of cached contracts (important when running bulk_decompile on all 2.2M contracts on the chain)

All of the above generated after the first run.

## Utilities
bulk_decompile.py - batch-decompiles contracts, with multi-processing support
bulk_compare.py - decompiles a set of test contracts, fetches the current decompiled from Eveem, and prepares two files, so you can diff them and see what changes were made

## Why **python3.8** and **Tilde**
Panoramix uses a ton of pattern matching operations, and python doesn't support those as a language.

There are some pattern-matching libraries for older python versions, but none of them seemed good enough.
Because of that, I built Tilde, which is a language extension adding a new operator.

Tilde replaces '~' pattern matching operator with a series of ':=' operators underneath.
Because of that, python3.8 is a must.

Believe me, I spent a lot of time looking for some other way to make pattern matching readable.
Nothing was close to this good.

But if you manage to figure out a way to do it without Tilde (and maintain readability), I'll gladly accept a PR :)
673 changes: 673 additions & 0 deletions benchmark/0x06012c8cf97BEaD5deAe237070F9587f8E7A266d.pan

Large diffs are not rendered by default.

3,077 changes: 3,077 additions & 0 deletions benchmark/0x06a6a7aF298129E3a2AB396c9C06F91D3C54aBA8.pan

Large diffs are not rendered by default.

225 changes: 225 additions & 0 deletions benchmark/0x165cFb9cCf8b185E03205Ab4118eA6afBdbA9203.pan
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#
# Panoramix 4 Oct 2019
# Decompiled source of 0x165cFb9cCf8b185E03205Ab4118eA6afBdbA9203
#
# Let's make the world open source
# 

const NOT_AUDITED = 0
const MAX_COMMISSION = 9
const MIN_AUDIT_TIME = (24 * 3600)
const MAX_AUDIT_TIME = (672 * 24 * 3600)

def storage:
owner is addr at storage 0
paused is uint8 at storage 0 offset 160
newContractAddress is addr at storage 1
totalRequestsAmount is uint256 at storage 2
availableCommission is uint256 at storage 3
commission is uint256 at storage 4
solidStampRegisterAddress is addr at storage 5
rewards is mapping of uint256 at storage 6
auditRequests is mapping of struct at storage 7

def Rewards(bytes32 _param1): # not payable
return rewards[_param1]

def AuditRequests(bytes32 _param1): # not payable
return auditRequests[_param1].field_0, auditRequests[_param1].field_256

def Commission(): # not payable
return commission

def SolidStampRegisterAddress(): # not payable
return solidStampRegisterAddress

def paused(): # not payable
return bool(paused)

def AvailableCommission(): # not payable
return availableCommission

def newContractAddress(): # not payable
return newContractAddress

def owner(): # not payable
return owner

def TotalRequestsAmount(): # not payable
return totalRequestsAmount

#
# Regular functions
#

def _fallback() payable: # default function
revert

def pause(): # not payable
require caller == owner
require not paused
paused = 1
log Pause()

def renounceOwnership(): # not payable
require caller == owner
log OwnershipRenounced(address previousOwner=owner)
owner = 0

def setNewAddress(address _v2Address): # not payable
require caller == owner
require paused
require _v2Address
newContractAddress = _v2Address
log ContractUpgrade(address newContract=_v2Address)

def unpause(): # not payable
require caller == owner
require paused
if newContractAddress:
revert with 0, 'new contract cannot be 0x0'
require caller == owner
require paused
paused = 0
log Unpause()

def transferOwnership(address _newOwner): # not payable
require caller == owner
require _newOwner
log OwnershipTransferred(
 address previousOwner=owner,
 address newOwner=_newOwner)
owner = _newOwner

def changeCommission(uint256 _newCommission): # not payable
require caller == owner
require not paused
if _newCommission > 9:
revert with 0x8c379a000000000000000000000000000000000000000000000000000000000, 'commission should be <= MAX_COMMISSION'
if _newCommission == commission:
revert with 0, '_newCommission==Commmission'
commission = _newCommission
log NewCommission(uint256 commmission=_newCommission)

def withdrawCommission(uint256 _amount): # not payable
require caller == owner
if _amount > availableCommission:
revert with 0x8c379a000000000000000000000000000000000000000000000000000000000, 'Cannot withdraw more than available'
availableCommission -= _amount
call caller with:
value _amount wei
gas 2300 * is_zero(value) wei
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]

def withdrawRequest(address _auditor, bytes32 _codeHash): # not payable
require ext_code.size(solidStampRegisterAddress)
call solidStampRegisterAddress.getAuditOutcome(address auditor, bytes32 codeHash) with:
gas gas_remaining wei
args addr(_auditor), _codeHash
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]
require return_data.size >= 32
if uint8(ext_call.return_data[0]):
revert with 0, 'contract already audited'
if auditRequests[_auditor][caller][_codeHash].field_0 <= 0:
revert with 0, 'nothing to withdraw'
if block.timestamp <= auditRequests[_auditor][caller][_codeHash].field_256:
revert with 0, 'cannot withdraw before request.expireDate'
auditRequests[_auditor][caller][_codeHash].field_0 = 0
auditRequests[_auditor][caller][_codeHash].field_256 = 0
require auditRequests[_auditor][caller][_codeHash].field_0 <= rewards[_auditor][_codeHash]
rewards[_auditor][_codeHash] -= auditRequests[_auditor][caller][_codeHash].field_0
require auditRequests[_auditor][caller][_codeHash].field_0 <= totalRequestsAmount
totalRequestsAmount -= auditRequests[_auditor][caller][_codeHash].field_0
log RequestWithdrawn(
 address auditor=addr(_auditor),
 address bidder=caller,
 bytes32 codeHash=_codeHash,
 uint256 amount=auditRequests[_auditor][caller][_codeHash].field_0)
call caller with:
value auditRequests[_auditor][caller][_codeHash].field_0 wei
gas 2300 * is_zero(value) wei
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]

def auditContract(address _auditor, bytes32 _codeHash, bytes _reportIPFS, bool _isApproved): # not payable
require not paused
if solidStampRegisterAddress != caller:
revert with 0, 'can be only run by SolidStampRegister contract'
require rewards[_auditor][_codeHash] <= totalRequestsAmount
totalRequestsAmount -= rewards[_auditor][_codeHash]
if not rewards[_auditor][_codeHash]:
require availableCommission >= availableCommission
log ContractAudited(
 address auditor=addr(_auditor),
 bytes32 codeHash=_codeHash,
 bytes reportIPFS=Array(len=_reportIPFS.length, data=_reportIPFS[all]),
 bool isApproved=_isApproved,
 uint256 reward=rewards[_auditor][_codeHash])
require 0 <= rewards[_auditor][_codeHash]
call _auditor with:
value rewards[_auditor][_codeHash] wei
gas 2300 * is_zero(value) wei
else:
require commission * rewards[_auditor][_codeHash] / rewards[_auditor][_codeHash] == commission
require (commission * rewards[_auditor][_codeHash] / 100) + availableCommission >= availableCommission
availableCommission += commission * rewards[_auditor][_codeHash] / 100
log ContractAudited(
 address auditor=addr(_auditor),
 bytes32 codeHash=_codeHash,
 bytes reportIPFS=Array(len=_reportIPFS.length, data=_reportIPFS[all]),
 bool isApproved=_isApproved,
 uint256 reward=rewards[_auditor][_codeHash])
require commission * rewards[_auditor][_codeHash] / 100 <= rewards[_auditor][_codeHash]
call _auditor with:
value rewards[_auditor][_codeHash] - (commission * rewards[_auditor][_codeHash] / 100) wei
gas 2300 * is_zero(value) wei
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]

def requestAudit(address _auditor, bytes32 _codeHash, uint256 _auditTime) payable:
require not paused
if not _auditor:
revert with 0, '_auditor cannot be 0x0'
if _auditTime < 24 * 3600:
revert with 0x8c379a000000000000000000000000000000000000000000000000000000000, '_auditTime should be >= MIN_AUDIT_TIME'
if _auditTime > 672 * 24 * 3600:
revert with 0x8c379a000000000000000000000000000000000000000000000000000000000, '_auditTime should be <= MIN_AUDIT_TIME'
if call.value <= 0:
revert with 0, 'msg.value should be >0'
require ext_code.size(solidStampRegisterAddress)
call solidStampRegisterAddress.getAuditOutcome(address auditor, bytes32 codeHash) with:
gas gas_remaining wei
args addr(_auditor), _codeHash
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]
require return_data.size >= 32
if ext_call.return_data[31 len 1]:
revert with 0, 'contract already audited'
require _auditTime + block.timestamp >= block.timestamp
require call.value + rewards[_auditor][_codeHash] >= rewards[_auditor][_codeHash]
rewards[_auditor][_codeHash] += call.value
require call.value + totalRequestsAmount >= totalRequestsAmount
totalRequestsAmount += call.value
if not auditRequests[_auditor][caller][_codeHash].field_0:
auditRequests[_auditor][caller][_codeHash].field_0 = call.value
auditRequests[_auditor][caller][_codeHash].field_256 = _auditTime + block.timestamp
log AuditRequested(
 address auditor=addr(_auditor),
 address bidder=caller,
 bytes32 codeHash=_codeHash,
 uint256 amount=call.value,
 uint256 expireDate=_auditTime + block.timestamp)
else:
require call.value + auditRequests[_auditor][caller][_codeHash].field_0 >= auditRequests[_auditor][caller][_codeHash].field_0
auditRequests[_auditor][caller][_codeHash].field_0 += call.value
if _auditTime + block.timestamp > auditRequests[_auditor][caller][_codeHash].field_256:
auditRequests[_auditor][caller][_codeHash].field_256 = _auditTime + block.timestamp
log AuditRequested(
 address auditor=addr(_auditor),
 address bidder=caller,
 bytes32 codeHash=_codeHash,
 uint256 amount=auditRequests[_auditor][caller][_codeHash].field_0,
 uint256 expireDate=auditRequests[_auditor][caller][_codeHash].field_256)

29 changes: 29 additions & 0 deletions benchmark/0x53F955c424F1378D67Bb5e05F728476dC75fB4bA.pan
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#
# Panoramix 4 Oct 2019
# Decompiled source of 0x53F955c424F1378D67Bb5e05F728476dC75fB4bA
#
# Let's make the world open source
# 

def storage:
stor0 is addr at storage 0

def _fallback() payable: # default function
stop

def tokenFallback(address _param1, uint256 _param2, bytes _param3): # not payable
stop

def sweep(address _token, uint256 _amount): # not payable
require ext_code.size(stor0)
call stor0.sweeperOf(address token) with:
gas gas_remaining - 710 wei
args _token
require ext_call.success
delegate addr(ext_call.return_data[0]) with:
funct call.data[0 len 4]
gas gas_remaining - 710 wei
args call.data[4 len calldata.size - 4]
require delegate.return_code
return bool(delegate.return_data[0])

Loading

0 comments on commit 35ba825

Please sign in to comment.