Polygon zkEVM Memory Align状态机

1. 引言

前序博客有:

Memory Align状态机为Polygon zkEVM的六个二级状态机之一,该状态机内包含:

  • executor part:sm_mem_align.js:负责生成execution trace,为常量多项式和隐私多项式赋值。
  • 验证规则集PIL:mem_align.pil:定义了约束系统。

Polygon zkEVM Memory状态机通过使用a 32-byte word access来检查内存读写,而EVM支持对单个byte级别的offset进行读写。

如下图,分别表示了相同内容在 byte级别 和 32-byte级别的内存布局:

Table 7: Sample memory layouts for byte and 32-byte access.

A D D R E S S B Y T E 0 0 x c 4 1 0 x 17 ⋮ ⋮ 30 0 x 81 31 0 x a 7 32 0 x 88 33 0 x d 1 ⋮ ⋮ 62 0 x b 7 63 0 x 23 \begin{array}{|c|c|} \hline \mathbf{ADDRESS} &\mathbf{BYTE} \\ \hline \mathtt{0} &\mathtt{0xc4} \\ \mathtt{1} &\mathtt{0x17} \\ \mathtt{\vdots} &\mathtt{\vdots} \\ \mathtt{30} &\mathtt{0x81} \\ \mathtt{31} &\mathtt{0xa7} \\ \mathtt{32} &\mathtt{0x88} \\ \mathtt{33} &\mathtt{0xd1} \\ \mathtt{\vdots} &\mathtt{\vdots} \\ \mathtt{62} &\mathtt{0xb7} \\ \mathtt{63} &\mathtt{0x23} \\ \hline \end{array} ADDRESS01303132336263BYTE0xc40x170x810xa70x880xd10xb70x23

ADDRESS 32-BYTE   WORD 0 0 x c 417...81 a 7 1 0 x 88 d 1... b 723 \begin{array}{|c|c|} \hline \textbf{ADDRESS} & \textbf{32-BYTE WORD} \\ \hline \mathtt{0} &\mathtt{0xc417...81a7} \\ \mathtt{1} &\mathtt{0x88d1...b723} \\ \hline \end{array} ADDRESS0132-BYTE WORD0xc417...81a70x88d1...b723

32-byte与byte级别布局之间的关系称为“memory alignment”,而Memory Align状态机负责检查二者之间关系的正确性。

EVM支持3种内存操作:

  • 1)MLOAD:按指定offset地址读取内存,返回内存中自该offset开始的32字节word。【注意,MLOAD可读取2个不同words的bytes。】【对应zkasm中的MEM_ALIGN_WR
  • 2)MSTORE:按指定offset地址存储32byte。【对应zkasm中的MEM_ALIGN_RD
  • 3)MSTOREE:将某单个byte数据存入指定offset地址。【注意,MSTOREE为按单个byte写入。】【对应zkasm中的MEM_ALIGN_WR8

