Skip to content

Commit

Permalink
noto: allow Paladin to choose the lock ID
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Richardson <[email protected]>
  • Loading branch information
awrichar committed Jan 16, 2025
1 parent 3bb8b67 commit ff1cafd
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 27 deletions.
25 changes: 21 additions & 4 deletions domains/integration-test/pvp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ func decodeTransactionResult(t *testing.T, resultInput map[string]any) *testbed.
return &result
}

func extractLockID(noto noto.Noto, invokeResult *testbed.TransactionResult) (tktypes.Bytes32, error) {
for _, state := range invokeResult.InfoStates {
if state.Schema.String() == noto.LockInfoSchemaID() {
var lockInfo map[string]any
err := json.Unmarshal(state.Data, &lockInfo)
if err != nil {
return tktypes.Bytes32{}, err
}
return tktypes.MustParseBytes32(lockInfo["lockId"].(string)), nil
}
}
return tktypes.Bytes32{}, nil
}

// TODO: make this easier to extract
func buildUnlock(ctx context.Context, notoDomain noto.Noto, abi abi.ABI, lockID tktypes.Bytes32, prepareUnlockResult *testbed.TransactionResult) ([]*pldapi.StateEncoded, []*pldapi.StateEncoded, []byte, error) {
notoInputStates := make([]*pldapi.StateEncoded, 0, len(prepareUnlockResult.ReadStates))
Expand Down Expand Up @@ -391,13 +405,16 @@ func TestNotoForZeto(t *testing.T) {
TokenValue2: tktypes.Int64ToInt256(1),
})

lockID := tktypes.RandBytes32()

log.L(ctx).Infof("Prepare the Noto transfer")
noto.Lock(ctx, &nototypes.LockParams{
LockID: lockID,
notoLock := noto.Lock(ctx, &nototypes.LockParams{
Amount: tktypes.Int64ToInt256(1),
}).SignAndSend(alice).Wait()
notoLockResult := decodeTransactionResult(t, notoLock)

lockID, err := extractLockID(notoDomain, notoLockResult)
require.NoError(t, err)
require.NotEmpty(t, lockID)

time.Sleep(1 * time.Second) // TODO: remove
notoPrepareUnlock := noto.PrepareUnlock(ctx, &nototypes.UnlockParams{
LockID: lockID,
Expand Down
1 change: 1 addition & 0 deletions domains/noto/internal/msgs/en_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ var (
MsgBurnNotAllowed = ffe("PD200025", "Burn is not enabled")
MsgNoStatesSpecified = ffe("PD200026", "No states were specified")
MsgUnlockNotAllowed = ffe("PD200027", "Cannot unlock states owned by '%s'")
MsgLockIDNotFound = ffe("PD200028", "Lock ID not found")
MsgMissingStateData = ffe("PD200029", "Missing state data for one or more states: %s")
MsgLockNotAllowed = ffe("PD200030", "Lock is not enabled")
MsgUnlockOnlyCreator = ffe("PD200031", "Only the lock creator can perform unlock: expected=%s actual=%s")
Expand Down
19 changes: 17 additions & 2 deletions domains/noto/internal/noto/e2e_noto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,19 @@ func findLockedCoins(t *testing.T, ctx context.Context, rpc rpcbackend.Backend,
return notoCoins
}

func extractLockID(noto *Noto, invokeResult *testbed.TransactionResult) (tktypes.Bytes32, error) {
for _, state := range invokeResult.InfoStates {
if state.Schema.String() == noto.lockInfoSchema.Id {
lockInfo, err := noto.unmarshalLock(string(state.Data))
if err != nil {
return tktypes.Bytes32{}, err
}
return lockInfo.LockID, nil
}
}
return tktypes.Bytes32{}, nil
}

// TODO: make this easier to extract
func buildUnlock(notoDomain *Noto, lockID tktypes.Bytes32, prepareUnlockResult *testbed.TransactionResult) *NotoUnlockParams {
lockedInputs := make([]string, 0)
Expand Down Expand Up @@ -528,14 +541,12 @@ func TestNotoLock(t *testing.T) {
assert.Equal(t, recipient1Key.Verifier.Verifier, coins[0].Data.Owner.String())

log.L(ctx).Infof("Lock 50 from recipient1")
lockID := tktypes.RandBytes32()
rpcerr = rpc.CallRPC(ctx, &invokeResult, "testbed_invoke", &pldapi.TransactionInput{
TransactionBase: pldapi.TransactionBase{
From: recipient1Name,
To: &notoAddress,
Function: "lock",
Data: toJSON(t, &types.LockParams{
LockID: lockID,
Amount: tktypes.Int64ToInt256(50),
}),
},
Expand All @@ -545,6 +556,10 @@ func TestNotoLock(t *testing.T) {
require.NoError(t, rpcerr.Error())
}

lockID, err := extractLockID(noto, &invokeResult)
require.NoError(t, err)
require.NotEmpty(t, lockID)

coins = findLockedCoins(t, ctx, rpc, noto, notoAddress, nil)
require.Len(t, coins, 1)
assert.Equal(t, int64(50), coins[0].Data.Amount.Int().Int64())
Expand Down
39 changes: 26 additions & 13 deletions domains/noto/internal/noto/handler_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ func (h *lockHandler) ValidateParams(ctx context.Context, config *types.NotoPars
if err := json.Unmarshal([]byte(params), &lockParams); err != nil {
return nil, err
}
if lockParams.LockID.IsZero() {
return nil, i18n.NewError(ctx, msgs.MsgParameterRequired, "lockId")
}
if lockParams.Amount == nil || lockParams.Amount.Int().Sign() != 1 {
return nil, i18n.NewError(ctx, msgs.MsgParameterGreaterThanZero, "amount")
}
Expand Down Expand Up @@ -95,7 +92,8 @@ func (h *lockHandler) Assemble(ctx context.Context, tx *types.ParsedTransaction,
return nil, err
}

lockedOutputStates, err := h.noto.prepareLockedOutputs(params.LockID, fromAddress, params.Amount, []string{notary, tx.Transaction.From})
lockID := tktypes.RandBytes32()
lockedOutputStates, err := h.noto.prepareLockedOutputs(lockID, fromAddress, params.Amount, []string{notary, tx.Transaction.From})
if err != nil {
return nil, err
}
Expand All @@ -115,7 +113,7 @@ func (h *lockHandler) Assemble(ctx context.Context, tx *types.ParsedTransaction,
if err != nil {
return nil, err
}
lockState, err := h.noto.prepareLockInfo(params.LockID, fromAddress, nil, []string{notary, tx.Transaction.From})
lockState, err := h.noto.prepareLockInfo(lockID, fromAddress, nil, []string{notary, tx.Transaction.From})
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -200,9 +198,7 @@ func (h *lockHandler) Endorse(ctx context.Context, tx *types.ParsedTransaction,
}, nil
}

func (h *lockHandler) baseLedgerInvoke(ctx context.Context, tx *types.ParsedTransaction, req *prototk.PrepareTransactionRequest) (*TransactionWrapper, error) {
inParams := tx.Params.(*types.LockParams)

func (h *lockHandler) baseLedgerInvoke(ctx context.Context, lockID tktypes.Bytes32, tx *types.ParsedTransaction, req *prototk.PrepareTransactionRequest) (*TransactionWrapper, error) {
inputs := make([]string, len(req.InputStates))
for i, state := range req.InputStates {
inputs[i] = state.Id
Expand Down Expand Up @@ -230,7 +226,7 @@ func (h *lockHandler) baseLedgerInvoke(ctx context.Context, tx *types.ParsedTran
return nil, err
}
params := &NotoLockParams{
LockID: inParams.LockID,
LockID: lockID,
Inputs: inputs,
Outputs: remainderOutputs,
LockedOutputs: []string{lockedOutput.String()},
Expand All @@ -247,7 +243,7 @@ func (h *lockHandler) baseLedgerInvoke(ctx context.Context, tx *types.ParsedTran
}, nil
}

func (h *lockHandler) hookInvoke(ctx context.Context, tx *types.ParsedTransaction, req *prototk.PrepareTransactionRequest, baseTransaction *TransactionWrapper) (*TransactionWrapper, error) {
func (h *lockHandler) hookInvoke(ctx context.Context, lockID tktypes.Bytes32, tx *types.ParsedTransaction, req *prototk.PrepareTransactionRequest, baseTransaction *TransactionWrapper) (*TransactionWrapper, error) {
inParams := tx.Params.(*types.LockParams)

fromAddress, err := h.noto.findEthAddressVerifier(ctx, "from", tx.Transaction.From, req.ResolvedVerifiers)
Expand All @@ -261,7 +257,7 @@ func (h *lockHandler) hookInvoke(ctx context.Context, tx *types.ParsedTransactio
}
params := &LockHookParams{
Sender: fromAddress,
LockID: inParams.LockID,
LockID: lockID,
From: fromAddress,
Amount: inParams.Amount,
Data: inParams.Data,
Expand All @@ -288,14 +284,31 @@ func (h *lockHandler) hookInvoke(ctx context.Context, tx *types.ParsedTransactio
}, nil
}

func (h *lockHandler) extractLockID(ctx context.Context, req *prototk.PrepareTransactionRequest) (tktypes.Bytes32, error) {
lockStates := h.noto.filterSchema(req.InfoStates, []string{h.noto.lockInfoSchema.Id})
if len(lockStates) == 1 {
lock, err := h.noto.unmarshalLock(lockStates[0].StateDataJson)
if err != nil {
return tktypes.Bytes32{}, err
}
return lock.LockID, nil
}
return tktypes.Bytes32{}, i18n.NewError(ctx, msgs.MsgLockIDNotFound)
}

func (h *lockHandler) Prepare(ctx context.Context, tx *types.ParsedTransaction, req *prototk.PrepareTransactionRequest) (*prototk.PrepareTransactionResponse, error) {
baseTransaction, err := h.baseLedgerInvoke(ctx, tx, req)
lockID, err := h.extractLockID(ctx, req)
if err != nil {
return nil, err
}

baseTransaction, err := h.baseLedgerInvoke(ctx, lockID, tx, req)
if err != nil {
return nil, err
}

if tx.DomainConfig.NotaryMode == types.NotaryModeHooks.Enum() {
hookTransaction, err := h.hookInvoke(ctx, tx, req, baseTransaction)
hookTransaction, err := h.hookInvoke(ctx, lockID, tx, req, baseTransaction)
if err != nil {
return nil, err
}
Expand Down
4 changes: 4 additions & 0 deletions domains/noto/internal/noto/noto.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ func (n *Noto) LockedCoinSchemaID() string {
return n.lockedCoinSchema.Id
}

func (n *Noto) LockInfoSchemaID() string {
return n.lockInfoSchema.Id
}

func (n *Noto) ConfigureDomain(ctx context.Context, req *prototk.ConfigureDomainRequest) (*prototk.ConfigureDomainResponse, error) {
err := json.Unmarshal([]byte(req.ConfigJson), &n.config)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions domains/noto/pkg/noto/noto.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Noto interface {
Name() string
CoinSchemaID() string
LockedCoinSchemaID() string
LockInfoSchemaID() string
}

func New(callbacks plugintk.DomainCallbacks) Noto {
Expand Down
1 change: 0 additions & 1 deletion domains/noto/pkg/types/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ type ApproveParams struct {
}

type LockParams struct {
LockID tktypes.Bytes32 `json:"lockId"`
Amount *tktypes.HexUint256 `json:"amount"`
Data tktypes.HexBytes `json:"data"`
}
Expand Down
14 changes: 9 additions & 5 deletions example/lock/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,19 @@ async function main(): Promise<boolean> {

// Lock some tokens
logger.log("Locking cash from investor1...");
const lockId = randomBytes(32).toString("hex");
receipt = await notoCash.using(paladin2).lock(investor1, {
lockId,
amount: 100,
data: "0x",
});
if (!checkReceipt(receipt)) return false;
receipt = await paladin2.getTransactionReceipt(receipt.id, true);

let domainReceipt = receipt?.domainReceipt as INotoDomainReceipt | undefined;
const lockId = domainReceipt?.lockInfo?.lockId;
if (lockId === undefined) {
logger.error("No lock ID found in domain receipt");
return false;
}

// Prepare unlock operation
logger.log("Preparing unlock to investor2...");
Expand All @@ -92,9 +98,7 @@ async function main(): Promise<boolean> {
});
if (!checkReceipt(receipt)) return false;
receipt = await paladin2.getTransactionReceipt(receipt.id, true);
const domainReceipt = receipt?.domainReceipt as
| INotoDomainReceipt
| undefined;
domainReceipt = receipt?.domainReceipt as INotoDomainReceipt | undefined;

// Approve unlock operation
logger.log("Delegating lock to investor2...");
Expand Down
1 change: 0 additions & 1 deletion sdk/typescript/src/domains/noto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export interface NotoApproveTransferParams {
}

export interface NotoLockParams {
lockId: string;
amount: string | number;
data: string;
}
Expand Down
1 change: 0 additions & 1 deletion solidity/contracts/domains/interfaces/INotoPrivate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ interface INotoPrivate {
) external;

function lock(
bytes32 lockId,
uint256 amount,
bytes calldata data
) external;
Expand Down

0 comments on commit ff1cafd

Please sign in to comment.