Commit c0a7e5fc authored by refcell's avatar refcell Committed by GitHub

fix(op-challenger): Packed Claim Clock (#9582)

parent 97b107b6
......@@ -3,6 +3,7 @@ package contracts
import (
"context"
"fmt"
"math"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
......@@ -317,6 +318,21 @@ func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall {
return f.contract.Call(methodResolve)
}
// decodeClock decodes a uint128 into a Clock duration and timestamp.
func decodeClock(clock *big.Int) *types.Clock {
maxUint64 := new(big.Int).Add(new(big.Int).SetUint64(math.MaxUint64), big.NewInt(1))
remainder := new(big.Int)
quotient, _ := new(big.Int).QuoRem(clock, maxUint64, remainder)
return types.NewClock(quotient.Uint64(), remainder.Uint64())
}
// packClock packs the Clock duration and timestamp into a uint128.
func packClock(c *types.Clock) *big.Int {
duration := new(big.Int).SetUint64(c.Duration)
encoded := new(big.Int).Lsh(duration, 64)
return new(big.Int).Or(encoded, new(big.Int).SetUint64(c.Timestamp))
}
func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0)
counteredBy := result.GetAddress(1)
......@@ -333,7 +349,7 @@ func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, cont
},
CounteredBy: counteredBy,
Claimant: claimant,
Clock: clock.Uint64(),
Clock: decodeClock(clock),
ContractIndex: contractIndex,
ParentContractIndex: int(parentIndex),
}
......
......@@ -105,6 +105,44 @@ func TestSimpleGetters(t *testing.T) {
}
}
func TestClock_EncodingDecoding(t *testing.T) {
t.Run("DurationAndTimestamp", func(t *testing.T) {
by := common.Hex2Bytes("00000000000000050000000000000002")
encoded := new(big.Int).SetBytes(by)
clock := decodeClock(encoded)
require.Equal(t, uint64(5), clock.Duration)
require.Equal(t, uint64(2), clock.Timestamp)
require.Equal(t, encoded, packClock(clock))
})
t.Run("ZeroDuration", func(t *testing.T) {
by := common.Hex2Bytes("00000000000000000000000000000002")
encoded := new(big.Int).SetBytes(by)
clock := decodeClock(encoded)
require.Equal(t, uint64(0), clock.Duration)
require.Equal(t, uint64(2), clock.Timestamp)
require.Equal(t, encoded, packClock(clock))
})
t.Run("ZeroTimestamp", func(t *testing.T) {
by := common.Hex2Bytes("00000000000000050000000000000000")
encoded := new(big.Int).SetBytes(by)
clock := decodeClock(encoded)
require.Equal(t, uint64(5), clock.Duration)
require.Equal(t, uint64(0), clock.Timestamp)
require.Equal(t, encoded, packClock(clock))
})
t.Run("ZeroClock", func(t *testing.T) {
by := common.Hex2Bytes("00000000000000000000000000000000")
encoded := new(big.Int).SetBytes(by)
clock := decodeClock(encoded)
require.Equal(t, uint64(0), clock.Duration)
require.Equal(t, uint64(0), clock.Timestamp)
require.Equal(t, encoded.Uint64(), packClock(clock).Uint64())
})
}
func TestGetOracleAddr(t *testing.T) {
stubRpc, game := setupFaultDisputeGameTest(t)
stubRpc.SetResponse(fdgAddr, methodVM, batching.BlockLatest, nil, []interface{}{vmAddr})
......@@ -136,7 +174,7 @@ func TestGetClaim(t *testing.T) {
},
CounteredBy: counteredBy,
Claimant: claimant,
Clock: 1234,
Clock: decodeClock(big.NewInt(1234)),
ContractIndex: int(idx.Uint64()),
ParentContractIndex: 1,
}, status)
......@@ -152,7 +190,7 @@ func TestGetAllClaims(t *testing.T) {
},
CounteredBy: common.Address{0x01},
Claimant: common.Address{0x02},
Clock: 1234,
Clock: decodeClock(big.NewInt(1234)),
ContractIndex: 0,
ParentContractIndex: math.MaxUint32,
}
......@@ -164,7 +202,7 @@ func TestGetAllClaims(t *testing.T) {
},
CounteredBy: common.Address{0x02},
Claimant: common.Address{0x01},
Clock: 4455,
Clock: decodeClock(big.NewInt(4455)),
ContractIndex: 1,
ParentContractIndex: 0,
}
......@@ -175,7 +213,7 @@ func TestGetAllClaims(t *testing.T) {
Bond: big.NewInt(5),
},
Claimant: common.Address{0x02},
Clock: 7777,
Clock: decodeClock(big.NewInt(7777)),
ContractIndex: 2,
ParentContractIndex: 1,
}
......@@ -253,7 +291,7 @@ func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
claim.Bond,
claim.Value,
claim.Position.ToGIndex(),
big.NewInt(int64(claim.Clock)),
packClock(claim.Clock),
})
}
......
......@@ -146,7 +146,7 @@ type Claim struct {
// to be changed/removed to avoid invalid/stale contract state.
CounteredBy common.Address
Claimant common.Address
Clock uint64
Clock *Clock
// Location of the claim & it's parent inside the contract. Does not exist
// for claims that have not made it to the contract.
ContractIndex int
......@@ -157,3 +157,35 @@ type Claim struct {
func (c *Claim) IsRoot() bool {
return c.Position.IsRootPosition()
}
// ChessTime returns the amount of time accumulated in the chess clock.
// Does not assume the claim is countered and uses the specified time
// to calculate the time since the claim was posted.
func (c *Claim) ChessTime(now time.Time) time.Duration {
timeSince := int64(0)
if now.Unix() > int64(c.Clock.Timestamp) {
timeSince = now.Unix() - int64(c.Clock.Timestamp)
}
return time.Duration(c.Clock.Duration) + time.Duration(timeSince)
}
// Clock is a packed uint128 with the upper 64 bits being the
// duration and the lower 64 bits being the timestamp.
// ┌────────────┬────────────────┐
// │ Bits │ Value │
// ├────────────┼────────────────┤
// │ [0, 64) │ Duration │
// │ [64, 128) │ Timestamp │
// └────────────┴────────────────┘
type Clock struct {
Duration uint64
Timestamp uint64
}
// NewClock creates a new Clock instance.
func NewClock(duration uint64, timestamp uint64) *Clock {
return &Clock{
Duration: duration,
Timestamp: timestamp,
}
}
......@@ -3,10 +3,67 @@ package types
import (
"math/big"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestClaim_RemainingDuration(t *testing.T) {
tests := []struct {
name string
duration uint64
timestamp uint64
now int64
expected uint64
}{
{
name: "AllZeros",
duration: 0,
timestamp: 0,
now: 0,
expected: 0,
},
{
name: "ZeroTimestamp",
duration: 5,
timestamp: 0,
now: 0,
expected: 5,
},
{
name: "ZeroTimestampWithNow",
duration: 5,
timestamp: 0,
now: 10,
expected: 15,
},
{
name: "ZeroNow",
duration: 5,
timestamp: 10,
now: 0,
expected: 5,
},
{
name: "ValidTimeSinze",
duration: 20,
timestamp: 10,
now: 15,
expected: 25,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
claim := &Claim{
Clock: NewClock(test.duration, test.timestamp),
}
require.Equal(t, time.Duration(test.expected), claim.ChessTime(time.Unix(test.now, 0)))
})
}
}
func TestNewPreimageOracleData(t *testing.T) {
t.Run("LocalData", func(t *testing.T) {
data := NewPreimageOracleData([]byte{1, 2, 3}, []byte{4, 5, 6}, 7)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment