Skip to content

Latest commit

 

History

History
832 lines (508 loc) · 28.7 KB

File metadata and controls

832 lines (508 loc) · 28.7 KB
timezone UTC+8

Max

GitHub ID: Max-wht

Telegram: @Max

Self-introduction

web3 developer

Notes

2025-12-07

完成了一个简单的solidity review:

https://github.com/Max-wht/solidity-audit-portfolio/blob/main/2025-12-07-puppy-raffle.pdf

2025-12-06

今天学了点DoS攻击

2025-12-05

今天看了点solidity audit。这个有意思

2025-12-04

今天在学审计相关的东西

2025-12-03

今天事情依旧很多

思考这个共学之后还要学习什么?

我现在阶段的主要目标其实提升web3的coding能力,对于各种defi的理解等等

可能和zetachain共学的方向有点偏差。

2025-12-02

Qwen Agent昨天弄了,今天学点别点

ECDSA

以太坊使用的是 secp256k1 曲线上的 ECDSA 签名。对一段消息的签名结果本质上是三部分:rsrecovery id(通常称为 v)。这三者一起能让你:

  • 在不知道公钥的情况下,从签名恢复出公钥(或地址);或

  • 在智能合约中验证签名者是否为预期地址(用 ecrecover)。

在 secp256k1 的 ECDSA 签名里,对消息哈希 z(通常是 keccak256 的结果)和私钥 d

  1. 随机选择临时私数 k(每次签名不同,若重复会泄露私钥);

  2. 计算椭圆曲线点 R = k·G,取 R.x mod n 得到 rn 是曲线阶);

  3. 计算

    s=k−1⋅(z+r⋅d) mod ns = k^{-1} \cdot (z + r \cdot d) \bmod ns=k−1⋅(z+r⋅d)modn

    其中 k^{-1}k 在模 n 下的乘法逆元。

所以 s 是一个模 n 的整数(0 < s < n,真实实现中 s 不允许为 0)。

2025-12-01

**

Qwen API调用**

感觉不是很聪明。。。

请求体如下:

requestBody := RequestBody{
  // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
  Model: "qwen-plus",
  Messages: []Message{
    {
      Role:    "system",
      Content: "You are a helpful assistant.",
    },
    {
      Role:    "user",
      Content: "介绍一下zetahcain",
    },
  },
}

终端响应:

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "截至目前(2024年),并没有一个广为人知的、名为“Zetahcain”的区块链项目、加密货币或技术平台。这个名字可能是拼写错误,或者是尚未广泛传播的新项目。\n\n如果你指的是 **Zcash**(发音类似“Z-cash”),那它是一个知名的隐私保护加密货币项目。以下是关于 Zcash 的简要介绍:\n\n---\n\n### ✅ 什么是 Zcash?\n\n**Zcash(ZEC)** 是一种注重隐私的去中心化加密货币,旨在提供比比特币更强的匿名性。它起源于 2016 年,基于“零知识证明”(Zero-Knowledge Proofs)技术中的 **zk-SNARKs** 实现交易的完全加密,同时仍能在区块链上验证其有效性。\n\n---\n\n### 🔐 核心特性\n\n1. **隐私保护**\n   - 支持“屏蔽交易”(shielded transactions),即发送方、接收方和交易金额都对公众不可见。\n   - 用户可以选择是否公开交易信息(透明地址 vs 隐私地址)。\n\n2. **zk-SNARKs 技术**\n   - 全称:Zero-Knowledge Succinct Non-Interactive Argument of Knowledge\n   - 允许在不透露任何具体信息的情况下证明交易的有效性。\n\n3. **两种地址类型**\n   - **t-addresses(透明地址)**:类似比特币,交易信息公开。\n   - **z-addresses(隐私地址)**:完全加密,保护用户隐私。\n\n4. **去中心化与挖矿**\n   - 使用工作量证明(PoW)机制,算法为 Equihash。\n   - 可通过 GPU 挖矿(尽管现在专业矿机更常见)。\n\n5. **供应总量**\n   - 最大供应量为 2100 万枚,与比特币相同。\n\n---\n\n### 📈 发展与应用\n\n- Zcash 被一些追求隐私的用户、机构和钱包支持。\n- 在合规与监管之间寻找平衡,部分交易所支持 ZEC 但限制 z-address 的使用(因反洗钱要求)。\n- 基金会:Zcash Foundation 致力于推动隐私技术的发展和公共利益应用。\n\n---\n\n### ❗ 注意事项\n\n- 隐私功能是一把双刃剑,可能被用于非法活动,因此受到各国监管关注。\n- 并非所有钱包和服务都支持完整的隐私交易功能。\n\n---\n\n如果你确实是指另一个名为 “Zetahcain” 的项目,请提供更多上下文(如官网、白皮书链接或应用场景),我可以帮你进一步分析。\n\n是否有可能是拼写错误?比如:\n- Zetachain?\n- ZetaChain?\n- Zeta Chain?\n\n> ⚡ 提示:有一个较新的项目叫 **ZetaChain**,是一个支持跨链智能合约的 Layer 1 区块链,允许直接与多个区块链(包括比特币)交互。如果你指的是这个,请告诉我,我可以详细介绍。\n\n请确认你要查询的是哪个项目?"
      },
      "finish_reason": "stop",
      "index": 0,
      "logprobs": null
    }
  ],
  "object": "chat.completion",
  "usage": {
    "prompt_tokens": 24,
    "completion_tokens": 638,
    "total_tokens": 662,
    "prompt_tokens_details": { "cached_tokens": 0 }
  },
  "created": 1764559306,
  "system_fingerprint": null,
  "model": "qwen-plus",
  "id": "chatcmpl-bff3ba8a-765b-4f77-b8cc-1685361a370f"
}

Agent搭建

用Python搭建,使用Qwen-agent框架。

截屏2025-12-01 12.09.58.png

这个是官方的demo,大概的逻辑是根据用户的描述生成图片

Solidity YUL

虽然不是学习内容,但是顺便加到笔记里

Solidity 的 inline assembly 使用的是 Yul 语言(以前叫 “Solidity Assembly”)。
它是一种低级语言,贴近 EVM 指令,但比纯 EVM bytecode 稍微高级一些。


1. 🧱 内联汇编的基本结构

assembly {
    // Yul 代码
}

变量用 let 声明:

let x := 1

所有变量都是 32 字节 word


2. 📦 基本语法

语法形式统一如下:

<variable> := <operation>(arg1, arg2, ...)

例如:

let x := add(1, 2)
let y := keccak256(ptr, 0x40)

3. 🔧 常用运算操作(算术 / 逻辑)

Yul 内置的操作几乎都是 EVM opcode,例如:

操作 说明
add(a, b) 加法
sub(a, b) 减法
mul(a, b) 乘法
div(a, b) 除法
mod(a, b) 取模
and(a, b) 位 AND
or(a, b) 位 OR
xor(a, b) 位 XOR
not(a) 位反
lt(a, b) 小于
gt(a, b) 大于
eq(a, b) 是否相等

4. 🧠 内存操作(Yul 最常用的部分)

mload(p)

从内存中读取 32 字节:

let value := mload(0x40)

mstore(p, value)

写入 32 字节:

mstore(ptr, myData)

mstore8(p, value)

写入 1 字节:

mstore8(ptr, 0xff)

5. 📦 Storage 操作(注意与 memory 区别)

sload(slot)

从 storage 读取:

let x := sload(5)

sstore(slot, value)

写入 storage(会产生 gas):

sstore(5, 123)

6. 🔐 哈希函数

keccak256(ptr, size)

对内存区域做哈希:

let hash := keccak256(ptr, 0x40)

等价于 Solidity 的 keccak256(bytes)


7. ⚙️ 外部调用指令

call

基本调用:

let success := call(
    gas(),          // gas
    target,         // 目标地址
    value,          // ETH
    inPtr, inSize,  // 输入数据
    outPtr, outSize // 输出数据
)

其他类似:

  • staticcall(...)

  • delegatecall(...)

返回数据

returndatacopy(outPtr, 0, returndatasize())

8. 🔁 控制流(if / switch / for)

if

if eq(x, 1) {
    mstore(0x0, 123)
}

switch

