|
| 1 | +#!/usr/bin/env python |
| 2 | +# Copyright (c) 2017 Christian Calderon |
| 3 | + |
| 4 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 5 | +# of this software and associated documentation files (the "Software"), to deal |
| 6 | +# in the Software without restriction, including without limitation the rights |
| 7 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 8 | +# copies of the Software, and to permit persons to whom the Software is |
| 9 | +# furnished to do so, subject to the following conditions: |
| 10 | + |
| 11 | +# The above copyright notice and this permission notice shall be included in all |
| 12 | +# copies or substantial portions of the Software. |
| 13 | + |
| 14 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 17 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 18 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 20 | +# SOFTWARE. |
| 21 | +from __future__ import print_function |
| 22 | +import argparse |
| 23 | +import serpent |
| 24 | +import os |
| 25 | +import re |
| 26 | +import binascii |
| 27 | +import json |
| 28 | +import shutil |
| 29 | +import sys |
| 30 | + |
| 31 | +if (3, 0) <= sys.version_info: |
| 32 | + _old_mk_signature = serpent.mk_signature |
| 33 | + def mk_signature(code): |
| 34 | + return _old_mk_signature(code).decode() |
| 35 | + serpent.mk_signature = mk_signature |
| 36 | + |
| 37 | +PYNAME = '[A-Za-z_][A-Za-z0-9_]*' |
| 38 | +IMPORT = re.compile('import ({0}) as ({0})'.format(PYNAME)) |
| 39 | + |
| 40 | +DEFAULT_RPCADDR = 'http://localhost:8545' |
| 41 | +DEFAULT_SRCDIR = './src' |
| 42 | +DEFAULT_BUILDDIR = './build' |
| 43 | + |
| 44 | +SERPENT_EXT = '.se' |
| 45 | +MACRO_EXT = '.sem' |
| 46 | + |
| 47 | +parser = argparse.ArgumentParser(description='Compiles collections of serpent contracts.') |
| 48 | +parser.add_argument('-R', '--rpcaddr', help='Address of RPC server', default=DEFAULT_RPCADDR) |
| 49 | +parser.add_argument('-S', '--srcdir', help='Directory to search for Serpent code', default=DEFAULT_SRCDIR) |
| 50 | +parser.add_argument('-B', '--builddir', help='Directory to save translated code in.', default=DEFAULT_BUILDDIR) |
| 51 | +modes = parser.add_mutually_exclusive_group(required=True) |
| 52 | +modes.add_argument('-T', '--translate', help='Just translate imports and save in builddir', action='store_true', default=False) |
| 53 | +modes.add_argument('-U', '--update', help='Updates the externs in all the contracts.', action='store_true', default=False) |
| 54 | + |
| 55 | + |
| 56 | +def find_files(source_dir, extension): |
| 57 | + """Makes a list of paths in the directory which end in the extenstion.""" |
| 58 | + paths = [] |
| 59 | + for entry in os.listdir(source_dir): |
| 60 | + entry = os.path.join(source_dir, entry) |
| 61 | + if os.path.isfile(entry) and entry.endswith(extension): |
| 62 | + paths.append(entry) |
| 63 | + elif os.path.isdir(entry): |
| 64 | + paths.extend(find_files(entry, extension)) |
| 65 | + else: |
| 66 | + continue |
| 67 | + return paths |
| 68 | + |
| 69 | + |
| 70 | +def strip_dependencies(serpent_file): |
| 71 | + """Separates dependency information from Serpent code in the file.""" |
| 72 | + dependencies = [] |
| 73 | + other_code = [] |
| 74 | + crud = [] |
| 75 | + done_with_crud = False |
| 76 | + with open(serpent_file) as code: |
| 77 | + for i, line in enumerate(code): |
| 78 | + line = line.rstrip() |
| 79 | + if line.startswith('#') and not done_with_crud: |
| 80 | + crud.append(line) |
| 81 | + continue |
| 82 | + done_with_crud = True |
| 83 | + |
| 84 | + if line.startswith('import'): |
| 85 | + m = IMPORT.match(line) |
| 86 | + if m is None: |
| 87 | + print('SAD! weird import at line', i, 'in contract', path) |
| 88 | + sys.exit(1) |
| 89 | + dependencies.append((m.group(1), m.group(2))) |
| 90 | + else: |
| 91 | + other_code.append(line) |
| 92 | + |
| 93 | + other_code.append('') |
| 94 | + crud.append('') |
| 95 | + |
| 96 | + return dependencies, '\n'.join(other_code), '\n'.join(crud) |
| 97 | + |
| 98 | + |
| 99 | +def imports_to_externs(source_dir, new_dir): |
| 100 | + """Translates code using import syntax to standard Serpent externs.""" |
| 101 | + source_dir = os.path.abspath(source_dir) |
| 102 | + new_dir = os.path.abspath(new_dir) |
| 103 | + |
| 104 | + shutil.copytree(source_dir, new_dir) |
| 105 | + |
| 106 | + serpent_files = find_files(new_dir, SERPENT_EXT) |
| 107 | + |
| 108 | + source_map = {} |
| 109 | + |
| 110 | + for path in serpent_files: |
| 111 | + name = os.path.basename(path).replace(SERPENT_EXT, '') |
| 112 | + info = {} |
| 113 | + info['dependencies'], info['stripped_code'], info['crud'] = strip_dependencies(path) |
| 114 | + |
| 115 | + with open(path, 'w') as temp: |
| 116 | + temp.write(info['stripped_code']) |
| 117 | + |
| 118 | + extern_name = 'extern {}:'.format(name) |
| 119 | + signature = serpent.mk_signature(path) |
| 120 | + name_end = signature.find(':') + 1 |
| 121 | + signature = extern_name + signature[name_end:] |
| 122 | + info['signature'] = signature |
| 123 | + info['path'] = path |
| 124 | + source_map[name] = info |
| 125 | + |
| 126 | + lookup_fmt = '{} = Controller.lookup(\'{}\')' |
| 127 | + for name in source_map: |
| 128 | + info = source_map[name] |
| 129 | + signatures = ['macro Controller: 0xC001D00D', 'extern Controller: [lookup:[int256]:int256, checkWhitelist:[int256]:int256]'] |
| 130 | + for oname, alias in info['dependencies']: |
| 131 | + signatures.append('') |
| 132 | + signatures.append(lookup_fmt.format(alias, oname)) |
| 133 | + signatures.append(source_map[oname]['signature']) |
| 134 | + signatures.append('') |
| 135 | + new_code = '\n'.join([info['crud']] + signatures + [info['stripped_code']]) |
| 136 | + path = info['path'] |
| 137 | + |
| 138 | + with open(path, 'w') as f: |
| 139 | + f.write(new_code) |
| 140 | + |
| 141 | + |
| 142 | +def update_externs(source_dir): |
| 143 | + pass |
| 144 | + |
| 145 | + |
| 146 | +def main(): |
| 147 | + args = parser.parse_args() |
| 148 | + |
| 149 | + if args.translate: |
| 150 | + imports_to_externs(args.srcdir, args.builddir) |
| 151 | + elif args.update: |
| 152 | + update_externs(args.srcdir) |
| 153 | + else: |
| 154 | + return 1 |
| 155 | + |
| 156 | + return 0 |
| 157 | + |
| 158 | +if __name__ == '__main__': |
| 159 | + sys.exit(main()) |
0 commit comments