From e3b473572fcc1dd4036128bfd4c4b6e551a03974 Mon Sep 17 00:00:00 2001 From: Silas Davis Date: Thu, 23 Feb 2017 16:10:18 +0000 Subject: [PATCH 1/3] 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 40c12f545..09ac0e4c3 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 a5740c6ca..e56947458 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 b15d51de5..73275c7b5 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 200f37bbf..a0722251a 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 28da49269..c8497f133 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 ddf456c21..0b12ba333 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 a0c75d3d8..2f7d45fe3 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, ", ") } From 1dbb9063cdc5f0da4cb5741c67ab3219f05219f9 Mon Sep 17 00:00:00 2001 From: Silas Davis Date: Thu, 23 Feb 2017 16:41:49 +0000 Subject: [PATCH 2/3] use last 20 bytes of sha3 hash for address --- manager/eris-mint/evm/snative.go | 5 +++-- manager/eris-mint/evm/snative_test.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/manager/eris-mint/evm/snative.go b/manager/eris-mint/evm/snative.go index 73275c7b5..92adbf300 100644 --- a/manager/eris-mint/evm/snative.go +++ b/manager/eris-mint/evm/snative.go @@ -247,11 +247,12 @@ func (contract *SNativeContractDescription) Dispatch(appState AppState, return function.F(appState, caller, remainingArgs, gas) } -// We define the address of an SNative contact as the first 20 bytes of the sha3 +// We define the address of an SNative contact as the last 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]) + hash := sha3.Sha3([]byte(contract.Name)) + copy(address[:], hash[len(hash)-abi.AddressLength:]) return address } diff --git a/manager/eris-mint/evm/snative_test.go b/manager/eris-mint/evm/snative_test.go index a0722251a..da0572b45 100644 --- a/manager/eris-mint/evm/snative_test.go +++ b/manager/eris-mint/evm/snative_test.go @@ -97,7 +97,7 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { func TestSNativeContractDescription_Address(t *testing.T) { contract := NewSNativeContract("A comment", "CoolButVeryLongNamedContractOfDoom") - assert.Equal(t, sha3.Sha3(([]byte)(contract.Name))[:20], contract.AddressBytes()) + assert.Equal(t, sha3.Sha3(([]byte)(contract.Name))[12:], contract.AddressBytes()) } // From 87baace0f24367d837b7265043a0e33a45131e7f Mon Sep 17 00:00:00 2001 From: Silas Davis Date: Thu, 23 Feb 2017 21:11:03 +0000 Subject: [PATCH 3/3] Improve comment --- manager/eris-mint/evm/snative.go | 6 +++ util/snatives/templates/solidity_templates.go | 38 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/manager/eris-mint/evm/snative.go b/manager/eris-mint/evm/snative.go index 92adbf300..c03d2bcd7 100644 --- a/manager/eris-mint/evm/snative.go +++ b/manager/eris-mint/evm/snative.go @@ -187,6 +187,12 @@ func SNativeContracts() map[string]*SNativeContractDescription { contractMap := make(map[string]*SNativeContractDescription, len(contracts)) for _, contract := range contracts { + if _, ok := contractMap[contract.Name]; ok { + // If this happens we have a pseudo compile time error that will be caught + // on native.go init() + panic(fmt.Errorf("Duplicate contract with name %s defined. " + + "Contract names must be unique.", contract.Name)) + } contractMap[contract.Name] = contract } return contractMap diff --git a/util/snatives/templates/solidity_templates.go b/util/snatives/templates/solidity_templates.go index 2f7d45fe3..cc6cabd2d 100644 --- a/util/snatives/templates/solidity_templates.go +++ b/util/snatives/templates/solidity_templates.go @@ -25,9 +25,10 @@ import ( const contractTemplateText = `/** [[.Comment]] -* @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]] +* @dev These functions can be accessed as if this contract were deployed at a special address ([[.Address]]). +* @dev This special address is defined as the last 20 bytes of the sha3 hash of the the contract name. +* @dev To instantiate the contract use: +* @dev [[.Name]] [[.InstanceName]] = [[.Name]](address(sha3("[[.Name]]"))); */ contract [[.Name]] {[[range .Functions]] [[.SolidityIndent 1]] @@ -69,11 +70,30 @@ type solidityFunction struct { *vm.SNativeFunctionDescription } +// +// Contract +// + // Create a templated solidityContract from an SNative contract description func NewSolidityContract(contract *vm.SNativeContractDescription) *solidityContract { return &solidityContract{contract} } +func (contract *solidityContract) Comment() string { + return comment(contract.SNativeContractDescription.Comment) +} + +// Get a version of the contract name to be used for an instance of the contract +func (contract *solidityContract) InstanceName() string { + // Hopefully the contract name is UpperCamelCase. If it's not, oh well, this + // is meant to be illustrative rather than cast iron compilable + instanceName := strings.ToLower(contract.Name[:1]) + contract.Name[1:] + if instanceName == contract.Name { + return "contractInstance" + } + return instanceName +} + func (contract *solidityContract) Address() string { return fmt.Sprintf("0x%x", contract.SNativeContractDescription.Address()) @@ -98,6 +118,10 @@ func (contract *solidityContract) Functions() []*solidityFunction { return solidityFunctions } +// +// Function +// + // Create a templated solidityFunction from an SNative function description func NewSolidityFunction(function *vm.SNativeFunctionDescription) *solidityFunction { return &solidityFunction{function} @@ -133,9 +157,11 @@ func (function *solidityFunction) solidity(indentLevel uint) (string, error) { return buf.String(), nil } -func (contract *solidityContract) Comment() string { - return comment(contract.SNativeContractDescription.Comment) -} + +// +// Contract +// + func comment(comment string) string { commentLines := make([]string, 0, 5)