switch x
case 1 { mstore(0, 1) }
case 2 { mstore(0, 2) }
default { mstore(0, 0) }

for

for { let i := 0 } lt(i, 10) { i := add(i, 1) } {
    mstore(i, mul(i, 2))
}

9. 🧱 关键特殊指令

指令 说明
gas() 剩余 gas
address() 当前合约地址
caller() msg.sender
callvalue() msg.value
calldataload(p) 读取 call data
calldatasize() calldata 大小
calldatacopy(dst, src, size) 复制 calldata → memory

10. 🧯 revert / return / stop

revert(ptr, size)

revert(ptr, 0x20)

return(ptr, size)

返回数据给调用者。

stop

等价于成功结束,不返回值。


11. 🔥 内存指针 0x40:free memory pointer(非常重要)

0x40 位置保存当前可写内存的起始地址:

let ptr := mload(0x40)     // 获取空闲内存指针
mstore(0x40, add(ptr, 0x40)) // 更新空闲内存指针

2025-11-30

LSDFi

Liquid Derivatives Finance 流动性质押衍生品金融 建立在Liquid staking + staking derivatives 之上 所谓流动性质押,就是用户把 ETH 或者其他加密资产质押(staking)用来支持网络,同时代表一种质押权益的代币(LSD / LST),它是可以流动的,也就是可以被交易。通过 LST,持有者可以既获得质押奖励,同时也可以将手中的 LST 进行 DeFi 实用 借贷抵押流动性挖矿 (liquidity-pool)、收益耕种 (yield farming)、发行稳定币 (stablecoin)、或者参与组合策略 (yield-aggregation) 等。

通常 LSDFi 的流程如下:

NaN. 质押 (Staking) — 用户将原生加密资产 (例如 ETH) staking 到某个“流动质押 (liquid staking)”协议。

NaN. 发行衍生代币 (LSD / LST) — 协议给用户一个代币 (比如 stETH、rETH、wstETH 等),这个代币代表你质押的资产 + 随时间累积的质押奖励。

NaN. DeFi 使用 / 投资 (Composability) — 你可以将这个代币 (LST) 用到其他 DeFi 协议里:借贷、提供流动性、作为抵押品、参与收益耕作等。

获得双重收益 / 功能 — 你既持续获得 staking 奖励 (因为底层资产仍在质押中),也能通过 DeFi 活动获取额外收益 (如利息、手续费、奖励代币等);同时保持流动性 (你可以交易 / 转让 LST)。

在跨链场景下

流动性质押会更加灵活,比如

NaN. 在链 A 质押获得 LST

NaN. 桥到链 B 借稳定币

NaN. 再桥到链 C 做 LP 收手续费

NaN. 再回链 A 抵押获得更多 LST(杠杆策略)

Restaking

Restaking 的核心,是让资产在质押 (staking) 之后 “再利用 / 再质押”:也就是说,你将已经 stake(锁定或流动质押)的加密资产,再用于为额外协议 / 服务 (而不仅仅是原基础链) 提供安全保障。

restaking 可以分为两类/路径 (视具体协议而定):

NaN. 原生 (native) restaking:如果你运行自己的 validator 节点 (以 PoS 链为基础),便可以将你的 stake 直接参与多个服务。

NaN. 流动 / 衍生 (liquid / liquid-restaking):有些协议允许把通过流动性质押 (liquid staking) 得到的代币 (LST) 或其他代表权益 / 抵押品代币,再投入 restaking 协议 (可能换成 LRT / restaked token),这样即使没有自己跑节点,也能参与 restaking。d

restaking 在于让 stake 资产从“被锁住赚单一收益” → “复用 + 多重收益”,但对应的是更高复杂性与系统/协议风险。

Cross-Chain Aggregation

跨链聚合 = 把来自不同区块链的资产、流动性、应用功能 “聚合到一个统一界面 / 协议中” 使用。 但是如果单纯吧所有代币都放到 zetachain 里面,ZRC20 并不能生息(开发者不能自己 mint)。如果我的代币在 ETH 和 BTC 上面正在收取利息,为了统一管理把它们都放到了 Zetachain,原来链上的利息也就在这一个时间点停止了。这个方法只能说方便管理,但是不方便金融。