EVM是按byte级别操作的,仍以上表为例,对应2个word:
m 0 = 0 x 88 d 11 f ⋯ b 723 , m 1 = 0 x 6 e 21 f f ⋯ 54 f 9 . \mathtt{m}_0 = \mathtt{0x} \mathtt{88d11f} \cdots \mathtt{b723}, \quad \mathtt{m}_1 = \mathtt{0x} \mathtt{6e21ff} \cdots \mathtt{54f9}. m0=0x88d11fb723,m1=0x6e21ff54f9.
MLOAD 0x22时,返回的结果为:
v a l = 0 x 1 f ⋯ b 7236 e 21 . \mathtt{val} = \mathtt{0x1f \cdots b7236e21}. val=0x1fb7236e21.
跨越了2个word,将该读操作的结果标记为: m 0 ∣ m 1 \mathtt{m}_0 \mid \mathtt{m}_1 m0m1
在这里插入图片描述
验证MLOAD 0x22返回的结果 0 x 1 f ⋯ 7236 e 21 \mathtt{0x1f\dotsb7236e21} 0x1f7236e21是否正确的流程为:

  • 1)Main状态机需查询获得 m 0 \mathtt{m}_0 m0 m 1 \mathtt{m}_1 m1值;
  • 2)Main状态机需调用Memory状态机来验证所查询的结果有效;
  • 3)Main状态机可很容易extract the memory positions to query from the address 0x22。事实上,若 a a aMLOAD操作的内存位置,则 m 0 \mathtt{m}_0 m0总是存储在内存位置 ⌊ a 32 ⌋ \lfloor \frac{a}{32} \rfloor 32a m 1 \mathtt{m}_1 m1 存储在内存位置 ⌊ a 32 ⌋ + 1 \lfloor \frac{a}{32} \rfloor + 1 32a+1。本例中 a = 0 x 22 = 34 a = \mathtt{0x22} = 34 a=0x22=34,从而 m 0 \mathtt{m}_0 m0 存储在位置 ⌊ 32 34 ⌋ = 0 x 01 \lfloor \frac{32}{34} \rfloor = \mathtt{0x01} 3432=0x01 m 1 \mathtt{m}_1 m1 存储在位置 ⌊ 32 34 ⌋ + 1 = 0 x 02 \lfloor \frac{32}{34} \rfloor + 1= \mathtt{0x02} 3432+1=0x02
  • 4)应从正确的offset提取,offset应为0-31的index,表示从 m 0 \mathtt{m}_0 m0的读取 v a l \mathtt{val} val的偏移位置。如本例中,offset为2(即offset为 a m o d    32 a \mod 32 amod32)。可借助Plookup来验证已知 m 0 , m 1 \mathtt{m}_0,\mathtt{m}_1 m0,m1 o f f s e t \mathtt{offset} offset,所读取的 v a l \mathtt{val} val值是正确的。

同理,对于 M S T O R E \mathtt{MSTORE} MSTORE为write bytes in two words。假设 w 0 , w 1 \mathtt{w}_0,\mathtt{w}_1 w0,w1 m 0 , m 1 \mathtt{m}_0,\mathtt{m}_1 m0,m1进行 M S T O R E \mathtt{MSTORE} MSTORE操作之后的值。
MSTORE 0x22 0xe201e6...662b,想要向基于单个字节布局的以太坊内存中的0x22地址写入:
v a l = 0 x e 201 e 6 … 662 b \mathtt{val} = \mathtt{0xe201e6\dots662b} val=0xe201e6662b
写入成功之后,相应的 w 0 , w 1 \mathtt{w}_0,\mathtt{w}_1 w0,w1值为:
w 0 = 0 x 88 d 1 e 201 e 6 … , w 1 = 0 x 662 b f f … 54 f 9 \mathtt{w}_0 = \mathtt{0x88d1}\mathtt{e201e6\dots},\quad \mathtt{w}_1 = \mathtt{0x}\mathtt{662b}\mathtt{ff\dots54f9} w0=0x88d1e201e6,w1=0x662bff54f9
在这里插入图片描述
已知:

  • 1)an address a d d r \mathtt{addr} addr
  • 2)an offset value o f f s e t \mathtt{offset} offset
  • 3)待写入的值 v a l \mathtt{val} val

Main状态机的操作与上面MLOAD类似,只是改为需要检查 w 0 , w 1 \mathtt{w}_0,\mathtt{w}_1 w0,w1确实是根据已知的 v a l , m 0 , m 1 , o f f s e t \mathtt{val},\mathtt{m}_0,\mathtt{m}_1,\mathtt{offset} val,m0,m1,offset构建的。

2. Memory Align状态机的约束系统

mem_align.pil中的常量多项式赋值情况见:Polygon zkEVM中的常量多项式中“mem_align.pil中的常量多项式”。

