diff --git a/manager/eris-mint/evm/native.go b/manager/eris-mint/evm/native.go
index ed4b4f378c93a2a7be83031f59ac6c17c7bc22cd..ab9e0c9720ee40c14f30f788cdd86bd03f5609dc 100644
--- a/manager/eris-mint/evm/native.go
+++ b/manager/eris-mint/evm/native.go
@@ -40,6 +40,8 @@ func registerNativeContracts() {
 
 type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error)
 
+type FuncID [4]byte
+
 /* Removed due to C dependency
 func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
 	// Deduct gas
diff --git a/manager/eris-mint/evm/snative.go b/manager/eris-mint/evm/snative.go
index 0c03827eab16463537c693d07a88e66896bf1491..95021ed0d6d8d066b34d538ee913c3350f1b5ddb 100644
--- a/manager/eris-mint/evm/snative.go
+++ b/manager/eris-mint/evm/snative.go
@@ -1,104 +1,344 @@
 package vm
 
 import (
-	"encoding/hex"
 	"fmt"
 
 	"github.com/eris-ltd/eris-db/common/sanity"
 	"github.com/eris-ltd/eris-db/manager/eris-mint/evm/sha3"
 	ptypes "github.com/eris-ltd/eris-db/permission/types"
 	. "github.com/eris-ltd/eris-db/word256"
+
+	"bytes"
+	"strings"
 )
 
 //------------------------------------------------------------------------------------------------
 // Registered SNative contracts
 
-var PermissionsContract = "permissions_contract"
+type SNativeContractDescription struct {
+	Comment   string
+	Name      string
+	functions map[FuncID]SNativeFuncDescription
+}
+
+type SNativeFuncDescription struct {
+	Comment  string
+	Name     string
+	Args     []SolidityArg
+	Return   SolidityReturn
+	PermFlag ptypes.PermFlag
+	F        NativeContract
+}
+
+type SolidityType string
+
+type SolidityArg struct {
+	Name string
+	Type SolidityType
+}
+
+type SolidityReturn struct {
+	Name string
+	Type SolidityType
+}
+
+const (
+	// We don't need to be exhaustive here, just make what we used strongly typed
+	SolidityAddress SolidityType = "address"
+	SolidityInt     SolidityType = "int"
+	SolidityUint64  SolidityType = "uint64"
+	SolidityBytes32 SolidityType = "bytes32"
+	SolidityString  SolidityType = "string"
+	SolidityBool    SolidityType = "bool"
+)
 
 func registerSNativeContracts() {
-	registeredNativeContracts[LeftPadWord256([]byte(PermissionsContract))] = permissionsContract
+	for _, contract := range SNativeContracts() {
+		registeredNativeContracts[contract.Address()] = contract.Dispatch
+	}
+}
+
+// Returns a map of all SNative contracts defined indexed by name
+func SNativeContracts() map[string]SNativeContractDescription {
+	contracts := []SNativeContractDescription{
+		NewSNativeContract(`
+		* Interface for managing Secure Native authorizations.
+		* @dev This Solidity interface describes the functions exposed by the SNative permissions layer in the Monax blockchain (ErisDB).
+		`,
+			"permissions_contract",
+			SNativeFuncDescription{`
+			* @notice Adds a role to an account
+			* @param _account account
+			* @param _role role
+			* @return result whether role was added
+			`,
+				"add_role",
+				[]SolidityArg{
+					arg("_account", SolidityAddress),
+					arg("_role", SolidityBytes32),
+				},
+				ret("result", SolidityBool),
+				ptypes.AddRole,
+				add_role},
+
+			SNativeFuncDescription{`
+			* @notice Indicates whether an account has a role
+			* @param _account account
+			* @param _role role
+			* @return result whether account has role
+			`,
+				"has_role",
+				[]SolidityArg{
+					arg("_account", SolidityAddress),
+					arg("_role", SolidityBytes32),
+				},
+				ret("result", SolidityBool),
+				ptypes.HasRole,
+				has_role},
 
-	/*
-		// we could expose these but we moved permission and args checks into the permissionsContract
-		// so calling them would be unsafe ...
-		registeredNativeContracts[LeftPadWord256([]byte("has_base"))] = has_base
-		registeredNativeContracts[LeftPadWord256([]byte("set_base"))] = set_base
-		registeredNativeContracts[LeftPadWord256([]byte("unset_base"))] = unset_base
-		registeredNativeContracts[LeftPadWord256([]byte("set_global"))] = set_global
-		registeredNativeContracts[LeftPadWord256([]byte("has_role"))] = has_role
-		registeredNativeContracts[LeftPadWord256([]byte("add_role"))] = add_role
-		registeredNativeContracts[LeftPadWord256([]byte("rm_role"))] = rm_role
-	*/
+			SNativeFuncDescription{`
+			* @notice Removes a role from an account
+			* @param _account account
+			* @param _role role
+			* @return result whether role was removed
+			`,
+				"rm_role",
+				[]SolidityArg{
+					arg("_account", SolidityAddress),
+					arg("_role", SolidityBytes32),
+				},
+				ret("result", SolidityBool),
+				ptypes.RmRole,
+				rm_role},
+
+			SNativeFuncDescription{`
+			* @notice Sets a base authorization for an account
+			* @param _account account
+			* @param _authorization base authorization
+			* @param _value value of base authorization
+			* @return result value passed in
+			`,
+				"set_base",
+				[]SolidityArg{arg("_account", SolidityAddress),
+					arg("_authorization", SolidityInt),
+					arg("_value", SolidityInt)},
+				ret("result", SolidityBool),
+				ptypes.SetBase,
+				set_base},
+
+			SNativeFuncDescription{`
+			* @notice Indicates whether an account has a base authorization
+			* @param _account account
+			* @param _authorization base authorization
+			* @return result whether account has base authorization set
+			`,
+				"has_base",
+				[]SolidityArg{arg("_account", SolidityAddress),
+					arg("_authorization", SolidityInt)},
+				ret("result", SolidityBool),
+				ptypes.HasBase,
+				has_base},
+
+			SNativeFuncDescription{`
+			* @notice Sets a base authorization for an account to the global (default) value of the base authorization
+      * @param _account account
+      * @param _authorization base authorization
+      * @return authorization base authorization passed in
+      `,
+				"unset_base",
+				[]SolidityArg{arg("_account", SolidityAddress),
+					arg("_authorization", SolidityInt)},
+				ret("authorization", SolidityInt),
+				ptypes.UnsetBase,
+				unset_base},
+
+			SNativeFuncDescription{`
+			* @notice Sets global (default) value for a base authorization
+			* @param _account account
+			* @param _authorization base authorization
+			* @param _value value of base authorization
+			* @return authorization base authorization passed in
+			`,
+				"set_global",
+				[]SolidityArg{arg("_account", SolidityAddress),
+					arg("_authorization", SolidityInt),
+					arg("_value", SolidityInt)},
+				ret("authorization", SolidityInt),
+				ptypes.SetGlobal,
+				set_global},
+		),
+	}
+
+	contractMap := make(map[string]SNativeContractDescription, len(contracts))
+	for _, contract := range contracts {
+		contractMap[contract.Name] = contract
+	}
+	return contractMap
 }
 
 //-----------------------------------------------------------------------------
 // snative are native contracts that can access and modify an account's permissions
 
