airswap / airswap-protocols Goto Github PK
View Code? Open in Web Editor NEWAirSwap Contracts and Tools
Home Page: https://about.airswap.io/
License: MIT License
AirSwap Contracts and Tools
Home Page: https://about.airswap.io/
License: MIT License
Currently the peer allows trades with order.taker.wallet
of any address. While this doesn't enable any of the Peer owner's tokens to be taken, it does mean that the Peer's rules can be utilized by other addresses, thereby draining the maxTakerAmount
when the true owner hasn't carried out trades.
tradeWallet
address to the peer, which is set by the ownerrequire
that the order.taker.wallet
== tradeWallet
in the provideOrder
function.The addition of tradeWallet
means that the Peer's owner can store their tokens in a different wallet (e.g. a hardware wallet) from the wallet they used to deploy the contract (e.g. a hot wallet), whilst still allowing them to ensure no one uses their peer.
Ensure that the order that a Peer returns is actually fillable
do a balance, approval, and authorization prior to returning peerLocator and amount
Sample pseudocode:
* @notice Provides check on Peer to ensure meets reqs for complete trade
* @dev notice this is just checking peer not checking if peerfrontend is ok
* @param _peerAddress address
* @param _takerAmount uint256
* @param _takerToken address
* @return uint256
*/
function checkPeerValidity(
address _peerAddress,
uint256 _takerAmount,
address _takerToken
)
external view returns (uint256 allowedAmount) {
address tradeWalletAddress = IPeer(_peerAddress).tradeWallet()
bool authd = Swap(_swapAddress).delegateApprovals(tradeWalletAddress, _peerAddress)
uint256 currentAllowance = IERC20(_takerToken).allowance(tradeWalletAddress, _swapAddress)
if (authd) {
if (currentAllowance > _takerAmount) {
return _takerAmount;
} else {
return currentAllowance;
}
}
return 0;
}
In the current setup for Indexer and Index, to fetch a set of intents to trade, the caller can specify the number of intents to trade n
, and they are returned the first n
intents in the linked list. As the system grows and the linked list gets long, it may be that systems want/need to paginate through the intents in groups. For this reason it would be helpful to allow the starting point to be specified when fetching intents, instead of implicitly starting at the head of the list every time.
Changes:
The following 2 functions each need a second argument address startPoint
that specifies where the start of the traversal should be.
Indexer.fetchIntents
Index.getLocators
Currently the pricing logic on Delegate will drop decimals. While these amounts are small, we should be intentional about when to round values up or down, and the result should never be zero.
Found that not all contract return a bool on approve either similar to the transferFrom.
While most of these are not likely to become staking tokens used in the Indexer, wanted to call out these tokens are not suitable stakingTokens
PLR
BOXX
DAT
CDT
LRC
DNA
MTL
LNK
BNN
PAY
GOLD
ADX
USDT
IBTC
PAN
Separate the approvals to differentiate approvals for the different parties in a Swap
order.
signerApprovals
allows a Party to have someone else be their primary signer to submit orders such that a signature is not required.
example: The authorized signer may be a externally owned wallet while the signerWallet
may actually be a smart contract.
senderApprovals
allows a Party to have someone else be their primary sender of orders.
mapping (address => mapping (address => uint256)) public signerApprovals;
mapping (address => mapping (address => uint256)) public senderApprovals;
authorizeSigner(address _authorizedAddress, _expiry)
authorizeSender(address _authorizedAddress, _expiry)
revokeSigner(address _authorizedAddress)
revokeSender(address _authorizedAddress)
mapping (address => mapping (address => uint256)) public delegateApprovals;
Ensure that approvals stay clear cut
signerApprovals
does not impact a sendersenderApprovals
does not impact a signermsg.sender
is the party that can authorize or revoke their approvalCurrently, almost none of the return values of function calls are documented.
Make the events indexed for the Delegate
event SetRule(
address indexed senderToken,
address indexed signerToken,
uint256 maxSenderAmount,
uint256 priceCoef,
uint256 priceExp
);
event UnsetRule(
address indexed senderToken,
address indexed signerToken
);
Consequences:
There will be additional gas costs as indexed params require this.
the formula for emitting an event is:
k = 375, a is 8, and b is 375
k + unindexedBytes * a + indexedTopics * b
so will be adding 750 gas to each event emittance for the 2 indexed params.
With the additional support of REVERT reasons in Etherscan, may want to make reason for friendly.
Still will keep them < bytes32 but can offer more guidance and more intuitive verbiage.
Currently, only the Owner of a Peer has the right to manage its rules. This is a proposal to enable the Owner of a Peer to give other parties the right to manage its rules.
i.e. if a Manager is to act on behalf of an owner in order to add a rule and set an intent through a Peer. Peer needs the ability to grant such control.
This is a dependency for #144
event SetRule(
address senderToken,
address signerToken,
uint256 maxSenderAmount,
uint256 priceCoef,
uint256 priceExp
);
event UnsetRule(
address senderToken,
address signerToken
);
event SetRule(
address ruleOwner,
address senderToken,
address signerToken,
uint256 maxSenderAmount,
uint256 priceCoef,
uint256 priceExp
);
event UnsetRule(
address ruleOwner,
address senderToken,
address signerToken
);
Allows for developers/dapps to build delegate trackers and make it easier for users to track when something has been set or unset
Currently an indexer will happily let a user set intent for an arbitrary delegate contract. This means that a consumer must accept that all delegates are untrusted.
If however we create a delegate factory that the indexer trusts, then a consumer can transitively trust indexed delegates. Would like to host discussion here and explore developing a trusted delegate factory.
BLUF: Saving about 128 bytes with this change per Order. I also examined Peer and Indexer but did not find areas for optimization.
Solidity works in 32 bytes slots and so if we can fit fixed params within a single slot, re-arranged the params to do just that.
Current
struct Signature {
address signer; // 20 byte
uint8 v; // 1 byte
bytes32 r; // 32 bytes
bytes32 s; // 32 bytes
bytes1 version; // bytes 1
}
4 - 32 bytes
struct Party {
address wallet; // 20 byte
address token; // 20 byte
uint256 param; // 32 bytes
bytes4 kind; // 4 bytes
}
4 - 32 bytes
struct Order {
uint256 nonce; // 32 bytes
uint256 expiry; // 32 bytes
Party maker; // 4 - 32 bytes
Party taker; // 4 - 32 bytes
Party affiliate; // 4 - 32 bytes
Signature signature; // 4 - 32 bytes
}
18 - 32 bytes
Proposed
struct Signature {
address signer; //20 bytes
bytes1 version; //1 bytes
uint8 v; //1 byte
bytes32 r; //32 bytes
bytes32 s; //32 byetes
}
3 - 32 bytes
struct Party {
bytes4 kind; // 4 bytes
address wallet; // 20 bytes
address token; // 20 bytes
uint256 param; // 32 bytes
}
3 - 32 bytes
struct Order {
uint256 nonce; // 32 bytes
uint256 expiry; // 32 bytes
Party maker; // 3 - 32 bytes
Party taker; // 3 - 32 bytes
Party affiliate; // 3 - 32 bytes
Signature signature; // 3 - 32 bytes
}
14 - 32 bytes
We use the smallest unit for every token in all scenarios. This should be documented along with any usage of a token amount to reiterate this.
For example, WETH has 18 decimal places. While traders may think in terms of 1 WETH, the actual value within the contracts would be 1000000000000000000.
As the repo has grown, the different folders have become less structured, and the line between helper
vs protocol
has become more blurred.
This is just a reminder we should consider the stucture of the repo while we're tidying it up pre-launch.
We currently do not have test coverage reporting instrumented for any package. The solidity-coverage
project has been good when it works, but we have not had success configuring it yet.
Would like to add solidity-coverage
or an alternative so we can ensure proper test coverage.
When a market reaches a certain size, getIntents
and findPosition
will stop working for state-changing transactions. We should determine the limits in different scenarios and decide whether to hardcode a cap or just publish the limit.
Contracts like AirSwapToken explicitly prevent transfers and transferFrom of zero amounts and will revert.
Thus any places we transfer tokens in delegate or indexer should bypass trying to transfer 0 amount as that will leave indices non-removable right now.
Currently calling getIntents
on an indexer returns all types of intents. The locator
value of an intent indicates with the final byte its type, whether a delegate contract, instant peer, or arbitrary URL. Being able to filter getIntents
by type would be useful, particularly in onchain integrations that are only able to interface with delegate contracts.
This issue is to propose the renaming of the maker
and taker
variable names to signer
and sender
. A detailed description of the reasons for this proposal is given below.
maker
and the sender of the transaction the taker
- despite the fact that the party who signed the order could have been the taker of liquidity.Swap
contract from the delegate contract, the delegate is set as the taker of the order.taker
to be the party that sends the order.sender
and signer
it would abstract away from liquidity and trade initialisation, and instead focus the contracts back onto the approvals of the trade and the security of the smart contracts.maker
is not synonymous with signer
, and the same with taker
and sender
.maker
and taker
are removed from Airswap entirely, but just from the Swap part of the protocol - contract, interacting libraries etc. "nonce": "100",
"expiry": "1566941284",
"signer": {
"wallet": "0x655...",
"token": "0x27054b13b1b798b345b591a4d22e6562d47ea75a" (AST),
"param": "10000",
"kind": "0x277f8169"
},
"sender": {
"wallet": "0xdea...",
"token": "0xc778417e063141139fce010982780140aa0cd5ab (WETH)",
"param": "100000000",
"kind": "0x277f8169"
},
"affiliate": {
"wallet": "0x0000000000000000000000000000000000000000",
"token": "0x0000000000000000000000000000000000000000",
"param": "0",
"kind": "0x277f8169"
},
"signature": {
"signatory": "0xdea...",
"version": "0x45",
"r": "0x589...",
"s": "0x730...",
"v": "28"
}
}
As Index could be a standalone component, would like to put its features in the contract natspec
Current contract Natspec:
/**
* @title Index: A List of Locators
*/
Proposed
/**
* @title Index: A List of Locators
* @notice The Locators are sorted in reverse order based on the score
* meaning that the head has the largest score and tail is the smallest
* @dev A mapping is used to mimic a circular linked list structure
* where every mapping value contains a pointer to the next
*/
When using the Indexer, the score and identifier are also important information to have when fetching locators.
score
: The user needs to know how much they must to stake to rank on the Index.identifier
: The user needs to have the identifier of at least the final entry for pagination.They should be returned in the same call to represent the same state.
A route may be to return e.g. (bytes32[], uint256[], address[])
from the getLocators
call for locators, scores, and identifiers respectively. Alternatively, if only the last entry is useful for pagination, (bytes32[], uint256[], address)
.
Check the changes in the new version and upgrade the repo to the most recent Solidity version 0.5.12.
Currently the functions in Types.sol (our types library) are marked as external. This means we have to deploy and link the Types library for any contract that uses it. Due to the fact that the Types library is used rarely, and the number of uses of the 2 functions are once and twice respectively, it may make sense to make the functions as internal. This would mean we don't have to deploy Types.sol at all, and the contracts that utilise it can pull in what they need for deployment.
Currently, for example, with existing locator scores of 2, 1, 1 a new locator with score 1 will be placed at the end of the list ([2, 1, 1, 1]). We should consider adding the new locator at the beginning e.g. [2, 1, 1, 1].
This would encourage fresher locators in the higher rankings, and given that makers will need to observe the Indexer for changes anyway, would not require special or additional work from makers.
As AirSwap wants to expand the token types supported, creating a more generalized solution for supporting more token types.
The below document discusses why the current setup does not work for the current tokens AirSwap would like to support and certain short term solutions.
Alt to Token Registry idea if just trying to support non-standard ERC20 and ERC721
New token types:
ERC1155
, ERC721
, ERC20
, and custom tokens like USDT and KittyCore
Swap.swap
to work irrespective of what irregular tokens are used if they need to be supported with front end applications.TransferHandlerRegistry
into the Swap contract
ITransferHandler.sol
function transferTokens(address _from, address _to, uint256 _param, uint256 amount, uint256 id, address token,) external returns bool;
Sample ERC20TransferHandler
import IERC20.sol
import "./ITransferHandler.sol";
import SafeERC20.sol
contract ERC20TransferHandler is ITransferHandler {
using SafeERC20 for IERC20;
function transferTokens(address from, address to, uint256 amount, uint256 id, address *token*) return (bool){
require(id == 0, "ID_INVALID");
require(IERC20(token).transferFrom(from, to, amount));
return true;
}
}
Sample CryptoKittyTransferHandler
/**
* @title IKittyCoreTokenTransfer
* @dev transferFrom function from KittyCore
*/
interface IKittyCoreTokenTransfer {
function transferFrom(address from, address to, uint256 amount, uint256 id) external;
}
contract KittyCoreTransferHandler is ITransferHandler {
function transferTokens(address from, address_to, uint256 amount, uint256 id, address token) return (bool) {
require(amount == 0, "AMOUNT_INVALID");
require(IKittyCoreTokenTransfer(token).transferFrom(from, to, id));
return true;
}
}
Sample ERC721TransferHandler
import "./ITransferHandler.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC721.sol";
contract ERC721TransferHandler is ITransferHandler {
function transferTokens(address from, address to, uint256 amount, uint256 id, address token) return (bool) {
require(amount == 0, "AMOUNT_INVALID");
IERC721(_token).safeTransferFrom(from, to, id);
return true;
}
}
TransferHandlerRegistry.sol
bytes4
→ ITransferHandler
Functions
addTransferHandler(bytes4 kind, ITransferHandler transferHandler)
AddHandler(bytes4 kind, address asset)
Swap.sol
changesSwap
will contain a call to the TransferHandlerRegistry
which has a mapping that goes from bytes4
→ ITransferHandler()
Swap
removes the hard coded interface identifiersTransferHandlerRegistry
which will need to be injected into the constructor of swap require(from != to, "INVALID_SELF_TRANSFER");
ITransferHandler transferHandler = registry.transferHandlers(kind);
require(address(transferHandler) != address(0), "UNKNOWN_TRANSFER_HANDLER");
// delegatecall required to pass msg.sender as Swap contract to handle the
// token transfer in the calling contract
(bool success, bytes memory data) = address(transferHandler).
delegatecall(abi.encodeWithSelector(
transferHandler.transferTokens.selector,
from,
to,
amount,
id,
token
));
require(success && abi.decode(data, (bool)), "TRANSFER_FAILED");
Full test path coverage
Ensure that no one else can modify or update the TransferHandlerRegistry (or allow it to be added to by an owner)
Using SafeERC20 allows us to not have a two distinct handlers with ERC20 contracts anymore, they both will pass through to IERC20.
Currently if you try to setIntent
over an existing intent, it will fail with ENTRY_ALREADY_EXISTS
. However, there is no way to look this up ahead of time.
Don: Propose that we change the visibility of Index.entries
to public
so this can be checked before a user tries to update their intent.
Currently all tests rely on the tests before them executing correctly. Tests should be independent of one another. Snapshots allow all tests to work from the same start state.
makers
trade WETH, whereas takers
may prefer to trade ETH.sender
could trade ETH (as the sender is traditionally the order taker).0xFFFFFFF
or 0x0
. I think this would make the order clearer. require(_order.sender.wallet == msg.sender, "MSG_SENDER_MUST_BE_ORDER_SENDER");
sender.wallet
is not the delegateSwap.isAuthorizedSender(sender.wallet, msg.sender)
, or to check sender.wallet == Delegate(msg.sender).tradeWallet
Types
is a library that is used as a helper to Swap
- it stores constants and functions that are used to implement EIP712 for swap, and help with the security of the contract.Moving from #226 as the proposed changes evolved away from the original topic there.
In regards to: https://www.notion.so/fluidity/Indexer-Audit-ef2f668a578f4fefa050442933683bac
Related to issue: #170
Add Ownable functionality and Pausable functionality to the Indexer.
contract Indexer is IIndexer, Ownable {
contract Indexer is IIndexer, Ownable, Pausable {
We should also add additional functionality that will allow refunding of staked amounts if we've needed to pause due to a security vulnerability.
function refundStake(address) onlyOwner {
This will only be possible if the contract were paused.
As we're in the on going process of development. Until the contracts are audited and we have 100% confidence in the code we should be playing it safe.
Once audited and 100% confident we can relinquish the ownership so that it's entirely decentralized.
During development and before audited the Indexer
will be centralized and controlled.
Given that Peer.sol
partially implements the Peer API we think that naming it Delegate.sol
is an opportunity to define such a partially implemented Peer. Indeed, this contract behaves as a delegate for another peer in the system.
IPeer.sol
-> IDelegate.sol
Peer.sol
-> Delegate.sol
IPeerFactory.sol
-> IDelegateFactory.sol
PeerFactory.sol
-> DelegateFactory.sol
DelegateFactory
:ILocatorWhitelist
Delegates
Currently if a user wants to update their amount staked on a market, they have to call unsetIntent
and then setIntent
, which requires them signing and sending 2 transactions. Considering the indexer will revolve around only being able to return the first n staked locators, it should be expected that people might want to update their stake to ensure they remain competitive. For this reason I propose a function updateStakeAmount(signerToken, senderToken, newAmount
that updates the user's staked amount. This function need not be complex - it could merely call unsetIntent
and setIntent
internally, but it stops the user from having to sign 2 transactions.
Currently, the authorization feature, including both signer and sender authorization, takes an expiry value after which the authorization is invalid.
What we see, and will likely see often, is that in practice, these authorizations are set with essentially infinite expiry. This is a proposal to consider removing expiry in favor of a boolean value.
In this discussion we could consider alternate forms of controlling authorizations, for example, specifying a maximum number of swaps that can be performed for a given authorization.
Index is more of independent data structure versus an application specific contract.
Putting down a placeholder to think more carefully if it can or should be shifted to be a library.
In the current setup of the Delegate/Indexer/Index, a user cannot have multiple intents to trade on the same token pair. This is a situation that should be allowed on the contracts - take the following situation for example:
To set up such trades automatically on a Delegate contracts Bob would need multiple Rules on the same token pair, which isn't possible. Bob could instead deploy a second Delegate for his second rule (which is non-ideal but possible). However to actually stake his intent to trade on an Index (which is a token-pair-specific indexer), each user can only have one stake, due to the implementation using a mapping of user address.
This thereby makes the above situation impossible.
IIndexer.sol
Currently there are no indexed params in the Indexer, the below is my proposed change. I have not added/removed any parameters, that can also be up for discussion. I've added indexes to allow somone to query based on user wallets as well as signerToken/senderToken (ie tokenPair).
event CreateIndex(
address signerToken,
address senderToken
);
event Stake(
address indexed wallet,
address indexed signerToken,
address indexed senderToken,
uint256 amount
);
event Unstake(
address indexed wallet,
address indexed signerToken,
address indexed senderToken,
uint256 amount
);
event AddToBlacklist(
address token
);
event RemoveFromBlacklist(
address token
);
Consequences:
There will be additional gas costs as indexed params require this.
the formula for emitting an event is:
k = 375, a is 8, and b is 375
k + unindexedBytes * a + indexedTopics * b
so staked and unstake will add 1125 to each event as there are now 3 indexed topics.
The DelegateFactory
can be injected to create a Delegate where the swap address is any address.
function createDelegate(address _swapContract, address _delegateContractOwner) external returns (address delegateContractAddress) {
This gives the potential for somebody to create a Delegate that doesn't actually use AirSwap's Swap contract.
If the DelegateFactory
contains a list of deployedAddresses
that is referenced by
setIntent
when creating an intent
// Ensure the locator is whitelisted, if relevant
if (locatorWhitelist != address(0)) {
require(ILocatorWhitelist(locatorWhitelist).has(_locator),
"LOCATOR_NOT_WHITELISTED");
}
this doesn't really mean much if the created address can be compromised, see below.
this was added into Peer.js
('Peer' is in reference to Delegate)
it('Corrupt Peer with specialized swap', async () => {
let wrapper = await Wrapper.new(swapAddress, tokenWETH.address);
//provide peer with wrapper and not the swap address; this could be any address that swaps
let corruptPeer = await Peer.new(wrapper.address, aliceAddress)
await corruptPeer.setRule(
tokenDAI.address,
tokenDAI.address,
10,
1,
0
)
//alice authorizes the peer
await swapContract.authorize(corruptPeer.address, 1577836800, { from: aliceAddress });
// Note: Consumer is the order maker, Peer is the order taker.
const order = await orders.getOrder({
maker: {
wallet: bobAddress,
token: tokenDAI.address,
param: 1,
},
taker: {
wallet: corruptPeer.address,
token: tokenDAI.address,
param: 1,
},
})
// Bob submits to corrupt peer
await passes(
corruptPeer.provideOrder(order, { from: bobAddress }),
)
})
I am suggesting the DelegateFactory
to be provided a swap address on creation. This way DelegateFactory
will create Delegates for a particular swap address and give merit to its deployedAddresses
.
DelegateFactory
will need to have a constructor with the swap address that sets the swapContract state variable:
constructor(address _swapContract) {
swapContract = _swapContract;
}
The factory method also needs to be updated:
function createDelegate(address _swapContract, address _delegateContractOwner)
to become
function createDelegate(address _delegateContractOwner)
Given that there may be many Swap contracts, whether for different systems, or through incremental versions, two peers will need to agree on which contract they intend to settle trades.
The value of version
would be a Swap contract address.
Names are based off the renaming proposed here:
Note: All names used in this issue will be according to the namings set out in issue 137. However this renaming is not necessary for the concept of the Manager to be understood and discussed. If 137 is not approved, this Manager could be implemented using the old naming conventions
Delegates
andDelegate
using the createDelegate()
method from the DelegateFactory
Delegate
is brand new it has no rules and no intents set on Indexer
. A user would first need to send a transaction to setRule()
on the Delegate
and then subsequently send another transaction to setIntent()
on the Indexer
. Multiple transactions are necessary to setup the Delegate
DelegateManager
:The user interfaces with this contract
The manager acts on behalf of a user, keeping track of all the Delegates
constructed for a particular owner using a mapping(address ⇒ address[])
The manager facilitates adding rules to the Delegate
and setIntent()
on an Indexer
in a single transaction.
DelegateManager
constructor injects DelgateFactory
Functions:
createDelegate()
Delegate
by calling the DelegateFactory
. Where the owner of the newly created Delegate
would be the caller, msg.sender
.Delegate
, the manager saves the address in the owner to list of delegates mapping.constructor()
Delegate
, the manager saves the addresssetRuleAndIntent(address Delegate, struct Intent, struct Rule, address Indexer)
Rule
on a Delegate
and then subsequently setIntent()
on an Indexer
in a single transaction.unsetRuleAndIntent(address Delegate, struct Intent, struct Rule, address Indexer)
Rule
on a Delegate
and then subsequently unsetIntent()
on an Indexer
in a single transaction.DelegateManager
is indifferent to the chosen Indexer
it tries to set an intent on. The ultimate responsibility of successfully setting an intent is determined by the Indexer
. This is because the Indexer
references a LocatorWhitelist
of which the DelegateFactory
implements. If a provided DelegateManager
cannot be verified to be from the trusted DelegateFactory
the transaction will fail with LOCATOR_NOT_WHITELISTED
.DelegateManager
the msg.sender
will always be the staker. The DelegateManager
is only a facilitator to the Indexer
.Delegates
are akin to different strategies.In reference to: https://github.com/airswap/airswap-protocols/pull/159/files#r335149519
Update Indexer.setIntent()
and Delegate.setRule()
methods to use structs instead of 3+ parameters.
Currently Indexer.setIntent()
and Delegate.setRule
pass series of parameters in
function setIntent(
address _signerToken,
address _senderToken,
uint256 _amount,
bytes32 _locator
)
function setRule(
address _senderToken,
address _signerToken,
uint256 _maxSenderAmount,
uint256 _priceCoef,
uint256 _priceExp
)
These parameters can be better represented as structs
function setIntent(
Struct.Intent _newIntent
)
function setRule(
Struct.Rule _newRule
)
Struct.sol
fileStruct.sol
could be a libraryThe idea is that by passing them as objects it abstracts away the innards of the rule or intent.
With regards to gas passing many parameters downwards could be simplified, thus reducing the method signature and subsequently gas usage.
ether
and receives back some token
msg.sender
who must then also be the _order.sender.wallet
send ether
to the Swap contractmsg.sender
sends ether and it gets converted to WETH
when delivered to the _order.signer.wallet
ether
and receive back some token
msg.sender
who must then also be the _order.sender.wallet
send ether
to the Swap contractether
to become WETH
WETH
to the signer
and transfer the tokens to the msg.sender
(sender wallet)WETH
and now convert it to ether
before reaching the taker
_signer.token
will be checked to see if it's WETH
which will kick off the flowWETH
WETH
to the swap contract and then call weth.withdraw
fallback
function to receive the ether
returnedether
to the msg.sender
In reference to: https://www.notion.so/fluidity/Indexer-Audit-ef2f668a578f4fefa050442933683bac
Remove the array parameters in Indexer.addToBlacklist()
and Indexer.removeFromBlacklist()
function addToBlacklist(
address[] calldata _tokens
) external onlyOwner {
function removeFromBlacklist(
address[] calldata _tokens
) external onlyOwner {
function addToBlacklist(
address _tokenToAdd
) external onlyOwner {
function removeFromBlacklist(
address _tokenToRemove
) external onlyOwner {
We can't really imagine batch adding or removing blacklisted tokens. If we needed we can loop over the tokens.
Having done some reading and double-checked my findings with a quick code-check (https://github.com/airswap/airswap-protocols/tree/test-struct-duplication), I've found we're storing each locator twice in the Index contract.
E.g. if we have a simple linked list HEAD <-> ALICE <-> HEAD
, we are storing:
linkedList[HEAD][NEXT] = ALICE
linkedList[HEAD][PREV] = ALICE
These are not stored as references (as sometimes structs are), and are instead stored as separate copies of the same locator.
This isn't a problem from a security point of view - its very clear in the code the 2 copies of a locator can never be updated differently and cause discrepancies. However it is clearly a waste of storage and therefore gas.
To add a new locator to the linked list in the current setup, we have the following:
Storage before addition of BOB (who goes between ALICE and CHARLIE):
linkedList[ALICE][NEXT] = CHARLIE
linkedList[CHARLIE][PREV] = ALICE
Storage after the addition of BOB:
linkedList[ALICE][NEXT] = BOB // update
linkedList[CHARLIE][PREV] = BOB // update
linkedList[BOB][NEXT] = CHARLIE // new
linkedList[BOB][PREV] = ALICE // new
This change requires the storage of 2 new locators in storage, and 2 updated locators.
Locators are 3 'words' in size (they are 3 x 32bytes), meaning we have 6 new storage writes, and 6 updates storage slots.
It costs 20,000 to use a new 32 byte storage
It costs 5,000 to overwrite a previously used 32 byte storage
The addition of BOB to the linked list therefore costs (6x20000 + 6x5000) = 150,000
The new linked list would have a mapping as follows:
mapping(address => ListElement) linkedList;
struct ListElement {
address previous;
address next;
uint256 score;
bytes32 data;
}
Note that this no longer has a address user;
field as Locator did previously - it's not needed anymore.
Storage before addition of BOB (who goes between ALICE and CHARLIE):
linkedList[ALICE] = ListElement(p, CHARLIE, s, d)
linkedList[CHARLIE] = ListElement(ALICE, n, s, d)
Storage after addition of BOB:
linkedList[ALICE] = ListElement(p, BOB, s, d) // `next` address updated
linkedList[CHARLIE] = ListElement(BOB, n, s, d) // `previous` address updated
linkedList[BOB] = ListElement(ALICE, CHARLIE, s, d) // new
This change requires 2 32 byte updates, plus one new struct (which is 4 x 32 bytes).
The addition of BOB to the linked list therefore costs (4x20000 + 2x5000) = 90,000
Propose that having multiple statuses for an order nonce is unnecessarily informative. We can reduce complexity and remove a require
in Swap.sol
with the following.
Currently:
signerOrderStatus
is either OPEN
(0x0
), TAKEN
(0x0
), or CANCELED
(0x0
)Proposed:
signerOrderStatus
to signerNonceStatus
signerNonceStatus
is either AVAILABLE
(0x0
) or UNAVAILABLE
(0x1
)Purpose: Allow AirSwap to stay in control of deployed smart contracts in testing stages and remove them if issues are found, either through pausing or selfdestruct
Contracts to add these to:
Two functions to add:
Implement an indexer that enables multiple parties to stake for the same locator. This enables parties to pool their staking power to compete on an index.
The current Indexer is an "intent indexer" which creates many indexes based on signerToken senderToken pairs. The proposed design is a "multi-stake indexer" which manages a single index.
Why another indexer?
Index.sol
should be able to remain unaltered.createIndex
.setIntent
to add themselves to the Indexer.High level: Keep the Index to be agnostic and do all the staking and staking management on the Indexer
Index.sol
method for updateLocator(_user, _amount)
called only by the Indexer by the additional staker
This will update the score as well as place the locator in the correct position
Only the indexer will be able to update these scores.
This will be part of topupDelegate
and returnAmtDelegate
Indexer.sol
stakingPatrons
- name of 2-layer mapping
_address → Index() → _amt mapping to store how much each user staked
as well as array holding the patrons were Index
indexPatrons
- name of mapping to array
Index() → [] so that when an intent is unset all the values are given back
add in two new function
topupDelegate(Index address, stake amount)
will both store the amount from the msg.sender and how much they staked
returnAmtDelegate(Index address, stake amount)
or not have any stake amount and just remove value
topupDelegate(Index address, stake amount)
returnAmtDelegate(Index address, uint256 stakeamount)
Delegate
/ Locator
in a worse position then they had initially stakedCurrently when setRuleAndIntent()
is called staked tokens are provided by the msg.sender. Subsequently when unsetRuleAndIntent()
is called, tokens are returned to the msg.sender
.
msg.sender
and tradeWallet
should both be options on where staking tokens are pulled and returned from. A defaultStakeAccount
can be set which can either be msg.sender
or tradeWallet
. Both setRuleAndIntent()
and unsetRuleAndIntent()
would first check against the defaultStakeAccount to know where to pull/return funds from/to.
_order.maker.wallet
is checked with _order.signature
to ensure that the maker truly signed the ordermsg.sender.call.value(amount)("")
instead of transfer()
it('Send order where Bob sends AST to Alice for DAI', async () => {
const order = await orders.getOrder(
{
maker: {
wallet: aliceAddress,
token: tokenDAI.address,
param: 1,
},
taker: {
wallet: bobAddress,
token: tokenAST.address,
param: 100,
},
},
true
)
order.signature = signatures.getEmptySignature()
console.log(order)
let result = await wrappedSwap(order, {
from: bobAddress,
value: 0,
})
await passes(result)
result = await getResult(swapContract, result.tx)
emitted(result, 'Swap')
equal(await web3.eth.getBalance(wrapperAddress), 0)
ok(
await balances(wrapperAddress, [
[tokenAST, 0],
[tokenDAI, 0],
[tokenWETH, 5],
])
)
})
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.