mem_align.pil中的隐私多项式有:

    * OPERATIONS
    *
    *  (m0,m1,D) => v
    *  (m0,m1,D,v) => (w0, w1)
    *
    *  @m0 = addr   @m1 = addr + 32 (ethereum)
    *
    *  Ethereum => BigEndian
    *  m0[7],m0[6],...,m0[1],m0[0],m1[7],m1[6],...,m1[1],m1[0]
    *
    *  inM (8 bits, 32 steps)
    */

    // 32 bytes of M0, M1 ordered from HSB to LSB (32, 31, 30, ... == M[7].3, M[7].2, M[7].1, M[7].0, M[6].3, ..)
    pol commit inM[2];

    // 32 bytes of V
    pol commit inV;

    // write 32 bytes (256 bits)
    pol commit wr256;

    // write 1 byte (8 bits), its special case because need to ignore
    // the rest of bytes, only LSB of V was stored (on w0 with offset)
    pol commit wr8;

    pol commit m0[8];
    pol commit m1[8];
    pol commit w0[8];
    pol commit w1[8];
    pol commit v[8];

    // when m1 is "active", means aligned with inV
    // also when wr8 = 1, used to indicate when store the LSB
    pol commit selM1;

    // factors to build V[] in correct way, because order of V bytes
    // changes according the offset and wr8
    pol commit factorV[8];

    pol commit offset;

