From 4ecc033f3de343e60cdbc82df11e6723931fc502 Mon Sep 17 00:00:00 2001 From: Silas Davis <silas@erisindustries.com> Date: Thu, 23 Feb 2017 16:10:18 +0000 Subject: [PATCH] Use sha3 has for snative addresses for future-proofing --- manager/eris-mint/evm/abi/types.go | 34 ++++-- manager/eris-mint/evm/native.go | 4 - manager/eris-mint/evm/snative.go | 107 ++++++++++-------- manager/eris-mint/evm/snative_test.go | 12 +- manager/eris-mint/state/permissions_test.go | 4 +- util/snatives/cmd/main.go | 1 + util/snatives/templates/solidity_templates.go | 10 +- 7 files changed, 102 insertions(+), 70 deletions(-) diff --git a/manager/eris-mint/evm/abi/types.go b/manager/eris-mint/evm/abi/types.go index 40c12f54..09ac0e4c 100644 --- a/manager/eris-mint/evm/abi/types.go +++ b/manager/eris-mint/evm/abi/types.go @@ -18,24 +18,36 @@ package 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 +// We use TypeName rather than Type to reserve 'Type' for a possible future +// ABI type the can hold an ABI-native type mapping +type TypeName string type Arg struct { - Name string - Type Type + Name string + TypeName TypeName } type Return struct { - Name string - Type Type + Name string + TypeName TypeName } 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" + AddressTypeName TypeName = "address" + IntTypeName TypeName = "int" + Uint64TypeName TypeName = "uint64" + Bytes32TypeName TypeName = "bytes32" + StringTypeName TypeName = "string" + BoolTypeName TypeName = "bool" +) + +const ( + FunctionSelectorLength = 4 + AddressLength = 20 +) + +type ( + Address [AddressLength]byte + FunctionSelector [FunctionSelectorLength]byte ) diff --git a/manager/eris-mint/evm/native.go b/manager/eris-mint/evm/native.go index a5740c6c..e5694745 100644 --- a/manager/eris-mint/evm/native.go +++ b/manager/eris-mint/evm/native.go @@ -54,10 +54,6 @@ 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/snative.go b/manager/eris-mint/evm/snative.go index b15d51de..73275c7b 100644 --- a/manager/eris-mint/evm/snative.go +++ b/manager/eris-mint/evm/snative.go @@ -40,7 +40,7 @@ type SNativeContractDescription struct { Comment string // Name of the SNative contract Name string - functionsByID map[FuncID]*SNativeFunctionDescription + functionsByID map[abi.FunctionSelector]*SNativeFunctionDescription functions []*SNativeFunctionDescription } @@ -59,20 +59,20 @@ type SNativeFunctionDescription struct { // Permissions required to call function PermFlag ptypes.PermFlag // Native function to which calls will be dispatched when a containing - // contract is called with a FuncID matching this NativeContract + // contract is called with a FunctionSelector matching this NativeContract F NativeContract } func registerSNativeContracts() { for _, contract := range SNativeContracts() { - registeredNativeContracts[contract.Address()] = contract.Dispatch + registeredNativeContracts[contract.AddressWord256()] = contract.Dispatch } } // Returns a map of all SNative contracts defined indexed by name func SNativeContracts() map[string]*SNativeContractDescription { - permFlagType := abi.Uint64 - roleType := abi.Bytes32 + permFlagTypeName := abi.Uint64TypeName + roleTypeName := abi.Bytes32TypeName contracts := []*SNativeContractDescription{ NewSNativeContract(` * Interface for managing Secure Native authorizations. @@ -87,10 +87,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "addRole", []abi.Arg{ - arg("_account", abi.Address), - arg("_role", roleType), + arg("_account", abi.AddressTypeName), + arg("_role", roleTypeName), }, - ret("result", abi.Bool), + ret("result", abi.BoolTypeName), ptypes.AddRole, addRole}, @@ -102,10 +102,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "removeRole", []abi.Arg{ - arg("_account", abi.Address), - arg("_role", roleType), + arg("_account", abi.AddressTypeName), + arg("_role", roleTypeName), }, - ret("result", abi.Bool), + ret("result", abi.BoolTypeName), ptypes.RmRole, removeRole}, @@ -117,10 +117,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "hasRole", []abi.Arg{ - arg("_account", abi.Address), - arg("_role", roleType), + arg("_account", abi.AddressTypeName), + arg("_role", roleTypeName), }, - ret("result", abi.Bool), + ret("result", abi.BoolTypeName), ptypes.HasRole, hasRole}, @@ -133,11 +133,11 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "setBase", []abi.Arg{ - arg("_account", abi.Address), - arg("_permission", permFlagType), - arg("_set", abi.Bool), + arg("_account", abi.AddressTypeName), + arg("_permission", permFlagTypeName), + arg("_set", abi.BoolTypeName), }, - ret("result", permFlagType), + ret("result", permFlagTypeName), ptypes.SetBase, setBase}, @@ -149,9 +149,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "unsetBase", []abi.Arg{ - arg("_account", abi.Address), - arg("_permission", permFlagType)}, - ret("result", permFlagType), + arg("_account", abi.AddressTypeName), + arg("_permission", permFlagTypeName)}, + ret("result", permFlagTypeName), ptypes.UnsetBase, unsetBase}, @@ -163,9 +163,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "hasBase", []abi.Arg{ - arg("_account", abi.Address), - arg("_permission", permFlagType)}, - ret("result", permFlagType), + arg("_account", abi.AddressTypeName), + arg("_permission", permFlagTypeName)}, + ret("result", permFlagTypeName), ptypes.HasBase, hasBase}, @@ -177,9 +177,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "setGlobal", []abi.Arg{ - arg("_permission", permFlagType), - arg("_set", abi.Bool)}, - ret("result", permFlagType), + arg("_permission", permFlagTypeName), + arg("_set", abi.BoolTypeName)}, + ret("result", permFlagTypeName), ptypes.SetGlobal, setGlobal}, ), @@ -197,7 +197,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { func NewSNativeContract(comment, name string, functions ...*SNativeFunctionDescription) *SNativeContractDescription { - functionsByID := make(map[FuncID]*SNativeFunctionDescription, len(functions)) + functionsByID := make(map[abi.FunctionSelector]*SNativeFunctionDescription, len(functions)) for _, f := range functions { fid := f.ID() otherF, ok := functionsByID[fid] @@ -220,7 +220,7 @@ func NewSNativeContract(comment, name string, // 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 { + if len(args) < abi.FunctionSelectorLength { return nil, fmt.Errorf("SNatives dispatch requires a 4-byte function "+ "identifier but arguments are only %s bytes long", len(args)) } @@ -230,7 +230,7 @@ func (contract *SNativeContractDescription) Dispatch(appState AppState, return nil, err } - remainingArgs := args[FuncIDLength:] + remainingArgs := args[abi.FunctionSelectorLength:] // check if we have permission to call this function if !HasPermission(appState, caller, function.PermFlag) { @@ -247,14 +247,27 @@ 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)) +// We define the address of an SNative contact as the first 20 bytes of the sha3 +// hash of its name +func (contract *SNativeContractDescription) Address() abi.Address { + var address abi.Address + copy(address[:], sha3.Sha3([]byte(contract.Name))[:abi.AddressLength]) + return address } -// Get function by calling identifier FuncID -func (contract *SNativeContractDescription) FunctionByID(id FuncID) (*SNativeFunctionDescription, error) { +// Get address as a byte slice +func (contract *SNativeContractDescription) AddressBytes() []byte { + address := contract.Address() + return address[:] +} + +// Get address as a left-padded Word256 +func (contract *SNativeContractDescription) AddressWord256() Word256 { + return LeftPadWord256(contract.AddressBytes()) +} + +// Get function by calling identifier FunctionSelector +func (contract *SNativeContractDescription) FunctionByID(id abi.FunctionSelector) (*SNativeFunctionDescription, error) { f, ok := contract.functionsByID[id] if !ok { return nil, @@ -286,16 +299,16 @@ func (contract *SNativeContractDescription) Functions() []*SNativeFunctionDescri // Get function signature func (function *SNativeFunctionDescription) Signature() string { - argTypes := make([]string, len(function.Args)) + argTypeNames := make([]string, len(function.Args)) for i, arg := range function.Args { - argTypes[i] = string(arg.Type) + argTypeNames[i] = string(arg.TypeName) } return fmt.Sprintf("%s(%s)", function.Name, - strings.Join(argTypes, ",")) + strings.Join(argTypeNames, ",")) } -// Get function calling identifier FuncID -func (function *SNativeFunctionDescription) ID() FuncID { +// Get function calling identifier FunctionSelector +func (function *SNativeFunctionDescription) ID() abi.FunctionSelector { return firstFourBytes(sha3.Sha3([]byte(function.Signature()))) } @@ -304,17 +317,17 @@ func (function *SNativeFunctionDescription) NArgs() int { return len(function.Args) } -func arg(name string, abiType abi.Type) abi.Arg { +func arg(name string, abiTypeName abi.TypeName) abi.Arg { return abi.Arg{ - Name: name, - Type: abiType, + Name: name, + TypeName: abiTypeName, } } -func ret(name string, abiType abi.Type) abi.Return { +func ret(name string, abiTypeName abi.TypeName) abi.Return { return abi.Return{ - Name: name, - Type: abiType, + Name: name, + TypeName: abiTypeName, } } diff --git a/manager/eris-mint/evm/snative_test.go b/manager/eris-mint/evm/snative_test.go index 200f37bb..a0722251 100644 --- a/manager/eris-mint/evm/snative_test.go +++ b/manager/eris-mint/evm/snative_test.go @@ -21,9 +21,11 @@ import ( "strings" . "github.com/eris-ltd/eris-db/manager/eris-mint/evm/opcodes" + "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" "github.com/stretchr/testify/assert" + "github.com/eris-ltd/eris-db/manager/eris-mint/evm/abi" ) // Compiling the Permissions solidity contract at @@ -92,6 +94,12 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { assert.Equal(t, retValue, LeftPadBytes([]byte{1}, 32)) } +func TestSNativeContractDescription_Address(t *testing.T) { + contract := NewSNativeContract("A comment", + "CoolButVeryLongNamedContractOfDoom") + assert.Equal(t, sha3.Sha3(([]byte)(contract.Name))[:20], contract.AddressBytes()) +} + // // Helpers // @@ -105,11 +113,11 @@ func assertFunctionIDSignature(t *testing.T, contract *SNativeContractDescriptio } } -func funcIDFromHex(t *testing.T, hexString string) FuncID { +func funcIDFromHex(t *testing.T, hexString string) abi.FunctionSelector { 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, + t.Fatalf("FunctionSelector 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 28da4926..c8497f13 100644 --- a/manager/eris-mint/state/permissions_test.go +++ b/manager/eris-mint/state/permissions_test.go @@ -1206,7 +1206,7 @@ func permNameToFuncID(name string) []byte { } func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, pF ptypes.PermFlag, data []byte) { - addr = permissionsContract.Address().Postfix(20) + addr = permissionsContract.AddressBytes() switch name { case "hasBase", "unsetBase": data = LeftPadBytes(user.Address, 32) @@ -1242,7 +1242,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 = permissionsContract.Address().Postfix(20) + addr = permissionsContract.AddressBytes() data = LeftPadBytes(user.Address, 32) data = append(data, RightPadBytes([]byte(role), 32)...) data = append(permNameToFuncID(name), data...) diff --git a/util/snatives/cmd/main.go b/util/snatives/cmd/main.go index ddf456c2..0b12ba33 100644 --- a/util/snatives/cmd/main.go +++ b/util/snatives/cmd/main.go @@ -26,6 +26,7 @@ func main() { contracts := vm.SNativeContracts() // Index of next contract i := 1 + fmt.Print("pragma solidity >=0.0.0;\n\n") for _, contract := range contracts { solidity, err := templates.NewSolidityContract(contract).Solidity() if err != nil { diff --git a/util/snatives/templates/solidity_templates.go b/util/snatives/templates/solidity_templates.go index a0c75d3d..2f7d45fe 100644 --- a/util/snatives/templates/solidity_templates.go +++ b/util/snatives/templates/solidity_templates.go @@ -25,7 +25,9 @@ import ( const contractTemplateText = `/** [[.Comment]] -* @dev These functions can be accessed as if this contract were deployed at the address [[.Address]] +* @dev These functions can be accessed as if this contract were deployed at the address [[.Address]]. +* @dev For readability you may wish to define the following constant to refer to the SNative contract: +* @dev address constant [[.Name]]ContractAddress = [[.Address]] */ contract [[.Name]] {[[range .Functions]] [[.SolidityIndent 1]] @@ -34,7 +36,7 @@ contract [[.Name]] {[[range .Functions]] const functionTemplateText = `/** [[.Comment]] */ -function [[.Name]]([[.ArgList]]) constant returns ([[.Return.Type]] [[.Return.Name]]);` +function [[.Name]]([[.ArgList]]) constant returns ([[.Return.TypeName]] [[.Return.Name]]);` // Solidity style guide recommends 4 spaces per indentation level // (see: http://solidity.readthedocs.io/en/develop/style-guide.html) @@ -74,7 +76,7 @@ func NewSolidityContract(contract *vm.SNativeContractDescription) *solidityContr func (contract *solidityContract) Address() string { return fmt.Sprintf("0x%x", - contract.SNativeContractDescription.Address().Postfix(20)) + contract.SNativeContractDescription.Address()) } // Generate Solidity code for this SNative contract @@ -104,7 +106,7 @@ func NewSolidityFunction(function *vm.SNativeFunctionDescription) *solidityFunct 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) + argList[i] = fmt.Sprintf("%s %s", arg.TypeName, arg.Name) } return strings.Join(argList, ", ") } -- GitLab