收益管理

当前的区块链有几个痛点:

NaN. 收益来源碎片化 每一条链都由自己的 DeFi,Staking,流动性池等等

NaN. 流动性碎片话 各链的 USDT/USDC 都不一样

NaN. 用户操作成本极高

zetachain 有几个优势。首先是不同链的加密货币可以用 ZRC20 统一标识,也就是说,不同加密货币在 zetachain 侧的交换会非常方便,不需要 burn-mint 这种形式 (我只是说在 zetachain 内部,zetachain 和别的链交互的时候还是得 burn-mint 的) 昨天刚刚看了Messaging。zetachain 对于 ERC20 的跨链交易非常友好。

2025-11-29

Messaging

🄐 跨链消息流程

Example合约 --> Zetachain Router --> Example合约

| |

Gateway Gateway

( 有点丑,大概长这样)

🄑 Messaging代码(上图中的Example)

  • 构造函数

    • _gateway. 当前链的gateway address

    • owner 合约拥有者

    • _router zetachain侧的router

  • sendMessage

    • receiver 目标链的接受者

    • targetToken 目标链代币地址

    • data 数据

    • gaslimit gas限制

    • revertOpention 回滚选项

    用户调用Example.sendMessage(...) -> gateway -> 这时候已经到了zetachain!{router,处跨链路由} ->

    -> 目标链gateway -> 目标链Example.onCall

  • sendMessage ERC20

    类似上面的函数,但是在调用的时候需要代币转账,同时链路内部处理也就多了点approve的操作

  • onMessageReceive

    目标链在获得message的时候的回调函数,由gateway通过onCall触发。

  • 还有一些revert函数,用来处理回滚

🄒 Router

当通过gateway.depositAndCall(...) 发送跨链消息的时候,zetachain中的router负责handles routers

  1. 解析原链的message payload

  2. 将代币swap,用来付gas fee(if exist)

  3. 将消息转发到目标链,包括message payload和token

  4. 回滚逻辑

这样的router,解决了gas处理,token交互的机制。也就是说我们合约只需要关系message payload就好了

2025-11-28

### ZRC20

开发者不能铸造 ZRC20,开发者在 zetachain 铸造的 ERC20,不叫 ZRC20。简单来说,ZRC20 可以看作,外部链上的原生 gas 资产或在白名单的 ERC-20 在 ZetaChain 上的 representation,比如跨链的转账,ETH -> ZRC-ETH -> SOL。

开发者可以在 zetachain 中铸造 ERC20,不具备天生跨链提取的特性,这一部分可以叫做 Universal Token

### Universal Asset

包括 Universal Token 和 Universal NFT。这一部分是我们开发者要着重开发的部分。我认为一个可行的 Defi 方向是统一用户钱包,用户将自己有代币的钱包转移到 zetachain,在 zetachain 中开发一个借贷协议或者算法,自动将资产最优分配一下。

2025-11-27

hello和swap的demo都已经在前几天的笔记中分享过了。今天事情有点多,学习的时间实在是少。

大概的想法是做一个rebase token,可以在所有链上交易。

会用Foundry来写,用测试网(测试网的水能统一发一下吗,好少啊😂😂)

2025-11-26

Q1: Universal App 是什么?

简单来说,部署在zetachain,并且继承了UniversalContract.sol的合约,实现了OnCall函数,就可以是Universal App,它可以处理跨链请求。

Q2: Gateway 大概做什么?

可以理解为,其他链连接zetachain的入口 & 出口。它会接收交易,广播交易。广播出来的交易会被zetachain节点发现,而后节点对这笔交易进行“是否允许”的投票,如果投票通过,则进入对应的OnCall。OnCall会处理交易逻辑。同样可以通过zetachain侧的gateway向其他链发送交易。交易的参数定义在Universal App中 (OnCall function),自定义的参数会被encode成bytes,OnCall中会将这个bytes decode成对饮参数。

2025-11-25

  • 本地部署Swap合约, 使用zetachain实现swap跨链交换代币

    zetachain evm deposit-and-call \
      --rpc http://localhost:8545 \
      --chain-id 11155111 \
      --gateway $GATEWAY_ETHEREUM \
      --amount 0.001 \
      --types address bytes bool \
      --receiver $SWAP \     
      --private-key $PRIVATE_KEY \
      --values $ZRC20_BNB $RECIPIENT true
    ​
    From:   0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
    To:     0x5bf5b11053e734690269C6B9D438F8C9d48F528A on ZetaChain
    Amount: 0.001 native tokens
    Refund: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
    Call on revert: false
    ​
    Contract call details:
    Function parameters: 0x0000000000000000000000000000000000000000, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, true
    Parameter types: ["address","bytes","bool"]
    ​
    ? Proceed with the transaction? yes
    Transaction hash: 0x0b14edbce4863bcf8d6504a2982764e6a60a49b0e4993bec3033be6e6c7c2af7
    
  • 部署到testnet

    UNIVERSAL=$(npx tsx commands deploy --private-key $SEPOLIA_PK | jq -r .contractAddress) && echo $UNIVERSAL
    0x713C7D391d24323509c258BeFE95d6B08C0f8274
    

简单说一说整个Swap的调用链路

用户的需求是从链A转移一部分资产到链B,期间zetachain将A与B连接 (用户无感)

首先用户需要调用A链中zetachain已经部署好的gateway网关合约中deposit-and-call这个函数,可以使用zetachain CLI。

zetachain evm -h                 
​
Usage: zetachain evm [options] [command]
​
Interact from EVM chains: call contracts on ZetaChain or deposit tokens (with or without a call).
​
Options:
  -h, --help                  display help for command
​
Commands:
  call [options]              Call a contract on ZetaChain from an EVM-compatible chain
  deposit-and-call [options]  Deposit tokens and call a contract on ZetaChain from an EVM-compatible chain
  deposit [options]           Deposit tokens to ZetaChain from an EVM-compatible chain

可以看出evm中有三个函数,分别是call, deposit-and-call,deposit

在deposit-and-call这个函数中,会"触发"zetachain中swap合约的onCall函数。这个函数会处理swap逻辑。

会有几个核心入参:sender A.ZRC20 amount B.ZRC20 recipient

swap的第一步是通过A.ZRC20 amount B.ZRC20 withdraw获得跨链操作需要的gasfee, gasZRC20 ( 这个参数会在之后的逻辑中与targetToken也就是B.ZRC20做比较) 和需要swap的数量out

第二部是emit一个swap事件

第三部是调用zetachain.gateway来交换代币。在gateway中,会首先销毁B.ZRC20 然后通知B链的gateway

第四部是B.gateway释放对应代币给recipient

2025-11-24

Day 01

实现:

  • 注册千问 api,在终端里面完成 api 的调用

    {
      "choices": [
        {
          "message": {
            "role": "assistant",
            "content": "我是通义千问,阿里巴巴集团旗下的超大规模语言模型。我能够回答问题、创作文字,比如写故事、写公文、写邮件、写剧本、逻辑推理、编程等等,还能表达观点,玩游戏等。如果你有任何问题或需要帮助,欢迎随时告诉我!"
          },
          "finish_reason": "stop",
          "index": 0,
          "logprobs": null
        }
      ],
      "object": "chat.completion",
      "usage": {
        "prompt_tokens": 22,
        "completion_tokens": 60,
        "total_tokens": 82,
        "prompt_tokens_details": { "cached_tokens": 0 }
      },
      "created": 1763953889,
      "system_fingerprint": null,
      "model": "qwen-plus",
      "id": "chatcmpl-424809e7-b2e6-4aac-b88d-949b1721a057"
    }
    
  • 跑通 zetachain 的 hello demo , 可以在etherscan和zetascan中观测到交易

    [⠊] Compiling...
    No files changed, compilation skipped
    Deployer: 0x051bc958C4F12a608917DE400FbFB295561fd857
    Deployed to: 0xDF3B67F50e92852168Fb5cD6048D76cF3447D8a0
    Transaction hash: 0xed1d439f2101893f521bb29db112d74446968f7aad33c6ec89d1aa991b535b0e
    
    From:   0x051bc958C4F12a608917DE400FbFB295561fd857
    To:     0xDF3B67F50e92852168Fb5cD6048D76cF3447D8a0 on ZetaChain
    Call on revert: false
    ​
    Contract call details:
    Function parameters: max
    Parameter types: ["string"]
    ​
    ? Proceed with the transaction? yes
    Transaction hash: 0xc48f6c309a612d6e4c474ab2898689a40c3f59cde16839eec68efcfefed9a229
    
  • 安装zetachain cli, 可以启动local chain 完成docs/build/Tutorials的部分学习

    - [x] Ethereum (11155112)
      ┌───────────────┬────────────────────────────────────────────┐
      │ Contract      │ Address                                    │
      ├───────────────┼────────────────────────────────────────────┤
      │ erc20Custody  │ 0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f │
      ├───────────────┼────────────────────────────────────────────┤
      │ gateway       │ 0x09635F643e140090A9A8Dcd712eD6285858ceBef │
      ├───────────────┼────────────────────────────────────────────┤
      │ zetaConnector │ 0x67d269191c92Caf3cD7723F116c85e6E9bf55933 │
      ├───────────────┼────────────────────────────────────────────┤
      │ zetaToken     │ 0x7a2088a1bFc9d81c55368AE168C2C02570cB814F │
      ├───────────────┼────────────────────────────────────────────┤
      │ USDC.ETH      │ 0x1fA02b2d6A771842690194Cf62D91bdd92BfE28d │
      └───────────────┴────────────────────────────────────────────┘
    ​
    ​
      ZetaChain (31337)
      ┌───────────────────┬────────────────────────────────────────────┐
      │ Contract          │ Address                                    │
      ├───────────────────┼────────────────────────────────────────────┤
      │ gateway           │ 0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e │
      ├───────────────────┼────────────────────────────────────────────┤
      │ uniswapV2Factory  │ 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 │
      ├───────────────────┼────────────────────────────────────────────┤
      │ uniswapV2Router02 │ 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 │
      ├───────────────────┼────────────────────────────────────────────┤
      │ uniswapV3Factory  │ 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 │
      ├───────────────────┼────────────────────────────────────────────┤
      │ uniswapV3Router   │ 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 │
      ├───────────────────┼────────────────────────────────────────────┤
      │ zetaToken         │ 0x5FbDB2315678afecb367f032d93F642f64180aa3 │
      ├───────────────────┼────────────────────────────────────────────┤
      │ ZRC-20 ETH.ETH    │ 0x2ca7d64A7EFE2D62A725E2B35Cf7230D6677FfEe │
      ├───────────────────┼────────────────────────────────────────────┤
      │ ZRC-20 USDC.ETH   │ 0xd97B1de3619ed2c6BEb3860147E30cA8A7dC9891 │
      ├───────────────────┼────────────────────────────────────────────┤
      │ ZRC-20 BNB.BNB    │ 0x65a45c57636f9BcCeD4fe193A602008578BcA90b │
      ├───────────────────┼────────────────────────────────────────────┤
      │ ZRC-20 USDC.BNB   │ 0x05BA149A7bd6dC1F937fA9046A9e05C05f3b18b0 │
      └───────────────────┴────────────────────────────────────────────┘
    ​
      BNB (98)
      ┌───────────────┬────────────────────────────────────────────┐
      │ Contract      │ Address                                    │
      ├───────────────┼────────────────────────────────────────────┤
      │ erc20Custody  │ 0x70e0bA845a1A0F2DA3359C97E0285013525FFC49 │
      ├───────────────┼────────────────────────────────────────────┤
      │ gateway       │ 0x0E801D84Fa97b50751Dbf25036d067dCf18858bF │
      ├───────────────┼────────────────────────────────────────────┤
      │ zetaConnector │ 0x9d4454B023096f34B160D6B654540c56A1F81688 │
      ├───────────────┼────────────────────────────────────────────┤
      │ zetaToken     │ 0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf │
      ├───────────────┼────────────────────────────────────────────┤
      │ USDC.BNB      │ 0xf953b3A269d80e3eB0F2947630Da976B896A8C5b │
      └───────────────┴────────────────────────────────────────────┘