基本的约束为:

	//              RangeCheck                  Latch   Clock
    // factorV      Plookup (FACTORV)           no      yes
    // wr256        Plookup (WR256)             yes     no
    // wr8          Plookup (WR8)               yes     no
    // offset       Plookup (OFFSET)            yes     no
    // inV          RangeCheck (BYTE_C3072)     no      yes
    // inM[0..1]    RangeCheck (BYTE2A,BYTE2B)  no      yes
    // m0[0..7]     Built                       no      yes
    // m1[0..7]     Built                       no      yes
    // w0[0..7]     Built                       no      yes
    // w1[0..7]     Built                       no      yes
    // v[0..7]      Built                       no      yes
    // selM1        Plookup (SELM1)             no      yes
  • 1)借助RESET常量多项式,对wr256、offset、wr8隐私多项式进行锁存约束:
    // Latchs
    (1 - RESET) * wr256' = (1 - RESET) * wr256;
    (1 - RESET) * offset' = (1 - RESET) * offset;
    (1 - RESET) * wr8' = (1 - RESET) * wr8;
    
  • 2)约束inM[1]的取值范围为 BYTE2A 【= 0 (x256), 1 (x256), 2 (x256), …, 255 (x256)】,约束inM[0]的取值范围为 BYTE2B 【= 0, 1, 2, 4, **, 255, 0, 1, …, 255】:
    // RangeCheck
    {inM[1], inM[0]} in {BYTE2A, BYTE2B};
    
  • 3)约束offset、wr256、wr8、selM1、inv、factorV[0-7]的取值:
    // Plookup
    {
        STEP, offset', wr256', wr8', selM1, inV,
        factorV[0], factorV[1], factorV[2], factorV[3], factorV[4], factorV[5], factorV[6], factorV[7]
    } in {
        STEP, OFFSET, WR256, WR8, SELM1, BYTE_C3072,
        FACTORV[0], FACTORV[1], FACTORV[2], FACTORV[3], FACTORV[4], FACTORV[5], FACTORV[6], FACTORV[7]
    };
    
  • 4)约束m0[0-7]与m1[0-7]的transition约束为:
    m0[0]' = (1-RESET) * m0[0] + FACTOR[0] * inM[0];
    m0[1]' = (1-RESET) * m0[1] + FACTOR[1] * inM[0];
    m0[2]' = (1-RESET) * m0[2] + FACTOR[2] * inM[0];
    m0[3]' = (1-RESET) * m0[3] + FACTOR[3] * inM[0];
    m0[4]' = (1-RESET) * m0[4] + FACTOR[4] * inM[0];
    m0[5]' = (1-RESET) * m0[5] + FACTOR[5] * inM[0];
    m0[6]' = (1-RESET) * m0[6] + FACTOR[6] * inM[0];
    m0[7]' = (1-RESET) * m0[7] + FACTOR[7] * inM[0];
    
    m1[0]' = (1-RESET) * m1[0] + FACTOR[0] * inM[1];
    m1[1]' = (1-RESET) * m1[1] + FACTOR[1] * inM[1];
    m1[2]' = (1-RESET) * m1[2] + FACTOR[2] * inM[1];
    m1[3]' = (1-RESET) * m1[3] + FACTOR[3] * inM[1];
    m1[4]' = (1-RESET) * m1[4] + FACTOR[4] * inM[1];
    m1[5]' = (1-RESET) * m1[5] + FACTOR[5] * inM[1];
    m1[6]' = (1-RESET) * m1[6] + FACTOR[6] * inM[1];
    m1[7]' = (1-RESET) * m1[7] + FACTOR[7] * inM[1];
    
  • 5)约束selM1与wr256、wr8之间的关系:
    // selW0 contains data to be store in w0, must be 0 in "reading mode"
    // in "writting mode", in particular with wr8 only on byte must be
    // stored, for this reason use selM1 that was active only one clock (in wr8 mode)
    pol selW0 = (1 - selM1) * wr256' + selM1 * wr8';
    
    // selW1 contains data to be store in w1, must be 0 in "reading mode"
    pol selW1 = selM1 * wr256';
    
  • 6)w0[0-7]和w1[0-7]的transition约束为:
    // NOTE: when wr8 = 1 implies wr256 = 0, check in this situations where use selM1
    // to verify that non exists collateral effects
    //
    // pol selW0 = selM1 (because wr256 = 0 and wr8 = 1)
    //
    // selM1 used in pol _inM, but _inM is only used on dataV
    // pol dataV = (1 - wr256' - wr8') * _inM + (wr256' + wr8') * inV;
    // pol dataV = inV (because wr256 = 0 and wr8 = 1)
    //
    // CONCLUSION: it's safe reuse selM1 to indicate when store byte
    
    // data to "write" on w, if current byte must be override by V contains inV
    // if not contains inM "in reading mode" must be 0.
    pol dataW0 = (wr8' + wr256') * inM[0] + selW0 * (inV - inM[0]);
    pol dataW1 = (wr8' + wr256') * inM[1] + selW1 * (inV - inM[1]);
    
    w0[0]' = (1-RESET) * w0[0] + FACTOR[0] * dataW0;
    w0[1]' = (1-RESET) * w0[1] + FACTOR[1] * dataW0;
    w0[2]' = (1-RESET) * w0[2] + FACTOR[2] * dataW0;
    w0[3]' = (1-RESET) * w0[3] + FACTOR[3] * dataW0;
    w0[4]' = (1-RESET) * w0[4] + FACTOR[4] * dataW0;
    w0[5]' = (1-RESET) * w0[5] + FACTOR[5] * dataW0;
    w0[6]' = (1-RESET) * w0[6] + FACTOR[6] * dataW0;
    w0[7]' = (1-RESET) * w0[7] + FACTOR[7] * dataW0;
    
    w1[0]' = (1-RESET) * w1[0] + FACTOR[0] * dataW1;
    w1[1]' = (1-RESET) * w1[1] + FACTOR[1] * dataW1;
    w1[2]' = (1-RESET) * w1[2] + FACTOR[2] * dataW1;
    w1[3]' = (1-RESET) * w1[3] + FACTOR[3] * dataW1;
    w1[4]' = (1-RESET) * w1[4] + FACTOR[4] * dataW1;
    w1[5]' = (1-RESET) * w1[5] + FACTOR[5] * dataW1;
    w1[6]' = (1-RESET) * w1[6] + FACTOR[6] * dataW1;
    w1[7]' = (1-RESET) * w1[7] + FACTOR[7] * dataW1;
    
  • 7)v的transition约束:
    // _inM contains "active" value of inM
    pol _inM = (1 - selM1) * inM[0] + selM1 * inM[1];
    
    // contains data to store in V, could be one of inM if was reading or inV if was writting
    pol dataV = (1 - wr256' - wr8') * _inM + (wr256' + wr8') * inV;
    
    // factorV = f(STEP, offset, wr8)
    v[0]' = (1-RESET) * v[0] + factorV[0] * dataV;
    v[1]' = (1-RESET) * v[1] + factorV[1] * dataV;
    v[2]' = (1-RESET) * v[2] + factorV[2] * dataV;
    v[3]' = (1-RESET) * v[3] + factorV[3] * dataV;
    v[4]' = (1-RESET) * v[4] + factorV[4] * dataV;
    v[5]' = (1-RESET) * v[5] + factorV[5] * dataV;
    v[6]' = (1-RESET) * v[6] + factorV[6] * dataV;
    v[7]' = (1-RESET) * v[7] + factorV[7] * dataV;
    

