# SponsorWhitelistControl ## 总览 Conflux实现了一种赞助机制来补贴用户对智能合约的使用。 因此,只要对合约调用的交易被赞助(通常由Dapps的运营商赞助),使用余额为零的新帐户也能够调用智能合约。 通过引入内置的SponsorControl合约能够记录和管理智能合约的赞助信息。 在进行子调用(Message Call)时,Conflux不会再次检查赞助情况。例如,如果一个普通用户地址 `A` 调用合约 `B` ,然后合约 `B` 调用合约 `C`, Conflux仅仅会检查 `A` 是否被合约 `B` 赞助。如果 `A` 被赞助,`B` 会在交易执行过程中负担全部的燃料费用和/或存储抵押费用,包含 `B` 调用 `C` 的费用。换句话说,只有交易的发送者才能被赞助,`B` 不可能被赞助。 **SponsorControl**合约为每一个用户建立的合约保留了如下的信息: + `sponsor_for_gas` :是提供燃料补贴的账户; + `sponsor_for_collateral` :是提供存储抵押补贴的账户; + `sponsor_balance_for_gas` :可用于燃料补贴的余额; + `sponsor_balance_for_collateral` : 可用于提供存储抵押补贴的余额; + `sponsor_limit_for_gas_fee` :是向每笔交易赞助燃料费用的上界; + `whitelist` :有资格获得补贴的普通用户地址列表,全零地址则代表所有用户地址。只有合约自身和管理员有权限改动该列表。 有两种资源能够被赞助:燃料费用和存储抵押物。 + **对于燃料费用*: 如果一笔交易使用非空的 `sponsor_for_gas` 调用智能合约且交易发送者处于合约的 `whitelist` 列表内,且交易指定的燃料费用在 `sponsor_limit_for_gas_fee` 范围内,交易的燃料消耗将从合约的 `sponsor_balance_for_gas` 中支付(如果足够的话),而不是由交易发送者的账户余额支付,如果 `sponsor_balance_for_gas` 无法承担燃料消耗,则交易失败。否则,交易发送者应支付燃料费用。 + *对于存储抵押物*: 如果一笔交易使用非空的 `sponsor_balance_for_collateral` 调用智能合约且交易发送者处于合约的 `whitelist` 列表内,在执行交易的过程中存储抵押物将从智能合约的 `sponsor_balance_for_collateral` 中扣除,并将这些修改后的存储条目所有者相应设置为合约地址。 否则,交易发送方应在执行过程中支付存储抵押物。 当一个合约被创建的时候,它的 `sponsor_for_gas` 和 `sponsor_for_collateral` 会被置为零地址,相应的燃料补贴余额也是零。 提供燃料补贴的账户和存储押金补贴的账户都可以通过与 SponsorControl 合约交互完成。合约当前的赞助账户可以直接追加补贴余额,也可以在满足一定条件下提高 `sponsor_limit_for_gas_fee`。其他普通用户账户如果提供高于当前余额的资金,可以将原先的赞助者取而代之。更具体的细节如下。 ## 赞助者替换 为了替换合约的 `sponsor_for_gas` ,新的赞助者需要调用函数 `setSponsorForGas(address contractAddr, uint upperBound)` 并向内置合约转移一笔资金。只有在满足下述条件时才能完成燃料费用赞助者的替换: 1. 转移的资金应当比合约当前的 `sponsor_balance_for_gas` 高。 2. `sponsor_limit_for_gas_fee` 的新值(被指定为 `upperBound` 参数)应当不小于原赞助者的限制,除非原本的 `sponsor_balance_for_gas` 无法负担原赞助者的限制。 3. 转移的资金应为新限额的1000倍以上,以便足以补贴至少 `1000` 次调用合约的交易。 如果上述条件满足,剩余的 `sponsor_balance_for_gas` 会返还给原赞助人 `sponsor_for_gas` ,随后转给内置合约的资金被注入 `sponsor_balance_for_gas`。 `sponsor_for_gas` 以及 `sponsor_limit_for_gas_fee` 会根据新赞助人指定的值进行更新。如果上述条件没有满足,会触发异常。 对 `sponsor_for_collateral` 的替换与替换燃料赞助者的方式类似,处理没有燃料限额的逻辑。方法是 `setSponsorForCollateral(address contractAddr)` 。新的赞助者需要向合约转移一笔比目前余额更多的资金。随后当前 `sponsor_for_collateral` 赞助的金额会被全部退还,即 `sponsor_balance_for_collateral` 和合约当前所有存储抵押金之和,两个与存储押金赞助相关的字段将按照新赞助者的要求进行相应的变更。 一个合约账户也被允许成为赞助者。 ## 提高赞助额度 赞助者可以在无需更换赞助者的情况下提供额外的赞助资金。在该情况下,赞助人需要调用函数 `setSponsorForGas(address contractAddr, uint upperBound)` 或 `setSponsorForCollateral(address contractAddr)` 并满足赞助者替换的三条要求中的条件 2,3. 如果满足相关需求,交易的赞助资金会被加入赞助余额中且 `sponsor_limit_for_gas_fee` 也会相应地被更新。 ## 白名单列表维护 只有合约本身或合约管理者可以更新合约白名单列表。赞助者则无权变更白名单列表。 合约可以通过调用 `addPrivilege(address[] memory)` 将任何地址加入到白名单列表中。这意味着如果 `sponsor_for_gas` 被设置,合约会为白名单列表中的账户支付存储押金。全零地址是一个特殊的地址 `0x0000000000000000000000000000000000000000` 。如果该地址被加入白名单列表,则所有调用该合约的交易都会被赞助。合约还可以调用方法 `removePrivilege(address[] memory)` 将部分正常账户从白名单列表中移除。移除一个不存在的地址不会导致错误或异常。 **需要注意的细节:** 1. 一个合约地址也可被加入到白名单列表中,但该操作无任何意义,因为只有交易的发送者可以被赞助。 合约的管理者可以使用 `addPrivilegeByAdmin(address contractAddr, address[] memory addresses)` 及 `removePrivilegeByAdmin(address contractAddr, address[] memory addresses)` 以维护白名单列表。 ## 样例 假定你有一个如下所示的简单合约。 ```solidity pragma solidity >=0.4.15; import "https://github.com/Conflux-Chain/conflux-rust/blob/master/internal_contract/contracts/SponsorWhitelistControl.sol"; contract CommissionPrivilegeTest { mapping(uint => uint) public ss; function add(address account) public payable { SponsorWhitelistControl cpc = SponsorWhitelistControl(0x0888000000000000000000000000000000000001); address[] memory a = new address[](1); a[0] = account; cpc.addPrivilege(a); } function remove(address account) public payable { SponsorWhitelistControl cpc = SponsorWhitelistControl(0x0888000000000000000000000000000000000001); address[] memory a = new address[](1); a[0] = account; cpc.removePrivilege(a); } function foo() public payable { } function par_add(uint start, uint end) public payable { for (uint i = start; i < end; i++) { ss[i] = 1; } } } ``` 部署合约且地址为 `contract_addr` 如果有些人希望赞助燃料费用,他/她可以发送如下的交易: ```javascript const PRIVATE_KEY = '0xxxxxxx'; const cfx = new Conflux({ url: 'http://test.confluxrpc.org', logger: console, }); const account = cfx.wallet.addPrivateKey(PRIVATE_KEY); // create account instance const sponsor_contract = cfx.InternalContract('SponsorWhitelistControl'); sponsor_contract.setSponsorForGas(contract_addr, your_upper_bound).sendTransaction({ from: account, value: your_sponsor_value }).confirmed(); ``` 如果需要赞助存储抵押物,可以简单的将 `setSponsorForGas(contract_addr, your_upper_bound)` 替代为 `setSponsorForCollateral(contract_addr)` 即可。 之后你可以使用 `addPrivilege` 和 `removePrivilege` 为你的合约维护 `whitelist` 。特殊的全零地址 `0x0000000000000000000000000000000000000000` 则表示 所有人都处于 `whitelist` 中。需要谨慎使用。 ```javascript you_contract.add(white_list_addr).sendTransaction({ from: account, }) you_contract.remove(white_list_addr).sendTransaction({ from: account, }) ``` 随后在 `whitelist` 中的账户在调用 `you_contract.foo()` 或 `you_contract.par_add(1, 10)` 时,不会支付任何费用。