Browse Source

SNativeTx -> PermissionTx, consolidate permissions and native contracts

pull/119/head
Ethan Buchman 9 years ago
parent
commit
31b9d8ee27
11 changed files with 233 additions and 305 deletions
  1. +55
    -25
      permission/types/permissions.go
  2. +20
    -93
      permission/types/snatives.go
  3. +12
    -19
      state/execution.go
  4. +38
    -38
      state/permissions_test.go
  5. +2
    -2
      types/events.go
  6. +11
    -12
      types/tx.go
  7. +5
    -5
      types/tx_test.go
  8. +8
    -8
      types/tx_utils.go
  9. +15
    -10
      vm/native.go
  10. +58
    -62
      vm/snative.go
  11. +9
    -31
      vm/vm.go

+ 55
- 25
permission/types/permissions.go View File

@ -17,19 +17,29 @@ type PermFlag uint64
// Base permission references are like unix (the index is already bit shifted)
const (
Root PermFlag = 1 << iota // 1
Send // 2
Call // 4
CreateContract // 8
CreateAccount // 16
Bond // 32
Name // 64
NumBasePermissions uint = 7 // NOTE Adjust this too.
TopBasePermFlag PermFlag = 1 << (NumBasePermissions - 1)
AllBasePermFlags PermFlag = TopBasePermFlag | (TopBasePermFlag - 1)
AllPermFlags PermFlag = AllBasePermFlags | AllSNativePermFlags
DefaultBasePermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name
// chain permissions
Root PermFlag = 1 << iota // 1
Send // 2
Call // 4
CreateContract // 8
CreateAccount // 16
Bond // 32
Name // 64
// moderator permissions
HasBase
SetBase
UnsetBase
SetGlobal
HasRole
AddRole
RmRole
NumPermissions uint = 14 // NOTE Adjust this too. We can support upto 64
TopPermFlag PermFlag = 1 << (NumPermissions - 1)
AllPermFlags PermFlag = TopPermFlag | (TopPermFlag - 1)
DefaultPermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name | HasBase | HasRole
)
var (
@ -39,7 +49,7 @@ var (
}
DefaultAccountPermissions = AccountPermissions{
Base: BasePermissions{
Perms: DefaultBasePermFlags,
Perms: DefaultPermFlags,
SetBit: AllPermFlags,
},
Roles: []string{},
@ -154,16 +164,8 @@ func (aP *AccountPermissions) RmRole(role string) bool {
//--------------------------------------------------------------------------------
// string utilities
// CONTRACT: PermFlagToString functions assume the permFlag is valid, else return "#-UNKNOWN-#"
func PermFlagToString(pf PermFlag) string {
if pf < FirstSNativePermFlag {
return BasePermFlagToString(pf)
} else {
return SNativePermFlagToString(pf)
}
}
func BasePermFlagToString(pf PermFlag) (perm string) {
// PermFlagToString assumes the permFlag is valid, else returns "#-UNKNOWN-#"
func PermFlagToString(pf PermFlag) (perm string) {
switch pf {
case Root:
perm = "root"
@ -179,13 +181,27 @@ func BasePermFlagToString(pf PermFlag) (perm string) {
perm = "bond"
case Name:
perm = "name"
case HasBase:
perm = "has_base"
case SetBase:
perm = "set_base"
case UnsetBase:
perm = "unset_base"
case SetGlobal:
perm = "set_global"
case HasRole:
perm = "has_role"
case AddRole:
perm = "add_role"
case RmRole:
perm = "rm_role"
default:
perm = "#-UNKNOWN-#"
}
return
}
func BasePermStringToFlag(perm string) (pf PermFlag, err error) {
func PermStringToFlag(perm string) (pf PermFlag, err error) {
switch perm {
case "root":
pf = Root
@ -201,6 +217,20 @@ func BasePermStringToFlag(perm string) (pf PermFlag, err error) {
pf = Bond
case "name":
pf = Name
case "has_base":
pf = HasBase
case "set_base":
pf = SetBase
case "unset_base":
pf = UnsetBase
case "set_global":
pf = SetGlobal
case "has_role":
pf = HasRole
case "add_role":
pf = AddRole
case "rm_role":
pf = RmRole
default:
err = fmt.Errorf("Unknown permission %s", perm)
}


+ 20
- 93
permission/types/snatives.go View File

@ -1,66 +1,40 @@
package types
import (
"fmt"
"github.com/tendermint/tendermint/binary"
)
//---------------------------------------------------------------------------------------------------
// snative permissions
const (
// first 32 bits of BasePermission are for chain, second 32 are for snative
FirstSNativePermFlag PermFlag = 1 << 32
)
// we need to reset iota with new const block
const (
// each snative has an associated permission flag
HasBase PermFlag = FirstSNativePermFlag << iota
SetBase
UnsetBase
SetGlobal
HasRole
AddRole
RmRole
NumSNativePermissions uint = 7 // NOTE adjust this too
TopSNativePermFlag PermFlag = FirstSNativePermFlag << (NumSNativePermissions - 1)
AllSNativePermFlags PermFlag = (TopSNativePermFlag | (TopSNativePermFlag - 1)) &^ (FirstSNativePermFlag - 1)
)
// PermissionsTx.PermArgs interface and argument encoding
//---------------------------------------------------------------------------------------------------
// snative tx interface and argument encoding
// SNativesArgs are a registered interface in the SNativeTx,
// so binary handles the arguments and each snative gets a type-byte
// Arguments are a registered interface in the PermissionsTx,
// so binary handles the arguments and each permission function gets a type-byte
// PermFlag() maps the type-byte to the permission
// The account sending the SNativeTx must have this PermFlag set
type SNativeArgs interface {
// The account sending the PermissionsTx must have this PermFlag set
type PermArgs interface {
PermFlag() PermFlag
}
const (
SNativeArgsTypeHasBase = byte(0x01)
SNativeArgsTypeSetBase = byte(0x02)
SNativeArgsTypeUnsetBase = byte(0x03)
SNativeArgsTypeSetGlobal = byte(0x04)
SNativeArgsTypeHasRole = byte(0x05)
SNativeArgsTypeAddRole = byte(0x06)
SNativeArgsTypeRmRole = byte(0x07)
PermArgsTypeHasBase = byte(0x01)
PermArgsTypeSetBase = byte(0x02)
PermArgsTypeUnsetBase = byte(0x03)
PermArgsTypeSetGlobal = byte(0x04)
PermArgsTypeHasRole = byte(0x05)
PermArgsTypeAddRole = byte(0x06)
PermArgsTypeRmRole = byte(0x07)
)
// for binary.readReflect
var _ = binary.RegisterInterface(
struct{ SNativeArgs }{},
binary.ConcreteType{&HasBaseArgs{}, SNativeArgsTypeHasBase},
binary.ConcreteType{&SetBaseArgs{}, SNativeArgsTypeSetBase},
binary.ConcreteType{&UnsetBaseArgs{}, SNativeArgsTypeUnsetBase},
binary.ConcreteType{&SetGlobalArgs{}, SNativeArgsTypeSetGlobal},
binary.ConcreteType{&HasRoleArgs{}, SNativeArgsTypeHasRole},
binary.ConcreteType{&AddRoleArgs{}, SNativeArgsTypeAddRole},
binary.ConcreteType{&RmRoleArgs{}, SNativeArgsTypeRmRole},
struct{ PermArgs }{},
binary.ConcreteType{&HasBaseArgs{}, PermArgsTypeHasBase},
binary.ConcreteType{&SetBaseArgs{}, PermArgsTypeSetBase},
binary.ConcreteType{&UnsetBaseArgs{}, PermArgsTypeUnsetBase},
binary.ConcreteType{&SetGlobalArgs{}, PermArgsTypeSetGlobal},
binary.ConcreteType{&HasRoleArgs{}, PermArgsTypeHasRole},
binary.ConcreteType{&AddRoleArgs{}, PermArgsTypeAddRole},
binary.ConcreteType{&RmRoleArgs{}, PermArgsTypeRmRole},
)
type HasBaseArgs struct {
@ -126,50 +100,3 @@ type RmRoleArgs struct {
func (*RmRoleArgs) PermFlag() PermFlag {
return RmRole
}
//------------------------------------------------------------
// string utilities
func SNativePermFlagToString(pF PermFlag) (perm string) {
switch pF {
case HasBase:
perm = "HasBase"
case SetBase:
perm = "SetBase"
case UnsetBase:
perm = "UnsetBase"
case SetGlobal:
perm = "SetGlobal"
case HasRole:
perm = "HasRole"
case AddRole:
perm = "AddRole"
case RmRole:
perm = "RmRole"
default:
perm = "#-UNKNOWN-#"
}
return
}
func SNativeStringToPermFlag(perm string) (pF PermFlag, err error) {
switch perm {
case "HasBase":
pF = HasBase
case "SetBase":
pF = SetBase
case "UnsetBase":
pF = UnsetBase
case "SetGlobal":
pF = SetGlobal
case "HasRole":
pF = HasRole
case "AddRole":
pF = AddRole
case "RmRole":
pF = RmRole
default:
err = fmt.Errorf("Unknown permission %s", perm)
}
return
}

+ 12
- 19
state/execution.go View File

@ -392,7 +392,6 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
// this may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee
// it may also be nil if its an snative (not a "real" account)
outAcc = blockCache.GetAccount(tx.Address)
}
@ -423,10 +422,9 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
if !createAccount {
if outAcc == nil || len(outAcc.Code) == 0 {
// check if its an snative
// TODO: should we restrict from calling natives too?
if _, ok := vm.RegisteredSNativeContracts[LeftPadWord256(tx.Address)]; ok {
return fmt.Errorf("SNatives can not be called using CallTx. Either use a contract or a SNativeTx")
// check if its a native contract
if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) {
return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)")
}
// if you call an account that doesn't exist
@ -794,7 +792,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
}
return nil
case *types.SNativeTx:
case *types.PermissionsTx:
var inAcc *acm.Account
// Validate input
@ -804,10 +802,10 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
return types.ErrTxInvalidAddress
}
permFlag := tx.SNative.PermFlag()
permFlag := tx.PermArgs.PermFlag()
// check permission
if !hasSNativePermission(blockCache, inAcc, permFlag) {
return fmt.Errorf("Account %X does not have permission to call snative %s (%b)", tx.Input.Address, ptypes.SNativePermFlagToString(permFlag), permFlag)
if !HasPermission(blockCache, inAcc, permFlag) {
return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag)
}
// pubKey should be present in either "inAcc" or "tx.Input"
@ -824,10 +822,10 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
value := tx.Input.Amount
log.Debug("New SNativeTx", "snative", ptypes.SNativePermFlagToString(permFlag), "args", tx.SNative)
log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs)
var permAcc *acm.Account
switch args := tx.SNative.(type) {
switch args := tx.PermArgs.(type) {
case *ptypes.HasBaseArgs:
// this one doesn't make sense from txs
return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain")
@ -863,7 +861,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address)
}
default:
PanicSanity(Fmt("invalid snative: %s", ptypes.SNativePermFlagToString(permFlag)))
PanicSanity(Fmt("invalid permission function: %s", ptypes.PermFlagToString(permFlag)))
}
// TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
@ -881,7 +879,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
if evc != nil {
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), tx)
evc.FireEvent(types.EventStringSNative(ptypes.SNativePermFlagToString(permFlag)), tx)
evc.FireEvent(types.EventStringPermissions(ptypes.PermFlagToString(permFlag)), tx)
}
return nil
@ -897,8 +895,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
// Get permission on an account or fall back to global value
func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag) bool {
if (perm > ptypes.AllBasePermFlags && perm < ptypes.FirstSNativePermFlag) ||
(perm > ptypes.AllSNativePermFlags) {
if perm > ptypes.AllPermFlags {
PanicSanity("Checking an unknown permission in state should never happen")
}
@ -969,7 +966,3 @@ func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account)
}
return true
}
func hasSNativePermission(state AccountGetter, acc *acm.Account, permFlag ptypes.PermFlag) bool {
return HasPermission(state, acc, permFlag)
}

+ 38
- 38
state/permissions_test.go View File

@ -867,7 +867,7 @@ func TestSNativeCALL(t *testing.T) {
fmt.Println("\n#### HasBase")
// HasBase
snativeAddress, data := snativePermTestInputCALL("HasBase", user[3], ptypes.Bond, false)
snativeAddress, data := snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
@ -879,10 +879,10 @@ func TestSNativeCALL(t *testing.T) {
fmt.Println("\n#### SetBase")
// SetBase
snativeAddress, data = snativePermTestInputCALL("SetBase", user[3], ptypes.Bond, false)
snativeAddress, data = snativePermTestInputCALL("set_base", user[3], ptypes.Bond, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInputCALL("HasBase", user[3], ptypes.Bond, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.Bond, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
// return value should be true or false as a 32 byte array...
if !IsZeros(ret) {
@ -890,9 +890,9 @@ func TestSNativeCALL(t *testing.T) {
}
return nil
})
snativeAddress, data = snativePermTestInputCALL("SetBase", user[3], ptypes.CreateContract, true)
snativeAddress, data = snativePermTestInputCALL("set_base", user[3], ptypes.CreateContract, true)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInputCALL("HasBase", user[3], ptypes.CreateContract, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, 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) {
@ -903,10 +903,10 @@ func TestSNativeCALL(t *testing.T) {
fmt.Println("\n#### UnsetBase")
// UnsetBase
snativeAddress, data = snativePermTestInputCALL("UnsetBase", user[3], ptypes.CreateContract, false)
snativeAddress, data = snativePermTestInputCALL("unset_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInputCALL("HasBase", user[3], ptypes.CreateContract, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
@ -916,10 +916,10 @@ func TestSNativeCALL(t *testing.T) {
fmt.Println("\n#### SetGlobal")
// SetGlobalPerm
snativeAddress, data = snativePermTestInputCALL("SetGlobal", user[3], ptypes.CreateContract, true)
snativeAddress, data = snativePermTestInputCALL("set_global", user[3], ptypes.CreateContract, true)
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativePermTestInputCALL("HasBase", user[3], ptypes.CreateContract, false)
snativeAddress, data = snativePermTestInputCALL("has_base", user[3], ptypes.CreateContract, false)
testSNativeCALLExpectPass(t, blockCache, doug, 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) {
@ -930,7 +930,7 @@ func TestSNativeCALL(t *testing.T) {
fmt.Println("\n#### HasRole")
// HasRole
snativeAddress, data = snativeRoleTestInputCALL("HasRole", user[3], "bumble")
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "bumble")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
@ -941,17 +941,17 @@ func TestSNativeCALL(t *testing.T) {
fmt.Println("\n#### AddRole")
// AddRole
snativeAddress, data = snativeRoleTestInputCALL("HasRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
}
return nil
})
snativeAddress, data = snativeRoleTestInputCALL("AddRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("add_role", user[3], "chuck")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativeRoleTestInputCALL("HasRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret[:31]) || ret[31] != byte(1) {
return fmt.Errorf("Expected 1. Got %X", ret)
@ -961,10 +961,10 @@ func TestSNativeCALL(t *testing.T) {
fmt.Println("\n#### RmRole")
// RmRole
snativeAddress, data = snativeRoleTestInputCALL("RmRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("rm_role", user[3], "chuck")
testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data)
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error { return nil })
snativeAddress, data = snativeRoleTestInputCALL("HasRole", user[3], "chuck")
snativeAddress, data = snativeRoleTestInputCALL("has_role", user[3], "chuck")
testSNativeCALLExpectPass(t, blockCache, doug, snativeAddress, data, func(ret []byte) error {
if !IsZeros(ret) {
return fmt.Errorf("Expected 0. Got %X", ret)
@ -988,14 +988,14 @@ func TestSNativeTx(t *testing.T) {
fmt.Println("\n#### SetBase")
// SetBase
snativeArgs := snativePermTestInputTx("SetBase", user[3], ptypes.Bond, false)
snativeArgs := snativePermTestInputTx("set_base", 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("SetBase", user[3], ptypes.CreateContract, true)
snativeArgs = snativePermTestInputTx("set_base", 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 {
@ -1004,7 +1004,7 @@ func TestSNativeTx(t *testing.T) {
fmt.Println("\n#### UnsetBase")
// UnsetBase
snativeArgs = snativePermTestInputTx("UnsetBase", user[3], ptypes.CreateContract, false)
snativeArgs = snativePermTestInputTx("unset_base", user[3], ptypes.CreateContract, false)
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.UnsetBase, snativeArgs)
acc = blockCache.GetAccount(user[3].Address)
@ -1014,7 +1014,7 @@ func TestSNativeTx(t *testing.T) {
fmt.Println("\n#### SetGlobal")
// SetGlobalPerm
snativeArgs = snativePermTestInputTx("SetGlobal", user[3], ptypes.CreateContract, true)
snativeArgs = snativePermTestInputTx("set_global", user[3], ptypes.CreateContract, true)
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.SetGlobal, snativeArgs)
acc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress)
@ -1024,7 +1024,7 @@ func TestSNativeTx(t *testing.T) {
fmt.Println("\n#### AddRole")
// AddRole
snativeArgs = snativeRoleTestInputTx("AddRole", user[3], "chuck")
snativeArgs = snativeRoleTestInputTx("add_role", user[3], "chuck")
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.AddRole, snativeArgs)
acc = blockCache.GetAccount(user[3].Address)
@ -1034,7 +1034,7 @@ func TestSNativeTx(t *testing.T) {
fmt.Println("\n#### RmRole")
// RmRole
snativeArgs = snativeRoleTestInputTx("RmRole", user[3], "chuck")
snativeArgs = snativeRoleTestInputTx("rm_role", user[3], "chuck")
testSNativeTxExpectFail(t, blockCache, snativeArgs)
testSNativeTxExpectPass(t, blockCache, ptypes.RmRole, snativeArgs)
acc = blockCache.GetAccount(user[3].Address)
@ -1096,7 +1096,7 @@ func testSNativeCALLExpectPass(t *testing.T, blockCache *BlockCache, doug *acm.A
func testSNativeCALL(t *testing.T, expectPass bool, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte, f func([]byte) error) {
if expectPass {
perm, err := ptypes.SNativeStringToPermFlag(TrimmedString(snativeAddress))
perm, err := ptypes.PermStringToFlag(TrimmedString(snativeAddress))
if err != nil {
t.Fatal(err)
}
@ -1130,21 +1130,21 @@ func testSNativeCALL(t *testing.T, expectPass bool, blockCache *BlockCache, doug
}
}
func testSNativeTxExpectFail(t *testing.T, blockCache *BlockCache, snativeArgs ptypes.SNativeArgs) {
func testSNativeTxExpectFail(t *testing.T, blockCache *BlockCache, snativeArgs ptypes.PermArgs) {
testSNativeTx(t, false, blockCache, 0, snativeArgs)
}
func testSNativeTxExpectPass(t *testing.T, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.SNativeArgs) {
func testSNativeTxExpectPass(t *testing.T, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) {
testSNativeTx(t, true, blockCache, perm, snativeArgs)
}
func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.SNativeArgs) {
func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) {
if expectPass {
acc := blockCache.GetAccount(user[0].Address)
acc.Permissions.Base.Set(perm, true)
blockCache.UpdateAccount(acc)
}
tx, _ := types.NewSNativeTx(blockCache, user[0].PubKey, snativeArgs)
tx, _ := types.NewPermissionsTx(blockCache, user[0].PubKey, snativeArgs)
tx.Sign(chainID, user[0])
err := ExecTx(blockCache, tx, true, nil)
if expectPass {
@ -1171,29 +1171,29 @@ func boolToWord256(v bool) Word256 {
func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, data []byte) {
addr = LeftPadWord256([]byte(name)).Postfix(20)
switch name {
case "HasBase", "UnsetBase":
case "has_base", "unset_base":
data = LeftPadBytes(user.Address, 32)
data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...)
case "SetBase":
case "set_base":
data = LeftPadBytes(user.Address, 32)
data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...)
data = append(data, boolToWord256(val).Bytes()...)
case "SetGlobal":
case "set_global":
data = Uint64ToWord256(uint64(perm)).Bytes()
data = append(data, boolToWord256(val).Bytes()...)
}
return
}
func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (snativeArgs ptypes.SNativeArgs) {
func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (snativeArgs ptypes.PermArgs) {
switch name {
case "HasBase":
case "has_base":
snativeArgs = &ptypes.HasBaseArgs{user.Address, perm}
case "UnsetBase":
case "unset_base":
snativeArgs = &ptypes.UnsetBaseArgs{user.Address, perm}
case "SetBase":
case "set_base":
snativeArgs = &ptypes.SetBaseArgs{user.Address, perm, val}
case "SetGlobal":
case "set_global":
snativeArgs = &ptypes.SetGlobalArgs{perm, val}
}
return
@ -1206,13 +1206,13 @@ func snativeRoleTestInputCALL(name string, user *acm.PrivAccount, role string) (
return
}
func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (snativeArgs ptypes.SNativeArgs) {
func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (snativeArgs ptypes.PermArgs) {
switch name {
case "HasRole":
case "has_role":
snativeArgs = &ptypes.HasRoleArgs{user.Address, role}
case "AddRole":
case "add_role":
snativeArgs = &ptypes.AddRoleArgs{user.Address, role}
case "RmRole":
case "rm_role":
snativeArgs = &ptypes.RmRoleArgs{user.Address, role}
}
return


+ 2
- 2
types/events.go View File

@ -22,8 +22,8 @@ func EventStringLogEvent(addr []byte) string {
return fmt.Sprintf("Log/%X", addr)
}
func EventStringSNative(name string) string {
return fmt.Sprintf("SNative/%s", name)
func EventStringPermissions(name string) string {
return fmt.Sprintf("Permissions/%s", name)
}
func EventStringBond() string {


+ 11
- 12
types/tx.go View File

@ -47,7 +47,7 @@ Validation Txs:
- DupeoutTx Validator dupes out (equivocates)
Admin Txs:
- SNativeTx (CapTx ?)
- PermissionsTx
*/
type Tx interface {
@ -68,7 +68,7 @@ const (
TxTypeDupeout = byte(0x14)
// Admin transactions
TxTypeSNative = byte(0x20)
TxTypePermissions = byte(0x20)
)
// for binary.readReflect
@ -81,7 +81,7 @@ var _ = binary.RegisterInterface(
binary.ConcreteType{&UnbondTx{}, TxTypeUnbond},
binary.ConcreteType{&RebondTx{}, TxTypeRebond},
binary.ConcreteType{&DupeoutTx{}, TxTypeDupeout},
binary.ConcreteType{&SNativeTx{}, TxTypeSNative},
binary.ConcreteType{&PermissionsTx{}, TxTypePermissions},
)
//-----------------------------------------------------------------------------
@ -323,23 +323,22 @@ func (tx *DupeoutTx) String() string {
//-----------------------------------------------------------------------------
type SNativeTx struct {
Input *TxInput `json:"input"`
SNative ptypes.SNativeArgs `json:"snative"`
type PermissionsTx struct {
Input *TxInput `json:"input"`
PermArgs ptypes.PermArgs `json:"args"`
}
func (tx *SNativeTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"args":"`, TxTypeSNative)), w, n, err)
binary.WriteJSON(tx.SNative, w, n, err)
binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err)
binary.WriteJSON(tx.PermArgs, w, n, err)
binary.WriteTo([]byte(`","input":`), w, n, err)
tx.Input.WriteSignBytes(w, n, err)
binary.WriteTo([]byte(Fmt(`,"snative":%s`, jsonEscape(ptypes.PermFlagToString(tx.SNative.PermFlag())))), w, n, err)
binary.WriteTo([]byte(`}]}`), w, n, err)
}
func (tx *SNativeTx) String() string {
return Fmt("SNativeTx{%v -> %v}", tx.Input, tx.SNative)
func (tx *PermissionsTx) String() string {
return Fmt("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs)
}
//-----------------------------------------------------------------------------


+ 5
- 5
types/tx_test.go View File

@ -157,22 +157,22 @@ func TestRebondTxSignable(t *testing.T) {
}
}
func TestSNativeTxSignable(t *testing.T) {
snativeTx := &SNativeTx{
func TestPermissionsTxSignable(t *testing.T) {
permsTx := &PermissionsTx{
Input: &TxInput{
Address: []byte("input1"),
Amount: 12345,
Sequence: 250,
},
SNative: &ptypes.SetBaseArgs{
PermArgs: &ptypes.SetBaseArgs{
Address: []byte("address1"),
Permission: 1,
Value: true,
},
}
signBytes := acm.SignBytes(chainID, snativeTx)
signBytes := acm.SignBytes(chainID, permsTx)
signStr := string(signBytes)
expected := Fmt(`{"chain_id":"%s","tx":[32,{"args":"[2,{"address":"6164647265737331","permission":1,"value":true}]","input":{"address":"696E70757431","amount":12345,"sequence":250},"snative":"SetBase"}]}`,
expected := Fmt(`{"chain_id":"%s","tx":[32,{"args":"[2,{"address":"6164647265737331","permission":1,"value":true}]","input":{"address":"696E70757431","amount":12345,"sequence":250}}]}`,
config.GetString("chain_id"))
if signStr != expected {
t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr)


+ 8
- 8
types/tx_utils.go View File

@ -225,9 +225,9 @@ func (tx *RebondTx) Sign(chainID string, privAccount *acm.PrivAccount) {
}
//----------------------------------------------------------------------------
// SNativeTx interface for creating tx
// PermissionsTx interface for creating tx
func NewSNativeTx(st AccountGetter, from acm.PubKey, snativeArgs ptypes.SNativeArgs) (*SNativeTx, error) {
func NewPermissionsTx(st AccountGetter, from acm.PubKey, args ptypes.PermArgs) (*PermissionsTx, error) {
addr := from.Address()
acc := st.GetAccount(addr)
if acc == nil {
@ -235,10 +235,10 @@ func NewSNativeTx(st AccountGetter, from acm.PubKey, snativeArgs ptypes.SNativeA
}
nonce := acc.Sequence + 1
return NewSNativeTxWithNonce(from, snativeArgs, nonce), nil
return NewPermissionsTxWithNonce(from, args, nonce), nil
}
func NewSNativeTxWithNonce(from acm.PubKey, snativeArgs ptypes.SNativeArgs, nonce int) *SNativeTx {
func NewPermissionsTxWithNonce(from acm.PubKey, args ptypes.PermArgs, nonce int) *PermissionsTx {
addr := from.Address()
input := &TxInput{
Address: addr,
@ -248,13 +248,13 @@ func NewSNativeTxWithNonce(from acm.PubKey, snativeArgs ptypes.SNativeArgs, nonc
PubKey: from,
}
return &SNativeTx{
Input: input,
SNative: snativeArgs,
return &PermissionsTx{
Input: input,
PermArgs: args,
}
}
func (tx *SNativeTx) Sign(chainID string, privAccount *acm.PrivAccount) {
func (tx *PermissionsTx) Sign(chainID string, privAccount *acm.PrivAccount) {
tx.Input.PubKey = privAccount.PubKey
tx.Input.Signature = privAccount.Sign(chainID, tx)
}

+ 15
- 10
vm/native.go View File

@ -8,7 +8,12 @@ import (
"github.com/tendermint/tendermint/vm/sha3"
)
var nativeContracts = make(map[Word256]NativeContract)
var registeredNativeContracts = make(map[Word256]NativeContract)
func RegisteredNativeContract(addr Word256) bool {
_, ok := registeredNativeContracts[addr]
return ok
}
func init() {
registerNativeContracts()
@ -16,17 +21,17 @@ func init() {
}
func registerNativeContracts() {
nativeContracts[Int64ToWord256(1)] = ecrecoverFunc
nativeContracts[Int64ToWord256(2)] = sha256Func
nativeContracts[Int64ToWord256(3)] = ripemd160Func
nativeContracts[Int64ToWord256(4)] = identityFunc
registeredNativeContracts[Int64ToWord256(1)] = ecrecoverFunc
registeredNativeContracts[Int64ToWord256(2)] = sha256Func
registeredNativeContracts[Int64ToWord256(3)] = ripemd160Func
registeredNativeContracts[Int64ToWord256(4)] = identityFunc
}
//-----------------------------------------------------------------------------
type NativeContract func(input []byte, gas *int64) (output []byte, err error)
type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error)
func ecrecoverFunc(input []byte, gas *int64) (output []byte, err error) {
func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := GasEcRecover
if *gas < gasRequired {
@ -47,7 +52,7 @@ func ecrecoverFunc(input []byte, gas *int64) (output []byte, err error) {
return LeftPadBytes(hashed, 32), nil
}
func sha256Func(input []byte, gas *int64) (output []byte, err error) {
func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := int64((len(input)+31)/32)*GasSha256Word + GasSha256Base
if *gas < gasRequired {
@ -62,7 +67,7 @@ func sha256Func(input []byte, gas *int64) (output []byte, err error) {
return hasher.Sum(nil), nil
}
func ripemd160Func(input []byte, gas *int64) (output []byte, err error) {
func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := int64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base
if *gas < gasRequired {
@ -77,7 +82,7 @@ func ripemd160Func(input []byte, gas *int64) (output []byte, err error) {
return LeftPadBytes(hasher.Sum(nil), 32), nil
}
func identityFunc(input []byte, gas *int64) (output []byte, err error) {
func identityFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) {
// Deduct gas
gasRequired := int64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase
if *gas < gasRequired {


+ 58
- 62
vm/snative.go View File

@ -7,70 +7,32 @@ import (
ptypes "github.com/tendermint/tendermint/permission/types"
)
type snativeInfo struct {
PermFlag ptypes.PermFlag
NArgs int
ArgsError error
Executable SNativeContract
}
// Takes an appState so it can lookup/update accounts,
// and an input byte array containing at least one Word256
// TODO: ABI
type SNativeContract func(appState AppState, input []byte) (output []byte, err error)
//------------------------------------------------------------------------------------------------
// Registered SNative contracts
var RegisteredSNativeContracts = make(map[Word256]*snativeInfo)
func registerSNativeContracts() {
RegisteredSNativeContracts[LeftPadWord256([]byte("HasBase"))] = getSNativeInfo("HasBase")
RegisteredSNativeContracts[LeftPadWord256([]byte("SetBase"))] = getSNativeInfo("SetBase")
RegisteredSNativeContracts[LeftPadWord256([]byte("UnsetBase"))] = getSNativeInfo("UnsetBase")
RegisteredSNativeContracts[LeftPadWord256([]byte("SetGlobal"))] = getSNativeInfo("SetGlobal")
RegisteredSNativeContracts[LeftPadWord256([]byte("HasRole"))] = getSNativeInfo("HasRole")
RegisteredSNativeContracts[LeftPadWord256([]byte("AddRole"))] = getSNativeInfo("AddRole")
RegisteredSNativeContracts[LeftPadWord256([]byte("RmRole"))] = getSNativeInfo("RmRole")
}
// sets the number of arguments, a friendly error message, and the snative function ("executable")
func getSNativeInfo(permString string) *snativeInfo {
permFlag, err := ptypes.SNativeStringToPermFlag(permString)
if err != nil {
PanicSanity(err)
}
si := &snativeInfo{PermFlag: permFlag}
var errS string
switch permFlag {
case ptypes.HasBase:
si.NArgs, errS, si.Executable = 2, "hasBase() takes two arguments (address, permFlag)", hasBasePerm
case ptypes.SetBase:
si.NArgs, errS, si.Executable = 3, "setBase() takes three arguments (address, permFlag, permission value)", setBasePerm
case ptypes.UnsetBase:
si.NArgs, errS, si.Executable = 2, "unsetBase() takes two arguments (address, permFlag)", unsetBasePerm
case ptypes.SetGlobal:
si.NArgs, errS, si.Executable = 2, "setGlobal() takes two arguments (permFlag, permission value)", setGlobalPerm
case ptypes.HasRole:
si.NArgs, errS, si.Executable = 2, "hasRole() takes two arguments (address, role)", hasRole
case ptypes.AddRole:
si.NArgs, errS, si.Executable = 2, "addRole() takes two arguments (address, role)", addRole
case ptypes.RmRole:
si.NArgs, errS, si.Executable = 2, "rmRole() takes two arguments (address, role)", rmRole
default:
PanicSanity(Fmt("should never happen. PermFlag: %b", permFlag))
}
si.ArgsError = fmt.Errorf(errS)
return si
registeredNativeContracts[LeftPadWord256([]byte("has_base"))] = hasBasePerm
registeredNativeContracts[LeftPadWord256([]byte("set_base"))] = setBasePerm
registeredNativeContracts[LeftPadWord256([]byte("unset_base"))] = unsetBasePerm
registeredNativeContracts[LeftPadWord256([]byte("set_global"))] = setGlobalPerm
registeredNativeContracts[LeftPadWord256([]byte("has_role"))] = hasRole
registeredNativeContracts[LeftPadWord256([]byte("add_role"))] = addRole
registeredNativeContracts[LeftPadWord256([]byte("rm_role"))] = rmRole
}
//-----------------------------------------------------------------------------
// snative are native contracts that can access and manipulate the chain state
// (in particular the permissions values)
// snative are native contracts that can access and modify an account's permissions
// TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?)
func hasBasePerm(appState AppState, args []byte) (output []byte, err error) {
func hasBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.HasBase) {
return nil, ErrInvalidPermission{caller.Address, "has_base"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("hasBasePerm() takes two arguments (address, permFlag)")
}
addr, permNum := returnTwoArgs(args)
vmAcc := appState.GetAccount(addr)
if vmAcc == nil {
@ -90,7 +52,13 @@ func hasBasePerm(appState AppState, args []byte) (output []byte, err error) {
return LeftPadWord256([]byte{permInt}).Bytes(), nil
}
func setBasePerm(appState AppState, args []byte) (output []byte, err error) {
func setBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.SetBase) {
return nil, ErrInvalidPermission{caller.Address, "set_base"}
}
if len(args) != 3*32 {
return nil, fmt.Errorf("setBase() takes three arguments (address, permFlag, permission value)")
}
addr, permNum, perm := returnThreeArgs(args)
vmAcc := appState.GetAccount(addr)
if vmAcc == nil {
@ -109,7 +77,13 @@ func setBasePerm(appState AppState, args []byte) (output []byte, err error) {
return perm.Bytes(), nil
}
func unsetBasePerm(appState AppState, args []byte) (output []byte, err error) {
func unsetBasePerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.UnsetBase) {
return nil, ErrInvalidPermission{caller.Address, "unset_base"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("unsetBase() takes two arguments (address, permFlag)")
}
addr, permNum := returnTwoArgs(args)
vmAcc := appState.GetAccount(addr)
if vmAcc == nil {
@ -127,7 +101,13 @@ func unsetBasePerm(appState AppState, args []byte) (output []byte, err error) {
return permNum.Bytes(), nil
}
func setGlobalPerm(appState AppState, args []byte) (output []byte, err error) {
func setGlobalPerm(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.SetGlobal) {
return nil, ErrInvalidPermission{caller.Address, "set_global"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("setGlobal() takes two arguments (permFlag, permission value)")
}
permNum, perm := returnTwoArgs(args)
vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256)
if vmAcc == nil {
@ -146,7 +126,13 @@ func setGlobalPerm(appState AppState, args []byte) (output []byte, err error) {
return perm.Bytes(), nil
}
func hasRole(appState AppState, args []byte) (output []byte, err error) {
func hasRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.HasRole) {
return nil, ErrInvalidPermission{caller.Address, "has_role"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("hasRole() takes two arguments (address, role)")
}
addr, role := returnTwoArgs(args)
vmAcc := appState.GetAccount(addr)
if vmAcc == nil {
@ -163,7 +149,13 @@ func hasRole(appState AppState, args []byte) (output []byte, err error) {
return LeftPadWord256([]byte{permInt}).Bytes(), nil
}
func addRole(appState AppState, args []byte) (output []byte, err error) {
func addRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.AddRole) {
return nil, ErrInvalidPermission{caller.Address, "add_role"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("addRole() takes two arguments (address, role)")
}
addr, role := returnTwoArgs(args)
vmAcc := appState.GetAccount(addr)
if vmAcc == nil {
@ -181,7 +173,13 @@ func addRole(appState AppState, args []byte) (output []byte, err error) {
return LeftPadWord256([]byte{permInt}).Bytes(), nil
}
func rmRole(appState AppState, args []byte) (output []byte, err error) {
func rmRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) {
if !HasPermission(appState, caller, ptypes.RmRole) {
return nil, ErrInvalidPermission{caller.Address, "rm_role"}
}
if len(args) != 2*32 {
return nil, fmt.Errorf("rmRole() takes two arguments (address, role)")
}
addr, role := returnTwoArgs(args)
vmAcc := appState.GetAccount(addr)
if vmAcc == nil {
@ -213,9 +211,7 @@ func (e ErrInvalidPermission) Error() string {
// Checks if a permission flag is valid (a known base chain or snative permission)
func ValidPermN(n ptypes.PermFlag) bool {
if n > ptypes.TopBasePermFlag && n < ptypes.FirstSNativePermFlag {
return false
} else if n > ptypes.TopSNativePermFlag {
if n > ptypes.TopPermFlag {
return false
}
return true


+ 9
- 31
vm/vm.go View File

@ -108,33 +108,6 @@ func (vm *VM) fireEvent(exception *string, output *[]byte, caller, callee *Accou
}
}
// call an snative contract (includes event processing)
// addr and permFlag refer to the same snative's address and it's permFlag
func (vm *VM) callSNative(addr Word256, snInfo *snativeInfo, caller *Account, input []byte) (ret []byte, err error) {
exception := new(string)
// fire the post call event (including exception if applicable)
value, gas := int64(0), new(int64)
defer vm.fireEvent(exception, &ret, caller, &Account{Address: addr}, input, value, gas)
if !HasPermission(vm.appState, caller, snInfo.PermFlag) {
err = ErrInvalidPermission{caller.Address, addr.TrimmedString()}
*exception = err.Error()
return
}
if len(input) != snInfo.NArgs*32 {
err = snInfo.ArgsError
*exception = err.Error()
return
}
// SNATIVE ACCESS
ret, err = snInfo.Executable(vm.appState, input)
// END SNATIVE ACCESS
if err != nil {
*exception = err.Error()
}
return
}
// CONTRACT appState is aware of caller and callee, so we can just mutate them.
// value: To be transferred from caller to callee. Refunded upon error.
// gas: Available gas. No refunds for gas.
@ -777,11 +750,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
// Begin execution
var ret []byte
var err error
if nativeContract := nativeContracts[addr]; nativeContract != nil {
if nativeContract := registeredNativeContracts[addr]; nativeContract != nil {
// Native contract
ret, err = nativeContract(args, &gasLimit)
} else if snInfo, ok := RegisteredSNativeContracts[addr]; ok {
ret, err = vm.callSNative(addr, snInfo, callee, input)
ret, err = nativeContract(vm.appState, callee, args, &gasLimit)
// for now we fire the Receive event. maybe later we'll fire more particulars
var exception string
if err != nil {
exception = err.Error()
}
vm.fireEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, gas)
} else {
// EVM contract
if ok = useGas(gas, GasGetAccount); !ok {


Loading…
Cancel
Save