3. Memory Align状态机示例

可参见zkevm-proverjs/test/sm/sm_mem_align_test.jszkevm-proverjs/test/counters/mem_align.js,输入为test/zkasm/counters/mem_align.zkasm
Memory Align状态机对应的zkasm基本语法为:【其中的值为BigEndian表示】

# 1. 按C偏移读取32-byte
0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E2021n => A
0xA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFn => B
5 => C
$ => A :MEM_ALIGN_RD
0x060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E2021A0A1A2A3A4n :ASSERT

# 2. 按C偏移写入32-byte
0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E2021n => A
0xA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFn => B
31 => C
0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E20C0n => D
0xC1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFBFn => E
0xC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFn :MEM_ALIGN_WR

# 3. 按C偏移写入一个byte。BigEndian表示,最低byte在最右侧。
0x0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E2021n => A
1 => C       
0x01DF030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E2021n => D      
0xC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFn :MEM_ALIGN_WR8

参考资料

[1] Memory Align SM

附录:Polygon Hermez 2.0 zkEVM系列博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POLYGON - 幻想王国资源包 一款宏大的低多边形资源包,包含建筑物、角色、道具、武器、环境资源,可制作幻想主题的多边形风格游戏。无需翻遍整个商店寻找匹配的资源包,这可能是资源商店中发布的最大、最全面的资源包。 这一资源包中包含超过 2100 个精细的预制件。 主要特点 - 完全模块化的城堡和房屋系统,具有可进入的内部结构! - 攻城武器和军队道具! - 包含大量演示场景 - (角色姿势仅供参考) Assets 角色 (x22) 酒保、男铁匠、女铁匠、仙女、占卜师、刽子手、隐士、小丑、国王、法师、商人、僧侣、修女、男农民、女农民、牧师、王子、公主、女王、骑士、男士兵、女士兵 角色附件 (x10) 铁匠护目镜 (x3)、国王披肩和王冠、法师披肩、牧师帽、公主皇冠、女王皇冠、士兵头盔。 车辆 (x11) 手推车、攻城武器(弩车、加农炮、攻城塔、抛石机、石弩、攻城锤、迫击炮、小船 模块化城堡和房屋(x600 多件) 墙壁、窗户、门、楼梯、柱/梁、城垛、地板、屋顶、尖顶、拱门、烟囱、破损构件等 其他预设 (x26) 优化的预设房屋外观 环境(x240 件) 路径、草、灌木、树木、花朵、花盆、常春藤、岩石等 道具 (x794) 横幅和旗帜、家具、灯/蜡烛/吊灯、战场道具(尸体、箭矢、靶子、防御工事等)、板条箱/桶/麻袋、集市/商店道具(书籍、香料、肉等)、铁匠铺、工作台、标牌、地毯、雕像等 物品 (x241) 工艺材料、瓶子、盘子和杯子、食品、工具、珠宝、羊皮纸等 完整的武器 (x111) 箭、弓、箭袋、斧头、锤子、狼牙棒、权杖、盾牌、矛、手杖、法杖、剑、匕首、猎象枪 模块化武器(x72 件) 刀片、刀柄、矛柄/枪头 特效 (x28) 万箭齐发、烛焰、乱石横飞、灰尘、火、雾、喷泉、魔幻灯、雨、烟、水车、太阳光束
POLYGON – Office Pack – Out now! Synty Studios Presents – An Epic Low Poly asset pack of zombie characters to create an apocalypse themed polygonal style game. (Includes a custom shader to allow for changing the blood color and transparency) Key Features – Zombie Characters (x50) – Bellboy Male 01, Biker Male 01, Bio Hazard Suit Male 01, Bride Female 01, Business Female 01, Businessman Male 01, Business Shirt Male 01, Cheerleader Female 01, Clown Male 01, Coat Female 01, Daughter Female 01, Diver Male 01, Fast food worker Male 01, Father Male 01, Father Male 02, Firefighter Male 01, Footballer Male 01, Gamer-girl Female 01, Gangster Male 01, Hipster Female 01, Hipster Male 01, Hobo Male 01, Hoodie Male 01, Hot Dog Suit Male 01, Jacket Female 01, Jacket Male 01, Jock Male 01, Military Male 01, Mother Female 01, Mother Male 01, Paramedic Female 01, Patient Female 01, Police Female 01, Police Male 01, Prisoner Male 01, Punk Female 01, Punk Male 01, Riot Cop Male 01, Road worker Male 01, School Boy Male 01, School Girl Female 01, Shopkeeper Female 01, Shopkeeper Male 01, Son Male 01, Summer Girl Female 01, Tourist Male 01, Underwear Female 01, Underwear Male 01. (with Alternative colors) Props – Axe 01, Bar 01, Bar 02, Cleaver 01, Knife 01, Scissors 01. Zombie gibs – Male Head 01, Female Head 01, Upper Arm 01, Lower Arm 01, Upper Leg 01, Lower Leg 01. – Heaps of awesome content included (see the layout screenshots for more details) Compatibility – Works in Unity 2017.4 and above – Support Universal Render Pipeline (URP) – URP Guide Here for Custom Shaders – Characters are setup to work with Mecanim (no animations included in this pack) Works well with our MASSIVE Apocalypse Pack POLYGON – Apocalypse Pack Follow us for updates and news Web | Facebook | Discord | Email Check out our other Assets POLYGON – 3D Asset Series Mini – 3D Asset Series Simple – 3D Asset Series
A low poly asset pack of Buildings, Vehicles, Characters, and Props assets to create a crime themed polygonal style game. Includes a detailed demo scene (Character poses indicative only) A great addition to POLYGON - Heist Pack Key Features Characters (x17) - Asian Gangster, British Gangster, Female Cook, Male Cook, DEA Agent Female, DEA Agent Male, DEA Plainclothes, GangBoss, Gang Member 01, Gang Member 02, Gang Member 03, Gang Member Female, Hazmat Suit, Italian Gangster, StreetGirl, Underwear Female, Underwear Male. (with Alternative Colors) Vehicles (x5) - Forklift, LowCar 01, LowCar 02, Truck, Van (with Alternative Colors) Modular Buildings (x88 Pieces) - Ceilings, Fences, Floors, Walls, Roofs, Loading Docks, Pipes, Walkways, Beams, Doors etc. Props (x324) - Bags of powder, Air Vents, Bottles, Barrels, Billboards, Blender, Books,Bucket, Cardboard boxes, Chairs, Chemical Equipment, Cigarettes, containers, dumpster, Evidence Bags, Gas Can, Grass, Lab equipment, Ladders, Lights, Money, Office Furniture, Paint cans, Brushes, Pills, Papers, Plants, Powder, Puddles, Gun range equipment, Security cameras, Shelves, Signs, Lab tables, Toys etc Weapons (x22) - Bat, Broken Bottle, Crowbar, Knives, Knuckle Dusters, Pistols, Pipe, Rifles, Shiv, Shotgun, Straight Razor, Sub Machine Gun, Swords. FX (x18) - Bullet Trail, Blood Splat, Bug Lights, Bullet Shells, Bunsen Burner, Dust Interior, Dust Small, Fire, Fog, Gunshot, Rain, Rain Splash, Rain Splash small, Smoke Large, Smoke Medium, Smoke Small, Light ray cube, Light ray Round. - Heaps of awesome content included (see the layout screenshots for more details)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值