diff --git a/Makefile b/Makefile index 29bbd1a87..a05fe57e2 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,11 @@ hell: go build -o ${REPO}/target/hell ./util/hell/cmd/hell/main.go ./target/hell $(filter-out $@,$(MAKECMDGOALS)) +# Dumps Solidity interface contracts for SNatives +.PHONY: snatives +snatives: + @go run ./util/snatives/cmd/main.go + ### Building github.com/eris-ltd/eris-db # build all targets in github.com/eris-ltd/eris-db diff --git a/client/rpc/client.go b/client/rpc/client.go index 77ed13b52..72c2002f6 100644 --- a/client/rpc/client.go +++ b/client/rpc/client.go @@ -99,19 +99,6 @@ func Name(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addr, return tx, nil } -type PermFunc struct { - Name string - Args string -} - -var PermsFuncs = []PermFunc{ - {"set_base", "address, permission flag, value"}, - {"unset_base", "address, permission flag"}, - {"set_global", "permission flag, value"}, - {"add_role", "address, role"}, - {"rm_role", "address, role"}, -} - func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, addrS, nonceS, permFunc string, argsS []string) (*txs.PermissionsTx, error) { pub, _, nonce, err := checkCommon(nodeClient, keyClient, pubkey, addrS, "0", nonceS) if err != nil { @@ -119,13 +106,13 @@ func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, } var args ptypes.PermArgs switch permFunc { - case "set_base": + case "setBase": addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) if err != nil { return nil, err } if len(argsS) != 3 { - return nil, fmt.Errorf("set_base also takes a value (true or false)") + return nil, fmt.Errorf("setBase also takes a value (true or false)") } var value bool if argsS[2] == "true" { @@ -136,13 +123,13 @@ func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, return nil, fmt.Errorf("Unknown value %s", argsS[2]) } args = &ptypes.SetBaseArgs{addr, pF, value} - case "unset_base": + case "unsetBase": addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) if err != nil { return nil, err } args = &ptypes.UnsetBaseArgs{addr, pF} - case "set_global": + case "setGlobal": pF, err := ptypes.PermStringToFlag(argsS[0]) if err != nil { return nil, err @@ -156,13 +143,13 @@ func Permissions(nodeClient client.NodeClient, keyClient keys.KeyClient, pubkey, return nil, fmt.Errorf("Unknown value %s", argsS[1]) } args = &ptypes.SetGlobalArgs{pF, value} - case "add_role": + case "addRole": addr, err := hex.DecodeString(argsS[0]) if err != nil { return nil, err } args = &ptypes.AddRoleArgs{addr, argsS[1]} - case "rm_role": + case "removeRole": addr, err := hex.DecodeString(argsS[0]) if err != nil { return nil, err diff --git a/client/rpc/client_test.go b/client/rpc/client_test.go index 6541bd3f9..0c240a4c0 100644 --- a/client/rpc/client_test.go +++ b/client/rpc/client_test.go @@ -147,7 +147,7 @@ func testPermissions(t *testing.T, nonceString := "" _, err := Permissions(nodeClient, keyClient, publicKeyString, addressString, - nonceString, "set_base", []string{permAddressString, "root", "true"}) + nonceString, "setBase", []string{permAddressString, "root", "true"}) if err != nil { t.Logf("Error in PermissionsTx: %s", err) t.Fail() diff --git a/event/event_cache_test.go b/event/event_cache_test.go index 35ce6d7ed..041a0650d 100644 --- a/event/event_cache_test.go +++ b/event/event_cache_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/assert" ) -var mockInterval = 10 * time.Millisecond +var mockInterval = 40 * time.Millisecond type mockSub struct { subId string diff --git a/manager/eris-mint/evm/abi/types.go b/manager/eris-mint/evm/abi/types.go new file mode 100644 index 000000000..2c299e6d1 --- /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/test/fake_app_state.go b/manager/eris-mint/evm/fake_app_state.go similarity index 97% rename from manager/eris-mint/evm/test/fake_app_state.go rename to manager/eris-mint/evm/fake_app_state.go index 9b7ac96d9..d8de25035 100644 --- a/manager/eris-mint/evm/test/fake_app_state.go +++ b/manager/eris-mint/evm/fake_app_state.go @@ -3,7 +3,6 @@ package vm import ( "fmt" - . "github.com/eris-ltd/eris-db/manager/eris-mint/evm" "github.com/eris-ltd/eris-db/manager/eris-mint/evm/sha3" . "github.com/eris-ltd/eris-db/word256" ) diff --git a/manager/eris-mint/evm/test/log_event_test.go b/manager/eris-mint/evm/log_event_test.go similarity index 97% rename from manager/eris-mint/evm/test/log_event_test.go rename to manager/eris-mint/evm/log_event_test.go index 34c8ae330..e85c79fe9 100644 --- a/manager/eris-mint/evm/test/log_event_test.go +++ b/manager/eris-mint/evm/log_event_test.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - . "github.com/eris-ltd/eris-db/manager/eris-mint/evm" . "github.com/eris-ltd/eris-db/manager/eris-mint/evm/opcodes" "github.com/eris-ltd/eris-db/txs" . "github.com/eris-ltd/eris-db/word256" diff --git a/manager/eris-mint/evm/native.go b/manager/eris-mint/evm/native.go index ed4b4f378..7470ee32c 100644 --- a/manager/eris-mint/evm/native.go +++ b/manager/eris-mint/evm/native.go @@ -40,6 +40,9 @@ func registerNativeContracts() { type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) +const FuncIDLength = 4 +type FuncID [FuncIDLength]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/opcodes/opcodes.go b/manager/eris-mint/evm/opcodes/opcodes.go index b4205f2fe..bdb3218a1 100644 --- a/manager/eris-mint/evm/opcodes/opcodes.go +++ b/manager/eris-mint/evm/opcodes/opcodes.go @@ -3,6 +3,7 @@ package opcodes import ( "fmt" + "github.com/eris-ltd/eris-db/word256" "gopkg.in/fatih/set.v0" ) @@ -378,6 +379,8 @@ func Bytecode(bytelikes ...interface{}) []byte { if int64(bytes[i]) != b { panic(fmt.Sprintf("The int64 %v does not fit inside a byte", b)) } + case word256.Word256: + return Concat(bytes[:i], b[:], Bytecode(bytelikes[i+1:]...)) case []byte: // splice return Concat(bytes[:i], b, Bytecode(bytelikes[i+1:]...)) diff --git a/manager/eris-mint/evm/snative.go b/manager/eris-mint/evm/snative.go index abc45974e..83edac168 100644 --- a/manager/eris-mint/evm/snative.go +++ b/manager/eris-mint/evm/snative.go @@ -1,99 +1,313 @@ 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" + + "strings" + + "github.com/eris-ltd/eris-db/manager/eris-mint/evm/abi" ) -//------------------------------------------------------------------------------------------------ -// Registered SNative contracts +// +// SNative (from 'secure natives') are native (go) contracts that are dispatched +// based on account permissions and can access and modify an account's permissions +// -var PermissionsContract = "permissions_contract" +// Metadata for SNative contract. Acts as a call target from the EVM. Can be +// used to generate bindings in a smart contract languages. +type SNativeContractDescription struct { + // Comment describing purpose of SNative contract and reason for assembling + // the particular functions + Comment string + // Name of the SNative contract + Name string + functionsByID map[FuncID]*SNativeFunctionDescription + functions []*SNativeFunctionDescription +} -func registerSNativeContracts() { - registeredNativeContracts[LeftPadWord256([]byte(PermissionsContract))] = permissionsContract - - /* - // 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 - */ -} - -//----------------------------------------------------------------------------- -// snative are native contracts that can access and modify an account's permissions - -type SNativeFuncDescription struct { - Name string - NArgs int +// Metadata for SNative functions. Act as call targets for the EVM when +// collected into an SNativeContractDescription. Can be used to generate +// bindings in a smart contract languages. +type SNativeFunctionDescription struct { + // Comment describing function's purpose, parameters, and return value + Comment string + // Function name (used to form signature) + Name string + // Function arguments (used to form signature) + Args []abi.Arg + // Function return value + Return abi.Return + // Permissions required to call function PermFlag ptypes.PermFlag - F NativeContract -} - -/* The solidity interface used to generate the abi function ids below -contract Permissions { - function has_base(address addr, int permFlag) constant returns (bool value) {} - function set_base(address addr, int permFlag, bool value) constant returns (bool val) {} - function unset_base(address addr, int permFlag) constant returns (int pf) {} - function set_global(address addr, int permFlag, bool value) constant returns (int 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) {} -} -*/ - -// function identifiers from the solidity abi -var PermsMap = map[string]SNativeFuncDescription{ - "e8145855": SNativeFuncDescription{"has_role", 2, ptypes.HasRole, has_role}, - "180d26f2": SNativeFuncDescription{"unset_base", 2, ptypes.UnsetBase, unset_base}, - "3a3fcc59": SNativeFuncDescription{"set_global", 2, ptypes.SetGlobal, set_global}, - "3fbf7da5": SNativeFuncDescription{"add_role", 2, ptypes.AddRole, add_role}, - "9ea53314": SNativeFuncDescription{"set_base", 3, ptypes.SetBase, set_base}, - "bb37737a": SNativeFuncDescription{"has_base", 2, ptypes.HasBase, has_base}, - "28fd0194": SNativeFuncDescription{"rm_role", 2, ptypes.RmRole, rm_role}, -} - -func permissionsContract(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") - } - - // 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) + // Native function to which calls will be dispatched when a containing + // contract is called with a FuncID matching this NativeContract + F NativeContract +} + +func registerSNativeContracts() { + 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 { + permFlagType := abi.Uint64 + roleType := abi.Bytes32 + contracts := []*SNativeContractDescription{ + NewSNativeContract(` + * Interface for managing Secure Native authorizations. + * @dev This interface describes the functions exposed by the SNative permissions layer in the Monax blockchain (ErisDB). + `, + "Permissions", + &SNativeFunctionDescription{` + * @notice Adds a role to an account + * @param _account account address + * @param _role role name + * @return result whether role was added + `, + "addRole", + []abi.Arg{ + arg("_account", abi.Address), + arg("_role", roleType), + }, + ret("result", abi.Bool), + ptypes.AddRole, + addRole}, + + &SNativeFunctionDescription{` + * @notice Removes a role from an account + * @param _account account address + * @param _role role name + * @return result whether role was removed + `, + "removeRole", + []abi.Arg{ + arg("_account", abi.Address), + arg("_role", roleType), + }, + ret("result", abi.Bool), + ptypes.RmRole, + removeRole}, + + &SNativeFunctionDescription{` + * @notice Indicates whether an account has a role + * @param _account account address + * @param _role role name + * @return result whether account has role + `, + "hasRole", + []abi.Arg{ + arg("_account", abi.Address), + arg("_role", roleType), + }, + ret("result", abi.Bool), + ptypes.HasRole, + hasRole}, + + &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 effective permissions flags on the account after the call + `, + "setBase", + []abi.Arg{ + arg("_account", abi.Address), + arg("_permission", permFlagType), + arg("_set", abi.Bool), + }, + ret("result", permFlagType), + ptypes.SetBase, + setBase}, + + &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 effective permissions flags on the account after the call + `, + "unsetBase", + []abi.Arg{ + arg("_account", abi.Address), + arg("_permission", permFlagType)}, + ret("result", permFlagType), + ptypes.UnsetBase, + unsetBase}, + + &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 + `, + "hasBase", + []abi.Arg{ + arg("_account", abi.Address), + arg("_permission", permFlagType)}, + ret("result", permFlagType), + ptypes.HasBase, + hasBase}, + + &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 global permissions flags after the call + `, + "setGlobal", + []abi.Arg{ + arg("_permission", permFlagType), + arg("_set", abi.Bool)}, + ret("result", permFlagType), + ptypes.SetGlobal, + setGlobal}, + ), } + contractMap := make(map[string]*SNativeContractDescription, len(contracts)) + for _, contract := range contracts { + contractMap[contract.Name] = contract + } + return contractMap +} + +// Create a new SNative contract description object by passing a comment, name +// and a list of member functions descriptions +func NewSNativeContract(comment, name string, + functions ...*SNativeFunctionDescription) *SNativeContractDescription { + + functionsByID := make(map[FuncID]*SNativeFunctionDescription, len(functions)) + for _, f := range functions { + fid := f.ID() + otherF, ok := functionsByID[fid] + if ok { + panic(fmt.Errorf("Function with ID %x already defined: %s", fid, + otherF)) + } + functionsByID[fid] = f + } + return &SNativeContractDescription{ + Comment: comment, + Name: name, + functionsByID: functionsByID, + functions: functions, + } +} + +// This function is designed to be called from the EVM once a SNative contract +// has been selected. It is also placed in a registry by registerSNativeContracts +// So it can be looked up by SNative address +func (contract *SNativeContractDescription) Dispatch(appState AppState, + caller *Account, args []byte, gas *int64) (output []byte, err error) { + if len(args) < FuncIDLength { + 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 nil, err + } + + remainingArgs := args[FuncIDLength:] + // 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()*Word256Length { + 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?) +// 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 has_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { +// Get function by calling identifier FuncID +func (contract *SNativeContractDescription) FunctionByID(id FuncID) (*SNativeFunctionDescription, error) { + f, ok := contract.functionsByID[id] + if !ok { + return nil, + fmt.Errorf("Unknown SNative function with ID %x", id) + } + return f, nil +} + +// Get function by name +func (contract *SNativeContractDescription) FunctionByName(name string) (*SNativeFunctionDescription, error) { + for _, f := range contract.functions { + if f.Name == name { + return f, nil + } + } + return nil, fmt.Errorf("Unknown SNative function with name %s", name) +} + +// Get functions in order of declaration +func (contract *SNativeContractDescription) Functions() []*SNativeFunctionDescription { + functions := make([]*SNativeFunctionDescription, len(contract.functions)) + copy(functions, contract.functions) + return functions +} + +// +// SNative functions +// + +// Get function signature +func (function *SNativeFunctionDescription) 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, ",")) +} + +// Get function calling identifier FuncID +func (function *SNativeFunctionDescription) ID() FuncID { + return firstFourBytes(sha3.Sha3([]byte(function.Signature()))) +} + +// Get number of function arguments +func (function *SNativeFunctionDescription) NArgs() int { + return len(function.Args) +} + +func arg(name string, abiType abi.Type) abi.Arg { + return abi.Arg{ + Name: name, + Type: abiType, + } +} + +func ret(name string, abiType abi.Type) abi.Return { + return abi.Return{ + Name: name, + Type: abiType, + } +} + +// Permission function defintions + +// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?) +func hasBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { addr, permNum := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -108,8 +322,8 @@ func has_base(appState AppState, caller *Account, args []byte, gas *int64) (outp return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func set_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum, perm := returnThreeArgs(args) +func setBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + addr, permNum, permVal := returnThreeArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { return nil, fmt.Errorf("Unknown account %X", addr) @@ -118,16 +332,16 @@ 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 effectivePermBytes(vmAcc.Permissions.Base, globalPerms(appState)), nil } -func unset_base(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { +func unsetBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { addr, permNum := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -142,11 +356,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 effectivePermBytes(vmAcc.Permissions.Base, globalPerms(appState)), nil } -func set_global(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - permNum, perm := returnTwoArgs(args) +func setGlobal(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { + permNum, permVal := returnTwoArgs(args) vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) if vmAcc == nil { sanity.PanicSanity("cant find the global permissions account") @@ -155,16 +369,16 @@ 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 permBytes(vmAcc.Permissions.Base.ResultantPerms()), nil } -func has_role(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { +func hasRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { addr, role := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -176,7 +390,7 @@ func has_role(appState AppState, caller *Account, args []byte, gas *int64) (outp return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func add_role(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { +func addRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { addr, role := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -189,7 +403,7 @@ func add_role(appState AppState, caller *Account, args []byte, gas *int64) (outp return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func rm_role(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { +func removeRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { addr, role := returnTwoArgs(args) vmAcc := appState.GetAccount(addr) if vmAcc == nil { @@ -222,6 +436,26 @@ func ValidPermN(n ptypes.PermFlag) bool { return true } +// Get the global BasePermissions +func globalPerms(appState AppState) ptypes.BasePermissions { + vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) + if vmAcc == nil { + sanity.PanicSanity("cant find the global permissions account") + } + return vmAcc.Permissions.Base +} + +// Compute the effective permissions from an Account's BasePermissions by +// taking the bitwise or with the global BasePermissions resultant permissions +func effectivePermBytes(basePerms ptypes.BasePermissions, + globalPerms ptypes.BasePermissions) []byte { + return permBytes(basePerms.ResultantPerms() | globalPerms.ResultantPerms()) +} + +func permBytes(basePerms ptypes.PermFlag) []byte { + return Uint64ToWord256(uint64(basePerms)).Bytes() +} + // CONTRACT: length has already been checked func returnTwoArgs(args []byte) (a Word256, b Word256) { copy(a[:], args[:32]) @@ -243,3 +477,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_test.go b/manager/eris-mint/evm/snative_test.go new file mode 100644 index 000000000..52810efbf --- /dev/null +++ b/manager/eris-mint/evm/snative_test.go @@ -0,0 +1,135 @@ +package vm + +import ( + "encoding/hex" + "testing" + + "strings" + + . "github.com/eris-ltd/eris-db/manager/eris-mint/evm/opcodes" + ptypes "github.com/eris-ltd/eris-db/permission/types" + . "github.com/eris-ltd/eris-db/word256" + "github.com/stretchr/testify/assert" +) + +// Compiling the Permissions solidity contract at +// (generated by with 'make snatives' function) and passing to +// https://ethereum.github.io/browser-solidity (toggle details to get list) +// yields: +// Keep this updated to drive TestPermissionsContractSignatures +const compiledSigs = ` +a73f7f8a addRole(address,bytes32) +225b6574 hasBase(address,uint64) +ac4ab3fb hasRole(address,bytes32) +6853920e removeRole(address,bytes32) +dbd4a8ea setBase(address,uint64,bool) +c4bc7b70 setGlobal(uint64,bool) +b7d4dc0d unsetBase(address,uint64) +` + +func TestPermissionsContractSignatures(t *testing.T) { + contract := SNativeContracts()["Permissions"] + + nFuncs := len(contract.functions) + + sigMap := idToSignatureMap() + + assert.Len(t, sigMap, nFuncs, + "Permissions contract defines %s functions so we need %s " + + "signatures in compiledSigs", + nFuncs, nFuncs) + + for funcID, signature := range sigMap { + assertFunctionIDSignature(t, contract, funcID, signature) + } +} + +func TestSNativeContractDescription_Dispatch(t *testing.T) { + contract := SNativeContracts()["Permissions"] + state := newAppState() + caller := &Account{ + Address: addr(1, 1, 1), + } + grantee := &Account{ + Address: addr(2, 2, 2), + } + state.UpdateAccount(grantee) + + function, err := contract.FunctionByName("addRole") + if err != nil { + t.Fatalf("Could not get function: %s", err) + } + funcID := function.ID() + gas := int64(1000) + + // Should fail since we have no permissions + retValue, err := contract.Dispatch(state, caller, Bytecode(funcID[:], + grantee.Address, permFlagToWord256(ptypes.CreateAccount)), &gas) + assert.Error(t, err) + if err != nil { + assert.Contains(t, err.Error(), "does not have permission") + } + + // Grant all permissions and dispatch should success + caller.Permissions = allAccountPermissions() + retValue, err = contract.Dispatch(state, caller, Bytecode(funcID[:], + grantee.Address, permFlagToWord256(ptypes.CreateAccount)), &gas) + assert.NoError(t, err) + assert.Equal(t, retValue, LeftPadBytes([]byte{1}, 32)) +} + +// +// Helpers +// +func assertFunctionIDSignature(t *testing.T, contract *SNativeContractDescription, + funcIDHex string, expectedSignature string) { + function, err := contract.FunctionByID(funcIDFromHex(t, funcIDHex)) + assert.NoError(t, err, + "Error retrieving SNativeFunctionDescription with ID %s", funcIDHex) + if err == nil { + assert.Equal(t, expectedSignature, function.Signature()) + } +} + +func funcIDFromHex(t *testing.T, hexString string) FuncID { + 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) +} + +func permFlagToWord256(permFlag ptypes.PermFlag) Word256 { + return Uint64ToWord256(uint64(permFlag)) +} + +func addr(rightBytes ...uint8) Word256 { + return LeftPadWord256(rightBytes) +} + +func allAccountPermissions() ptypes.AccountPermissions { + return ptypes.AccountPermissions{ + Base: ptypes.BasePermissions{ + Perms: ptypes.AllPermFlags, + SetBit: ptypes.AllPermFlags, + }, + Roles: []string{}, + } +} + +// turns the solidity compiler function summary into a map to drive signature +// test +func idToSignatureMap() map[string]string { + sigMap := make(map[string]string) + lines := strings.Split(compiledSigs, "\n") + for _, line := range lines { + trimmed := strings.Trim(line, " \t") + if trimmed != "" { + idSig := strings.Split(trimmed, " ") + sigMap[idSig[0]] = idSig[1] + } + } + return sigMap +} diff --git a/manager/eris-mint/evm/test/vm_test.go b/manager/eris-mint/evm/vm_test.go similarity index 99% rename from manager/eris-mint/evm/test/vm_test.go rename to manager/eris-mint/evm/vm_test.go index 248a4377a..4911965b3 100644 --- a/manager/eris-mint/evm/test/vm_test.go +++ b/manager/eris-mint/evm/vm_test.go @@ -10,7 +10,6 @@ import ( "errors" - . "github.com/eris-ltd/eris-db/manager/eris-mint/evm" . "github.com/eris-ltd/eris-db/manager/eris-mint/evm/opcodes" ptypes "github.com/eris-ltd/eris-db/permission/types" "github.com/eris-ltd/eris-db/txs" diff --git a/manager/eris-mint/state/permissions_test.go b/manager/eris-mint/state/permissions_test.go index fd4dd4219..73d1ae4fb 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"] ) /* @@ -885,7 +885,7 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### HasBase") // HasBase - snativeAddress, pF, data := snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false) + snativeAddress, pF, data := snativePermTestInputCALL("hasBase", user[3], ptypes.Bond, false) testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { // return value should be true or false as a 32 byte array... @@ -897,10 +897,10 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### SetBase") // SetBase - snativeAddress, pF, data = snativePermTestInputCALL("set_base", user[3], ptypes.Bond, false) + snativeAddress, pF, data = snativePermTestInputCALL("setBase", user[3], ptypes.Bond, false) testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.Bond, false) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { // return value should be true or false as a 32 byte array... if !IsZeros(ret) { @@ -908,9 +908,9 @@ func TestSNativeCALL(t *testing.T) { } return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("set_base", user[3], ptypes.CreateContract, true) + snativeAddress, pF, data = snativePermTestInputCALL("setBase", user[3], ptypes.CreateContract, true) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { // return value should be true or false as a 32 byte array... if !IsZeros(ret[:31]) || ret[31] != byte(1) { @@ -921,10 +921,10 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### UnsetBase") // UnsetBase - snativeAddress, pF, data = snativePermTestInputCALL("unset_base", user[3], ptypes.CreateContract, false) + snativeAddress, pF, data = snativePermTestInputCALL("unsetBase", user[3], ptypes.CreateContract, false) testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret) { return fmt.Errorf("Expected 0. Got %X", ret) @@ -934,10 +934,10 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### SetGlobal") // SetGlobalPerm - snativeAddress, pF, data = snativePermTestInputCALL("set_global", user[3], ptypes.CreateContract, true) + snativeAddress, pF, data = snativePermTestInputCALL("setGlobal", user[3], ptypes.CreateContract, true) testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false) + snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { // return value should be true or false as a 32 byte array... if !IsZeros(ret[:31]) || ret[31] != byte(1) { @@ -948,7 +948,7 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### HasRole") // HasRole - snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "bumble") + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "bumble") testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret[:31]) || ret[31] != byte(1) { @@ -959,17 +959,17 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### AddRole") // AddRole - snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "chuck") + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret) { return fmt.Errorf("Expected 0. Got %X", ret) } return nil }) - snativeAddress, pF, data = snativeRoleTestInputCALL("add_role", user[3], "chuck") + snativeAddress, pF, data = snativeRoleTestInputCALL("addRole", user[3], "chuck") testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "chuck") + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret[:31]) || ret[31] != byte(1) { return fmt.Errorf("Expected 1. Got %X", ret) @@ -979,10 +979,10 @@ func TestSNativeCALL(t *testing.T) { fmt.Println("\n#### RmRole") // RmRole - snativeAddress, pF, data = snativeRoleTestInputCALL("rm_role", user[3], "chuck") + snativeAddress, pF, data = snativeRoleTestInputCALL("removeRole", user[3], "chuck") testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativeRoleTestInputCALL("has_role", user[3], "chuck") + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { if !IsZeros(ret) { return fmt.Errorf("Expected 0. Got %X", ret) @@ -1006,14 +1006,14 @@ func TestSNativeTx(t *testing.T) { fmt.Println("\n#### SetBase") // SetBase - snativeArgs := snativePermTestInputTx("set_base", user[3], ptypes.Bond, false) + snativeArgs := snativePermTestInputTx("setBase", user[3], ptypes.Bond, false) testSNativeTxExpectFail(t, blockCache, snativeArgs) testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) acc := blockCache.GetAccount(user[3].Address) if v, _ := acc.Permissions.Base.Get(ptypes.Bond); v { t.Fatal("expected permission to be set false") } - snativeArgs = snativePermTestInputTx("set_base", user[3], ptypes.CreateContract, true) + snativeArgs = snativePermTestInputTx("setBase", user[3], ptypes.CreateContract, true) testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) acc = blockCache.GetAccount(user[3].Address) if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v { @@ -1022,7 +1022,7 @@ func TestSNativeTx(t *testing.T) { fmt.Println("\n#### UnsetBase") // UnsetBase - snativeArgs = snativePermTestInputTx("unset_base", user[3], ptypes.CreateContract, false) + snativeArgs = snativePermTestInputTx("unsetBase", user[3], ptypes.CreateContract, false) testSNativeTxExpectFail(t, blockCache, snativeArgs) testSNativeTxExpectPass(t, blockCache, ptypes.UnsetBase, snativeArgs) acc = blockCache.GetAccount(user[3].Address) @@ -1032,7 +1032,7 @@ func TestSNativeTx(t *testing.T) { fmt.Println("\n#### SetGlobal") // SetGlobalPerm - snativeArgs = snativePermTestInputTx("set_global", user[3], ptypes.CreateContract, true) + snativeArgs = snativePermTestInputTx("setGlobal", user[3], ptypes.CreateContract, true) testSNativeTxExpectFail(t, blockCache, snativeArgs) testSNativeTxExpectPass(t, blockCache, ptypes.SetGlobal, snativeArgs) acc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress) @@ -1042,7 +1042,7 @@ func TestSNativeTx(t *testing.T) { fmt.Println("\n#### AddRole") // AddRole - snativeArgs = snativeRoleTestInputTx("add_role", user[3], "chuck") + snativeArgs = snativeRoleTestInputTx("addRole", user[3], "chuck") testSNativeTxExpectFail(t, blockCache, snativeArgs) testSNativeTxExpectPass(t, blockCache, ptypes.AddRole, snativeArgs) acc = blockCache.GetAccount(user[3].Address) @@ -1052,7 +1052,7 @@ func TestSNativeTx(t *testing.T) { fmt.Println("\n#### RmRole") // RmRole - snativeArgs = snativeRoleTestInputTx("rm_role", user[3], "chuck") + snativeArgs = snativeRoleTestInputTx("removeRole", user[3], "chuck") testSNativeTxExpectFail(t, blockCache, snativeArgs) testSNativeTxExpectPass(t, blockCache, ptypes.RmRole, snativeArgs) acc = blockCache.GetAccount(user[3].Address) @@ -1182,27 +1182,26 @@ 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": + case "hasBase", "unsetBase": data = LeftPadBytes(user.Address, 32) data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) - case "set_base": + case "setBase": data = LeftPadBytes(user.Address, 32) data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) data = append(data, boolToWord256(val).Bytes()...) - case "set_global": + case "setGlobal": data = Uint64ToWord256(uint64(perm)).Bytes() data = append(data, boolToWord256(val).Bytes()...) } @@ -1216,20 +1215,20 @@ func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.Pe func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (snativeArgs ptypes.PermArgs) { switch name { - case "has_base": + case "hasBase": snativeArgs = &ptypes.HasBaseArgs{user.Address, perm} - case "unset_base": + case "unsetBase": snativeArgs = &ptypes.UnsetBaseArgs{user.Address, perm} - case "set_base": + case "setBase": snativeArgs = &ptypes.SetBaseArgs{user.Address, perm, val} - case "set_global": + case "setGlobal": snativeArgs = &ptypes.SetGlobalArgs{perm, val} } return } 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...) @@ -1243,11 +1242,11 @@ func snativeRoleTestInputCALL(name string, user *acm.PrivAccount, role string) ( func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (snativeArgs ptypes.PermArgs) { switch name { - case "has_role": + case "hasRole": snativeArgs = &ptypes.HasRoleArgs{user.Address, role} - case "add_role": + case "addRole": snativeArgs = &ptypes.AddRoleArgs{user.Address, role} - case "rm_role": + case "removeRole": snativeArgs = &ptypes.RmRoleArgs{user.Address, role} } return diff --git a/manager/eris-mint/state/tx_cache.go b/manager/eris-mint/state/tx_cache.go index 87ccd9133..dc322d668 100644 --- a/manager/eris-mint/state/tx_cache.go +++ b/manager/eris-mint/state/tx_cache.go @@ -19,6 +19,8 @@ type TxCache struct { storages map[Tuple256]Word256 } +var _ vm.AppState = &TxCache{} + func NewTxCache(backend *BlockCache) *TxCache { return &TxCache{ backend: backend, diff --git a/permission/types/permissions.go b/permission/types/permissions.go index d20908a0c..c6ebd5847 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) } @@ -198,19 +204,19 @@ func PermFlagToString(pf PermFlag) (perm string) { case Name: perm = "name" case HasBase: - perm = "has_base" + perm = "hasBase" case SetBase: - perm = "set_base" + perm = "setBase" case UnsetBase: - perm = "unset_base" + perm = "unsetBase" case SetGlobal: - perm = "set_global" + perm = "setGlobal" case HasRole: - perm = "has_role" + perm = "hasRole" case AddRole: - perm = "add_role" + perm = "addRole" case RmRole: - perm = "rm_role" + perm = "removeRole" default: perm = "#-UNKNOWN-#" } @@ -233,19 +239,19 @@ func PermStringToFlag(perm string) (pf PermFlag, err error) { pf = Bond case "name": pf = Name - case "has_base": + case "hasBase": pf = HasBase - case "set_base": + case "setBase": pf = SetBase - case "unset_base": + case "unsetBase": pf = UnsetBase - case "set_global": + case "setGlobal": pf = SetGlobal - case "has_role": + case "hasRole": pf = HasRole - case "add_role": + case "addRole": pf = AddRole - case "rm_role": + case "removeRole": pf = RmRole default: err = fmt.Errorf("Unknown permission %s", perm) diff --git a/util/snatives/cmd/main.go b/util/snatives/cmd/main.go new file mode 100644 index 000000000..a04bca2c6 --- /dev/null +++ b/util/snatives/cmd/main.go @@ -0,0 +1,29 @@ +package main + +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() { + contracts := vm.SNativeContracts() + // Index of next contract + i := 1 + for _, contract := range contracts { + solidity, err := templates.NewSolidityContract(contract).Solidity() + if err != nil { + fmt.Printf("Error generating solidity for contract %s: %s\n", + contract.Name, err) + } + fmt.Println(solidity) + if i < len(contracts) { + // Two new lines between contracts as per Solidity style guide + // (the template gives us 1 trailing new line) + fmt.Println() + } + i++ + } +} diff --git a/util/snatives/templates/indent_writer.go b/util/snatives/templates/indent_writer.go new file mode 100644 index 000000000..d553f435f --- /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 000000000..f4d87f16b --- /dev/null +++ b/util/snatives/templates/solidity_templates.go @@ -0,0 +1,133 @@ +package templates + +import ( + "bytes" + "fmt" + "strings" + "text/template" + + "github.com/eris-ltd/eris-db/manager/eris-mint/evm" +) + +const contractTemplateText = `/** +[[.Comment]] +* @dev These functions can be accessed as if this contract were deployed at the address [[.Address]] +*/ +contract [[.Name]] {[[range .Functions]] +[[.SolidityIndent 1]] +[[end]]} +` +const functionTemplateText = `/** +[[.Comment]] +*/ +function [[.Name]]([[.ArgList]]) constant returns ([[.Return.Type]] [[.Return.Name]]);` + +// Solidity style guide recommends 4 spaces per indentation level +// (see: http://solidity.readthedocs.io/en/develop/style-guide.html) +const indentString = " " + +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} +} + +func (contract *solidityContract) Address() string { + return fmt.Sprintf("0x%x", + contract.SNativeContractDescription.Address().Postfix(20)) +} + +// 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 000000000..d0a68e961 --- /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"] + function, err := contract.FunctionByName("removeRole") + 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"] + solidityContract := NewSolidityContract(contract) + solidity, err := solidityContract.Solidity() + assert.NoError(t, err) + fmt.Println(solidity) +} \ No newline at end of file diff --git a/word256/word.go b/word256/word.go index 2537830eb..9dfe7a673 100644 --- a/word256/word.go +++ b/word256/word.go @@ -30,7 +30,8 @@ var ( One256 = Word256{1} ) -type Word256 [32]byte +const Word256Length = 32 +type Word256 [Word256Length]byte func (w Word256) String() string { return string(w[:]) } func (w Word256) TrimmedString() string { return TrimmedString(w.Bytes()) }