Skip to content

Commit

Permalink
mint cd compress (#304)
Browse files Browse the repository at this point in the history
* Add calldata compression to mint function

* prettier

* fix

* fix

* fix

* fix test cmd
  • Loading branch information
vigneshka committed Dec 26, 2023
1 parent 4dce1f4 commit 23e48a8
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-eels-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@soundxyz/sdk': patch
---

Add calldata compression to mint function
8 changes: 4 additions & 4 deletions examples/nextjs/src/app/v2/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,17 @@ function EditionSchedule({ schedule }: { schedule: SuperMinterSchedule }) {
(schedule.tier === 0
? apiInfo?.data?.gaCoverImage?.url
: schedule.tier === 1
? apiInfo?.data?.vipCoverImage?.url
: null) ?? apiInfo?.data?.coverImage.url
? apiInfo?.data?.vipCoverImage?.url
: null) ?? apiInfo?.data?.coverImage.url

return (
<div key={schedule.scheduleNum.toString() + schedule.tier.toString()} className="flex flex-col gap-5">
<h2>
{schedule.mode === 'VERIFY_MERKLE'
? 'Presale Limited Edition'
: schedule.tier === 0
? 'Forever Edition'
: 'Limited Edition'}
? 'Forever Edition'
: 'Limited Edition'}
</h2>

{coverImage ? <img className="w-[100px]" src={coverImage} alt="Cover Image" /> : null}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"eslint-plugin-prettier": "^5.0.1",
"husky": "^8.0.3",
"lint-staged": "^15.0.1",
"prettier": "^3.0.3",
"prettier": "^3.1.1",
"rimraf": "^5.0.5",
"semver": "^7.5.4",
"typescript": "5.2.2",
Expand Down
4 changes: 1 addition & 3 deletions packages/legacy-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@
"prepack": "node build.mjs",
"prepare": "node build.mjs",
"pull-env": "dotenv-vault pull",
"test": "vitest dev",
"test:cov": "vitest dev --coverage",
"test:ci": "CI=true vitest --coverage",
"test": "vitest",
"tsc": "tsc -p tsconfig.build.json"
},
"dependencies": {
Expand Down
16 changes: 8 additions & 8 deletions packages/legacy-sdk/src/client/edition/mint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,14 @@ async function mintHelper(
abi: minterAbiMap[interfaceId],
})
: interfaceId === interfaceIds.IMerkleDropMinterV2
? client.readContract({
...params,
abi: minterAbiMap[interfaceId],
})
: client.readContract({
...params,
abi: minterAbiMap[interfaceId],
}))
? client.readContract({
...params,
abi: minterAbiMap[interfaceId],
})
: client.readContract({
...params,
abi: minterAbiMap[interfaceId],
}))

