Skip to content
This repository has been archived by the owner on Nov 26, 2024. It is now read-only.

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rachel-bousfield committed Jan 9, 2024
1 parent 9e0f943 commit 135fee7
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 45 deletions.
7 changes: 6 additions & 1 deletion arbitrator/stylus/tests/grow-and-call.wat
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
(import "vm_hooks" "pay_for_memory_grow" (func (param i32)))
(import "vm_hooks" "read_args" (func $read_args (param i32)))
(import "vm_hooks" "write_result" (func $write_result (param i32 i32)))
(import "vm_hooks" "msg_value" (func $msg_value (param i32)))
(import "vm_hooks" "call_contract" (func $call_contract (param i32 i32 i32 i32 i64 i32) (result i32)))
(import "console" "tee_i32" (func $tee (param i32) (result i32)))
(func (export "user_entrypoint") (param $args_len i32) (result i32)
Expand All @@ -19,11 +20,15 @@
memory.grow
drop

;; copy the message value
i32.const 0x1000
call $msg_value

;; static call target contract
i32.const 1 ;; address
i32.const 21 ;; calldata
(i32.sub (local.get $args_len) (i32.const 21)) ;; calldata len
i32.const 0x1000 ;; zero callvalue
i32.const 0x1000 ;; callvalue
i64.const -1 ;; all gas
i32.const 0x2000 ;; return_data_len ptr
call $call_contract
Expand Down
33 changes: 27 additions & 6 deletions arbos/programs/data_pricer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,42 @@ type dataPricer struct {
inertia storage.StorageBackedUint32
}

const (
demandOffset uint64 = iota
bytesPerSecondOffset
lastUpdateTimeOffset
minPriceOffset
inertiaOffset
)

const initialDemand = 0 // no demand
const initialHourlyBytes = 4 * (1 << 40) / (365 * 24) // 4Tb total footprint
const initialBytesPerSecond = initialHourlyBytes / (60 * 60) // refill each hour
const initialLastUpdateTime = 1421388000 // the day it all began
const initialMinPrice = 82928201 // 5Mb = $1
const initialInertia = 70177364 // expensive at 4Tb

func initDataPricer(sto *storage.Storage) {
demand := sto.OpenStorageBackedUint32(demandOffset)
bytesPerSecond := sto.OpenStorageBackedUint32(bytesPerSecondOffset)
lastUpdateTime := sto.OpenStorageBackedUint64(lastUpdateTimeOffset)
minPrice := sto.OpenStorageBackedUint32(minPriceOffset)
inertia := sto.OpenStorageBackedUint32(inertiaOffset)
_ = demand.Set(initialDemand)
_ = bytesPerSecond.Set(initialBytesPerSecond)
_ = lastUpdateTime.Set(initialLastUpdateTime)
_ = minPrice.Set(initialMinPrice)
_ = inertia.Set(initialInertia)
}

func openDataPricer(sto *storage.Storage) *dataPricer {
return &dataPricer{
backingStorage: sto,
demand: sto.OpenStorageBackedUint32(initialDemand),
bytesPerSecond: sto.OpenStorageBackedUint32(initialBytesPerSecond),
lastUpdateTime: sto.OpenStorageBackedUint64(initialLastUpdateTime),
minPrice: sto.OpenStorageBackedUint32(initialMinPrice),
inertia: sto.OpenStorageBackedUint32(initialInertia),
demand: sto.OpenStorageBackedUint32(demandOffset),
bytesPerSecond: sto.OpenStorageBackedUint32(bytesPerSecondOffset),
lastUpdateTime: sto.OpenStorageBackedUint64(lastUpdateTimeOffset),
minPrice: sto.OpenStorageBackedUint32(minPriceOffset),
inertia: sto.OpenStorageBackedUint32(inertiaOffset),
}
}

Expand All @@ -59,6 +80,6 @@ func (p *dataPricer) updateModel(tempBytes uint32, time uint64) (*big.Int, error
exponent := arbmath.OneInBips * arbmath.Bips(demand) / arbmath.Bips(inertia)
multiplier := arbmath.ApproxExpBasisPoints(exponent, 12).Uint64()
costPerByte := arbmath.SaturatingUMul(uint64(minPrice), multiplier)
costInWei := arbmath.SaturatingUMul(costPerByte, uint64(tempBytes))
costInWei := arbmath.SaturatingUMul(costPerByte, uint64(tempBytes)) / 10000
return arbmath.UintToBig(costInWei), nil
}
29 changes: 18 additions & 11 deletions arbos/programs/programs.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ func Initialize(sto *storage.Storage) {
_ = expiryDays.Set(initialExpiryDays)
_ = keepaliveDays.Set(initialKeepaliveDays)
_ = version.Set(1)

initDataPricer(sto.OpenSubStorage(dataPricerKey))
}

func Open(sto *storage.Storage) *Programs {
Expand Down Expand Up @@ -206,51 +208,56 @@ func (p Programs) SetKeepaliveDays(days uint16) error {
}

func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode bool) (
uint16, common.Hash, common.Hash, bool, error,
uint16, common.Hash, common.Hash, *big.Int, bool, error,
) {
statedb := evm.StateDB
codeHash := statedb.GetCodeHash(address)
burner := p.programs.Burner()

stylusVersion, err := p.StylusVersion()
if err != nil {
return 0, codeHash, common.Hash{}, false, err
return 0, codeHash, common.Hash{}, nil, false, err
}
currentVersion, err := p.programExists(codeHash)
if err != nil {
return 0, codeHash, common.Hash{}, false, err
return 0, codeHash, common.Hash{}, nil, false, err
}
if currentVersion == stylusVersion {
// already activated and up to date
return 0, codeHash, common.Hash{}, false, ProgramUpToDateError()
return 0, codeHash, common.Hash{}, nil, false, ProgramUpToDateError()
}
wasm, err := getWasm(statedb, address)
if err != nil {
return 0, codeHash, common.Hash{}, false, err
return 0, codeHash, common.Hash{}, nil, false, err
}

// require the program's footprint not exceed the remaining memory budget
pageLimit, err := p.PageLimit()
if err != nil {
return 0, codeHash, common.Hash{}, false, err
return 0, codeHash, common.Hash{}, nil, false, err
}
pageLimit = arbmath.SaturatingUSub(pageLimit, statedb.GetStylusPagesOpen())

info, err := activateProgram(statedb, address, wasm, pageLimit, stylusVersion, debugMode, burner)
if err != nil {
return 0, codeHash, common.Hash{}, true, err
return 0, codeHash, common.Hash{}, nil, true, err
}
if err := p.moduleHashes.Set(codeHash, info.moduleHash); err != nil {
return 0, codeHash, common.Hash{}, true, err
return 0, codeHash, common.Hash{}, nil, true, err
}

estimateKb, err := arbmath.IntToUint24((info.asmEstimate + 1023) / 1024) // stored in kilobytes
if err != nil {
return 0, codeHash, common.Hash{}, true, err
return 0, codeHash, common.Hash{}, nil, true, err
}
initGas24, err := arbmath.IntToUint24(info.initGas)
if err != nil {
return 0, codeHash, common.Hash{}, true, err
return 0, codeHash, common.Hash{}, nil, true, err
}

dataFee, err := p.dataPricer.updateModel(info.asmEstimate, evm.Context.Time)
if err != nil {
return 0, codeHash, common.Hash{}, nil, true, err
}

programData := Program{
Expand All @@ -260,7 +267,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode
footprint: info.footprint,
activatedAt: evm.Context.Time,
}
return stylusVersion, codeHash, info.moduleHash, false, p.setProgram(codeHash, programData)
return stylusVersion, codeHash, info.moduleHash, dataFee, false, p.setProgram(codeHash, programData)
}

func (p Programs) CallProgram(
Expand Down
39 changes: 27 additions & 12 deletions precompiles/ArbWasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,39 @@ func (con ArbWasm) ActivateProgram(c ctx, evm mech, value huge, program addr) (u
if err := c.Burn(1659168); err != nil {
return 0, err
}
version, codeHash, moduleHash, takeAllGas, err := c.State.Programs().ActivateProgram(evm, program, debug)
version, codeHash, moduleHash, dataFee, takeAllGas, err := c.State.Programs().ActivateProgram(evm, program, debug)
if takeAllGas {
_ = c.BurnOut()
}
if err != nil {
return version, err
}
if err := con.payActivationDataFee(c, evm, value, dataFee); err != nil {
return version, err
}
return version, con.ProgramActivated(c, evm, codeHash, moduleHash, program, version)
}

// Pays the data component of activation costs
func (con ArbWasm) payActivationDataFee(c ctx, evm mech, value, dataFee huge) error {
if arbmath.BigLessThan(value, dataFee) {
return con.ProgramInsufficientValueError(value, dataFee)
}
network, err := c.State.NetworkFeeAccount()
if err != nil {
return err
}
scenario := util.TracingDuringEVM
repay := arbmath.BigSub(value, dataFee)

// transfer the fee to the network account, and the rest back to the user
err = util.TransferBalance(&con.Address, &network, dataFee, evm, scenario, "activate")
if err != nil {
return err
}
return util.TransferBalance(&con.Address, &c.caller, repay, evm, scenario, "reimburse")
}

// Gets the latest stylus version
func (con ArbWasm) StylusVersion(c ctx, evm mech) (uint16, error) {
return c.State.Programs().StylusVersion()
Expand Down Expand Up @@ -80,21 +103,13 @@ func (con ArbWasm) CodehashVersion(c ctx, evm mech, codehash bytes32) (uint16, e
return c.State.Programs().CodehashVersion(codehash, evm.Context.Time)
}

// @notice extends a program's expiration date (reverts if too soon)
// Extends a program's expiration date (reverts if too soon)
func (con ArbWasm) CodehashKeepalive(c ctx, evm mech, value huge, codehash bytes32) error {
cost, err := c.State.Programs().ProgramKeepalive(codehash, evm.Context.Time)
if err != nil {
return err
}
if arbmath.BigLessThan(value, cost) {
return con.ProgramInsufficientValueError(value, cost)
}
network, err := c.State.NetworkFeeAccount()
dataFee, err := c.State.Programs().ProgramKeepalive(codehash, evm.Context.Time)
if err != nil {
return err
}
scenario := util.TracingDuringEVM
return util.TransferBalance(&con.Address, &network, value, evm, scenario, "activate")
return con.payActivationDataFee(c, evm, value, dataFee)
}

// Gets the stylus version that program at addr was most recently compiled with
Expand Down
37 changes: 22 additions & 15 deletions system_tests/program_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import (
"github.com/wasmerio/wasmer-go/wasmer"
)

var oneEth = arbmath.UintToBig(1e18)

func TestProgramKeccak(t *testing.T) {
t.Parallel()
keccakTest(t, true)
Expand Down Expand Up @@ -203,18 +205,18 @@ func testActivateTwice(t *testing.T, jit bool) {
args := argsForMulticall(vm.CALL, types.ArbWasmAddress, nil, pack(activateProgram(keccakA)))
args = multicallAppend(args, vm.CALL, types.ArbDebugAddress, pack(legacyError()))

tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, nil, args)
tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, oneEth, args)
Require(t, l2client.SendTransaction(ctx, tx))
EnsureTxFailed(t, ctx, l2client, tx)

// Ensure the revert also reverted keccak's activation
checkReverts()

// Compile keccak program A, then call into B, which should succeed due to being the same codehash
args = argsForMulticall(vm.CALL, types.ArbWasmAddress, nil, pack(activateProgram(keccakA)))
// Activate keccak program A, then call into B, which should succeed due to being the same codehash
args = argsForMulticall(vm.CALL, types.ArbWasmAddress, oneEth, pack(activateProgram(keccakA)))
args = multicallAppend(args, vm.CALL, mockAddr, pack(callKeccak(keccakB, keccakArgs)))

tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, nil, args)
tx = l2info.PrepareTxTo("Owner", &multiAddr, 1e9, oneEth, args)
ensure(tx, l2client.SendTransaction(ctx, tx))

validateBlocks(t, 7, jit, ctx, node, l2client)
Expand Down Expand Up @@ -611,6 +613,8 @@ func testCreate(t *testing.T, jit bool) {
ctx, node, l2info, l2client, auth, cleanup := setupProgramTest(t, jit)
defer cleanup()
createAddr := deployWasm(t, ctx, auth, l2client, rustFile("create"))
activateAuth := auth
activateAuth.Value = oneEth

ensure := func(tx *types.Transaction, err error) *types.Receipt {
t.Helper()
Expand Down Expand Up @@ -639,10 +643,10 @@ func testCreate(t *testing.T, jit bool) {
Fatal(t, "storage.wasm has the wrong balance", balance, startValue)
}

// compile the program
// activate the program
arbWasm, err := precompilesgen.NewArbWasm(types.ArbWasmAddress, l2client)
Require(t, err)
tx, err = arbWasm.ActivateProgram(&auth, storeAddr)
tx, err = arbWasm.ActivateProgram(&activateAuth, storeAddr)
if err != nil {
if !strings.Contains(err.Error(), "ProgramUpToDate") {
Fatal(t, err)
Expand Down Expand Up @@ -825,7 +829,7 @@ func testMemory(t *testing.T, jit bool) {
multiAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall"))
growCallAddr := deployWasm(t, ctx, auth, l2client, watFile("grow-and-call"))

expectFailure := func(to common.Address, data []byte) {
expectFailure := func(to common.Address, data []byte, value *big.Int) {
t.Helper()
msg := ethereum.CallMsg{
To: &to,
Expand All @@ -839,7 +843,7 @@ func testMemory(t *testing.T, jit bool) {
}

// execute onchain for proving's sake
tx := l2info.PrepareTxTo("Owner", &to, 1e9, nil, data)
tx := l2info.PrepareTxTo("Owner", &to, 1e9, value, data)
Require(t, l2client.SendTransaction(ctx, tx))
EnsureTxFailed(t, ctx, l2client, tx)
}
Expand All @@ -863,9 +867,9 @@ func testMemory(t *testing.T, jit bool) {

// check that we'd normally run out of gas
ensure(arbOwner.SetMaxTxGasLimit(&auth, 32000000))
expectFailure(multiAddr, args)
expectFailure(multiAddr, args, oneEth)

// check that compilation fails when out of memory
// check that activation fails when out of memory
wasm, _ := readWasmFile(t, watFile("grow-120"))
growHugeAddr := deployContract(t, ctx, auth, l2client, wasm)
colors.PrintGrey("memory.wat ", memoryAddr)
Expand All @@ -878,16 +882,16 @@ func testMemory(t *testing.T, jit bool) {
return data
}
args = arbmath.ConcatByteSlices([]byte{60}, types.ArbWasmAddress[:], pack(activate(growHugeAddr)))
expectFailure(growCallAddr, args) // consumes 64, then tries to compile something 120
expectFailure(growCallAddr, args, oneEth) // consumes 64, then tries to compile something 120

// check that compilation then succeeds
// check that arctivation then succeeds
args[0] = 0x00
tx = l2info.PrepareTxTo("Owner", &growCallAddr, 1e9, nil, args)
tx = l2info.PrepareTxTo("Owner", &growCallAddr, 1e9, oneEth, args)
ensure(tx, l2client.SendTransaction(ctx, tx)) // TODO: check receipt after compilation pricing

// check footprint can induce a revert
args = arbmath.ConcatByteSlices([]byte{122}, growCallAddr[:], []byte{0}, common.Address{}.Bytes())
expectFailure(growCallAddr, args)
expectFailure(growCallAddr, args, oneEth)

// check same call would have succeeded with fewer pages
args = arbmath.ConcatByteSlices([]byte{119}, growCallAddr[:], []byte{0}, common.Address{}.Bytes())
Expand Down Expand Up @@ -927,6 +931,7 @@ func testActivateFails(t *testing.T, jit bool) {

blockToValidate := uint64(0)
timed(t, "activate bad-export", func() {
auth.Value = oneEth
tx, err := arbWasm.ActivateProgram(&auth, badExportAddr)
Require(t, err)
txRes, err := WaitForTx(ctx, l2client, tx.Hash(), time.Second*5)
Expand Down Expand Up @@ -1009,7 +1014,7 @@ func testSdkStorage(t *testing.T, jit bool) {
check()
}

func TestProgramAcivationLogs(t *testing.T) {
func TestProgramActivationLogs(t *testing.T) {
t.Parallel()
ctx, _, _, l2client, auth, cleanup := setupProgramTest(t, true)
defer cleanup()
Expand All @@ -1023,6 +1028,7 @@ func TestProgramAcivationLogs(t *testing.T) {

programAddress := deployContract(t, ctx, nolimitAuth, l2client, wasm)

auth.Value = oneEth
tx, err := arbWasm.ActivateProgram(&auth, programAddress)
Require(t, err)
receipt, err := EnsureTxSucceeded(ctx, l2client, tx)
Expand Down Expand Up @@ -1139,6 +1145,7 @@ func activateWasm(
Require(t, err)

timed(t, "activate "+name, func() {
auth.Value = oneEth
tx, err := arbWasm.ActivateProgram(&auth, program)
Require(t, err)
_, err = EnsureTxSucceeded(ctx, l2client, tx)
Expand Down

0 comments on commit 135fee7

Please sign in to comment.