diff --git a/Makefile b/Makefile index 53fd84ee4616b39e8191b8ed818f02e83f8981d2..a05fe57e210c74affc132397a405f4cee63813fb 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ hell: # Dumps Solidity interface contracts for SNatives .PHONY: snatives snatives: - @go run ./util/snatives/main.go + @go run ./util/snatives/cmd/main.go ### Building github.com/eris-ltd/eris-db diff --git a/manager/eris-mint/evm/abi/types.go b/manager/eris-mint/evm/abi/types.go new file mode 100644 index 0000000000000000000000000000000000000000..2c299e6d10f217f3ca94669d50682316dc0bed29 --- /dev/null +++ b/manager/eris-mint/evm/abi/types.go @@ -0,0 +1,27 @@ +package abi + +// Ethereum defines types and calling conventions for the ABI +// (application binary interface) here: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI +// We make a start of representing them here + +type Type string + +type Arg struct { + Name string + Type Type +} + +type Return struct { + Name string + Type Type +} + +const ( + // We don't need to be exhaustive here, just make what we used strongly typed + Address Type = "address" + Int Type = "int" + Uint64 Type = "uint64" + Bytes32 Type = "bytes32" + String Type = "string" + Bool Type = "bool" +) diff --git a/manager/eris-mint/evm/snative.go b/manager/eris-mint/evm/snative.go index c74acc4d05657f01d2d49fedd3fdc51c1d9017be..20463dc8db6a9b4855be874d722bcd9570baea3b 100644 --- a/manager/eris-mint/evm/snative.go +++ b/manager/eris-mint/evm/snative.go @@ -8,8 +8,8 @@ import ( ptypes "github.com/eris-ltd/eris-db/permission/types" . "github.com/eris-ltd/eris-db/word256" - "bytes" "strings" + "github.com/eris-ltd/eris-db/manager/eris-mint/evm/abi" ) //------------------------------------------------------------------------------------------------ @@ -18,40 +18,18 @@ import ( type SNativeContractDescription struct { Comment string Name string - functions map[FuncID]SNativeFuncDescription + functions map[FuncID]*SNativeFunctionDescription } -type SNativeFuncDescription struct { +type SNativeFunctionDescription struct { Comment string Name string - Args []SolidityArg - Return SolidityReturn + Args []abi.Arg + Return abi.Return 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() { for _, contract := range SNativeContracts() { registeredNativeContracts[contract.Address()] = contract.Dispatch @@ -59,121 +37,122 @@ func registerSNativeContracts() { } // Returns a map of all SNative contracts defined indexed by name -func SNativeContracts() map[string]SNativeContractDescription { - permFlagType := SolidityUint64 - roleType := SolidityBytes32 - contracts := []SNativeContractDescription{ +func SNativeContracts() map[string]*SNativeContractDescription { + permFlagType := abi.Uint64 + roleType := abi.Bytes32 + 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). + * @dev This interface describes the functions exposed by the SNative permissions layer in the Monax blockchain (ErisDB). `, "permissions_contract", - SNativeFuncDescription{` + &SNativeFunctionDescription{` * @notice Adds a role to an account - * @param _account account - * @param _role role + * @param _account account address + * @param _role role name * @return result whether role was added `, "add_role", - []SolidityArg{ - arg("_account", SolidityAddress), + []abi.Arg{ + arg("_account", abi.Address), arg("_role", roleType), }, - ret("result", SolidityBool), + ret("result", abi.Bool), ptypes.AddRole, add_role}, - SNativeFuncDescription{` + &SNativeFunctionDescription{` * @notice Indicates whether an account has a role - * @param _account account - * @param _role role + * @param _account account address + * @param _role role name * @return result whether account has role `, "has_role", - []SolidityArg{ - arg("_account", SolidityAddress), + []abi.Arg{ + arg("_account", abi.Address), arg("_role", roleType), }, - ret("result", SolidityBool), + ret("result", abi.Bool), ptypes.HasRole, has_role}, - SNativeFuncDescription{` + &SNativeFunctionDescription{` * @notice Removes a role from an account - * @param _account account - * @param _role role + * @param _account account address + * @param _role role name * @return result whether role was removed `, "rm_role", - []SolidityArg{ - arg("_account", SolidityAddress), + []abi.Arg{ + arg("_account", abi.Address), arg("_role", roleType), }, - ret("result", SolidityBool), + ret("result", abi.Bool), 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 + &SNativeFunctionDescription{` + * @notice Sets the permission flags for an account. Makes them explicitly set (on or off). + * @param _account account address + * @param _permission the base permissions flags to set for the account + * @param _set whether to set or unset the permissions flags at the account level + * @return result the resultant permissions flags on the account after the call `, "set_base", - []SolidityArg{ - arg("_account", SolidityAddress), - arg("_authorization", permFlagType), - arg("_value", permFlagType)}, - ret("result", SolidityBool), + []abi.Arg{ + arg("_account", abi.Address), + arg("_permission", permFlagType), + arg("_set", abi.Bool), + }, + ret("result", permFlagType), 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 + &SNativeFunctionDescription{` + * @notice Indicates whether an account has a subset of permissions set + * @param _account account address + * @param _permission the permissions flags (mask) to check whether enabled against base permissions for the account + * @return result whether account has the passed permissions flags set `, "has_base", - []SolidityArg{ - arg("_account", SolidityAddress), - arg("_authorization", permFlagType)}, - ret("result", SolidityBool), + []abi.Arg{ + arg("_account", abi.Address), + arg("_permission", permFlagType)}, + ret("result", permFlagType), 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 + &SNativeFunctionDescription{` + * @notice Unsets the permissions flags for an account. Causes permissions being unset to fall through to global permissions. + * @param _account account address + * @param _permission the permissions flags to unset for the account + * @return result the resultant permissions flags on the account after the call `, "unset_base", - []SolidityArg{ - arg("_account", SolidityAddress), - arg("_authorization", permFlagType)}, - ret("authorization", permFlagType), + []abi.Arg{ + arg("_account", abi.Address), + arg("_permission", permFlagType)}, + ret("result", permFlagType), ptypes.UnsetBase, unset_base}, - SNativeFuncDescription{` - * @notice Sets global (default) value for a base authorization - * @param _authorization base authorization - * @param _value value of base authorization - * @return authorization base authorization passed in + &SNativeFunctionDescription{` + * @notice Sets the global (default) permissions flags for the entire chain + * @param _permission the permissions flags to set + * @param _set whether to set (or unset) the permissions flags + * @return result the resultant permissions flags on the account after the call `, "set_global", - []SolidityArg{ - arg("_authorization", permFlagType), - arg("_value", permFlagType)}, - ret("authorization", permFlagType), + []abi.Arg{ + arg("_permission", permFlagType), + arg("_set", abi.Bool)}, + ret("result", permFlagType), ptypes.SetGlobal, set_global}, ), } - contractMap := make(map[string]SNativeContractDescription, len(contracts)) + contractMap := make(map[string]*SNativeContractDescription, len(contracts)) for _, contract := range contracts { contractMap[contract.Name] = contract } @@ -183,8 +162,8 @@ func SNativeContracts() map[string]SNativeContractDescription { //----------------------------------------------------------------------------- // snative are native contracts that can access and modify an account's permissions -func NewSNativeContract(comment, name string, functions ...SNativeFuncDescription) SNativeContractDescription { - fs := make(map[FuncID]SNativeFuncDescription, len(functions)) +func NewSNativeContract(comment, name string, functions ...*SNativeFunctionDescription) *SNativeContractDescription { + fs := make(map[FuncID]*SNativeFunctionDescription, len(functions)) for _, f := range functions { fid := f.ID() otherF, ok := fs[fid] @@ -194,7 +173,7 @@ func NewSNativeContract(comment, name string, functions ...SNativeFuncDescriptio } fs[fid] = f } - return SNativeContractDescription{ + return &SNativeContractDescription{ Comment: comment, Name: name, functions: fs, @@ -207,25 +186,25 @@ func NewSNativeContract(comment, name string, functions ...SNativeFuncDescriptio func (contract *SNativeContractDescription) Dispatch(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { if len(args) < FuncIDLength { - return Zero256.Bytes(), fmt.Errorf("SNatives dispatch requires a 4-byte function "+ + return nil, fmt.Errorf("SNatives dispatch requires a 4-byte function "+ "identifier but arguments are only %s bytes long", len(args)) } function, err := contract.FunctionByID(firstFourBytes(args)) if err != nil { - return Zero256.Bytes(), err + return nil, err } remainingArgs := args[FuncIDLength:] // check if we have permission to call this function if !HasPermission(appState, caller, function.PermFlag) { - return Zero256.Bytes(), ErrInvalidPermission{caller.Address, function.Name} + return nil, ErrInvalidPermission{caller.Address, function.Name} } // ensure there are enough arguments if len(remainingArgs) != function.NArgs()*Word256Length { - return Zero256.Bytes(), fmt.Errorf("%s() takes %d arguments", function.Name, + return nil, fmt.Errorf("%s() takes %d arguments", function.Name, function.NArgs()) } @@ -233,54 +212,42 @@ func (contract *SNativeContractDescription) Dispatch(appState AppState, return function.F(appState, caller, remainingArgs, gas) } +// We define the address of an SNative contact as the simplest possible hash of +// its canonical name func (contract *SNativeContractDescription) Address() Word256 { return LeftPadWord256([]byte(contract.Name)) } -func (contract *SNativeContractDescription) FunctionByID(id FuncID) (*SNativeFuncDescription, error) { +func (contract *SNativeContractDescription) FunctionByID(id FuncID) (*SNativeFunctionDescription, error) { f, ok := contract.functions[id] if !ok { return nil, fmt.Errorf("Unknown SNative function with ID %x", id) } - return &f, nil + return f, nil } -func (contract *SNativeContractDescription) FunctionByName(name string) (*SNativeFuncDescription, error) { +func (contract *SNativeContractDescription) FunctionByName(name string) (*SNativeFunctionDescription, error) { for _, f := range contract.functions { if f.Name == name { - return &f, nil + return f, nil } } return nil, fmt.Errorf("Unknown SNative function with name %s", name) } -func (contract *SNativeContractDescription) Functions() []SNativeFuncDescription { - fs := make([]SNativeFuncDescription, 0, len(contract.functions)) +func (contract *SNativeContractDescription) Functions() []*SNativeFunctionDescription { + fs := make([]*SNativeFunctionDescription, 0, len(contract.functions)) for _, f := range contract.functions { fs = append(fs, f) } return fs } -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 { +func (function *SNativeFunctionDescription) Signature() string { argTypes := make([]string, len(function.Args)) for i, arg := range function.Args { argTypes[i] = string(arg.Type) @@ -289,57 +256,25 @@ func (function *SNativeFuncDescription) Signature() string { strings.Join(argTypes, ",")) } -func (function *SNativeFuncDescription) ID() FuncID { +func (function *SNativeFunctionDescription) ID() FuncID { return firstFourBytes(sha3.Sha3([]byte(function.Signature()))) } -func (function *SNativeFuncDescription) NArgs() int { +func (function *SNativeFunctionDescription) 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{ +func arg(name string, abiType abi.Type) abi.Arg { + return abi.Arg{ Name: name, - Type: solidityType, + Type: abiType, } } -func ret(name string, solidityType SolidityType) SolidityReturn { - return SolidityReturn{ +func ret(name string, abiType abi.Type) abi.Return { + return abi.Return{ Name: name, - Type: solidityType, + Type: abiType, } } @@ -362,7 +297,7 @@ func has_base(appState AppState, caller *Account, args []byte, gas *int64) (outp } func set_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum, perm := returnThreeArgs(args) + addr, permNum, permVal := returnThreeArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { return nil, fmt.Errorf("Unknown account %X", addr) @@ -371,13 +306,13 @@ func set_base(appState AppState, caller *Account, args []byte, gas *int64) (outp if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } - permV := !perm.IsZero() + permV := !permVal.IsZero() if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { return nil, err } appState.UpdateAccount(vmAcc) dbg.Printf("snative.setBasePerm(0x%X, %b, %v)\n", addr.Postfix(20), permN, permV) - return perm.Bytes(), nil + return resultantPermBytes(vmAcc.Permissions.Base), nil } func unset_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { @@ -395,11 +330,11 @@ func unset_base(appState AppState, caller *Account, args []byte, gas *int64) (ou } appState.UpdateAccount(vmAcc) dbg.Printf("snative.unsetBasePerm(0x%X, %b)\n", addr.Postfix(20), permN) - return permNum.Bytes(), nil + return resultantPermBytes(vmAcc.Permissions.Base), nil } func set_global(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - permNum, perm := returnTwoArgs(args) + permNum, permVal := returnTwoArgs(args) vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) if vmAcc == nil { sanity.PanicSanity("cant find the global permissions account") @@ -408,13 +343,13 @@ func set_global(appState AppState, caller *Account, args []byte, gas *int64) (ou if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } - permV := !perm.IsZero() + permV := !permVal.IsZero() if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { return nil, err } appState.UpdateAccount(vmAcc) dbg.Printf("snative.setGlobalPerm(%b, %v)\n", permN, permV) - return perm.Bytes(), nil + return resultantPermBytes(vmAcc.Permissions.Base), nil } func has_role(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { @@ -475,6 +410,10 @@ func ValidPermN(n ptypes.PermFlag) bool { return true } +func resultantPermBytes(basePerms ptypes.BasePermissions) []byte { + return Uint64ToWord256(uint64(basePerms.ResultantPerms())).Bytes() +} + // CONTRACT: length has already been checked func returnTwoArgs(args []byte) (a Word256, b Word256) { copy(a[:], args[:32]) diff --git a/manager/eris-mint/evm/snative_templates.go b/manager/eris-mint/evm/snative_templates.go deleted file mode 100644 index dc47388e2dc515693743148e2d53e4c844ca1532..0000000000000000000000000000000000000000 --- a/manager/eris-mint/evm/snative_templates.go +++ /dev/null @@ -1,37 +0,0 @@ -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 index 5738d6180dd23a504a4b0edff5f0a65b97c7e42e..5677e20b73ed8ab4202086e89a863631dc801786 100644 --- a/manager/eris-mint/evm/snative_test.go +++ b/manager/eris-mint/evm/snative_test.go @@ -6,7 +6,6 @@ import ( . "github.com/eris-ltd/eris-db/word256" "github.com/stretchr/testify/assert" - "fmt" . "github.com/eris-ltd/eris-db/manager/eris-mint/evm/opcodes" ptypes "github.com/eris-ltd/eris-db/permission/types" ) @@ -20,8 +19,8 @@ Functions 744f5998 has_base(address,uint64) e8145855 has_role(address,bytes32) 28fd0194 rm_role(address,bytes32) -3f0ebb30 set_base(address,uint64,uint64) -d54a562d set_global(uint64,uint64) +c2174d8f set_base(address,uint64,bool) +85f1522b set_global(uint64,bool) 73448c99 unset_base(address,uint64) */ @@ -41,11 +40,11 @@ func TestPermissionsContractSignatures(t *testing.T) { assertFunctionIDSignature(t, contract, "28fd0194", "rm_role(address,bytes32)") - assertFunctionIDSignature(t, contract, "3f0ebb30", - "set_base(address,uint64,uint64)") + assertFunctionIDSignature(t, contract, "c2174d8f", + "set_base(address,uint64,bool)") - assertFunctionIDSignature(t, contract, "d54a562d", - "set_global(uint64,uint64)") + assertFunctionIDSignature(t, contract, "85f1522b", + "set_global(uint64,bool)") assertFunctionIDSignature(t, contract, "73448c99", "unset_base(address,uint64)") @@ -83,30 +82,10 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { assert.Equal(t, retValue, LeftPadBytes([]byte{1},32)) } -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) -} - -// This test checks that we can generate the SNative contract interface and -// prints it to stdout -func TestSNativeContractTemplate(t *testing.T) { - contract := SNativeContracts()["permissions_contract"] - solidity, err := contract.Solidity() - assert.NoError(t, err) - fmt.Println(solidity) -} - // // Helpers // -func assertFunctionIDSignature(t *testing.T, contract SNativeContractDescription, +func assertFunctionIDSignature(t *testing.T, contract *SNativeContractDescription, funcIDHex string, expectedSignature string) { function, err := contract.FunctionByID(funcIDFromHex(t, funcIDHex)) assert.NoError(t, err, diff --git a/permission/types/permissions.go b/permission/types/permissions.go index 15706c9f8ca2a2922610522a4075b3f128693465..bd6cd9ad24e6d474cb97b4291368f0ab4ab5876c 100644 --- a/permission/types/permissions.go +++ b/permission/types/permissions.go @@ -112,6 +112,12 @@ func (p *BasePermissions) IsSet(ty PermFlag) bool { return p.SetBit&ty > 0 } +// Returns the Perms PermFlag masked with SetBit bit field to give the resultant +// permissions enabled by this BasePermissions +func (p *BasePermissions) ResultantPerms() PermFlag { + return p.Perms & p.SetBit +} + func (p BasePermissions) String() string { return fmt.Sprintf("Base: %b; Set: %b", p.Perms, p.SetBit) } diff --git a/util/snatives/main.go b/util/snatives/cmd/main.go similarity index 71% rename from util/snatives/main.go rename to util/snatives/cmd/main.go index e15d26ffca708a58e9744235e8cc02ae25b83d1c..5b1f6b49938e17bb27e986c7dc8aef6f20d8f4da 100644 --- a/util/snatives/main.go +++ b/util/snatives/cmd/main.go @@ -4,12 +4,13 @@ import ( "fmt" "github.com/eris-ltd/eris-db/manager/eris-mint/evm" + "github.com/eris-ltd/eris-db/util/snatives/templates" ) // Dump SNative contracts func main() { for _, contract := range vm.SNativeContracts() { - solidity, err := contract.Solidity() + solidity, err := templates.NewSolidityContract(contract).Solidity() if err != nil { fmt.Printf("Error generating solidity for contract %s: %s\n", contract.Name, err) diff --git a/util/snatives/templates/indent_writer.go b/util/snatives/templates/indent_writer.go new file mode 100644 index 0000000000000000000000000000000000000000..d553f435fca31f3ff15ecdc9c4f846be2309e446 --- /dev/null +++ b/util/snatives/templates/indent_writer.go @@ -0,0 +1,47 @@ +package templates + +import "io" + +const newLine = byte('\n') + +type indentWriter struct { + writer io.Writer + indentLevel uint + indentBytes []byte + indent bool +} + +var _ io.Writer = (*indentWriter)(nil) + +// indentWriter indents all lines written to it with a specified indent string +// indented the specified number of indents +func NewIndentWriter(indentLevel uint, indentString string, + writer io.Writer) *indentWriter { + return &indentWriter{ + writer: writer, + indentLevel: indentLevel, + indentBytes: []byte(indentString), + indent: true, + } +} + +func (iw *indentWriter) Write(p []byte) (int, error) { + bs := make([]byte, 0, len(p)) + for _, b := range p { + if iw.indent { + for i := uint(0); i < iw.indentLevel; i++ { + bs = append(bs, iw.indentBytes...) + } + iw.indent = false + } + if b == newLine { + iw.indent = true + } + bs = append(bs, b) + } + return iw.writer.Write(bs) +} + +func (iw *indentWriter) SetIndent(level uint) { + iw.indentLevel = level +} diff --git a/util/snatives/templates/solidity_templates.go b/util/snatives/templates/solidity_templates.go new file mode 100644 index 0000000000000000000000000000000000000000..f98f15e5b182848d0e3d1f7d9247d3768c13668e --- /dev/null +++ b/util/snatives/templates/solidity_templates.go @@ -0,0 +1,125 @@ +package templates + +import ( + "bytes" + "fmt" + "strings" + "text/template" + + "github.com/eris-ltd/eris-db/manager/eris-mint/evm" +) + +const contractTemplateText = `/** +[[.Comment]] +*/ +contract [[.Name]] {[[range .Functions]] +[[.SolidityIndent 1]] +[[end]]} +` +const functionTemplateText = `/** +[[.Comment]] +*/ +function [[.Name]]([[.ArgList]]) constant returns ([[.Return.Type]] [[.Return.Name]]);` + +const indentString = "\t" + +var contractTemplate *template.Template +var functionTemplate *template.Template + +func init() { + var err error + functionTemplate, err = template.New("SolidityFunctionTemplate"). + Delims("[[", "]]"). + Parse(functionTemplateText) + if err != nil { + panic(fmt.Errorf("Couldn't parse SNative function template: %s", err)) + } + contractTemplate, err = template.New("SolidityContractTemplate"). + Delims("[[", "]]"). + Parse(contractTemplateText) + if err != nil { + panic(fmt.Errorf("Couldn't parse SNative contract template: %s", err)) + } +} + +type solidityContract struct { + *vm.SNativeContractDescription +} + +type solidityFunction struct { + *vm.SNativeFunctionDescription +} + +// Create a templated solidityContract from an SNative contract description +func NewSolidityContract(contract *vm.SNativeContractDescription) *solidityContract { + return &solidityContract{contract} +} + +// Generate Solidity code for this SNative contract +func (contract *solidityContract) Solidity() (string, error) { + buf := new(bytes.Buffer) + err := contractTemplate.Execute(buf, contract) + if err != nil { + return "", err + } + return buf.String(), nil +} + +func (contract *solidityContract) Functions() []*solidityFunction { + functions := contract.SNativeContractDescription.Functions() + solidityFunctions := make([]*solidityFunction, len(functions)) + for i, function := range functions { + solidityFunctions[i] = NewSolidityFunction(function) + } + return solidityFunctions +} + +// Create a templated solidityFunction from an SNative function description +func NewSolidityFunction(function *vm.SNativeFunctionDescription) *solidityFunction { + return &solidityFunction{function} +} + +func (function *solidityFunction) ArgList() 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 *solidityFunction) Comment() string { + return comment(function.SNativeFunctionDescription.Comment) +} + +func (function *solidityFunction) SolidityIndent(indentLevel uint) (string, error) { + return function.solidity(indentLevel) +} + +func (function *solidityFunction) Solidity() (string, error) { + return function.solidity(0) +} + +func (function *solidityFunction) solidity(indentLevel uint) (string, error) { + buf := new(bytes.Buffer) + iw := NewIndentWriter(indentLevel, indentString, buf) + err := functionTemplate.Execute(iw, function) + if err != nil { + return "", err + } + return buf.String(), nil +} + +func (contract *solidityContract) Comment() string { + return comment(contract.SNativeContractDescription.Comment) +} + +func comment(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") +} diff --git a/util/snatives/templates/solidity_templates_test.go b/util/snatives/templates/solidity_templates_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ffc92b026a5c9ec034814c108c020a3db80ec370 --- /dev/null +++ b/util/snatives/templates/solidity_templates_test.go @@ -0,0 +1,30 @@ +package templates + +import ( + "testing" + "github.com/stretchr/testify/assert" + "fmt" + "github.com/eris-ltd/eris-db/manager/eris-mint/evm" +) + +func TestSNativeFuncTemplate(t *testing.T) { + contract := vm.SNativeContracts()["permissions_contract"] + function, err := contract.FunctionByName("rm_role") + if err != nil { + t.Fatal("Couldn't get function") + } + solidityFunction := NewSolidityFunction(function) + solidity, err := solidityFunction.Solidity() + assert.NoError(t, err) + fmt.Println(solidity) +} + +// This test checks that we can generate the SNative contract interface and +// prints it to stdout +func TestSNativeContractTemplate(t *testing.T) { + contract := vm.SNativeContracts()["permissions_contract"] + solidityContract := NewSolidityContract(contract) + solidity, err := solidityContract.Solidity() + assert.NoError(t, err) + fmt.Println(solidity) +} \ No newline at end of file