Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
B
burrow
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Wiki
Code
Merge requests
0
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Hang Yu
burrow
Commits
babad345
Unverified
Commit
babad345
authored
8 years ago
by
Silas Davis
Browse files
Options
Downloads
Patches
Plain Diff
Use gasLimit for DELEGATECALL and test
parent
6e0b3bc9
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
manager/eris-mint/evm/test/vm_test.go
+229
-42
229 additions, 42 deletions
manager/eris-mint/evm/test/vm_test.go
manager/eris-mint/evm/vm.go
+5
-7
5 additions, 7 deletions
manager/eris-mint/evm/vm.go
with
234 additions
and
49 deletions
manager/eris-mint/evm/test/vm_test.go
+
229
−
42
View file @
babad345
...
@@ -8,13 +8,19 @@ import (
...
@@ -8,13 +8,19 @@ import (
"testing"
"testing"
"time"
"time"
"errors"
.
"github.com/eris-ltd/eris-db/manager/eris-mint/evm"
.
"github.com/eris-ltd/eris-db/manager/eris-mint/evm"
ptypes
"github.com/eris-ltd/eris-db/permission/types"
ptypes
"github.com/eris-ltd/eris-db/permission/types"
"github.com/eris-ltd/eris-db/txs"
"github.com/eris-ltd/eris-db/txs"
.
"github.com/tendermint/go-common"
.
"github.com/tendermint/go-common"
"github.com/tendermint/go-events"
"github.com/tendermint/go-events"
"github.com/stretchr/testify/assert"
)
)
func
init
()
{
SetDebug
(
true
)
}
func
newAppState
()
*
FakeAppState
{
func
newAppState
()
*
FakeAppState
{
fas
:=
&
FakeAppState
{
fas
:=
&
FakeAppState
{
accounts
:
make
(
map
[
string
]
*
Account
),
accounts
:
make
(
map
[
string
]
*
Account
),
...
@@ -154,65 +160,164 @@ func TestSendCall(t *testing.T) {
...
@@ -154,65 +160,164 @@ func TestSendCall(t *testing.T) {
//----------------------------------------------
//----------------------------------------------
// account2 has insufficient balance, should fail
// account2 has insufficient balance, should fail
fmt
.
Println
(
"Should fail with insufficient balance"
)
_
,
err
:=
runVMWaitError
(
ourVm
,
account1
,
account2
,
addr
,
contractCode
,
1000
)
assert
.
Error
(
t
,
err
,
"Expected insufficient balance error"
)
exception
:=
runVMWaitEvents
(
t
,
ourVm
,
account1
,
account2
,
addr
,
contractCode
,
1000
)
if
exception
==
""
{
t
.
Fatal
(
"Expected exception"
)
}
//----------------------------------------------
//----------------------------------------------
// give account2 sufficient balance, should pass
// give account2 sufficient balance, should pass
account2
.
Balance
=
100000
account2
.
Balance
=
100000
exception
=
runVMWaitEvents
(
t
,
ourVm
,
account1
,
account2
,
addr
,
contractCode
,
1000
)
_
,
err
=
runVMWaitError
(
ourVm
,
account1
,
account2
,
addr
,
contractCode
,
1000
)
if
exception
!=
""
{
assert
.
NoError
(
t
,
err
,
"Should have sufficient balance"
)
t
.
Fatal
(
"Unexpected exception"
,
exception
)
}
//----------------------------------------------
//----------------------------------------------
// insufficient gas, should fail
// insufficient gas, should fail
fmt
.
Println
(
"Should fail with insufficient gas"
)
account2
.
Balance
=
100000
account2
.
Balance
=
100000
exception
=
runVMWaitEvents
(
t
,
ourVm
,
account1
,
account2
,
addr
,
contractCode
,
100
)
_
,
err
=
runVMWaitError
(
ourVm
,
account1
,
account2
,
addr
,
contractCode
,
100
)
if
exception
==
""
{
assert
.
Error
(
t
,
err
,
"Expected insufficient gas error"
)
t
.
Fatal
(
"Expected exception"
)
}
// This test was introduced to cover an issues exposed in our handling of the
// gas limit passed from caller to callee on various forms of CALL
// this ticket gives some background: https://github.com/eris-ltd/eris-pm/issues/212
// The idea of this test is to implement a simple DelegateCall in EVM code
// We first run the DELEGATECALL with _just_ enough gas expecting a simple return,
// and then run it with 1 gas unit less, expecting a failure
func
TestDelegateCallGas
(
t
*
testing
.
T
)
{
appState
:=
newAppState
()
ourVm
:=
NewVM
(
appState
,
newParams
(),
Zero256
,
nil
)
inOff
:=
0
inSize
:=
0
// no call data
retOff
:=
0
retSize
:=
32
calleeReturnValue
:=
int64
(
20
)
// DELEGATECALL(retSize, refOffset, inSize, inOffset, addr, gasLimit)
// 6 pops
delegateCallCost
:=
GasStackOp
*
6
// 1 push
gasCost
:=
GasStackOp
// 2 pops, 1 push
subCost
:=
GasStackOp
*
3
pushCost
:=
GasStackOp
costBetweenGasAndDelegateCall
:=
gasCost
+
subCost
+
delegateCallCost
+
pushCost
// Do a simple operation using 1 gas unit
calleeAccount
,
calleeAddress
:=
makeAccountWithCode
(
appState
,
"callee"
,
bytecode
(
PUSH1
,
calleeReturnValue
,
return1
()))
// Here we split up the caller code so we can make a DELEGATE call with
// different amounts of gas. The value we sandwich in the middle is the amount
// we subtract from the available gas (that the caller has available), so:
// code := bytecode(callerCodePrefix, <amount to subtract from GAS> , callerCodeSuffix)
// gives us the code to make the call
callerCodePrefix
:=
bytecode
(
PUSH1
,
retSize
,
PUSH1
,
retOff
,
PUSH1
,
inSize
,
PUSH1
,
inOff
,
PUSH20
,
calleeAddress
,
PUSH1
)
callerCodeSuffix
:=
bytecode
(
GAS
,
SUB
,
DELEGATECALL
,
returnWord
())
// Perform a delegate call
callerAccount
,
_
:=
makeAccountWithCode
(
appState
,
"caller"
,
bytecode
(
callerCodePrefix
,
// Give just enough gas to make the DELEGATECALL
costBetweenGasAndDelegateCall
,
callerCodeSuffix
))
// Should pass
output
,
err
:=
runVMWaitError
(
ourVm
,
callerAccount
,
calleeAccount
,
calleeAddress
,
callerAccount
.
Code
,
100
)
assert
.
NoError
(
t
,
err
,
"Should have sufficient funds for call"
)
assert
.
Equal
(
t
,
Int64ToWord256
(
calleeReturnValue
)
.
Bytes
(),
output
)
callerAccount
.
Code
=
bytecode
(
callerCodePrefix
,
// Shouldn't be enough gas to make call
costBetweenGasAndDelegateCall
-
1
,
callerCodeSuffix
)
// Should fail
_
,
err
=
runVMWaitError
(
ourVm
,
callerAccount
,
calleeAccount
,
calleeAddress
,
callerAccount
.
Code
,
100
)
assert
.
Error
(
t
,
err
,
"Should have insufficient funds for call"
)
}
// Store the top element of the stack (which is a 32-byte word) in memory
// and return it. Useful for a simple return value.
func
return1
()
[]
byte
{
return
bytecode
(
PUSH1
,
0
,
MSTORE
,
returnWord
())
}
func
returnWord
()
[]
byte
{
// PUSH1 => return size, PUSH1 => return offset, RETURN
return
bytecode
(
PUSH1
,
32
,
PUSH1
,
0
,
RETURN
)
}
func
makeAccountWithCode
(
appState
AppState
,
name
string
,
code
[]
byte
)
(
*
Account
,
[]
byte
)
{
account
:=
&
Account
{
Address
:
LeftPadWord256
([]
byte
(
name
)),
Balance
:
9999999
,
Code
:
code
,
Nonce
:
0
,
}
account
.
Code
=
code
appState
.
UpdateAccount
(
account
)
// Sanity check
address
:=
new
([
20
]
byte
)
for
i
,
b
:=
range
account
.
Address
.
Postfix
(
20
)
{
address
[
i
]
=
b
}
}
return
account
,
address
[
:
]
}
}
// subscribes to an AccCall, runs the vm, returns the exception
// Subscribes to an AccCall, runs the vm, returns the output any direct exception
func
runVMWaitEvents
(
t
*
testing
.
T
,
ourVm
*
VM
,
caller
,
callee
*
Account
,
subscribeAddr
,
contractCode
[]
byte
,
gas
int64
)
string
{
// and then waits for any exceptions transmitted by EventData in the AccCall
// event (in the case of no direct error from call we will block waiting for
// at least 1 AccCall event)
func
runVMWaitError
(
ourVm
*
VM
,
caller
,
callee
*
Account
,
subscribeAddr
,
contractCode
[]
byte
,
gas
int64
)
(
output
[]
byte
,
err
error
)
{
eventCh
:=
make
(
chan
txs
.
EventData
)
output
,
err
=
runVM
(
eventCh
,
ourVm
,
caller
,
callee
,
subscribeAddr
,
contractCode
,
gas
)
if
err
!=
nil
{
return
}
msg
:=
<-
eventCh
var
errString
string
switch
ev
:=
msg
.
(
type
)
{
case
txs
.
EventDataTx
:
errString
=
ev
.
Exception
case
txs
.
EventDataCall
:
errString
=
ev
.
Exception
}
if
errString
!=
""
{
err
=
errors
.
New
(
errString
)
}
return
}
// Subscribes to an AccCall, runs the vm, returns the output and any direct
// exception
func
runVM
(
eventCh
chan
txs
.
EventData
,
ourVm
*
VM
,
caller
,
callee
*
Account
,
subscribeAddr
,
contractCode
[]
byte
,
gas
int64
)
([]
byte
,
error
)
{
// we need to catch the event from the CALL to check for exceptions
// we need to catch the event from the CALL to check for exceptions
evsw
:=
events
.
NewEventSwitch
()
evsw
:=
events
.
NewEventSwitch
()
evsw
.
Start
()
evsw
.
Start
()
ch
:=
make
(
chan
interface
{})
fmt
.
Printf
(
"subscribe to %x
\n
"
,
subscribeAddr
)
fmt
.
Printf
(
"subscribe to %x
\n
"
,
subscribeAddr
)
evsw
.
AddListenerForEvent
(
"test"
,
txs
.
EventStringAccCall
(
subscribeAddr
),
func
(
msg
events
.
EventData
)
{
evsw
.
AddListenerForEvent
(
"test"
,
txs
.
EventStringAccCall
(
subscribeAddr
),
ch
<-
msg
func
(
msg
events
.
EventData
)
{
})
eventCh
<-
msg
.
(
txs
.
EventData
)
})
evc
:=
events
.
NewEventCache
(
evsw
)
evc
:=
events
.
NewEventCache
(
evsw
)
ourVm
.
SetFireable
(
evc
)
ourVm
.
SetFireable
(
evc
)
go
func
()
{
start
:=
time
.
Now
()
start
:=
time
.
Now
()
output
,
err
:=
ourVm
.
Call
(
caller
,
callee
,
contractCode
,
[]
byte
{},
0
,
&
gas
)
output
,
err
:=
ourVm
.
Call
(
caller
,
callee
,
contractCode
,
[]
byte
{},
0
,
&
gas
)
fmt
.
Printf
(
"Output: %v Error: %v
\n
"
,
output
,
err
)
fmt
.
Printf
(
"Output: %v Error: %v
\n
"
,
output
,
err
)
fmt
.
Println
(
"Call took:"
,
time
.
Since
(
start
))
fmt
.
Println
(
"Call took:"
,
time
.
Since
(
start
))
go
func
()
{
evc
.
Flush
()
}()
if
err
!=
nil
{
return
output
,
err
ch
<-
err
.
Error
()
}
evc
.
Flush
()
}()
msg
:=
<-
ch
switch
ev
:=
msg
.
(
type
)
{
case
txs
.
EventDataTx
:
return
ev
.
Exception
case
txs
.
EventDataCall
:
return
ev
.
Exception
case
string
:
return
ev
}
return
""
}
}
// this is code to call another contract (hardcoded as addr)
// this is code to call another contract (hardcoded as addr)
...
@@ -222,10 +327,92 @@ func callContractCode(addr []byte) []byte {
...
@@ -222,10 +327,92 @@ func callContractCode(addr []byte) []byte {
inOff
,
inSize
:=
byte
(
0x0
),
byte
(
0x0
)
// no call data
inOff
,
inSize
:=
byte
(
0x0
),
byte
(
0x0
)
// no call data
retOff
,
retSize
:=
byte
(
0x0
),
byte
(
0x20
)
retOff
,
retSize
:=
byte
(
0x0
),
byte
(
0x20
)
// this is the code we want to run (send funds to an account and return)
// this is the code we want to run (send funds to an account and return)
return
bytecode
(
PUSH1
,
retSize
,
PUSH1
,
retOff
,
PUSH1
,
inSize
,
PUSH1
,
inOff
,
PUSH1
,
value
,
PUSH20
,
addr
,
PUSH2
,
gas1
,
gas2
,
CALL
,
PUSH1
,
retSize
,
PUSH1
,
retOff
,
RETURN
)
}
func
TestBytecode
(
t
*
testing
.
T
)
{
assert
.
Equal
(
t
,
bytecode
(
1
,
2
,
3
,
4
,
5
,
6
),
bytecode
(
1
,
2
,
3
,
bytecode
(
4
,
5
,
6
)))
assert
.
Equal
(
t
,
bytecode
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
),
bytecode
(
1
,
2
,
3
,
bytecode
(
4
,
bytecode
(
5
),
6
),
7
,
8
))
assert
.
Equal
(
t
,
bytecode
(
PUSH1
,
2
),
bytecode
(
byte
(
PUSH1
),
0x02
))
assert
.
Equal
(
t
,
[]
byte
{},
bytecode
(
bytecode
(
bytecode
())))
contractAccount
:=
&
Account
{
Address
:
Int64ToWord256
(
102
)}
addr
:=
contractAccount
.
Address
.
Postfix
(
20
)
gas1
,
gas2
:=
byte
(
0x1
),
byte
(
0x1
)
value
:=
byte
(
0x69
)
inOff
,
inSize
:=
byte
(
0x0
),
byte
(
0x0
)
// no call data
retOff
,
retSize
:=
byte
(
0x0
),
byte
(
0x20
)
contractCodeBytecode
:=
bytecode
(
PUSH1
,
retSize
,
PUSH1
,
retOff
,
PUSH1
,
inSize
,
PUSH1
,
inOff
,
PUSH1
,
value
,
PUSH20
,
addr
,
PUSH2
,
gas1
,
gas2
,
CALL
,
PUSH1
,
retSize
,
PUSH1
,
retOff
,
RETURN
)
contractCode
:=
[]
byte
{
0x60
,
retSize
,
0x60
,
retOff
,
0x60
,
inSize
,
0x60
,
inOff
,
0x60
,
value
,
0x73
}
contractCode
:=
[]
byte
{
0x60
,
retSize
,
0x60
,
retOff
,
0x60
,
inSize
,
0x60
,
inOff
,
0x60
,
value
,
0x73
}
contractCode
=
append
(
contractCode
,
addr
...
)
contractCode
=
append
(
contractCode
,
addr
...
)
contractCode
=
append
(
contractCode
,
[]
byte
{
0x61
,
gas1
,
gas2
,
0xf1
,
0x60
,
0x20
,
0x60
,
0x0
,
0xf3
}
...
)
contractCode
=
append
(
contractCode
,
[]
byte
{
0x61
,
gas1
,
gas2
,
0xf1
,
0x60
,
0x20
,
0x60
,
0x0
,
0xf3
}
...
)
return
contractCode
assert
.
Equal
(
t
,
contractCode
,
contractCodeBytecode
)
}
func
TestConcat
(
t
*
testing
.
T
)
{
assert
.
Equal
(
t
,
[]
byte
{
0x01
,
0x02
,
0x03
,
0x04
},
concat
([]
byte
{
0x01
,
0x02
},
[]
byte
{
0x03
,
0x04
}))
}
// Convenience function to allow us to mix bytes, ints, and OpCodes that
// represent bytes in an EVM assembly code to make assembly more readable.
// Also allows us to splice together assembly
// fragments because any []byte arguments are flattened in the result.
func
bytecode
(
bytelikes
...
interface
{})
[]
byte
{
bytes
:=
make
([]
byte
,
len
(
bytelikes
))
for
i
,
bytelike
:=
range
bytelikes
{
switch
b
:=
bytelike
.
(
type
)
{
case
byte
:
bytes
[
i
]
=
b
case
OpCode
:
bytes
[
i
]
=
byte
(
b
)
case
int
:
bytes
[
i
]
=
byte
(
b
)
if
int
(
bytes
[
i
])
!=
b
{
panic
(
fmt
.
Sprintf
(
"The int %v does not fit inside a byte"
,
b
))
}
case
int64
:
bytes
[
i
]
=
byte
(
b
)
if
int64
(
bytes
[
i
])
!=
b
{
panic
(
fmt
.
Sprintf
(
"The int64 %v does not fit inside a byte"
,
b
))
}
case
[]
byte
:
// splice
return
concat
(
bytes
[
:
i
],
b
,
bytecode
(
bytelikes
[
i
+
1
:
]
...
))
default
:
panic
(
"Only byte-like codes (and []byte sequences) can be used to form bytecode"
)
}
}
return
bytes
}
func
concat
(
bss
...
[]
byte
)
[]
byte
{
offset
:=
0
for
_
,
bs
:=
range
bss
{
offset
+=
len
(
bs
)
}
bytes
:=
make
([]
byte
,
offset
)
offset
=
0
for
_
,
bs
:=
range
bss
{
for
i
,
b
:=
range
bs
{
bytes
[
offset
+
i
]
=
b
}
offset
+=
len
(
bs
)
}
return
bytes
}
}
/*
/*
...
...
This diff is collapsed.
Click to expand it.
manager/eris-mint/evm/vm.go
+
5
−
7
View file @
babad345
...
@@ -17,7 +17,7 @@ var (
...
@@ -17,7 +17,7 @@ var (
ErrUnknownAddress
=
errors
.
New
(
"Unknown address"
)
ErrUnknownAddress
=
errors
.
New
(
"Unknown address"
)
ErrInsufficientBalance
=
errors
.
New
(
"Insufficient balance"
)
ErrInsufficientBalance
=
errors
.
New
(
"Insufficient balance"
)
ErrInvalidJumpDest
=
errors
.
New
(
"Invalid jump dest"
)
ErrInvalidJumpDest
=
errors
.
New
(
"Invalid jump dest"
)
ErrInsufficientGas
=
errors
.
New
(
"Insuffient gas"
)
ErrInsufficientGas
=
errors
.
New
(
"Insuffi
ci
ent gas"
)
ErrMemoryOutOfBounds
=
errors
.
New
(
"Memory out of bounds"
)
ErrMemoryOutOfBounds
=
errors
.
New
(
"Memory out of bounds"
)
ErrCodeOutOfBounds
=
errors
.
New
(
"Code out of bounds"
)
ErrCodeOutOfBounds
=
errors
.
New
(
"Code out of bounds"
)
ErrInputOutOfBounds
=
errors
.
New
(
"Input out of bounds"
)
ErrInputOutOfBounds
=
errors
.
New
(
"Input out of bounds"
)
...
@@ -194,7 +194,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
...
@@ -194,7 +194,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
)
)
for
{
for
{
// Use BaseOp gas.
// Use BaseOp gas.
if
useGasNegative
(
gas
,
GasBaseOp
,
&
err
)
{
if
useGasNegative
(
gas
,
GasBaseOp
,
&
err
)
{
return
nil
,
err
return
nil
,
err
...
@@ -816,7 +815,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
...
@@ -816,7 +815,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
exception
=
err
.
Error
()
exception
=
err
.
Error
()
}
}
// NOTE: these fire call events and not particular events for eg name reg or permissions
// NOTE: these fire call events and not particular events for eg name reg or permissions
vm
.
fireCallEvent
(
&
exception
,
&
ret
,
callee
,
&
Account
{
Address
:
addr
},
args
,
value
,
gas
)
vm
.
fireCallEvent
(
&
exception
,
&
ret
,
callee
,
&
Account
{
Address
:
addr
},
args
,
value
,
&
gas
Limit
)
}
else
{
}
else
{
// EVM contract
// EVM contract
if
useGasNegative
(
gas
,
GasGetAccount
,
&
err
)
{
if
useGasNegative
(
gas
,
GasGetAccount
,
&
err
)
{
...
@@ -831,12 +830,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
...
@@ -831,12 +830,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
if
acc
==
nil
{
if
acc
==
nil
{
return
nil
,
firstErr
(
err
,
ErrUnknownAddress
)
return
nil
,
firstErr
(
err
,
ErrUnknownAddress
)
}
}
ret
,
err
=
vm
.
Call
(
callee
,
callee
,
acc
.
Code
,
args
,
value
,
gas
)
ret
,
err
=
vm
.
Call
(
callee
,
callee
,
acc
.
Code
,
args
,
value
,
&
gas
Limit
)
}
else
if
op
==
DELEGATECALL
{
}
else
if
op
==
DELEGATECALL
{
if
acc
==
nil
{
if
acc
==
nil
{
return
nil
,
firstErr
(
err
,
ErrUnknownAddress
)
return
nil
,
firstErr
(
err
,
ErrUnknownAddress
)
}
}
ret
,
err
=
vm
.
DelegateCall
(
caller
,
callee
,
acc
.
Code
,
args
,
value
,
gas
)
ret
,
err
=
vm
.
DelegateCall
(
caller
,
callee
,
acc
.
Code
,
args
,
value
,
&
gas
Limit
)
}
else
{
}
else
{
// nil account means we're sending funds to a new account
// nil account means we're sending funds to a new account
if
acc
==
nil
{
if
acc
==
nil
{
...
@@ -847,7 +846,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
...
@@ -847,7 +846,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
}
}
// add account to the tx cache
// add account to the tx cache
vm
.
appState
.
UpdateAccount
(
acc
)
vm
.
appState
.
UpdateAccount
(
acc
)
ret
,
err
=
vm
.
Call
(
callee
,
acc
,
acc
.
Code
,
args
,
value
,
gas
)
ret
,
err
=
vm
.
Call
(
callee
,
acc
,
acc
.
Code
,
args
,
value
,
&
gas
Limit
)
}
}
}
}
...
@@ -906,7 +905,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
...
@@ -906,7 +905,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
}
}
pc
++
pc
++
}
}
}
}
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment