Skip to content

Commit

Permalink
Merge pull request #444 from waves-exchange/l2mp-new-swap
Browse files Browse the repository at this point in the history
L2mp new swap
  • Loading branch information
JunkiJay authored Mar 11, 2024
2 parents 28bbc00 + 24aa4f2 commit 0e307d2
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 111 deletions.
35 changes: 35 additions & 0 deletions docs/l2mp_swap/l2mp_swap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# L2MP Swap

## Keys
| key | type | value |
| :----------------------------------- | --------: | :-------------------------------------------------------------- |
| `%s__stakingAddress` | `String` | `'3PAbc...'` |
| `%s__assetInId` | `String` | `'ABC...'` |
| `%s__assetOutId` | `String` | `'ABC...'` |
| `%s__assetOutPrice` | `Integer` | `1000000` |
| `%s%s__stats__totalIn` | `Integer` | `123456` |
| `%s%s__stats__totalOut` | `Integer` | `12345678` |
| `%s%s%s__stats__totalIn__{address}` | `Integer` | `12345` |
| `%s%s%s__stats__totalOut__{address}` | `Integer` | `1234567` |
| `%s%s%s__history__{address}__{txId}` | `String` | `'%d%d%b%s__{AmountIn}__{amountOut}__{isStake}__{nodeAddress}'` |

# Functions

## Swap
- Should be with 1 payment in XTN
```
@Callable(i)
func swap()
```

## Swap and Stake
- Should be with 1 payment in XTN

Arguments:
- `stakingNode` - base58 string of staking node address

_Note: If `stakingNode` is equal to empty string (`""`) it swaps without staking_
```
@Callable(i)
func swapAndStake(stakingNode: String)
```
38 changes: 38 additions & 0 deletions migrations/2024_02_05_l2mp_new_swap/01_l2mp_new_swap_data_tx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"type": 12,
"fee": 500000,
"version": 2,
"senderPublicKey": "EfRKQWb1FWaWmo9fkorRwMA4BrSzUWNAVhXqB9vLmv7g",
"data": [
{
"key": "%s__assetInId",
"type": "string",
"value": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p"
},
{
"key": "%s__assetOutId",
"type": "string",
"value": "7scqyYoVsNrpWbTAc78eRqNVcYLxMPzZs8EQfX7ruJAg"
},
{
"key": "%s__assetOutPrice",
"type": "integer",
"value": 1000000
},
{
"key": "%s__stakingAddress",
"type": "string",
"value": "3PF1QWiN4A3CCJoKh1kGtqYqMNaBnRZbC6t"
},
{
"key": "%s__allowedAddress",
"type": "string",
"value": "3P87zrU6UmJq7wEZz84Fm9PJXr9tLGfEwPB"
},
{
"key": "%s__adminAddressList",
"type": "string",
"value": "3PBUguU83ccjXz7NBbR3vn67fVHawbxwaTH__3PLU2bd4CDhHC35bf5qsQxYLpkEkpgkrcvc__3PF3AgB3cEE8F9ZUxuA9aLyM2PBjKrrszZQ__3PHdaP2BLUXFacnqZm6WoFjidvzqZZMbb8j__3P33RBZUXcdx5JW918DqFfrscEuEZpAPUPz"
}
]
}
81 changes: 56 additions & 25 deletions ride/l2mp_swap.ride
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
let contractFile = "l2mp_swap.ride"
let SEP = "__"
let scale8 = 100_000_000
let scale18 = 1_000_000_000_000_000_000
let ADDRESS_BYTES_SIZE = 26
let BLOCKS_IN_DAY = 1440

func wrapErr(msg: String) = {
contractFile + ": " + msg
Expand All @@ -34,8 +31,8 @@ func keyStatsTotalOutByAddress(address: Address) = {
func keyHistory(address: Address, txId: ByteVector) = {
["%s%s%s", "history", address.toString(), txId.toBase58String()].makeString(SEP)
}
func formatHistory(amountIn: Int, amountOut: Int, stake: Boolean) = {
["%d%d%d", amountIn.toString(), amountOut.toString(), stake.toString()].makeString(SEP)
func formatHistory(amountIn: Int, amountOut: Int, stake: Boolean, stakingNode: String) = {
["%d%d%b%s", amountIn.toString(), amountOut.toString(), stake.toString(), stakingNode].makeString(SEP)
}

let assetInId = this.getString(keyAssetInId)
Expand Down Expand Up @@ -63,6 +60,16 @@ let allowedAddressOption = match this.getString(keyAllowedAddress) {
let allowedAddress = allowedAddressOption
.valueOrErrorMessage(wrapErr("invalid allowedAddress"))

func isValidAddress(addressString: String) = {
match (addressFromString(addressString)) {
case t:Address => true
case _ => false
}
}

######################
# MULTISIG FUNCTIONS #
######################
let ADMIN_LIST_SIZE = 5
let QUORUM = 3
let TXID_BYTES_LENGTH = 32
Expand Down Expand Up @@ -143,23 +150,35 @@ func voteINTERNAL(
[ IntegerEntry(voteKey, 1) ]
}
}
##########################
# MULTISIG FUNCTIONS END #
##########################


func getSwapActions(i: Invocation, stake: Boolean, stakingNode: String) = {
let userAddress = i.caller
let paymentSizeExpected = 1
if (i.payments.size() != paymentSizeExpected) then throwErr("invalid payments") else
func getSwapActions(i: Invocation, stakingNode: String) = {
let userAddress = i.originCaller
let payment = i.payments[0]
if (payment.assetId != assetInId) then throwErr("invalid payment assetId") else
if (assetOutPrice == 0) then throwErr("invalid assetOutPrice") else
let assetInAmount = payment.amount
let assetOutAmount = fraction(assetInAmount, scale8, assetOutPrice)
if (assetOutAmount == 0) then throwErr("invalid assetOutAmount") else
let stake = if (stakingNode.isValidAddress()) then true else false

strict checks = [
i.payments.size() == 1 || throwErr("invalid payments size"),
payment.assetId == assetInId || throwErr("invalid payment assetId"),
assetOutPrice > 0 || throwErr("invalid assetOutPrice"),
assetOutAmount > 0 || throwErr("invalid assetOutAmount")
]

let stakeAction = if (!stake)
then [ ScriptTransfer(userAddress, assetOutAmount, assetOutId) ]
else {
strict stakeInvoke = stakingAddress.invoke(
"leaseByAddress",
[stakingNode, userAddress.toString()],
[AttachedPayment(assetOutId, assetOutAmount)])

strict stakeInv = if (stake) then stakingAddress.invoke(
"stakeForSwapHELPER",
[userAddress.toString(), stakingNode],
[AttachedPayment(assetOutId, assetOutAmount)]
) else unit
[]
}

(
[
Expand All @@ -181,23 +200,27 @@ func getSwapActions(i: Invocation, stake: Boolean, stakingNode: String) = {
),
StringEntry(
keyHistory(userAddress, i.transactionId),
formatHistory(assetInAmount, assetOutAmount, stake)
formatHistory(assetInAmount, assetOutAmount, stake, stakingNode)
)
] ++ if (stake) then [] else [
ScriptTransfer(userAddress, assetOutAmount, assetOutId)
],
] ++ stakeAction,
assetOutAmount
)
}

@Callable(i)
func swap(stake: Boolean) = {
getSwapActions(i, stake, "")
func swap() = {
getSwapActions(i, "NULL")
}

@Callable(i)
func swapAndSetStakingNode(stake: Boolean, stakingNode: String) = {
getSwapActions(i, stake, stakingNode)
func swapAndStake(stakingNode: String) = {
strict check = [
stakingNode.isValidAddress() || stakingNode == "" || "staking node address is no valid".throwErr()
]

let node = if (stakingNode == "") then "NULL" else stakingNode

getSwapActions(i, node)
}

@Callable(i)
Expand All @@ -209,6 +232,10 @@ func claim() = {
], unit)
}


######################
# MULTISIG FUNCTIONS #
######################
# Vote for txId that is allowed in Verifier
@Callable(i)
func voteForTxId(txId: String) = {
Expand All @@ -224,6 +251,10 @@ func voteForTxId(txId: String) = {

voteINTERNAL(callerAddressString, keyPrefix, QUORUM, result)
}
##########################
# MULTISIG FUNCTIONS END #
##########################


@Verifier(tx)
func verify() = {
Expand Down
8 changes: 4 additions & 4 deletions test/components/l2mp_swap/_hooks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ const seedWordsCount = 5;
const ridePath = '../ride';
const mockPath = 'components/l2mp_swap/mock';
const l2mpSwapPath = format({ dir: ridePath, base: 'l2mp_swap.ride' });
const l2mpStakingMockPath = format({ dir: mockPath, base: 'l2mp_staking.mock.ride' });
const l2mpLeasingMockPath = format({ dir: mockPath, base: 'l2mp_leasing.mock.ride' });

export const mochaHooks = {
async beforeAll() {
const names = [
'l2mpSwap',
'l2mpStaking',
'l2mpLeasing',
'admin1',
'admin2',
'admin3',
Expand Down Expand Up @@ -94,13 +94,13 @@ export const mochaHooks = {
{
key: '%s__stakingAddress',
type: 'string',
value: this.accounts.l2mpStaking.addr,
value: this.accounts.l2mpLeasing.addr,
},
],
chainId,
}, this.accounts.l2mpSwap.seed));

await setScriptFromFile(l2mpSwapPath, this.accounts.l2mpSwap.seed);
await setScriptFromFile(l2mpStakingMockPath, this.accounts.l2mpStaking.seed);
await setScriptFromFile(l2mpLeasingMockPath, this.accounts.l2mpLeasing.seed);
},
};
18 changes: 18 additions & 0 deletions test/components/l2mp_swap/mock/l2mp_leasing.mock.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{-# STDLIB_VERSION 6 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}

let contractFile = "l2mp_leasing.ride"

func wrapErr(msg: String) = {
contractFile + ": " + msg
}

func throwErr(msg: String) = {
throw(wrapErr(msg))
}

@Callable(i)
func leaseByAddress(nodeAddress: String, userAddress: String) = {
(nil, unit)
}
27 changes: 0 additions & 27 deletions test/components/l2mp_swap/mock/l2mp_staking.mock.ride

This file was deleted.

45 changes: 34 additions & 11 deletions test/components/l2mp_swap/swap.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@ const { expect } = chai;

describe('l2mp_swap: swap', /** @this {MochaSuiteModified} */() => {
it(
'should successfully swap token',
'should successfully swap without staking and receive tokens',
async function () {
const assetInAmount = 1e6;
const price = 1e6;
const expectedAssetOutAmount = (assetInAmount * 1e8) / price;

const { stateChanges } = await broadcastAndWait(invokeScript({
const { id: txId, stateChanges } = await broadcastAndWait(invokeScript({
dApp: this.accounts.l2mpSwap.addr,
call: {
function: 'swap',
args: [
{ type: 'boolean', value: true },
],
},
payment: [
{ assetId: this.xtnAssetId, amount: assetInAmount },
Expand All @@ -32,12 +29,38 @@ describe('l2mp_swap: swap', /** @this {MochaSuiteModified} */() => {
}, this.accounts.user1.seed));

expect(stateChanges.burns).to.have.lengthOf(0);
expect(stateChanges.invokes[0].dApp).to.equal(this.accounts.l2mpStaking.addr);
expect(stateChanges.invokes[0].call.function).to.equal('stakeForSwapHELPER');
expect(stateChanges.invokes[0].call.args[0].value).to.equal(this.accounts.user1.addr);
expect(stateChanges.invokes[0].call.args[1].value).to.equal('');
expect(stateChanges.invokes[0].payment[0].assetId).to.equal(this.l2mpAssetId);
expect(stateChanges.invokes[0].payment[0].amount).to.equal(expectedAssetOutAmount);
expect(stateChanges.transfers).to.deep.equal([{
asset: this.l2mpAssetId,
amount: expectedAssetOutAmount,
address: this.accounts.user1.addr,
}]);
expect(stateChanges.data).to.deep.equal([
{
key: '%s%s__stats__totalIn',
type: 'integer',
value: assetInAmount,
},
{
key: '%s%s__stats__totalOut',
type: 'integer',
value: expectedAssetOutAmount,
},
{
key: `%s%s%s__stats__totalIn__${this.accounts.user1.addr}`,
type: 'integer',
value: assetInAmount,
},
{
key: `%s%s%s__stats__totalOut__${this.accounts.user1.addr}`,
type: 'integer',
value: expectedAssetOutAmount,
},
{
key: `%s%s%s__history__${this.accounts.user1.addr}__${txId}`,
type: 'string',
value: `%d%d%b%s__${assetInAmount}__${expectedAssetOutAmount}__false__NULL`,
},
]);
},
);
});
Loading

0 comments on commit 0e307d2

Please sign in to comment.