/
contracts_write.go
305 lines (288 loc) · 10.4 KB
/
contracts_write.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package vm
import (
"errors"
"math/big"
"strings"
"github.com/PositionExchange/posichain/accounts/abi"
"github.com/PositionExchange/posichain/core/types"
"github.com/PositionExchange/posichain/shard"
"github.com/PositionExchange/posichain/staking"
stakingTypes "github.com/PositionExchange/posichain/staking/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
)
// WriteCapablePrecompiledContractsStaking lists out the write capable precompiled contracts
// which are available after the StakingPrecompileEpoch
// for now, we have only one contract at 252 or 0xfc - which is the staking precompile
var WriteCapablePrecompiledContractsStaking = map[common.Address]WriteCapablePrecompiledContract{
common.BytesToAddress([]byte{252}): &stakingPrecompile{},
}
// WriteCapablePrecompiledContractsCrossXfer lists out the write capable precompiled contracts
// which are available after the CrossShardXferPrecompileEpoch
// It includes the staking precompile and the cross-shard transfer precompile
var WriteCapablePrecompiledContractsCrossXfer = map[common.Address]WriteCapablePrecompiledContract{
// reserve 250 for read only staking precompile and 251 for epoch
common.BytesToAddress([]byte{249}): &crossShardXferPrecompile{},
common.BytesToAddress([]byte{252}): &stakingPrecompile{},
}
// WriteCapablePrecompiledContract represents the interface for Native Go contracts
// which are available as a precompile in the EVM
// As with (read-only) PrecompiledContracts, these need a RequiredGas function
// Note that these contracts have the capability to alter the state
// while those in contracts.go do not
type WriteCapablePrecompiledContract interface {
// RequiredGas calculates the contract gas use
RequiredGas(evm *EVM, contract *Contract, input []byte) (uint64, error)
// use a different name from read-only contracts to be safe
RunWriteCapable(evm *EVM, contract *Contract, input []byte) ([]byte, error)
}
// RunWriteCapablePrecompiledContract runs and evaluates the output of a write capable precompiled contract.
func RunWriteCapablePrecompiledContract(
p WriteCapablePrecompiledContract,
evm *EVM,
contract *Contract,
input []byte,
readOnly bool,
) ([]byte, error) {
// immediately error out if readOnly
if readOnly {
return nil, errWriteProtection
}
gas, err := p.RequiredGas(evm, contract, input)
if err != nil {
return nil, err
}
if !contract.UseGas(gas) {
return nil, ErrOutOfGas
}
return p.RunWriteCapable(evm, contract, input)
}
type stakingPrecompile struct{}
// RequiredGas returns the gas required to execute the pre-compiled contract.
//
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *stakingPrecompile) RequiredGas(
evm *EVM,
contract *Contract,
input []byte,
) (uint64, error) {
// if invalid data or invalid shard
// set payload to blank and charge minimum gas
var payload []byte = make([]byte, 0)
// availability of staking and precompile has already been checked
if evm.Context.ShardID == shard.BeaconChainShardID {
// check that input is well formed
// meaning all the expected parameters are available
// and that we are only trying to perform staking tx
// on behalf of the correct entity
stakeMsg, err := staking.ParseStakeMsg(contract.Caller(), input)
if err == nil {
// otherwise charge similar to a regular staking tx
if migrationMsg, ok := stakeMsg.(*stakingTypes.MigrationMsg); ok {
// charge per delegation to migrate
return evm.CalculateMigrationGas(evm.StateDB,
migrationMsg,
evm.ChainConfig().IsS3(evm.EpochNumber),
evm.ChainConfig().IsIstanbul(evm.EpochNumber),
)
} else if encoded, err := rlp.EncodeToBytes(stakeMsg); err == nil {
payload = encoded
}
}
}
if gas, err := IntrinsicGas(
payload,
false, // contractCreation
evm.ChainConfig().IsS3(evm.EpochNumber), // homestead
evm.ChainConfig().IsIstanbul(evm.EpochNumber), // istanbul
false, // isValidatorCreation
); err != nil {
return 0, err // ErrOutOfGas occurs when gas payable > uint64
} else {
return gas, nil
}
}
// RunWriteCapable runs the actual contract (that is it performs the staking)
func (c *stakingPrecompile) RunWriteCapable(
evm *EVM,
contract *Contract,
input []byte,
) ([]byte, error) {
if evm.Context.ShardID != shard.BeaconChainShardID {
return nil, errors.New("Staking not supported on this shard")
}
stakeMsg, err := staking.ParseStakeMsg(contract.Caller(), input)
if err != nil {
return nil, err
}
var rosettaBlockTracer RosettaTracer
if tmpTracker, ok := evm.vmConfig.Tracer.(RosettaTracer); ok {
rosettaBlockTracer = tmpTracker
}
if delegate, ok := stakeMsg.(*stakingTypes.Delegate); ok {
if err := evm.Delegate(evm.StateDB, rosettaBlockTracer, delegate); err != nil {
return nil, err
} else {
evm.StakeMsgs = append(evm.StakeMsgs, delegate)
return nil, nil
}
}
if undelegate, ok := stakeMsg.(*stakingTypes.Undelegate); ok {
return nil, evm.Undelegate(evm.StateDB, rosettaBlockTracer, undelegate)
}
if collectRewards, ok := stakeMsg.(*stakingTypes.CollectRewards); ok {
return nil, evm.CollectRewards(evm.StateDB, rosettaBlockTracer, collectRewards)
}
// Migrate is not supported in precompile and will be done in a batch hard fork
//if migrationMsg, ok := stakeMsg.(*stakingTypes.MigrationMsg); ok {
// stakeMsgs, err := evm.MigrateDelegations(evm.StateDB, migrationMsg)
// if err != nil {
// return nil, err
// } else {
// for _, stakeMsg := range stakeMsgs {
// if delegate, ok := stakeMsg.(*stakingTypes.Delegate); ok {
// evm.StakeMsgs = append(evm.StakeMsgs, delegate)
// } else {
// return nil, errors.New("[StakingPrecompile] Received incompatible stakeMsg from evm.MigrateDelegations")
// }
// }
// return nil, nil
// }
//}
return nil, errors.New("[StakingPrecompile] Received incompatible stakeMsg from staking.ParseStakeMsg")
}
var abiCrossShardXfer abi.ABI
func init() {
// msg.Value is used for transfer and is also a parameter
// otherwise it might be possible for a user to retrieve money from the precompile
// that was sent by someone else prior to the hard fork
// contract.Caller is used as fromAddress, not a parameter
// originating ShardID is pulled from the EVM object, not a parameter
crossShardXferABIJSON := `
[
{
"inputs": [
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint64",
"name": "toShardID",
"type": "uint32"
}
],
"name": "crossShardTransfer",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]`
var err error
abiCrossShardXfer, err = abi.JSON(strings.NewReader(crossShardXferABIJSON))
if err != nil {
// means an error in the code
panic("Invalid cross shard transfer ABI JSON")
}
}
type crossShardXferPrecompile struct{}
// RequiredGas returns the gas required to execute the pre-compiled contract.
//
// This method does not require any overflow checking as the input size gas costs
// required for anything significant is so high it's impossible to pay for.
func (c *crossShardXferPrecompile) RequiredGas(
evm *EVM,
contract *Contract,
input []byte,
) (uint64, error) {
// multiple instances of the precompile in one transaction
// are blocked, so there is no way for a smart contract
// to subsidize this transaction by an EOA via delegatecall
// therefore no need to charge any gas
return 0, nil
}
// RunWriteCapable runs the actual contract
func (c *crossShardXferPrecompile) RunWriteCapable(
evm *EVM,
contract *Contract,
input []byte,
) ([]byte, error) {
// make sure that cxReceipt is already nil to
// prevent multiple calls to the precompile
// in the same transaction
if evm.CXReceipt != nil {
return nil, errors.New("cannot call cross shard precompile again in same tx")
}
fromAddress, toAddress, fromShardID, toShardID, value, err :=
parseCrossShardXferData(evm, contract, input)
if err != nil {
return nil, err
}
// validate not a contract (toAddress can still be a contract)
if len(evm.StateDB.GetCode(fromAddress)) > 0 && !evm.IsValidator(evm.StateDB, fromAddress) {
return nil, errors.New("cross shard xfer not yet implemented for contracts")
}
// can't have too many shards
if toShardID >= evm.Context.NumShards {
return nil, errors.New("toShardId out of bounds")
}
// not for simple transfers
if fromShardID == toShardID {
return nil, errors.New("from and to shard id can't be equal")
}
// make sure nobody sends extra or less money
if contract.Value().Cmp(value) != 0 {
return nil, errors.New("argument value and msg.value not equal")
}
// now do the actual transfer
// step 1 -> remove funds from the precompile address
if !evm.CanTransfer(evm.StateDB, contract.Address(), value) {
return nil, errors.New("not enough balance received")
}
evm.Transfer(evm.StateDB, contract.Address(), toAddress, value, types.SubtractionOnly)
// step 2 -> make a cross link
// note that the transaction hash is added by state_processor.go to this receipt
// and that the receiving shard does not care about the `From` but we use the original
// instead of the precompile address for consistency
evm.CXReceipt = &types.CXReceipt{
From: fromAddress,
To: &toAddress,
ShardID: fromShardID,
ToShardID: toShardID,
Amount: value,
}
return nil, nil
}
// parseCrossShardXferData does a simple parse with only data types validation
func parseCrossShardXferData(evm *EVM, contract *Contract, input []byte) (
common.Address, common.Address, uint32, uint32, *big.Int, error) {
method, err := abiCrossShardXfer.MethodById(input)
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
input = input[4:]
args := map[string]interface{}{}
if err = method.Inputs.UnpackIntoMap(args, input); err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
value, err := abi.ParseBigIntFromKey(args, "value")
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
toAddress, err := abi.ParseAddressFromKey(args, "to")
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
toShardID, err := abi.ParseUint32FromKey(args, "toShardID")
if err != nil {
return common.Address{}, common.Address{}, 0, 0, nil, err
}
return contract.Caller(), toAddress, evm.ShardID, toShardID, value, nil
}