proof = await getMerkleProof.call(this, {
merkleRoot,
Expand Down
4 changes: 2 additions & 2 deletions packages/legacy-sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export declare type PromiseOrValue<T> = T | Promise<T>
type TupleSplit<T, N extends number, O extends readonly any[] = readonly []> = O['length'] extends N
? [O, T]
: T extends readonly [infer F, ...infer R]
? TupleSplit<readonly [...R], N, readonly [...O, F]>
: [O, T]
? TupleSplit<readonly [...R], N, readonly [...O, F]>
: [O, T]

export type TakeFirst<T extends readonly any[], N extends number> = TupleSplit<T, N>[0]

Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"prepack": "node build.mjs",
"prepare": "node build.mjs",
"postpublish": "gh-release",
"test": "vitest",
"tsc": "tsc -p tsconfig.build.json",
"validate": "graphql-inspector validate \"./graphql/*.gql\" \"./schema.graphql\" --deprecated"
},
"devDependencies": {
Expand All @@ -49,7 +51,7 @@
"bob-watch": "^0.1.2",
"concurrently": "^8.2.1",
"esbuild": "^0.19.4",
"prettier": "^3.0.3",
"prettier": "^3.1.1",
"typescript": "5.2.2",
"viem": "^1.20.0",
"zod": "^3.22.4"
Expand Down
25 changes: 19 additions & 6 deletions packages/sdk/src/contract/edition-v2/write/mint.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import type { Chain, WalletClient } from 'viem'
import { encodeFunctionData, type WalletClient } from 'viem'
import { curry } from '../../../utils/helpers'
import type { EditionMintContractInput } from '../read/mint'
import { cdCompress } from '../../../utils/calldata'

export function editionMint<Client extends Pick<WalletClient, 'writeContract'>>(
export function editionMint<Client extends Pick<WalletClient, 'writeContract' | 'sendTransaction'>>(
client: Client,
{ input }: EditionMintContractInput,
) {
return client.writeContract<typeof input.abi, typeof input.functionName, Chain>(input)
const calldata = encodeFunctionData({ abi: input.abi, functionName: input.functionName, args: input.args })
const compressedCalldata = cdCompress(calldata)

return client.sendTransaction({
account: input.account,
chain: input.chain,
value: input.value,
to: input.address,
data: compressedCalldata,
gas: input.gas,
maxFeePerGas: input.maxFeePerGas,
maxPriorityFeePerGas: input.maxPriorityFeePerGas,
})
}

export function editionV2WalletActionsMint<Client extends Pick<WalletClient, 'writeContract'> & { editionV2?: {} }>(
client: Client,
) {
export function editionV2WalletActionsMint<
Client extends Pick<WalletClient, 'writeContract' | 'sendTransaction'> & { editionV2?: {} },
>(client: Client) {
return {
editionV2: {
...client.editionV2,
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/contract/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function soundEditionVersionPublicActions<Client extends Pick<PublicClien
abi: SOUND_EDITION_V2_ABI,
address: contractAddress,
functionName: 'supportsInterface',
// v2 and v2.1 have the same interface id
args: [editionV2InterfaceIds.ISoundEditionV2],
},
],
Expand Down
23 changes: 23 additions & 0 deletions packages/sdk/src/utils/calldata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, test } from 'vitest'
import { cdCompress, cdDecompress } from './calldata'

// minimal tests ported to sanity check typescript adaptation
// see full test suite on https://github.com/Vectorized/solady/blob/main/js/solady.test.js
describe('cdCompress', () => {
test('LibZip: Calldata compress', () => {
const data =
'0xac9650d80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005b70e00000000000000000000000000000000000000000000000000000dfc79825feb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000645c48a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005b70e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064df2ab5bb0000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c3160700000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a100000000000000000000000000000000000000000000000000000000'
const expected =
'0x5369af27001e20001e04001e80001d0160001d0220001d02a0001ea40c49ccbe001c05b70e00190dfc79825feb005b645c48a7003a84fc6f7865001c05b70e002f008f000f008f003a4449404b7c002b1f1cdf1a632eaaab40d1c263edf49faf749010a1003a64df2ab5bb000b7f5c764cbc14f9669b88837ca1490cca17c31607002b1f1cdf1a632eaaab40d1c263edf49faf749010a1001b'
expect(cdCompress(data)).toEqual(expected)
})
})

describe('cdDecompress', () => {
test('LibZip: Calldata decompress on invalid input', () => {
const data = '0xffffffff00ff'
const expected =
'0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
expect(cdDecompress(data)).toEqual(expected)
})
})
106 changes: 106 additions & 0 deletions packages/sdk/src/utils/calldata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* eslint-disable no-bitwise */

import type { Hex } from 'viem'

/**
* typescript adaptations of cdCompress and cdDecompress from
* https://github.com/Vectorized/solady/blob/main/js/solady.js
*
* these are custom calldata compression and decompression functions
* that are supported by Sound contracts (sound creator v2, sound edition v2, and super minter)
* and can reduce calldata size (biggest influence on L2 gas costs) when there are lots of zeros or lots of 0xffs
*/
export function cdCompress(original: Hex): Hex {
const data = hexString(original)
let output: Hex = '0x'
let zeroCount: number = 0
let ffCount: number = 0

const pushByte = (b: number): void => {
output += byteToString((+(output.length < 4 * 2 + 2) * 0xff) ^ b)
}

const rle = (value: number, distance: number): void => {
pushByte(0x00)
pushByte(distance - 1 + value * 0x80)
}

for (let i = 0; i < data.length; i += 2) {
const currentByte: number = parseByte(data, i)
if (currentByte === 0) {
if (ffCount) {
rle(1, ffCount)
ffCount = 0
}
if (++zeroCount === 0x80) {
rle(0, 0x80)
zeroCount = 0
}
continue
}
if (currentByte === 0xff) {
if (zeroCount) {
rle(0, zeroCount)
zeroCount = 0
}
if (++ffCount === 0x20) {
rle(1, 0x20)
ffCount = 0
}
continue
}
if (ffCount) {
rle(1, ffCount)
ffCount = 0
}
if (zeroCount) {
rle(0, zeroCount)
zeroCount = 0
}
pushByte(currentByte)
}
if (ffCount) {
rle(1, ffCount)
}
if (zeroCount) {
rle(0, zeroCount)
}
return output
}

export function cdDecompress(compressed: Hex): Hex {
const data = hexString(compressed)
let output: Hex = '0x'

for (let i = 0; i < data.length; ) {
let c: number = (+(i < 4 * 2) * 0xff) ^ parseByte(data, i)
i += 2
if (!c) {
c = (+(i < 4 * 2) * 0xff) ^ parseByte(data, i)
const size: number = (c & 0x7f) + 1
i += 2
for (let j = 0; j < size; ++j) {
output += byteToString((c >> 7 !== 0 && j < 32 ? 1 : 0) * 0xff)
}
continue
}
output += byteToString(c)
}
return output
}

function hexString(data: Hex): string {
const match = data.trim().match(/^(0x)?([0-9A-Fa-f]*)$/)
if (match && match[2] && match[2].length % 2 === 0) {
return match[2]
}
throw new Error('Data must be a valid hex string with even length.')
}

function byteToString(b: number): string {
return (b | 0x100).toString(16).substr(1)
}

function parseByte(data: string, i: number): number {
return parseInt(data.substr(i, 2), 16)
}
18 changes: 18 additions & 0 deletions packages/sdk/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
benchmark: {
outputFile: './bench/report.json',
reporters: process.env['CI'] ? ['json'] : ['verbose'],
},
coverage: {
reporter: process.env['CI'] ? ['lcov'] : ['text', 'json', 'html'],
exclude: ['**/errors/utils.ts', '**/dist/**', '**/*.test.ts', '**/_test/**'],
},
environment: 'node',
// setupFiles: ['./test/setup.ts'],
// globalSetup: ['./test/globalSetup.ts'],
testTimeout: 30_000,
},
})
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,5 @@
"include": ["packages"],

// subgraph typechecking is not necessary since the build scripts there convert AS code
"exclude": ["node_modules", "packages/subgraph"]
"exclude": ["node_modules", "**/*.test.ts", "vitest.config.ts"]
}

0 comments on commit 23e48a8

Please sign in to comment.