-type SNativeFuncDescription struct {
-	Name     string
-	NArgs    int
-	PermFlag ptypes.PermFlag
-	F        NativeContract
+func NewSNativeContract(comment, name string, functions ...SNativeFuncDescription) SNativeContractDescription {
+	fs := make(map[FuncID]SNativeFuncDescription, len(functions))
+	for _, f := range functions {
+		fid := f.ID()
+		otherF, ok := fs[fid]
+		if ok {
+			panic(fmt.Errorf("Function with ID %x already defined: %s", fid,
+				otherF))
+		}
+		fs[fid] = f
+	}
+	return SNativeContractDescription{
+		Comment:   comment,
+		Name:      name,
+		functions: fs,
+	}
 }
 
-/* The solidity interface used to generate the abi function ids below
-contract Permissions {
-	function has_base(address addr, uint64 permFlag) constant returns (bool value) {}
-	function set_base(address addr, uint64 permFlag, bool value) constant returns (bool val) {}
-	function unset_base(address addr, uint64 permFlag) constant returns (uint64 pf) {}
-	function set_global(uint64 permFlag, bool value) constant returns (uint64 pf) {}
-	function has_role(address addr, string role) constant returns (bool val) {}
-	function add_role(address addr, string role) constant returns (bool added) {}
-	function rm_role(address addr, string role) constant returns (bool removed) {}
+func (contract *SNativeContractDescription) Address() Word256 {
+	return LeftPadWord256([]byte(contract.Name))
 }
-*/
 
-// function identifiers from the solidity abi
-var PermsMap = map[string]SNativeFuncDescription{
-	getFuncIdentifiersFromSignature("has_role(address,bytes32)"):    SNativeFuncDescription{"has_role", 2, ptypes.HasRole, has_role},
-	getFuncIdentifiersFromSignature("unset_base(address,uint64)"):   SNativeFuncDescription{"unset_base", 2, ptypes.UnsetBase, unset_base},
-	getFuncIdentifiersFromSignature("set_global(uint64,bool)"):      SNativeFuncDescription{"set_global", 2, ptypes.SetGlobal, set_global},
-	getFuncIdentifiersFromSignature("add_role(address,bytes32)"):    SNativeFuncDescription{"add_role", 2, ptypes.AddRole, add_role},
-	getFuncIdentifiersFromSignature("set_base(address,uint64,bool"): SNativeFuncDescription{"set_base", 3, ptypes.SetBase, set_base},
-	getFuncIdentifiersFromSignature("has_base(address,uint64)"):     SNativeFuncDescription{"has_base", 2, ptypes.HasBase, has_base},
-	getFuncIdentifiersFromSignature("rm_role(address,bytes32)"):     SNativeFuncDescription{"rm_role", 2, ptypes.RmRole, rm_role},
+func (contract *SNativeContractDescription) FunctionByID(id FuncID) (*SNativeFuncDescription, error) {
+	f, ok := contract.functions[id]
+	if !ok {
+		return nil,
+			fmt.Errorf("Unknown SNative function with ID %x", id)
+	}
+	return &f, nil
+}
+
+func (contract *SNativeContractDescription) FunctionByName(name string) (*SNativeFuncDescription, error) {
+	for _, f := range contract.functions {
+		if f.Name == name {
+			return &f, nil
+		}
+	}
+	return nil, fmt.Errorf("Unknown SNative function with name %s", name)
 }
 
-func getFuncIdentifiersFromSignature(signature string) string {
-	identifier := sha3.Sha3([]byte(signature))
-	return hex.EncodeToString(identifier[:4])
+func (contract *SNativeContractDescription) Functions() []SNativeFuncDescription {
+	fs := make([]SNativeFuncDescription, 0, len(contract.functions))
+	for _, f := range contract.functions {
+		fs = append(fs, f)
+	}
+	return fs
 }
 
-func permissionsContract(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
+func (contract *SNativeContractDescription) Dispatch(appState AppState,
+	caller *Account, args []byte, gas *int64) (output []byte, err error) {
 	if len(args) < 4 {
-		return nil, fmt.Errorf("permissionsContract expects at least a 4-byte function identifier")
+		return nil, fmt.Errorf("SNatives dispatch requires a 4-byte function "+
+			"identifier but arguments are only %s bytes long", len(args))
 	}
 
-	// map solidity abi function id to snative
-	funcIDbytes := args[:4]
-	args = args[4:]
-	funcID := hex.EncodeToString(funcIDbytes)
-	d, ok := PermsMap[funcID]
-	if !ok {
-		return nil, fmt.Errorf("unknown permissionsContract funcID %s", funcID)
+	function, err := contract.FunctionByID(firstFourBytes(args))
+	if err != nil {
+		return nil, err
 	}
 
+	remainingArgs := args[4:]
+
 	// check if we have permission to call this function
-	if !HasPermission(appState, caller, d.PermFlag) {
-		return nil, ErrInvalidPermission{caller.Address, d.Name}
+	if !HasPermission(appState, caller, function.PermFlag) {
+		return nil, ErrInvalidPermission{caller.Address, function.Name}
 	}
 
 	// ensure there are enough arguments
-	if len(args) != d.NArgs*32 {
-		return nil, fmt.Errorf("%s() takes %d arguments", d.Name)
+	if len(remainingArgs) != function.NArgs()*32 {
+		return nil, fmt.Errorf("%s() takes %d arguments", function.Name,
+			function.NArgs())
 	}
 
 	// call the function
-	return d.F(appState, caller, args, gas)
+	return function.F(appState, caller, remainingArgs, gas)
 }
 
-// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?)
+func (contract *SNativeContractDescription) SolidityComment() string {
+	return solidityComment(contract.Comment)
+}
+
+// Generate solidity code for this SNative contract
+func (contract *SNativeContractDescription) Solidity() (string, error) {
+	buf := new(bytes.Buffer)
+	err := snativeContractTemplate.Execute(buf, contract)
+	if err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+//
+// SNative functions
+//
+func (function *SNativeFuncDescription) Signature() string {
+	argTypes := make([]string, len(function.Args))
+	for i, arg := range function.Args {
+		argTypes[i] = string(arg.Type)
+	}
+	return fmt.Sprintf("%s(%s)", function.Name,
+		strings.Join(argTypes, ","))
+}
+
+func (function *SNativeFuncDescription) ID() FuncID {
+	return firstFourBytes(sha3.Sha3([]byte(function.Signature())))
+}
+
+func (function *SNativeFuncDescription) NArgs() int {
+	return len(function.Args)
+}
+
+func (function *SNativeFuncDescription) SolidityArgList() string {
+	argList := make([]string, len(function.Args))
+	for i, arg := range function.Args {
+		argList[i] = fmt.Sprintf("%s %s", arg.Type, arg.Name)
+	}
+	return strings.Join(argList, ", ")
+}
 
+func (function *SNativeFuncDescription) SolidityComment() string {
+	return solidityComment(function.Comment)
+}
+
+func (function *SNativeFuncDescription) Solidity() (string, error) {
+	buf := new(bytes.Buffer)
+	err := snativeFuncTemplate.Execute(buf, function)
+	if err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+func solidityComment(comment string) string {
+	commentLines := make([]string, 0, 5)
+	for _, line := range strings.Split(comment, "\n") {
+		trimLine := strings.TrimLeft(line, " \t\n")
+		if trimLine != "" {
+			commentLines = append(commentLines, trimLine)
+		}
+	}
+	return strings.Join(commentLines, "\n")
+}
+
+func arg(name string, solidityType SolidityType) SolidityArg {
+	return SolidityArg{
+		Name: name,
+		Type: solidityType,
+	}
+}
+
+func ret(name string, solidityType SolidityType) SolidityReturn {
+	return SolidityReturn{
+		Name: name,
+		Type: solidityType,
+	}
+}
+
+// Permission function defintions
+
+// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?)
 func has_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
 	addr, permNum := returnTwoArgs(args)
 	vmAcc := appState.GetAccount(addr)
@@ -249,3 +489,9 @@ func byteFromBool(b bool) byte {
 	}
 	return 0x0
 }
+
+func firstFourBytes(byteSlice []byte) [4]byte {
+	var bs [4]byte
+	copy(bs[:], byteSlice[:4])
+	return bs
+}
diff --git a/manager/eris-mint/evm/snative_templates.go b/manager/eris-mint/evm/snative_templates.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc47388e2dc515693743148e2d53e4c844ca1532
--- /dev/null
+++ b/manager/eris-mint/evm/snative_templates.go
@@ -0,0 +1,37 @@
+package vm
+
+import (
+	"text/template"
+	"fmt"
+)
+
+const snativeContractTemplateText=`/**
+{{.SolidityComment}}
+*/
+contract {{.Name}} {
+{{range .Functions}}
+{{.Solidity}}
+{{end}}
+}
+`
+const snativeFuncTemplateText=`/**
+{{.SolidityComment}}
+*/
+function {{.Name}}({{.SolidityArgList}}) constant returns ({{.Return.Type}} {{.Return.Name}});`
+
+var snativeContractTemplate *template.Template
+var snativeFuncTemplate *template.Template
+
+func init() {
+	var err error
+	snativeFuncTemplate, err = template.New("snativeFuncTemplate").
+	Parse(snativeFuncTemplateText)
+	if err != nil {
+		panic(fmt.Errorf("Couldn't parse SNative function template: %s", err))
+	}
+	snativeContractTemplate, err = template.New("snativeFuncTemplate").
+			Parse(snativeContractTemplateText)
+	if err != nil {
+		panic(fmt.Errorf("Couldn't parse SNative contract template: %s", err))
+	}
+}
\ No newline at end of file
diff --git a/manager/eris-mint/evm/snative_test.go b/manager/eris-mint/evm/snative_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..731a52179ee91ff034ca43492a490f0f57966e57
--- /dev/null
+++ b/manager/eris-mint/evm/snative_test.go
@@ -0,0 +1,88 @@
+package vm
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"fmt"
+)
+
+/* Compiling the Permissions solidity contract at
+https://ethereum.github.io/browser-solidity yields:
+
+3fbf7da5 add_role(address,bytes32)
+bb37737a has_base(address,int256)
+e8145855 has_role(address,bytes32)
+28fd0194 rm_role(address,bytes32)
+9ea53314 set_base(address,int256,bool)
+d69186a6 set_global(int256,bool)
+180d26f2 unset_base(address,int256)
+*/
+
+func TestPermissionsContract(t *testing.T) {
+	registerNativeContracts()
+	contract := SNativeContracts()["permissions_contract"]
+
+	assertContractFunction(t, contract, "3fbf7da5",
+		"add_role(address,bytes32)")
+
+	assertContractFunction(t, contract, "bb37737a",
+		"has_base(address,int256)")
+
+	assertContractFunction(t, contract, "054556ac",
+		"has_role(address,bytes32)")
+
+	assertContractFunction(t, contract, "ded3350a",
+		"rm_role(address,bytes32)")
+
+	assertContractFunction(t, contract, "c2174d8f",
+		"set_base(address,int256,bool)")
+
+	assertContractFunction(t, contract, "85f1522b",
+		"set_global(int256,bool)")
+
+	assertContractFunction(t, contract, "73448c99",
+		"unset_base(address,int256)")
+}
+
+func TestSNativeFuncTemplate(t *testing.T) {
+	contract := SNativeContracts()["permissions_contract"]
+	function, err := contract.FunctionByName("rm_role")
+	if err != nil {
+		t.Fatal("Couldn't get function")
+	}
+	solidity, err := function.Solidity()
+	assert.NoError(t, err)
+	fmt.Println(solidity)
+}
+
+func TestSNativeContractTemplate(t *testing.T) {
+	contract := SNativeContracts()["permissions_contract"]
+	solidity, err := contract.Solidity()
+	assert.NoError(t, err)
+	fmt.Println(solidity)
+}
+
+// Helpers
+
+func assertContractFunction(t *testing.T, contract SNativeContractDescription,
+	funcIDHex string, expectedSignature string) {
+	function, err := contract.FunctionByID(fourBytesFromHex(t, funcIDHex))
+	assert.NoError(t, err,
+		"Error retrieving SNativeFunctionDescription with ID %s", funcIDHex)
+	if err == nil {
+		assert.Equal(t, expectedSignature, function.Signature())
+	}
+}
+
+func fourBytesFromHex(t *testing.T, hexString string) [4]byte {
+	bs, err := hex.DecodeString(hexString)
+	assert.NoError(t, err, "Could not decode hex string '%s'", hexString)
+	if len(bs) != 4 {
+		t.Fatalf("FuncID must be 4 bytes but '%s' is %v bytes", hexString,
+			len(bs))
+	}
+	return firstFourBytes(bs)
+}
+
diff --git a/manager/eris-mint/state/permissions_test.go b/manager/eris-mint/state/permissions_test.go
index fd4dd421935b6a58b0cdaf592b0f61dbd4849722..a264178de5bb0f807cee132bbd9be86b9d12229a 100644
--- a/manager/eris-mint/state/permissions_test.go
+++ b/manager/eris-mint/state/permissions_test.go
@@ -2,7 +2,6 @@ package state
 
 import (
 	"bytes"
-	"encoding/hex"
 	"fmt"
 	"strconv"
 	"testing"
@@ -29,6 +28,7 @@ func init() {
 var (
 	dbBackend = "memdb"
 	dbDir     = ""
+	permissionsContract = vm.SNativeContracts()["permissions_contract"]
 )
 
 /*
@@ -1182,18 +1182,17 @@ func boolToWord256(v bool) Word256 {
 	return LeftPadWord256([]byte{vint})
 }
 
-func permNameToFuncID(s string) []byte {
-	for k, v := range vm.PermsMap {
-		if s == v.Name {
-			b, _ := hex.DecodeString(k)
-			return b
-		}
+func permNameToFuncID(name string) []byte {
+  function, err := permissionsContract.FunctionByName(name)
+	if err != nil {
+		panic("didn't find snative function signature!")
 	}
-	panic("didn't find snative function signature!")
+	id := function.ID()
+	return id[:]
 }
 
 func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, pF ptypes.PermFlag, data []byte) {
-	addr = LeftPadWord256([]byte(vm.PermissionsContract)).Postfix(20)
+	addr = permissionsContract.Address().Postfix(20)
 	switch name {
 	case "has_base", "unset_base":
 		data = LeftPadBytes(user.Address, 32)
@@ -1229,7 +1228,7 @@ func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.Perm
 }
 
 func snativeRoleTestInputCALL(name string, user *acm.PrivAccount, role string) (addr []byte, pF ptypes.PermFlag, data []byte) {
-	addr = LeftPadWord256([]byte(vm.PermissionsContract)).Postfix(20)
+	addr = permissionsContract.Address().Postfix(20)
 	data = LeftPadBytes(user.Address, 32)
 	data = append(data, RightPadBytes([]byte(role), 32)...)
 	data = append(permNameToFuncID(name), data...)